mirror of
https://github.com/acmesh-official/acme.sh.git
synced 2026-08-19 15:23:29 +08:00
Compare commits
6295 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
261653ebf4 | ||
|
|
41bdd4cd0e | ||
|
|
a89ba9c2e5 | ||
|
|
2e2782f0d8 | ||
|
|
4a3bc2c919 | ||
|
|
05367d3598 | ||
|
|
f1cbba05f6 | ||
|
|
f67be78ff4 | ||
|
|
9aad4dcbd5 | ||
|
|
603a126a7c | ||
|
|
5e6c263211 | ||
|
|
b4925052dd | ||
|
|
ea5e70564d | ||
|
|
bf90b845b2 | ||
|
|
0565443622 | ||
|
|
057c940895 | ||
|
|
7c12deb7ef | ||
|
|
830782fd1d | ||
|
|
698f6c7329 | ||
|
|
749fa2487e | ||
|
|
24d573b6d3 | ||
|
|
1774d838ca | ||
|
|
4c8a143086 | ||
|
|
6feb1df83c | ||
|
|
97c5aca136 | ||
|
|
7fa3018219 | ||
|
|
9bfcf2cf1a | ||
|
|
2feb392bd0 | ||
|
|
59a97d7f8b | ||
|
|
a836e747d1 | ||
|
|
3661fd86b6 | ||
|
|
745a42f193 | ||
|
|
8f98786d93 | ||
|
|
6d559ae69f | ||
|
|
24501ac0ca | ||
|
|
a82cf763cf | ||
|
|
3faf65c46d | ||
|
|
7cc16cd09a | ||
|
|
1dbabf0da9 | ||
|
|
2499ee1ab9 | ||
|
|
1a746d98b8 | ||
|
|
4558a8aa98 | ||
|
|
1a54307dbf | ||
|
|
020123d812 | ||
|
|
17964cfd6e | ||
|
|
d621d6952a | ||
|
|
5e33e9f5f1 | ||
|
|
3fddea2962 | ||
|
|
a6766d4186 | ||
|
|
b9ce911eb1 | ||
|
|
15a1067f1b | ||
|
|
447dc3c7e7 | ||
|
|
5a8c685fd3 | ||
|
|
dd6540ce46 | ||
|
|
cd486cfbb9 | ||
|
|
e828b285ad | ||
|
|
f5e7e6b225 | ||
|
|
d45b6fe8e6 | ||
|
|
237f2d9c3b | ||
|
|
ebb5cc4981 | ||
|
|
3989eef5e2 | ||
|
|
2c51ac1c27 | ||
|
|
eacf0d6a87 | ||
|
|
099d88e6a9 | ||
|
|
897e219743 | ||
|
|
a9590c5bd7 | ||
|
|
c0d62eb934 | ||
|
|
44c045b056 | ||
|
|
e94631de44 | ||
|
|
a49f8c1992 | ||
|
|
7d0283ca2c | ||
|
|
3e64b5e6f5 | ||
|
|
76811857a0 | ||
|
|
22a5ae3ceb | ||
|
|
04e0422526 | ||
|
|
2215f1b988 | ||
|
|
9366c2e065 | ||
|
|
2e4acba105 | ||
|
|
fef90e15e1 | ||
|
|
50e5e771d5 | ||
|
|
ebde8345ae | ||
|
|
58cd667d65 | ||
|
|
98c30912fb | ||
|
|
2058a77d83 | ||
|
|
660a5e322c | ||
|
|
2af543a358 | ||
|
|
fa763db105 | ||
|
|
90b4795bb1 | ||
|
|
534a1714dc | ||
|
|
45c0ad4112 | ||
|
|
83b52e0cd7 | ||
|
|
bed15ba844 | ||
|
|
1324dcd472 | ||
|
|
cf3eab95ee | ||
|
|
ca118be754 | ||
|
|
b1b539695f | ||
|
|
347cc207cd | ||
|
|
cc64a73230 | ||
|
|
1651a5a609 | ||
|
|
f2b37b32ff | ||
|
|
73df21abc4 | ||
|
|
0c76c1f211 | ||
|
|
919492df13 | ||
|
|
e00d3cfde3 | ||
|
|
e4eaa59063 | ||
|
|
4187ec23c1 | ||
|
|
2b5a19d34a | ||
|
|
9f0ef7abcd | ||
|
|
dc1b06006f | ||
|
|
8585d9f4a7 | ||
|
|
0e2659b768 | ||
|
|
ca8ab7f8b5 | ||
|
|
f50401a342 | ||
|
|
ff9b969bdb | ||
|
|
1f778e6ef1 | ||
|
|
58423df3e8 | ||
|
|
8f3c1701f3 | ||
|
|
2a175f97e8 | ||
|
|
d3af3315da | ||
|
|
7b6d96387c | ||
|
|
cabe432539 | ||
|
|
1746fbdb25 | ||
|
|
d2b3772631 | ||
|
|
e964157bff | ||
|
|
934711e51d | ||
|
|
7def43481a | ||
|
|
defd64022d | ||
|
|
4256e3532b | ||
|
|
1f94fd7fd5 | ||
|
|
507baff2ef | ||
|
|
504540e67c | ||
|
|
cacafc9c23 | ||
|
|
24895a15c8 | ||
|
|
1cd63e1480 | ||
|
|
31b13caf8b | ||
|
|
1e2cd50fc9 | ||
|
|
524d96a3a8 | ||
|
|
0eb5cc8384 | ||
|
|
77047eb0ef | ||
|
|
4978782fb8 | ||
|
|
9764f67619 | ||
|
|
988afd0f59 | ||
|
|
b92516f79e | ||
|
|
fbf3b41c54 | ||
|
|
ede9a86d46 | ||
|
|
843a7efa7d | ||
|
|
d3e12694b9 | ||
|
|
a105126063 | ||
|
|
917bebd460 | ||
|
|
33704fc274 | ||
|
|
bcbfe25d08 | ||
|
|
bbfb6f50ae | ||
|
|
6df2d9e451 | ||
|
|
1a36823461 | ||
|
|
8f2a476d21 | ||
|
|
e64529ab50 | ||
|
|
7653eaab31 | ||
|
|
6cd0c00a21 | ||
|
|
b4de9e8621 | ||
|
|
0a6abaf8a1 | ||
|
|
f4dc9fd9d1 | ||
|
|
2229330c48 | ||
|
|
0df051577c | ||
|
|
7fb40f0ccf | ||
|
|
780f2ad5dc | ||
|
|
adf69c4e7e | ||
|
|
ac5624536b | ||
|
|
b974bbd6d6 | ||
|
|
0ce8c24736 | ||
|
|
20254cbaf0 | ||
|
|
f4d2db64ef | ||
|
|
61400500e2 | ||
|
|
92a1b47108 | ||
|
|
5038d12d62 | ||
|
|
7e7c0ee984 | ||
|
|
92bd80c07d | ||
|
|
ad99628e50 | ||
|
|
992886c4eb | ||
|
|
eabd23a551 | ||
|
|
1241649501 | ||
|
|
9900adb007 | ||
|
|
01d6d46914 | ||
|
|
f038958192 | ||
|
|
fdf528c26c | ||
|
|
ced8d72808 | ||
|
|
116c05fbff | ||
|
|
42e13fa797 | ||
|
|
ccd2f04c33 | ||
|
|
a50fad865f | ||
|
|
5433ea86c8 | ||
|
|
e52d75d762 | ||
|
|
cb40b47a4e | ||
|
|
b039ff3087 | ||
|
|
c83eed4994 | ||
|
|
81100db2f3 | ||
|
|
a7ccfcf91d | ||
|
|
d0fcafe29b | ||
|
|
ae8ef74c4b | ||
|
|
0925d5c522 | ||
|
|
0d53d29f7e | ||
|
|
c38182897d | ||
|
|
2998106bd1 | ||
|
|
b3579ff18d | ||
|
|
0dc97187e1 | ||
|
|
d60c75b4e3 | ||
|
|
365d2d10f3 | ||
|
|
db098055de | ||
|
|
4575877d48 | ||
|
|
d98fa53f62 | ||
|
|
9b597b3f1b | ||
|
|
58d9c8d7f6 | ||
|
|
a2f046306e | ||
|
|
b463471951 | ||
|
|
0e5f1518aa | ||
|
|
3c848adbb7 | ||
|
|
2e4e5d7955 | ||
|
|
c7c903fba3 | ||
|
|
dfbe2c5bff | ||
|
|
5713c1d39d | ||
|
|
b7e9214e2d | ||
|
|
206f4494ac | ||
|
|
d9ce7fefa1 | ||
|
|
ce07759ced | ||
|
|
010bd1111a | ||
|
|
f265c304da | ||
|
|
85408bfb4d | ||
|
|
7c91ee1fc0 | ||
|
|
1687cbd5b4 | ||
|
|
d2780eb04e | ||
|
|
eaf4b62ba9 | ||
|
|
47378b5630 | ||
|
|
ac75c54ade | ||
|
|
36667ab656 | ||
|
|
7b19070d98 | ||
|
|
7985319687 | ||
|
|
7735cdf3ab | ||
|
|
d35876e885 | ||
|
|
355b121c79 | ||
|
|
3230d00c3d | ||
|
|
fe5490e0ec | ||
|
|
0d77231350 | ||
|
|
8d5a5a0e0d | ||
|
|
cbb8e9068c | ||
|
|
96f87844cd | ||
|
|
8e71268d03 | ||
|
|
10e7e458af | ||
|
|
76d1377fc1 | ||
|
|
15a0f52577 | ||
|
|
fd6ff7b173 | ||
|
|
ed6d1f3447 | ||
|
|
0b2187ab3f | ||
|
|
4b8b23bb90 | ||
|
|
d00b2722ee | ||
|
|
df49dd2ec9 | ||
|
|
e9b0cafac5 | ||
|
|
9882d534af | ||
|
|
539b46adc9 | ||
|
|
f89a9a5de3 | ||
|
|
00090d24b8 | ||
|
|
73a682e561 | ||
|
|
a1b94db94d | ||
|
|
5c94af86f3 | ||
|
|
3843495397 | ||
|
|
a739bf3e3a | ||
|
|
75642a1252 | ||
|
|
668427f285 | ||
|
|
d0e123cb02 | ||
|
|
8587c3e744 | ||
|
|
934870fc77 | ||
|
|
8eea7ca307 | ||
|
|
28f1f07f49 | ||
|
|
ad71a785ec | ||
|
|
d5c8060a65 | ||
|
|
4178c33524 | ||
|
|
044371b00a | ||
|
|
86d98b0461 | ||
|
|
6efd6d5b5a | ||
|
|
454cec6e43 | ||
|
|
afba1455b8 | ||
|
|
f3e61a8ef4 | ||
|
|
0894955895 | ||
|
|
5b5ef91d88 | ||
|
|
618735d11e | ||
|
|
08b2186afe | ||
|
|
3509f6404f | ||
|
|
50dbdd781b | ||
|
|
346acc3f33 | ||
|
|
a79bdb9ef3 | ||
|
|
563415d21f | ||
|
|
c2c5c3cdb7 | ||
|
|
4aeb7bbab0 | ||
|
|
12f639116c | ||
|
|
4cb1c6e1ea | ||
|
|
9aad08ef14 | ||
|
|
3b503a009c | ||
|
|
13d6496653 | ||
|
|
605299947e | ||
|
|
d050f3458b | ||
|
|
d66264e741 | ||
|
|
cf9c70a6c7 | ||
|
|
ef49a9fd23 | ||
|
|
fe5d2e3ef7 | ||
|
|
bf486bb988 | ||
|
|
3c8c735362 | ||
|
|
c397bd6573 | ||
|
|
e26ce2f19c | ||
|
|
af5e592fe4 | ||
|
|
e21be4455f | ||
|
|
5842e6ff4f | ||
|
|
8aea731bd4 | ||
|
|
5d158b1640 | ||
|
|
3198c1af6e | ||
|
|
fc8a61f10f | ||
|
|
d08b4de794 | ||
|
|
cc677ba9f1 | ||
|
|
03860a4978 | ||
|
|
da5e818907 | ||
|
|
f0146bd90e | ||
|
|
20722ea030 | ||
|
|
fdd2e4f19a | ||
|
|
89c86efc56 | ||
|
|
8ca1c83b95 | ||
|
|
8304d3e323 | ||
|
|
b5bfd08e35 | ||
|
|
70f9e255d3 | ||
|
|
12f147bf40 | ||
|
|
bef0fdb1ae | ||
|
|
020a4bb5b3 | ||
|
|
acaaca89ab | ||
|
|
bdfa988b65 | ||
|
|
0e93d2bee1 | ||
|
|
83424e7ba4 | ||
|
|
5bc44caf50 | ||
|
|
47412d1822 | ||
|
|
f68a758974 | ||
|
|
fb4ae10e08 | ||
|
|
824e7b3d80 | ||
|
|
34052e56d7 | ||
|
|
3374c22169 | ||
|
|
4807df0c3e | ||
|
|
b79146281c | ||
|
|
ab3093d58c | ||
|
|
c21db5c5d5 | ||
|
|
ec0dc40ad8 | ||
|
|
7236ba2d7c | ||
|
|
b440e49164 | ||
|
|
80e39eb63c | ||
|
|
61e986f23c | ||
|
|
6a60695549 | ||
|
|
28c088611a | ||
|
|
274fd53c83 | ||
|
|
a4595f334e | ||
|
|
477277bd2d | ||
|
|
d8053ed6b2 | ||
|
|
e44809c180 | ||
|
|
9c245eb37a | ||
|
|
713251c65b | ||
|
|
2e85e6f9bb | ||
|
|
9e584e346d | ||
|
|
163eb1acb9 | ||
|
|
25a3ee48df | ||
|
|
30c9332327 | ||
|
|
dc65223da1 | ||
|
|
01c93b9bbd | ||
|
|
b4ac517c7b | ||
|
|
bfd1f9bf6c | ||
|
|
0cef5edac2 | ||
|
|
8c384a92bf | ||
|
|
ce2d9e9746 | ||
|
|
282b048557 | ||
|
|
f39d066ced | ||
|
|
e2882c536b | ||
|
|
892b3ca219 | ||
|
|
4de00d4603 | ||
|
|
40b29c1879 | ||
|
|
d57ab0ab7d | ||
|
|
d8c062defb | ||
|
|
3e36b61823 | ||
|
|
09009794e0 | ||
|
|
5e670e0d93 | ||
|
|
bc646d120e | ||
|
|
c58da45917 | ||
|
|
b37867d027 | ||
|
|
880d93f7f7 | ||
|
|
5ad2bea129 | ||
|
|
9e51432a6f | ||
|
|
903a53991d | ||
|
|
188aa515ce | ||
|
|
6a9776b1f0 | ||
|
|
2092d6061b | ||
|
|
35f99c545c | ||
|
|
4b278dc1bb | ||
|
|
9bf5179b55 | ||
|
|
ca94221d08 | ||
|
|
70462b5ac3 | ||
|
|
ef035248c3 | ||
|
|
877cbe04c9 | ||
|
|
94c670a759 | ||
|
|
045e4dee2e | ||
|
|
06d9ca61a3 | ||
|
|
dc6a996cc3 | ||
|
|
b08bb2ef69 | ||
|
|
64ac537e72 | ||
|
|
ef2089ceb1 | ||
|
|
2ad984d8ad | ||
|
|
6a37f23b14 | ||
|
|
cf2f9ef251 | ||
|
|
f9ffdbe407 | ||
|
|
778b4a38ed | ||
|
|
397c0605e5 | ||
|
|
b4f30ff026 | ||
|
|
0b66acf332 | ||
|
|
185d92f1e7 | ||
|
|
e031457cfa | ||
|
|
162cfebbbb | ||
|
|
66ef351f36 | ||
|
|
6a98b9f81e | ||
|
|
4219f7b2f6 | ||
|
|
e260b86414 | ||
|
|
21d52b5995 | ||
|
|
e03f8d3ad6 | ||
|
|
6f5a0c5d5e | ||
|
|
57db388932 | ||
|
|
d795cb4850 | ||
|
|
1bd2922bc3 | ||
|
|
47f24126f5 | ||
|
|
76fdac59bc | ||
|
|
49a3d586a3 | ||
|
|
361e7c5ad4 | ||
|
|
3ae9711892 | ||
|
|
fc7168e11d | ||
|
|
260df0048b | ||
|
|
11cae37405 | ||
|
|
61b59831c4 | ||
|
|
96f38655b4 | ||
|
|
ab7835ec58 | ||
|
|
b8b1f1e9b4 | ||
|
|
6b66e734a9 | ||
|
|
4f0a4850a6 | ||
|
|
8a78865174 | ||
|
|
69dd2cf78b | ||
|
|
a961e03a59 | ||
|
|
1eee4dee9c | ||
|
|
1d8788767f | ||
|
|
986a6138eb | ||
|
|
f850e8d0e4 | ||
|
|
d375012c5d | ||
|
|
37c25aa107 | ||
|
|
7b16526e7f | ||
|
|
093f36b4d6 | ||
|
|
e5b47f6402 | ||
|
|
a55d40be97 | ||
|
|
c1c49d5a01 | ||
|
|
88e4d64c1a | ||
|
|
17e0bbcbb6 | ||
|
|
95c7546051 | ||
|
|
c1e17c366f | ||
|
|
88cde7be6d | ||
|
|
c16e059535 | ||
|
|
88d4637ee3 | ||
|
|
74ed0354a3 | ||
|
|
2cc5e66517 | ||
|
|
ba7c368ee5 | ||
|
|
768de270bf | ||
|
|
db1dc4de0d | ||
|
|
fb0926dc81 | ||
|
|
34eb2a655a | ||
|
|
23e1a53ec8 | ||
|
|
67d58a12e7 | ||
|
|
0ed5e21232 | ||
|
|
b2eb1d2bbc | ||
|
|
3c184486c3 | ||
|
|
5a730bf00d | ||
|
|
cbd5dae3b4 | ||
|
|
b9c877adb9 | ||
|
|
d24c7e977e | ||
|
|
6a1ff1c0a6 | ||
|
|
10126410b6 | ||
|
|
f4a575fee1 | ||
|
|
383557df61 | ||
|
|
dba4be8065 | ||
|
|
f1aac43f0f | ||
|
|
94783f46ad | ||
|
|
1d26d4fc91 | ||
|
|
7fc45226da | ||
|
|
a670c07caf | ||
|
|
09cc2bdfa5 | ||
|
|
cc897cab4c | ||
|
|
7ba9597928 | ||
|
|
d2d862420e | ||
|
|
fd6a14de8a | ||
|
|
0eb40c6ce6 | ||
|
|
e321b3c75c | ||
|
|
03d8d3bc1b | ||
|
|
f85de2b0d3 | ||
|
|
5fb42b7339 | ||
|
|
1b2630dc0d | ||
|
|
3fb4c313ec | ||
|
|
65892453be | ||
|
|
cba0ff8321 | ||
|
|
b6523c2301 | ||
|
|
e92d0a7492 | ||
|
|
0e5aab346f | ||
|
|
a5ad15be02 | ||
|
|
27ebf09c5c | ||
|
|
1c65c04b54 | ||
|
|
987882ea37 | ||
|
|
00aaed1b14 | ||
|
|
e3b1bccb6a | ||
|
|
6ca19fb003 | ||
|
|
85ff92170b | ||
|
|
7ac8c6c75b | ||
|
|
6004e7f5cd | ||
|
|
4a7e5d0720 | ||
|
|
b4042d5ccb | ||
|
|
6f66e294de | ||
|
|
5017c12324 | ||
|
|
70bc5a6fba | ||
|
|
3063973744 | ||
|
|
1a2071a120 | ||
|
|
e5dea48d3c | ||
|
|
1413aa332b | ||
|
|
37cf431e80 | ||
|
|
0c9d2dafe3 | ||
|
|
e8708a7489 | ||
|
|
79592c700f | ||
|
|
ad3783170e | ||
|
|
329dab9a67 | ||
|
|
f142f37064 | ||
|
|
0d2955b48d | ||
|
|
95da407de8 | ||
|
|
503ca1e9c2 | ||
|
|
d8722c46d9 | ||
|
|
546c2d47d5 | ||
|
|
2ba615555c | ||
|
|
e94c6be4a1 | ||
|
|
11eaad1fa7 | ||
|
|
daf7f7c268 | ||
|
|
4965c704d7 | ||
|
|
b7fe7a40ba | ||
|
|
5fcca7c7e0 | ||
|
|
b0088c82dc | ||
|
|
875cf056b7 | ||
|
|
3b2c2b16b2 | ||
|
|
45cb36f6d9 | ||
|
|
ed1bd01592 | ||
|
|
a1857af6de | ||
|
|
70e965fd55 | ||
|
|
bee01c938a | ||
|
|
6b6d22c5ba | ||
|
|
67a389cbbf | ||
|
|
d3930639db | ||
|
|
65292b010e | ||
|
|
f39a6fe517 | ||
|
|
2775def93a | ||
|
|
5cbae50ec1 | ||
|
|
51b4fa0080 | ||
|
|
64a6ea68fa | ||
|
|
d97b4477b2 | ||
|
|
b8e394e76a | ||
|
|
671d542898 | ||
|
|
9980ad0fef | ||
|
|
004deaeea1 | ||
|
|
36b8ca2bc0 | ||
|
|
890ab4a7bb | ||
|
|
490b9e2d09 | ||
|
|
ca35e8c118 | ||
|
|
eeb91de6a3 | ||
|
|
657b7195d6 | ||
|
|
054a73f297 | ||
|
|
5c6d8aacbe | ||
|
|
ac0df6bc88 | ||
|
|
f007b46c1b | ||
|
|
c5566eafeb | ||
|
|
40290ad42a | ||
|
|
705fbcd570 | ||
|
|
75ee17aeeb | ||
|
|
9b30bd5a03 | ||
|
|
94a163397b | ||
|
|
46a2608783 | ||
|
|
88e9681481 | ||
|
|
20ef8cd369 | ||
|
|
ded539b11c | ||
|
|
5e76ea820c | ||
|
|
90d2ff8fad | ||
|
|
0f42b06b48 | ||
|
|
b500ac3dbb | ||
|
|
d2539c3f1a | ||
|
|
8faebdc99c | ||
|
|
894dfdd5d8 | ||
|
|
d0d97a40a6 | ||
|
|
cf5fd403e8 | ||
|
|
72a6a5ce04 | ||
|
|
9381835a7c | ||
|
|
2c9ba9b3df | ||
|
|
3d3053f427 | ||
|
|
90e6c9b87b | ||
|
|
a9f96bf709 | ||
|
|
c950b67e4b | ||
|
|
9a74c86327 | ||
|
|
6715320e78 | ||
|
|
77805086d0 | ||
|
|
692a21ee0d | ||
|
|
ff8c12ac75 | ||
|
|
0d1f9edf3f | ||
|
|
4bfc5aafd3 | ||
|
|
a6ff1d6924 | ||
|
|
7c7c99656a | ||
|
|
66bad853ae | ||
|
|
52ac922014 | ||
|
|
4a7f35dea7 | ||
|
|
ad2cb507a4 | ||
|
|
dca23a98f1 | ||
|
|
8955732c05 | ||
|
|
762b140808 | ||
|
|
57f8221bab | ||
|
|
b7a575b837 | ||
|
|
0a4500e85b | ||
|
|
9a994e7f36 | ||
|
|
839d611f64 | ||
|
|
421946a923 | ||
|
|
1f598a0a79 | ||
|
|
d5c669bcd2 | ||
|
|
32c664d450 | ||
|
|
adb46748de | ||
|
|
59a286b0b7 | ||
|
|
c5f41479a9 | ||
|
|
b694019a40 | ||
|
|
72259059ef | ||
|
|
0fa53d62cb | ||
|
|
47d17c1298 | ||
|
|
d187b982eb | ||
|
|
b7c8601540 | ||
|
|
693b1f7a74 | ||
|
|
5f81460504 | ||
|
|
b65f432ee0 | ||
|
|
3d21ac4525 | ||
|
|
7c5b9a5b92 | ||
|
|
82ff65c3d4 | ||
|
|
68eb6defd3 | ||
|
|
e25e30dcdd | ||
|
|
48c48cb344 | ||
|
|
a1f4f9ddb7 | ||
|
|
7ca8a9e449 | ||
|
|
0c7943868d | ||
|
|
3cdce86339 | ||
|
|
8cb9713493 | ||
|
|
ef76831d37 | ||
|
|
08246f7005 | ||
|
|
c377ec69d3 | ||
|
|
a2c2b7ffee | ||
|
|
65bd3d67b4 | ||
|
|
c4671272c0 | ||
|
|
25c564bae1 | ||
|
|
3c3ec2c97c | ||
|
|
15197455f2 | ||
|
|
f7cc72be35 | ||
|
|
17da49bb78 | ||
|
|
a5754e9ec4 | ||
|
|
2584f09083 | ||
|
|
d439933b52 | ||
|
|
094d03bf7a | ||
|
|
0f5093c0b7 | ||
|
|
80748b9fe0 | ||
|
|
b244c76dd5 | ||
|
|
a91ab54449 | ||
|
|
f2dbf56db1 | ||
|
|
e5214ea2e5 | ||
|
|
11995b958a | ||
|
|
493ec4be52 | ||
|
|
604e6873ba | ||
|
|
5954f0dde5 | ||
|
|
b8e91036dd | ||
|
|
f22b490a10 | ||
|
|
49ed112ef5 | ||
|
|
471e0c05f9 | ||
|
|
c3ec827fdd | ||
|
|
ca7bdd9101 | ||
|
|
070cd0f4df | ||
|
|
86f2584162 | ||
|
|
1b00ced7ad | ||
|
|
44c7473ef9 | ||
|
|
20c80f553f | ||
|
|
b4a5149ba2 | ||
|
|
df350e6660 | ||
|
|
d76f4b27b0 | ||
|
|
a1ea2a5aa6 | ||
|
|
8608e9cd3a | ||
|
|
5f8f7ee576 | ||
|
|
d8a92a2e65 | ||
|
|
67ed930100 | ||
|
|
020c52e583 | ||
|
|
03dd318265 | ||
|
|
fc3bfda3bd | ||
|
|
7eb0ce6e90 | ||
|
|
5aa964cde9 | ||
|
|
31d7264583 | ||
|
|
93dc22a71f | ||
|
|
c4d228ad8d | ||
|
|
30faf500eb | ||
|
|
26d4bac85f | ||
|
|
e0e3cdc316 | ||
|
|
1deb52f86d | ||
|
|
39cb87dc4b | ||
|
|
04e2549239 | ||
|
|
e06cdbf0ac | ||
|
|
d366b7e4fc | ||
|
|
5aae3333bc | ||
|
|
6d40ac8644 | ||
|
|
d7c428fc8d | ||
|
|
28f8f56fa3 | ||
|
|
b1f6b5314c | ||
|
|
fdb1e8c2e4 | ||
|
|
5b02e86334 | ||
|
|
8713918bdb | ||
|
|
10627e4974 | ||
|
|
1b89e7c2ec | ||
|
|
bcf0afb25e | ||
|
|
ab22c8ca1c | ||
|
|
49513cb2ee | ||
|
|
1b5e66f9c2 | ||
|
|
1f486fc9a5 | ||
|
|
45c4a98f1d | ||
|
|
4cdbdf2125 | ||
|
|
ec5ecabdec | ||
|
|
e391301348 | ||
|
|
49866b6cf4 | ||
|
|
3252e0ce2e | ||
|
|
09d326de3a | ||
|
|
eda8614754 | ||
|
|
f1209ce06a | ||
|
|
06c1911a28 | ||
|
|
8cac773e34 | ||
|
|
365ec1fd5e | ||
|
|
40e58ed12d | ||
|
|
0c98dc54fe | ||
|
|
3b0f624302 | ||
|
|
5808b8d176 | ||
|
|
c2f8b4d1f2 | ||
|
|
56242423b2 | ||
|
|
014a781426 | ||
|
|
8113711b7a | ||
|
|
85ec6343ff | ||
|
|
daf183e2cc | ||
|
|
c6819cbd6b | ||
|
|
01ed3c3326 | ||
|
|
76b68f7ccb | ||
|
|
2bea808251 | ||
|
|
52e051bb02 | ||
|
|
74fdf649d3 | ||
|
|
2402f463e1 | ||
|
|
424d33faa0 | ||
|
|
b64c57fa36 | ||
|
|
6966b3810d | ||
|
|
02cb4764c2 | ||
|
|
89071f7226 | ||
|
|
d6eca62e56 | ||
|
|
b025e7f0f2 | ||
|
|
0f7ecad4e8 | ||
|
|
4a16aaacb6 | ||
|
|
ca08ce4262 | ||
|
|
21c758c3d3 | ||
|
|
c338f1a3fa | ||
|
|
242085d676 | ||
|
|
289d895423 | ||
|
|
eb22a84db4 | ||
|
|
d66dd99621 | ||
|
|
f2b248243c | ||
|
|
fcd358eb71 | ||
|
|
06d3739a8d | ||
|
|
bff1064dbd | ||
|
|
19678db933 | ||
|
|
3d8b682380 | ||
|
|
7b76502ee6 | ||
|
|
28687ad7c7 | ||
|
|
d420ff0e56 | ||
|
|
5a085f2514 | ||
|
|
e79ee7fb74 | ||
|
|
ed1adfd878 | ||
|
|
58a7b9ffa7 | ||
|
|
af5d046698 | ||
|
|
9e75b707ce | ||
|
|
8241b078ce | ||
|
|
c8f1e41197 | ||
|
|
cddf098f47 | ||
|
|
500cfbc19c | ||
|
|
133ae8555a | ||
|
|
f132010acb | ||
|
|
426305dc33 | ||
|
|
55282851c4 | ||
|
|
9e7d1b9ce7 | ||
|
|
e0da5f1703 | ||
|
|
4f5a70b804 | ||
|
|
42bbd1b44a | ||
|
|
fdeaf861e3 | ||
|
|
8e032a27c4 | ||
|
|
5bfd43b718 | ||
|
|
ce7f4cb9a8 | ||
|
|
99a4cf9e07 | ||
|
|
b82f6801cd | ||
|
|
184cb0b9a8 | ||
|
|
bf2e99efa6 | ||
|
|
7543d5220c | ||
|
|
ca73e1f024 | ||
|
|
718ff3a5f5 | ||
|
|
c2ccc1f980 | ||
|
|
f34d4bd6da | ||
|
|
8a4b436314 | ||
|
|
42aaf7c2a0 | ||
|
|
eb00852a71 | ||
|
|
b0535d8b40 | ||
|
|
8b4d93cc14 | ||
|
|
e2d0923122 | ||
|
|
2928d84339 | ||
|
|
b5e3883891 | ||
|
|
a2e52dadb9 | ||
|
|
ca4cb018d0 | ||
|
|
c2762d3b6f | ||
|
|
947e872850 | ||
|
|
3baa5e145f | ||
|
|
67fd35127c | ||
|
|
c421e2ddfc | ||
|
|
42febe97b5 | ||
|
|
74ca0fb763 | ||
|
|
13631ea2de | ||
|
|
a1eee5923a | ||
|
|
af92bbca2a | ||
|
|
c9287071e3 | ||
|
|
292026288a | ||
|
|
1f056998f3 | ||
|
|
4d7cb7de5f | ||
|
|
67855f21d4 | ||
|
|
8484565e95 | ||
|
|
f7d8abe8ea | ||
|
|
8ca90297e7 | ||
|
|
e08f9080c2 | ||
|
|
d01aefd1eb | ||
|
|
e1d447847f | ||
|
|
419738fbd5 | ||
|
|
7f1423dd6f | ||
|
|
9f09dcd18c | ||
|
|
91081ade3c | ||
|
|
4d933c23a8 | ||
|
|
f29bfd995d | ||
|
|
24a1b93842 | ||
|
|
bed1c311e3 | ||
|
|
1ce8d3ae9b | ||
|
|
676d76696b | ||
|
|
1d18432f8d | ||
|
|
277f13ff9d | ||
|
|
30d5d1aea9 | ||
|
|
7a0450a7f4 | ||
|
|
5bb09f469f | ||
|
|
90e9d8ff52 | ||
|
|
59a43ce5d1 | ||
|
|
5bc01aa251 | ||
|
|
9eeb979c7b | ||
|
|
eabd7592fe | ||
|
|
e089a3d8a1 | ||
|
|
7560375502 | ||
|
|
1f77b89266 | ||
|
|
7c610124d9 | ||
|
|
a0c5ef4e6f | ||
|
|
218934e767 | ||
|
|
c6a9825c0a | ||
|
|
2d365efe56 | ||
|
|
e81635f23d | ||
|
|
827315e059 | ||
|
|
e55a54f3d4 | ||
|
|
5e8b40faf6 | ||
|
|
45b9982172 | ||
|
|
9b267bb572 | ||
|
|
20c7bfaff2 | ||
|
|
dd29f970a2 | ||
|
|
b6a9a1a7ee | ||
|
|
1d87d9b370 | ||
|
|
40b6db6a27 | ||
|
|
9ba662a53e | ||
|
|
dc228c3dbb | ||
|
|
dd5dd422d1 | ||
|
|
73fdf4b957 | ||
|
|
a1de13657e | ||
|
|
93c19356d0 | ||
|
|
8a27c902ff | ||
|
|
be35b55444 | ||
|
|
a16ccf40c2 | ||
|
|
5c7b1cd5f2 | ||
|
|
fc5e593e2d | ||
|
|
f1dc79f0b5 | ||
|
|
34ea97dcea | ||
|
|
f2931163aa | ||
|
|
7e320d2748 | ||
|
|
b38d15adb3 | ||
|
|
29342e036f | ||
|
|
45660902ed | ||
|
|
72318cb687 | ||
|
|
f277223a65 | ||
|
|
262fa74f5e | ||
|
|
ebdd221c72 | ||
|
|
2b8d162c9e | ||
|
|
dc8cf0e02e | ||
|
|
a054f6a97b | ||
|
|
63d11c3eea | ||
|
|
dd2199c472 | ||
|
|
661c3bbdb0 | ||
|
|
5236e81068 | ||
|
|
c459b5eb9d | ||
|
|
384f0fb89a | ||
|
|
1d628bcf9e | ||
|
|
b8644f49ec | ||
|
|
091c1f7ef1 | ||
|
|
c838c3fa73 | ||
|
|
8bb9106b42 | ||
|
|
a7e3b0d184 | ||
|
|
449c542d5a | ||
|
|
7f19f8cf0e | ||
|
|
b2eb704394 | ||
|
|
899da0c6ea | ||
|
|
def41a7878 | ||
|
|
6dbe49089e | ||
|
|
5883e5159f | ||
|
|
26a5d48f63 | ||
|
|
aa6feb4b62 | ||
|
|
2fef0ebea8 | ||
|
|
66dd51a09a | ||
|
|
077868604d | ||
|
|
6c555cb666 | ||
|
|
0ae80272fb | ||
|
|
7bef7cf9e2 | ||
|
|
6fa24cad18 | ||
|
|
6313020ba9 | ||
|
|
b2eead386d | ||
|
|
b826123d98 | ||
|
|
cf537070d8 | ||
|
|
7a00a1dced | ||
|
|
80c37ebab2 | ||
|
|
5fb4136bcd | ||
|
|
6a249ca1dd | ||
|
|
5610d4782f | ||
|
|
fef4574c3c | ||
|
|
65239c7c25 | ||
|
|
1b123054b3 | ||
|
|
b29d17f522 | ||
|
|
04aefbf28d | ||
|
|
f8fcf02448 | ||
|
|
0e4fd5269b | ||
|
|
6328496bfb | ||
|
|
524b40b75f | ||
|
|
0241552c0d | ||
|
|
fd3f6687ce | ||
|
|
93d63749e6 | ||
|
|
41bf5450d9 | ||
|
|
d37553e7b8 | ||
|
|
101d7138c2 | ||
|
|
11de3aed51 | ||
|
|
4bb41cf886 | ||
|
|
1ae7dd9b11 | ||
|
|
a3250fac6d | ||
|
|
fabe6317ef | ||
|
|
23a4af9a52 | ||
|
|
9526dbadad | ||
|
|
6ad469c637 | ||
|
|
b30cdf0873 | ||
|
|
234bc93ddb | ||
|
|
42862852b8 | ||
|
|
7a6101c417 | ||
|
|
588123ed11 | ||
|
|
ac9852f9df | ||
|
|
d093476da5 | ||
|
|
9ad794f2cc | ||
|
|
b0f566a80d | ||
|
|
d3f3a70350 | ||
|
|
3f262fe872 | ||
|
|
150c708726 | ||
|
|
7512dbffbb | ||
|
|
3cfa882fe1 | ||
|
|
35f3b7088d | ||
|
|
07220a324d | ||
|
|
c7cecd5b4f | ||
|
|
ab1a2045d9 | ||
|
|
5e9a067e87 | ||
|
|
4cc460be64 | ||
|
|
112bfccfd5 | ||
|
|
b29cb08956 | ||
|
|
e42ad9be24 | ||
|
|
02da1700e0 | ||
|
|
a9f97e1fe2 | ||
|
|
d75077c6f9 | ||
|
|
b81939f02d | ||
|
|
0e1d90dd0c | ||
|
|
03e9c612b9 | ||
|
|
ee661e5d71 | ||
|
|
5ddffc9172 | ||
|
|
f981c782bb | ||
|
|
40dd085ef8 | ||
|
|
3fbdb7a007 | ||
|
|
d5b5bcef56 | ||
|
|
2997a15ba8 | ||
|
|
b4c02eca16 | ||
|
|
223dc87cef | ||
|
|
3ccdae0634 | ||
|
|
11af6f49c6 | ||
|
|
22dfedf6ae | ||
|
|
157d3f9dc9 | ||
|
|
f3b2ab5e60 | ||
|
|
fca53a0c2c | ||
|
|
6d302889fa | ||
|
|
a94ac63e74 | ||
|
|
19e73f8889 | ||
|
|
aacc26179e | ||
|
|
36f060dcf6 | ||
|
|
cb12c8dfd5 | ||
|
|
9cd1d1a9dc | ||
|
|
ebaf4c9c01 | ||
|
|
44240339d9 | ||
|
|
9110375173 | ||
|
|
c3557bbe3f | ||
|
|
cd924099e4 | ||
|
|
f2a311bb81 | ||
|
|
d7855e8fe5 | ||
|
|
1e52efa867 | ||
|
|
5d6f1bd2d7 | ||
|
|
48ccaa8b91 | ||
|
|
54ac0048c4 | ||
|
|
276e089419 | ||
|
|
3b8608761b | ||
|
|
413a91646c | ||
|
|
4299c6a802 | ||
|
|
4232923641 | ||
|
|
75b4bb306b | ||
|
|
4e0686f73c | ||
|
|
709f1e76d3 | ||
|
|
0c2d7b9c06 | ||
|
|
412e14a41c | ||
|
|
532b425dd9 | ||
|
|
2bb5fbdee5 | ||
|
|
38c41b72d6 | ||
|
|
30e89c3cdc | ||
|
|
b665014707 | ||
|
|
e98e7a232f | ||
|
|
5f7ad72d45 | ||
|
|
9c25365b12 | ||
|
|
0cc74b7cfe | ||
|
|
8a972ea213 | ||
|
|
2229bcc98b | ||
|
|
1bfd4672e1 | ||
|
|
15e9e8575a | ||
|
|
d7bafa6dfa | ||
|
|
35632f2109 | ||
|
|
08807b498e | ||
|
|
e8a453c567 | ||
|
|
717802611a | ||
|
|
b6a77e0231 | ||
|
|
21b966c8e6 | ||
|
|
64a1e88982 | ||
|
|
34c8b882c6 | ||
|
|
fa420d70cc | ||
|
|
2c67934191 | ||
|
|
821adcf178 | ||
|
|
8bf9482bc0 | ||
|
|
fe8ad3548b | ||
|
|
4f17bc0d86 | ||
|
|
1a43c81840 | ||
|
|
d3cf3f7a5c | ||
|
|
4f96a2a667 | ||
|
|
7b63ebfcaa | ||
|
|
724f3aa301 | ||
|
|
1ff326c89c | ||
|
|
2663f500cf | ||
|
|
43ed998ed6 | ||
|
|
a3032ab945 | ||
|
|
af9425ed8f | ||
|
|
d058ac6174 | ||
|
|
a4e7806d21 | ||
|
|
03906cc055 | ||
|
|
10833dcf39 | ||
|
|
a00323412b | ||
|
|
838a20ea95 | ||
|
|
e8ed8cbfcd | ||
|
|
5c78a5e4fe | ||
|
|
30ed4af38d | ||
|
|
6a9304dd1c | ||
|
|
1782eeb785 | ||
|
|
1aabb7d6de | ||
|
|
d4b8f9700b | ||
|
|
2ebecf1aa0 | ||
|
|
7031df4948 | ||
|
|
e0381dd757 | ||
|
|
9b2eae24d2 | ||
|
|
7362e8de4d | ||
|
|
87beb0a5f2 | ||
|
|
fc69cea4f7 | ||
|
|
ad4780a1ac | ||
|
|
72f487d2e3 | ||
|
|
0c10bd7dd7 | ||
|
|
b0418cb394 | ||
|
|
a30c81dadc | ||
|
|
40df6e87db | ||
|
|
0dff1dce8f | ||
|
|
ffd3b3e6b5 | ||
|
|
1305b0d8dd | ||
|
|
5fc41a3ea2 | ||
|
|
fc87fc7cab | ||
|
|
8887a0a6d3 | ||
|
|
ba7764aeaf | ||
|
|
28afe6f29f | ||
|
|
138ab6dbbd | ||
|
|
26c2fc21c8 | ||
|
|
fef74c3bca | ||
|
|
c390f1bfee | ||
|
|
f6698d4a84 | ||
|
|
4193196c8b | ||
|
|
da06dc3728 | ||
|
|
a0b8be5941 | ||
|
|
fe971680ea | ||
|
|
254eb8f304 | ||
|
|
c9d15901d1 | ||
|
|
59b3f5fb19 | ||
|
|
dbe9dd47ce | ||
|
|
8e35f8c3aa | ||
|
|
80970a0ac6 | ||
|
|
432f6ac4d7 | ||
|
|
eaf11009d1 | ||
|
|
df93fb773f | ||
|
|
f6887a4dac | ||
|
|
1c58c4c409 | ||
|
|
a972901438 | ||
|
|
25703296a6 | ||
|
|
7ebe97b931 | ||
|
|
76719d1bf5 | ||
|
|
cb113437f6 | ||
|
|
e6b3e42d61 | ||
|
|
c20b0169a9 | ||
|
|
89342bcb75 | ||
|
|
e036eea362 | ||
|
|
2044d633e9 | ||
|
|
8cb684e6bd | ||
|
|
997bd3392f | ||
|
|
ad44c87746 | ||
|
|
45ea2f82ba | ||
|
|
df6aa99ec2 | ||
|
|
1029dd3504 | ||
|
|
114eb6288d | ||
|
|
2ea37e6a0d | ||
|
|
ea2330b49f | ||
|
|
610bb2b85c | ||
|
|
fca6e9b932 | ||
|
|
fc7f86104e | ||
|
|
d057a9bb6d | ||
|
|
167aba6f26 | ||
|
|
9ecd84080b | ||
|
|
22d260f4e6 | ||
|
|
ea94477cd4 | ||
|
|
522c953860 | ||
|
|
f86ee84457 | ||
|
|
2d282597ca | ||
|
|
8635d89cc8 | ||
|
|
dc341ef9c1 | ||
|
|
beb31ab2fa | ||
|
|
e0214a2c2a | ||
|
|
5275daa66c | ||
|
|
fb27261568 | ||
|
|
5a20ce81e3 | ||
|
|
1454b85671 | ||
|
|
fc4dba4468 | ||
|
|
adff28dade | ||
|
|
c345ecae63 | ||
|
|
e6461380c6 | ||
|
|
deb2a3e415 | ||
|
|
c71dcd7611 | ||
|
|
c1e05664ed | ||
|
|
0f1e5f4fa4 | ||
|
|
8860915fb9 | ||
|
|
f0d486d1ff | ||
|
|
0fa20da990 | ||
|
|
b27767e8f4 | ||
|
|
0b2edd28df | ||
|
|
dd634382d7 | ||
|
|
c7fb155733 | ||
|
|
1eaa2cc619 | ||
|
|
92a47aaac5 | ||
|
|
fcffe8beb9 | ||
|
|
02fb40c507 | ||
|
|
dc6ea97877 | ||
|
|
031d53b04f | ||
|
|
3006c90fb8 | ||
|
|
9cec2688ed | ||
|
|
65c3dc21f4 | ||
|
|
cefa7d940a | ||
|
|
fab292d2de | ||
|
|
00bbe68f78 | ||
|
|
fa3591f4f2 | ||
|
|
10cfc6838d | ||
|
|
42e78f9a3e | ||
|
|
0122eabd44 | ||
|
|
435bb3f1d3 | ||
|
|
1d59d43286 | ||
|
|
fe30bf7d09 | ||
|
|
5f68ad4e19 | ||
|
|
3fc39aad33 | ||
|
|
a94653ba77 | ||
|
|
d8637b2c0f | ||
|
|
f1b6016157 | ||
|
|
de99d6d9fc | ||
|
|
bb8386ab85 | ||
|
|
ca6226359b | ||
|
|
adfafe5c54 | ||
|
|
a8d8fefceb | ||
|
|
3bb5943b20 | ||
|
|
57da04b5ec | ||
|
|
e711d168df | ||
|
|
833632eee3 | ||
|
|
abc76299c0 | ||
|
|
c96fcf319a | ||
|
|
5214a7c3ec | ||
|
|
1700f064b3 | ||
|
|
de902166a8 | ||
|
|
2f08bd1965 | ||
|
|
13c68cd799 | ||
|
|
c7d78f4594 | ||
|
|
2f1ca949f0 | ||
|
|
a6488ff9ac | ||
|
|
cb5eae888d | ||
|
|
b6f7710621 | ||
|
|
3e36f05a8c | ||
|
|
bcb7e5f2c8 | ||
|
|
3cefcd8204 | ||
|
|
4cd1871816 | ||
|
|
7aaa9583fa | ||
|
|
dd582c0306 | ||
|
|
5cded5b53e | ||
|
|
0a64567822 | ||
|
|
7ca861805d | ||
|
|
6b6faa8129 | ||
|
|
f84577bcda | ||
|
|
37f9fd3498 | ||
|
|
11aef82993 | ||
|
|
9861e2d724 | ||
|
|
7ae0d0caa3 | ||
|
|
2f5ea120cb | ||
|
|
93d2c9a3f0 | ||
|
|
60569fdd83 | ||
|
|
0f61e9c15e | ||
|
|
d81fc155cb | ||
|
|
60f7750d77 | ||
|
|
9bdfd8f4fe | ||
|
|
945b7de76f | ||
|
|
3c35eadbc4 | ||
|
|
ab86e056a2 | ||
|
|
a2bc79ddd5 | ||
|
|
51151293d7 | ||
|
|
5789e80d74 | ||
|
|
2beb2f5659 | ||
|
|
65868ab8a7 | ||
|
|
89586530a5 | ||
|
|
0d93145834 | ||
|
|
617f4acfd6 | ||
|
|
fe4113d623 | ||
|
|
9a0e15cced | ||
|
|
e0c63d58b2 | ||
|
|
f7f8ea9b97 | ||
|
|
58cad98cd8 | ||
|
|
2f8fb360aa | ||
|
|
05ec3922f1 | ||
|
|
1c9423ef31 | ||
|
|
74ffbb2172 | ||
|
|
cc9c85cc1a | ||
|
|
3f40380c69 | ||
|
|
1116b73a08 | ||
|
|
a8f252e45d | ||
|
|
b821836dc4 | ||
|
|
a42fef0905 | ||
|
|
f7e09af5c9 | ||
|
|
304cb56337 | ||
|
|
6b7b5caf54 | ||
|
|
fd461fe015 | ||
|
|
2797d2c535 | ||
|
|
f440656572 | ||
|
|
b1d019146a | ||
|
|
1dbc58d4e0 | ||
|
|
47ccb28482 | ||
|
|
744dea00ca | ||
|
|
373c2b379c | ||
|
|
95b51cf9e6 | ||
|
|
a375e924b4 | ||
|
|
957bbab440 | ||
|
|
bd48c99383 | ||
|
|
d989617825 | ||
|
|
177d9b7cb0 | ||
|
|
7a1305c1bb | ||
|
|
52d1d421a3 | ||
|
|
c3cc13595d | ||
|
|
adc8031e34 | ||
|
|
0974c74a89 | ||
|
|
c64aae6f39 | ||
|
|
b7b1714637 | ||
|
|
d8525493a1 | ||
|
|
30d0945855 | ||
|
|
ffde1f8343 | ||
|
|
96c35b41ed | ||
|
|
dc2979926f | ||
|
|
ff357dd3fb | ||
|
|
f35e15204d | ||
|
|
3ae4ba3300 | ||
|
|
8bb29f53d1 | ||
|
|
2e3c1ef4ac | ||
|
|
ada7e12b5a | ||
|
|
b8949ba3dd | ||
|
|
73fe47ba79 | ||
|
|
577920de86 | ||
|
|
0d8a314bcf | ||
|
|
e7cfde1904 | ||
|
|
517baa3235 | ||
|
|
aa41df4e7d | ||
|
|
d73953af3d | ||
|
|
54eec82311 | ||
|
|
dab244ad25 | ||
|
|
dbe7cb8dbb | ||
|
|
e814cccc44 | ||
|
|
1078fdc157 | ||
|
|
4bf4259dda | ||
|
|
03b53cbb60 | ||
|
|
7ef1340e2a | ||
|
|
c0b87adee5 | ||
|
|
4a8c2251e0 | ||
|
|
d1df5f3021 | ||
|
|
e1acea52f8 | ||
|
|
2e9f159225 | ||
|
|
2fcda9a73a | ||
|
|
78ba205f4d | ||
|
|
f44dec2c8d | ||
|
|
8c07af6fc7 | ||
|
|
cd01104de9 | ||
|
|
28f438a6bd | ||
|
|
9ff89b570f | ||
|
|
bc90376489 | ||
|
|
43b5ea801f | ||
|
|
9863e7ea6e | ||
|
|
bfba44fbad | ||
|
|
c466f063c8 | ||
|
|
ebaa39b03f | ||
|
|
fa3d7ad14b | ||
|
|
c51104f956 | ||
|
|
84795ff4d9 | ||
|
|
cc5c722e29 | ||
|
|
4fcddd1893 | ||
|
|
295af01687 | ||
|
|
c8604255e4 | ||
|
|
492826a7f2 | ||
|
|
e7284df1df | ||
|
|
3b46060caa | ||
|
|
696182cfa4 | ||
|
|
d1a1d1da8f | ||
|
|
c508984f56 | ||
|
|
54eba51b35 | ||
|
|
5e64781d65 | ||
|
|
2d4b900e33 | ||
|
|
d2481f5790 | ||
|
|
49f6104f03 | ||
|
|
2728d2aa6e | ||
|
|
0588fc6b7c | ||
|
|
e3cd52cab4 | ||
|
|
b2c6b9a320 | ||
|
|
39fa40ab12 | ||
|
|
0bf87bf4af | ||
|
|
92d37f6eaf | ||
|
|
d3b022fe17 | ||
|
|
79640f6b7d | ||
|
|
2cbdf274b1 | ||
|
|
6af5293315 | ||
|
|
bd3a2b1bb5 | ||
|
|
ff090d2f74 | ||
|
|
68e3a12a91 | ||
|
|
50eda6b678 | ||
|
|
192ec598a3 | ||
|
|
5b449999a5 | ||
|
|
afed62f6de | ||
|
|
59d1e16f9c | ||
|
|
dbe0d477d6 | ||
|
|
7248560169 | ||
|
|
f840f7d75b | ||
|
|
6e14a073ff | ||
|
|
cf3839ecec | ||
|
|
b0ca4435fd | ||
|
|
aa8cf76fb1 | ||
|
|
10b4bb598a | ||
|
|
de14d59bb3 | ||
|
|
d76272f0ea | ||
|
|
e04093efe2 | ||
|
|
bd6bbba948 | ||
|
|
37e4f35c93 | ||
|
|
0084cb7403 | ||
|
|
99e5c159a7 | ||
|
|
802121d54a | ||
|
|
160b2e95c9 | ||
|
|
7ec692cdef | ||
|
|
3dca67112d | ||
|
|
f8dac5905c | ||
|
|
48e4e41e05 | ||
|
|
22374b81de | ||
|
|
b9157e29cb | ||
|
|
bd247c35f2 | ||
|
|
7da9a45c61 | ||
|
|
122dfa12ac | ||
|
|
1905830b20 | ||
|
|
bfb41ce123 | ||
|
|
85e3ecfe0b | ||
|
|
9e073c954d | ||
|
|
b79c3f5cc4 | ||
|
|
ad5acb80fe | ||
|
|
7b7c834b08 | ||
|
|
42827be7c3 | ||
|
|
7022d27b8e | ||
|
|
ab911f1ce9 | ||
|
|
a6a1de50c8 | ||
|
|
97723fbbc9 | ||
|
|
2e58cf1168 | ||
|
|
3ca97d7258 | ||
|
|
9786dccdee | ||
|
|
a3612f53dd | ||
|
|
112257c49e | ||
|
|
d8e2b96bce | ||
|
|
6992659ba9 | ||
|
|
05696d443a | ||
|
|
f59a925897 | ||
|
|
afacdfcb95 | ||
|
|
8cb1b6b5d5 | ||
|
|
f7d9d53ad2 | ||
|
|
f4315e2c6f | ||
|
|
f0ac566c93 | ||
|
|
50f6a459cf | ||
|
|
179c80ae6d | ||
|
|
6e72f161a6 | ||
|
|
f71d8d7348 | ||
|
|
a12a3640a7 | ||
|
|
3b7bc5a56a | ||
|
|
e09d45c844 | ||
|
|
36fc321096 | ||
|
|
98a7a01dbb | ||
|
|
0f7be90500 | ||
|
|
7aaf4432d4 | ||
|
|
884a8995b4 | ||
|
|
bb42595275 | ||
|
|
a4bd89c938 | ||
|
|
f364d4fbef | ||
|
|
f899d0d8ed | ||
|
|
074cf00a7c | ||
|
|
15d10eeebc | ||
|
|
bea71f3411 | ||
|
|
eb99803b53 | ||
|
|
a60d0c4108 | ||
|
|
1cc3a13c49 | ||
|
|
feffbba6de | ||
|
|
6ea09444ec | ||
|
|
f1f486dacf | ||
|
|
fec4af3194 | ||
|
|
5342c7c82b | ||
|
|
8454ffa331 | ||
|
|
199977be6a | ||
|
|
00dbc3881f | ||
|
|
d93a5b2d20 | ||
|
|
8ca5ca6594 | ||
|
|
fe890c62f4 | ||
|
|
ed72b090af | ||
|
|
e15513bfdd | ||
|
|
dbe569c0d9 | ||
|
|
f2e1b589b5 | ||
|
|
377a37e4c9 | ||
|
|
6e163208b4 | ||
|
|
87a7bde618 | ||
|
|
37b0498699 | ||
|
|
59f976dc48 | ||
|
|
8565a853a8 | ||
|
|
dfd49e46ad | ||
|
|
73bbaced62 | ||
|
|
0c8870cb7f | ||
|
|
1a90f66f73 | ||
|
|
558e706bde | ||
|
|
1a08be0a3f | ||
|
|
29b2960805 | ||
|
|
f7f3a0bf0d | ||
|
|
ae4c186f55 | ||
|
|
af534a73fc | ||
|
|
772bbdc862 | ||
|
|
86521ec443 | ||
|
|
e3c4c9265d | ||
|
|
b3f8612e61 | ||
|
|
27b1dd04c4 | ||
|
|
46a876445f | ||
|
|
9bb58e47a7 | ||
|
|
b8447fcab8 | ||
|
|
3abcfd8fa9 | ||
|
|
f4ff2d5d2e | ||
|
|
09b41aa667 | ||
|
|
87dc4fe388 | ||
|
|
eed8a7f078 | ||
|
|
c18364c755 | ||
|
|
04946e992e | ||
|
|
5533782152 | ||
|
|
3f42487f0a | ||
|
|
8bdcd22486 | ||
|
|
b32d22731b | ||
|
|
b788cc24d1 | ||
|
|
94948f6d34 | ||
|
|
e5b76ed4c4 | ||
|
|
29a2920a2c | ||
|
|
089d35708b | ||
|
|
9b0b5bce9f | ||
|
|
ef20a0128f | ||
|
|
3039e4eb6d | ||
|
|
9143cd1485 | ||
|
|
13d31ecb7f | ||
|
|
a936b2f1f6 | ||
|
|
8d00f489cd | ||
|
|
b793dbf977 | ||
|
|
d52b38777a | ||
|
|
56cf93dff2 | ||
|
|
67d84cadad | ||
|
|
b384a24c0e | ||
|
|
66a68edbe6 | ||
|
|
dcf3d7234e | ||
|
|
0da839cce3 | ||
|
|
b6f62ac446 | ||
|
|
15ee036db1 | ||
|
|
6db8ae451a | ||
|
|
a7f3d413ef | ||
|
|
3b7be478aa | ||
|
|
7f39cdc856 | ||
|
|
80006f4730 | ||
|
|
a51025fe8f | ||
|
|
c42ed9c693 | ||
|
|
c48c8d07de | ||
|
|
4d4b6edbc2 | ||
|
|
2014ca9feb | ||
|
|
a9f631f404 | ||
|
|
ba468bb5e4 | ||
|
|
cf86d57a9f | ||
|
|
9e958f4e32 | ||
|
|
c7f6f20c9d | ||
|
|
1984f44ffe | ||
|
|
02de281e40 | ||
|
|
ae035deb92 | ||
|
|
edd1b60c3d | ||
|
|
55a3709bd1 | ||
|
|
d1fc01a407 | ||
|
|
65293f81d9 | ||
|
|
0afb0f7958 | ||
|
|
dd958872a8 | ||
|
|
62a2ce1d35 | ||
|
|
b556908cab | ||
|
|
e69a19db5c | ||
|
|
d86414febb | ||
|
|
832318fab1 | ||
|
|
80ad62ff56 | ||
|
|
ee50f254df | ||
|
|
cc0be6cd90 | ||
|
|
a7455d7edd | ||
|
|
b7c370fff7 | ||
|
|
8fd3a64e35 | ||
|
|
3761fb4377 | ||
|
|
0472f5da6a | ||
|
|
09041fb81d | ||
|
|
299a157409 | ||
|
|
53ede7b0d8 | ||
|
|
cd13aee3e7 | ||
|
|
8b3acb719e | ||
|
|
2961a90e7f | ||
|
|
db8a2d0c65 | ||
|
|
a6b5f0c9d4 | ||
|
|
8d136c6a25 | ||
|
|
4d94270cde | ||
|
|
e0d96bcb39 | ||
|
|
0d7b831661 | ||
|
|
0c9e4f67a8 | ||
|
|
da2c386b60 | ||
|
|
4770364d42 | ||
|
|
db3f131dfc | ||
|
|
d7f58c64f8 | ||
|
|
41b6aebe7c | ||
|
|
7d50332246 | ||
|
|
0d0478245f | ||
|
|
f680ede980 | ||
|
|
b7caf7a016 | ||
|
|
891198e4f3 | ||
|
|
38c5910be4 | ||
|
|
327e2fb0a4 | ||
|
|
c20c219990 | ||
|
|
4c30250782 | ||
|
|
caf23f9a04 | ||
|
|
beab808b76 | ||
|
|
6c8920f63e | ||
|
|
d108072bfb | ||
|
|
8cc7c5349a | ||
|
|
fb33ea2a0b | ||
|
|
63fca33b04 | ||
|
|
0548ad2fc6 | ||
|
|
623d615cd7 | ||
|
|
126df9647b | ||
|
|
2e2e7cd054 | ||
|
|
0ebc9f7a44 | ||
|
|
a8fba65cbd | ||
|
|
0b0476e196 | ||
|
|
f99d6dac08 | ||
|
|
cd2b7cd943 | ||
|
|
af759f2330 | ||
|
|
51be15f66d | ||
|
|
110e25e784 | ||
|
|
8414100d0b | ||
|
|
a3f4cb154e | ||
|
|
e6e22a1ca1 | ||
|
|
bb5f3cc326 | ||
|
|
7623025b90 | ||
|
|
56c98e9295 | ||
|
|
b937665b90 | ||
|
|
a7bc2293c0 | ||
|
|
0d25f7612b | ||
|
|
84e4181ed7 | ||
|
|
f66a29d1c3 | ||
|
|
dbd3881cea | ||
|
|
df753e2619 | ||
|
|
7eb6bbe65f | ||
|
|
a570fda1cb | ||
|
|
3b06fa6523 | ||
|
|
dcdbe2fbb8 | ||
|
|
dc1f36da43 | ||
|
|
cbb7082afd | ||
|
|
6ee72e119c | ||
|
|
05dbd395e6 | ||
|
|
69e7360cc3 | ||
|
|
7ef2533b98 | ||
|
|
97f87c4229 | ||
|
|
42a5cd961d | ||
|
|
bf00d3157f | ||
|
|
cf3ff4c136 | ||
|
|
7fe06adcfd | ||
|
|
ae3e5dbf2c | ||
|
|
20304590b4 | ||
|
|
2d8c0c0131 | ||
|
|
70f4cad2ca | ||
|
|
1f777a94a7 | ||
|
|
7560c64f46 | ||
|
|
bddde60522 | ||
|
|
e9366f8c76 | ||
|
|
4dba84d09e | ||
|
|
0cce2d6098 | ||
|
|
acbd8bce21 | ||
|
|
dea8a08b64 | ||
|
|
dde1bab1a8 | ||
|
|
799e402077 | ||
|
|
ce629e8e70 | ||
|
|
20cfc4ac66 | ||
|
|
132d5e8253 | ||
|
|
cb8b341fb4 | ||
|
|
982c54b605 | ||
|
|
67f543332a | ||
|
|
88ac4086c2 | ||
|
|
15f96b7239 | ||
|
|
0ea84ad799 | ||
|
|
1522b713da | ||
|
|
fe6b27bb59 | ||
|
|
df14b15397 | ||
|
|
7dd12044de | ||
|
|
06e12a30e7 | ||
|
|
d3fefd223d | ||
|
|
c0639c6608 | ||
|
|
05a2eb3df4 | ||
|
|
d4befeb536 | ||
|
|
f7f1168aad | ||
|
|
d6cf15368a | ||
|
|
a5fbf3fb80 | ||
|
|
3cf8f78745 | ||
|
|
59dab6eac7 | ||
|
|
553d861b8a | ||
|
|
7b5d94d062 | ||
|
|
cb021efaee | ||
|
|
c2344f3717 | ||
|
|
f537c606f7 | ||
|
|
c8f48a4a90 | ||
|
|
c2ad1b4e46 | ||
|
|
ba9d146d6c | ||
|
|
a5b04a0328 | ||
|
|
01249d0cb9 | ||
|
|
71c273fbcb | ||
|
|
aa9cbf7c55 | ||
|
|
429b18ed48 | ||
|
|
2690c05781 | ||
|
|
e3b688c9d8 | ||
|
|
41b6f18a5d | ||
|
|
5a59c39036 | ||
|
|
a02dd18ad7 | ||
|
|
16bdc7d0a3 | ||
|
|
40002e8040 | ||
|
|
6748c55c04 | ||
|
|
deb63b4adf | ||
|
|
015a9b9271 | ||
|
|
ab2305e259 | ||
|
|
b99c998057 | ||
|
|
6c0a7144f6 | ||
|
|
bf50fce5bd | ||
|
|
8718b156c4 | ||
|
|
7bbdd1f839 | ||
|
|
ffed1a4afa | ||
|
|
1bfd3642e8 | ||
|
|
577f4e0cc3 | ||
|
|
e2f05f3fc9 | ||
|
|
27f30631ed | ||
|
|
1ccfa96c2e | ||
|
|
a2c64e79ff | ||
|
|
7b623f85cd | ||
|
|
ed63eb6833 | ||
|
|
b8d0d3c242 | ||
|
|
fe1bfe9ae1 | ||
|
|
7154c9ee5d | ||
|
|
057c95bd1c | ||
|
|
0cafc00c4f | ||
|
|
75d2898efd | ||
|
|
764a4c99fa | ||
|
|
160513c671 | ||
|
|
a2af26635f | ||
|
|
63869deeb2 | ||
|
|
91e387e8b9 | ||
|
|
7d13146859 | ||
|
|
699d2b7e7e | ||
|
|
257de15c73 | ||
|
|
5a0225d033 | ||
|
|
bd2d0e6ad3 | ||
|
|
0c0d1d4e52 | ||
|
|
eab9603921 | ||
|
|
c07db3aa14 | ||
|
|
a19f7481b2 | ||
|
|
2905edce35 | ||
|
|
04a5d794ac | ||
|
|
264b9819ff | ||
|
|
4610204c83 | ||
|
|
ecf1f17cf4 | ||
|
|
9f942a6b65 | ||
|
|
f4ed1b32b8 | ||
|
|
ec0e871592 | ||
|
|
7a756ebc4d | ||
|
|
16dc21afff | ||
|
|
3a1c6d84f0 | ||
|
|
e684abdacd | ||
|
|
e275cb1efd | ||
|
|
60315e5b91 | ||
|
|
43b1a4bf5a | ||
|
|
cdb238e41c | ||
|
|
3871e44d9c | ||
|
|
5a51454d13 | ||
|
|
778ee02803 | ||
|
|
d102943a32 | ||
|
|
a0b27ddbd8 | ||
|
|
b950b04e89 | ||
|
|
c9a55f395b | ||
|
|
424da01878 | ||
|
|
3c933158c8 | ||
|
|
7221d488e5 | ||
|
|
1c16931e26 | ||
|
|
0a4b70dbd2 | ||
|
|
666c716bda | ||
|
|
be477d7ae3 | ||
|
|
6c8a623b88 | ||
|
|
ff8de34415 | ||
|
|
f8ca6d9833 | ||
|
|
dbab519004 | ||
|
|
e888c96591 | ||
|
|
59519f0493 | ||
|
|
e02f07d356 | ||
|
|
2c90d220b8 | ||
|
|
9feeba8d4b | ||
|
|
45090fc897 | ||
|
|
d761bdc1b1 | ||
|
|
287a8c76b5 | ||
|
|
a7dd86de71 | ||
|
|
c541a2e5de | ||
|
|
41dbf1ddac | ||
|
|
70ed6b96d1 | ||
|
|
ef26075a1c | ||
|
|
91c87446be | ||
|
|
dd207e1f02 | ||
|
|
e947870da9 | ||
|
|
f0b5f592dc | ||
|
|
773a2a6cfe | ||
|
|
c2a7e384ba | ||
|
|
4a8b35ef5f | ||
|
|
5cd0db32df | ||
|
|
ef01de6149 | ||
|
|
4e9749f655 | ||
|
|
abb7a1fd47 | ||
|
|
60bcee8c1d | ||
|
|
8ded524236 | ||
|
|
0a47f48191 | ||
|
|
8601267b90 | ||
|
|
6767e0c52d | ||
|
|
5141d1775d | ||
|
|
374af996d9 | ||
|
|
dcc9624c15 | ||
|
|
b3df1a2bf8 | ||
|
|
3fc3c02a4f | ||
|
|
874ddf9a32 | ||
|
|
69aeb70cc3 | ||
|
|
47e60cefe3 | ||
|
|
4ea7f3cda5 | ||
|
|
03288b521a | ||
|
|
4b0a7a6e1f | ||
|
|
383287adcd | ||
|
|
5c00afc6fe | ||
|
|
025e0e8093 | ||
|
|
35fb1f8585 | ||
|
|
9fb14eef0e | ||
|
|
5d6d0c6176 | ||
|
|
dd707242ef | ||
|
|
4f32f1285a | ||
|
|
a6ecdbae29 | ||
|
|
4904d100ff | ||
|
|
7a1f94bc20 | ||
|
|
f9320fff8f | ||
|
|
3dde83d8a0 | ||
|
|
f3e77a989c | ||
|
|
f3539b0354 | ||
|
|
4e5d4b9695 | ||
|
|
864315f6d1 | ||
|
|
713b7338ea | ||
|
|
53117b2f4c | ||
|
|
90623142e1 | ||
|
|
43503a20e5 | ||
|
|
ec53b27dfe | ||
|
|
238ecfc539 | ||
|
|
b888792940 | ||
|
|
e3d1ab52f8 | ||
|
|
f9c2874c35 | ||
|
|
2304f005e3 | ||
|
|
b95f836256 | ||
|
|
c94f9f21af | ||
|
|
fc336e3733 | ||
|
|
fc1df9f9a5 | ||
|
|
5a604bfdee | ||
|
|
281951a86b | ||
|
|
35ec3adadc | ||
|
|
b9256a1ba7 | ||
|
|
734c9a1aa5 | ||
|
|
6502a71083 | ||
|
|
f2634b44cd | ||
|
|
dcf9c467c3 | ||
|
|
3dcacc1f8d | ||
|
|
7cb81b0f35 | ||
|
|
8155ba5224 | ||
|
|
7169060425 | ||
|
|
ddb9dd4e45 | ||
|
|
2a05f24cb6 | ||
|
|
039e4c662d | ||
|
|
70351677a1 | ||
|
|
33cadfb97d | ||
|
|
5fbaeda217 | ||
|
|
b44ba0d21a | ||
|
|
b42532afe9 | ||
|
|
da6a335b87 | ||
|
|
0e8fef73bb | ||
|
|
7122a960fa | ||
|
|
d5b649a1a4 | ||
|
|
233c724b2d | ||
|
|
e1eb001872 | ||
|
|
5899d7034f | ||
|
|
23c3e9482f | ||
|
|
dd980d9dca | ||
|
|
d4ed50a915 | ||
|
|
d986c7d126 | ||
|
|
67a2a4f249 | ||
|
|
a6e87e7e08 | ||
|
|
33da8a7f62 | ||
|
|
68c533f777 | ||
|
|
671eecf203 | ||
|
|
2454ac8ef1 | ||
|
|
20f097faa4 | ||
|
|
5dba8b493d | ||
|
|
eb27013fba | ||
|
|
4f8d1c5c9d | ||
|
|
74168c3e05 | ||
|
|
cece848801 | ||
|
|
7ddbeaa078 | ||
|
|
2d4aa7ff8b | ||
|
|
79e044ac31 | ||
|
|
15ae5a5135 | ||
|
|
5684b7c329 | ||
|
|
7e96120353 | ||
|
|
cc36421fe5 | ||
|
|
683aa727d5 | ||
|
|
ea07b495ac | ||
|
|
8d211c3524 | ||
|
|
ab8df82563 | ||
|
|
8ba9c4ab97 | ||
|
|
2f70b8682e | ||
|
|
86dd4ea480 | ||
|
|
204e5f4418 | ||
|
|
044a9bb6d3 | ||
|
|
a31143328e | ||
|
|
2bb29a105c | ||
|
|
0013d98d04 | ||
|
|
916743f44b | ||
|
|
edebe65d95 | ||
|
|
c43fcd0af6 | ||
|
|
9a5c2b88dc | ||
|
|
aaee0414c8 | ||
|
|
d0c2fb9761 | ||
|
|
4f076c6924 | ||
|
|
51d4d1451a | ||
|
|
7b9d76dc65 | ||
|
|
f91aeea91c | ||
|
|
4cabf84be9 | ||
|
|
8a1f038a80 | ||
|
|
c9cab9ab74 | ||
|
|
bd78120bd5 | ||
|
|
0de3bf0ac7 | ||
|
|
2d144a8b43 | ||
|
|
1a140a5515 | ||
|
|
1ea8cfbfb0 | ||
|
|
64fda95186 | ||
|
|
7843c0c1b0 | ||
|
|
a3784854a7 | ||
|
|
8b7c000f47 | ||
|
|
5b8d7a3f29 | ||
|
|
328dbd57d4 | ||
|
|
a8c448f4cb | ||
|
|
de0419228f | ||
|
|
d8a4e47a13 | ||
|
|
bc920949cb | ||
|
|
0717f8591c | ||
|
|
affb65219e | ||
|
|
3e628f2678 | ||
|
|
ddabc38e3f | ||
|
|
c0097497be | ||
|
|
78198bfdaf | ||
|
|
2fb9c923f4 | ||
|
|
c485011ed1 | ||
|
|
0b8ae68213 | ||
|
|
0e73128f40 | ||
|
|
927c003d22 | ||
|
|
bd73823828 | ||
|
|
11582bc7d3 | ||
|
|
20ed4f96ba | ||
|
|
19790e9011 | ||
|
|
3a29e03458 | ||
|
|
e2eb685d76 | ||
|
|
86cb28fe34 | ||
|
|
bcc9679339 | ||
|
|
e5aeff50dc | ||
|
|
5f44c195e9 | ||
|
|
e4387e4aad | ||
|
|
b1b336804d | ||
|
|
9985c43817 | ||
|
|
190cac394c | ||
|
|
ae71a5abf6 | ||
|
|
df199c5788 | ||
|
|
c1ba4f1b55 | ||
|
|
afc0097b12 | ||
|
|
4e9f971c91 | ||
|
|
b1cc28bbda | ||
|
|
80d30bdd30 | ||
|
|
f27566669b | ||
|
|
29f12ddaf4 | ||
|
|
ed15ff0515 | ||
|
|
a8f71f79fe | ||
|
|
68c2478e0e | ||
|
|
be07fe9e9f | ||
|
|
4d8b661d51 | ||
|
|
ca0981645f | ||
|
|
bc7e02b47a | ||
|
|
55a55e9f74 | ||
|
|
b41d40da40 | ||
|
|
ba3e088b23 | ||
|
|
1b59b0b739 | ||
|
|
093cfcdf42 | ||
|
|
41801a60ad | ||
|
|
5ff0957861 | ||
|
|
6913b8beb5 | ||
|
|
c8d17bc363 | ||
|
|
2cbf1259a8 | ||
|
|
59dc513ac3 | ||
|
|
87b110bb86 | ||
|
|
1b3e1a7abe | ||
|
|
0afabc60ae | ||
|
|
2d4ea720eb | ||
|
|
9c757bbe6e | ||
|
|
40e0d72105 | ||
|
|
eba788e8c9 | ||
|
|
444b111a62 | ||
|
|
a364ab4ea7 | ||
|
|
be7840c827 | ||
|
|
a46e51e8db | ||
|
|
789ebb8990 | ||
|
|
e3cd96bf19 | ||
|
|
7746042adc | ||
|
|
dfe80556d3 | ||
|
|
b3529dc748 | ||
|
|
543c4423a2 | ||
|
|
3bd4d32b8d | ||
|
|
56a686d3e0 | ||
|
|
2f97c789dd | ||
|
|
04ca808e80 | ||
|
|
9b79743c5d | ||
|
|
a386826808 | ||
|
|
668894fc4d | ||
|
|
dc882e6279 | ||
|
|
d6eebf82be | ||
|
|
799f509ba9 | ||
|
|
688a234127 | ||
|
|
095697900b | ||
|
|
6ccf617d62 | ||
|
|
b3b4811b2c | ||
|
|
966e4246e5 | ||
|
|
9377c4f3ad | ||
|
|
b876128635 | ||
|
|
c0da801580 | ||
|
|
149310e1ec | ||
|
|
4e625c18dc | ||
|
|
a5d5113be3 | ||
|
|
7900c493af | ||
|
|
76fe5d8831 | ||
|
|
37031721dd | ||
|
|
3cc283cbee | ||
|
|
35cf98fff2 | ||
|
|
ca41ea2d5c | ||
|
|
daffc4e6a4 | ||
|
|
5f3cb9019b | ||
|
|
13c7182948 | ||
|
|
4951b58b21 | ||
|
|
7be7586971 | ||
|
|
edc76795d4 | ||
|
|
6d64098288 | ||
|
|
4351110082 | ||
|
|
6652138d3e | ||
|
|
c8d0d475e4 | ||
|
|
39b25029fc | ||
|
|
b5cabd6d8e | ||
|
|
b169a5c707 | ||
|
|
4f816c06b0 | ||
|
|
b5f49d9563 | ||
|
|
274fd5ab8b | ||
|
|
e48d7de763 | ||
|
|
f426940bd2 | ||
|
|
6c11dd12d7 | ||
|
|
c2b14d3075 | ||
|
|
8a144ebfee | ||
|
|
5440fcdf54 | ||
|
|
66b2d496af | ||
|
|
444a0282d7 | ||
|
|
f897ab4eb8 | ||
|
|
993c187e37 | ||
|
|
3ce67b282f | ||
|
|
606e59a5d0 | ||
|
|
0f26b1eafb | ||
|
|
d2a9d731ed | ||
|
|
80e6b1fc01 | ||
|
|
58a89edad7 | ||
|
|
bfe47eb152 | ||
|
|
32adc38e94 | ||
|
|
1584971908 | ||
|
|
025bebb3e2 | ||
|
|
4047adcc35 | ||
|
|
d4cf03c9fd | ||
|
|
5ba2068fc2 | ||
|
|
3ce7d410c8 | ||
|
|
74f28021e7 | ||
|
|
f90cbb636a | ||
|
|
c8929ca0cb | ||
|
|
9fb5bb620d | ||
|
|
ed58f32052 | ||
|
|
6d5743c506 | ||
|
|
a551619de6 | ||
|
|
873b113cb3 | ||
|
|
2280e66d73 | ||
|
|
2133897bbe | ||
|
|
bee5cb55a1 | ||
|
|
0a0838b616 | ||
|
|
b376dfa1e6 | ||
|
|
7ac0577b34 | ||
|
|
f16e060e87 | ||
|
|
9aaae24583 | ||
|
|
915ced7b92 | ||
|
|
5a36b9075f | ||
|
|
b5a7f46ecc | ||
|
|
4381657c5e | ||
|
|
d440b2f2b2 | ||
|
|
5b42aea9e7 | ||
|
|
e1d7a6b9ac | ||
|
|
38778f8adc | ||
|
|
8b7a86bd17 | ||
|
|
619bae745b | ||
|
|
2cf72bad30 | ||
|
|
e6959f093c | ||
|
|
8d783e8e1f | ||
|
|
f03098551e | ||
|
|
6887805402 | ||
|
|
7f9074adbf | ||
|
|
64847afc3f | ||
|
|
c4df8090e2 | ||
|
|
fe3523f47a | ||
|
|
199d846acb | ||
|
|
b07e479840 | ||
|
|
9bf37fde02 | ||
|
|
1054325b2d | ||
|
|
ef8cb11707 | ||
|
|
c23c40df8a | ||
|
|
77d606df34 | ||
|
|
e717c9dba2 | ||
|
|
3d312e2140 | ||
|
|
18de21f723 | ||
|
|
6aebaf6f47 | ||
|
|
641f6977a9 | ||
|
|
a092a2fa43 | ||
|
|
84c2b0c3d7 | ||
|
|
24ce7c1991 | ||
|
|
8be3465f94 | ||
|
|
227eac10f1 | ||
|
|
1cbd5485e7 | ||
|
|
610c3cf681 | ||
|
|
96d45cc341 | ||
|
|
8ba7d02fdb | ||
|
|
ef8a199a5a | ||
|
|
db83643c1e | ||
|
|
9b6f775276 | ||
|
|
69040dd668 | ||
|
|
14b5914233 | ||
|
|
a0eabd2298 | ||
|
|
9b27298d54 | ||
|
|
3075b4515a | ||
|
|
39bc417706 | ||
|
|
e11e32cd52 | ||
|
|
019a7bd66b | ||
|
|
4d89ce5d50 | ||
|
|
c31027b284 | ||
|
|
f17ec7a4f5 | ||
|
|
deec6aab1a | ||
|
|
3e8d9a1987 | ||
|
|
5e465a298f | ||
|
|
515c9e7811 | ||
|
|
03c8309703 | ||
|
|
2c28d6b10c | ||
|
|
df79443ed8 | ||
|
|
2b891f7f1d | ||
|
|
e4ed0b1884 | ||
|
|
c8c1c09189 | ||
|
|
08ae8cc3cb | ||
|
|
201673ca8a | ||
|
|
29e23ac9ce | ||
|
|
00483e8cdd | ||
|
|
83da01a2e1 | ||
|
|
7cd6ff054b | ||
|
|
6be2bb2289 | ||
|
|
439defca42 | ||
|
|
8a85bb2989 | ||
|
|
5e7519183d | ||
|
|
40e7eca1ee | ||
|
|
481f02de88 | ||
|
|
6a90856f0e | ||
|
|
dcbbee8adb | ||
|
|
225adcc836 | ||
|
|
0f607413d0 | ||
|
|
922553032b | ||
|
|
b49999721c | ||
|
|
de4c4eedd8 | ||
|
|
bcc984fc09 | ||
|
|
d53262fab6 | ||
|
|
532e44bcea | ||
|
|
3fb67629c1 | ||
|
|
6145465823 | ||
|
|
fb5091a388 | ||
|
|
d6c68f1a84 | ||
|
|
0d05f9ba80 | ||
|
|
a300df0020 | ||
|
|
a50158cbeb | ||
|
|
7278fd25e5 | ||
|
|
6fb8c0ec4c | ||
|
|
07cedc55e2 | ||
|
|
ae3cc81f03 | ||
|
|
97a45e3b02 | ||
|
|
451b290b79 | ||
|
|
499ea07934 | ||
|
|
afa06267a2 | ||
|
|
d4a6d9c076 | ||
|
|
c3f6112443 | ||
|
|
3411b736dd | ||
|
|
c603b9c40b | ||
|
|
1e2c5d038f | ||
|
|
9d6d96adf3 | ||
|
|
8d574ecb34 | ||
|
|
9ebb2ac2e4 | ||
|
|
7b935eec5d | ||
|
|
b209f66654 | ||
|
|
b98b4951b4 | ||
|
|
227d62a5dc | ||
|
|
7fae5553a8 | ||
|
|
6ead019873 | ||
|
|
6d2ab4d270 | ||
|
|
a6b58bc88d | ||
|
|
ea3c37d754 | ||
|
|
e58b00d9a2 | ||
|
|
b75e90f8c9 | ||
|
|
e82f3439c3 | ||
|
|
0bc8e3bee5 | ||
|
|
13f80acb2d | ||
|
|
8fe813acff | ||
|
|
0ed4fc6a12 | ||
|
|
b37bf06de8 | ||
|
|
27bbf0ccaf | ||
|
|
ed56d52af3 | ||
|
|
fb457968ec | ||
|
|
0bea2e2b94 | ||
|
|
72d02f442e | ||
|
|
bcf63b5d27 | ||
|
|
e88442cb46 | ||
|
|
930609e875 | ||
|
|
8752d08ce9 | ||
|
|
c46ceb06b4 | ||
|
|
92e4ecce3b | ||
|
|
8a2f673903 | ||
|
|
ac0dd90c37 | ||
|
|
9470850258 | ||
|
|
2982e9943e | ||
|
|
80e13bc24a | ||
|
|
9a677534a7 | ||
|
|
af08d67fad | ||
|
|
a2901d61ea | ||
|
|
aaae83efec | ||
|
|
7369298638 | ||
|
|
a761bd20fa | ||
|
|
01ace11293 | ||
|
|
205e95a246 | ||
|
|
2c2a43e1ec | ||
|
|
0c9a6da623 | ||
|
|
888d91d14a | ||
|
|
2c0cc87b4c | ||
|
|
ee0fadf247 | ||
|
|
9fb89d7fd2 | ||
|
|
af5c36e4ad | ||
|
|
a5f943e227 | ||
|
|
f8532ba812 | ||
|
|
fac4e151cc | ||
|
|
f3a0a25380 | ||
|
|
5a237795ea | ||
|
|
3a99a77104 | ||
|
|
5ce8050e46 | ||
|
|
5ae3a020bd | ||
|
|
af193291fa | ||
|
|
b39df5cef0 | ||
|
|
dc61c9e277 | ||
|
|
10a15e1188 | ||
|
|
a2bb6a4f1f | ||
|
|
38a19fa574 | ||
|
|
9ec4b59afb | ||
|
|
20877146df | ||
|
|
2a2d556551 | ||
|
|
0f762d98a4 | ||
|
|
36752cb6a8 | ||
|
|
a7f2d89e3f | ||
|
|
90b65c6618 | ||
|
|
190ec0c14c | ||
|
|
7250a300df | ||
|
|
34bb00450d | ||
|
|
63dadd8983 | ||
|
|
67c990e8cf | ||
|
|
0292e20c86 | ||
|
|
9088c8741a | ||
|
|
faedea2120 | ||
|
|
e1a0f5706d | ||
|
|
a78a4e6716 | ||
|
|
6bbf927f57 | ||
|
|
4c32bc8e22 | ||
|
|
df671a77f6 | ||
|
|
dca9def42c | ||
|
|
edee7ea284 | ||
|
|
658d09ed84 | ||
|
|
c41de8f270 | ||
|
|
9d2ee2127d | ||
|
|
e49ece8793 | ||
|
|
188274277a | ||
|
|
c0cb3945f1 | ||
|
|
e07795e8f0 | ||
|
|
bda454fe9c | ||
|
|
856e77053e | ||
|
|
62dad721fc | ||
|
|
2ce145f359 | ||
|
|
7e7291ace9 | ||
|
|
61c853a3c1 | ||
|
|
4346139d65 | ||
|
|
1476f83ba7 | ||
|
|
c959d64099 | ||
|
|
e67d26caeb | ||
|
|
86c3fa0df0 | ||
|
|
75ae57e194 | ||
|
|
d42feae0af | ||
|
|
45971b8083 | ||
|
|
8e9bbd1bb3 | ||
|
|
ec10a3eab4 | ||
|
|
49deb4af24 | ||
|
|
10f171b6e4 | ||
|
|
735db1a12b | ||
|
|
b2f4cc2dc5 | ||
|
|
d43b587d17 | ||
|
|
6b07a955f2 | ||
|
|
e23c02575d | ||
|
|
b1bf634136 | ||
|
|
459faf4dfb | ||
|
|
8cdceb83b2 | ||
|
|
6aa1ec0802 | ||
|
|
0727f7054b | ||
|
|
37cc611e3f | ||
|
|
c39e6c4423 | ||
|
|
1566656af3 | ||
|
|
737eba57bd | ||
|
|
d32cedd7dc | ||
|
|
2b6aa26703 | ||
|
|
95f1336060 | ||
|
|
f46ee93597 | ||
|
|
56d799f449 | ||
|
|
6251652c93 | ||
|
|
6a2c9a0dc1 | ||
|
|
4dd709b543 | ||
|
|
aa9f5b8c4a | ||
|
|
f485f3fdb5 | ||
|
|
c6a0ec64cb | ||
|
|
e0b179e5f3 | ||
|
|
dac7a3d272 | ||
|
|
beed123fb0 | ||
|
|
267e582827 | ||
|
|
69c02cae76 | ||
|
|
4f386663e7 | ||
|
|
eaae0547f2 | ||
|
|
cc5cfc7525 | ||
|
|
795dee85ef | ||
|
|
dbd5bef038 | ||
|
|
dd2a420578 | ||
|
|
eeee30ca03 | ||
|
|
82e8792737 | ||
|
|
6b18b3df34 | ||
|
|
fcebed19b9 | ||
|
|
af5f6176b5 | ||
|
|
424cc46db0 | ||
|
|
ce47ccecc4 | ||
|
|
d940f17390 | ||
|
|
6351b5d0dc | ||
|
|
9c4ac24a66 | ||
|
|
bf66df2a29 | ||
|
|
962ce380cd | ||
|
|
9769afb944 | ||
|
|
052b45a510 | ||
|
|
84a96e862e | ||
|
|
3105235a7a | ||
|
|
71a32477e4 | ||
|
|
342bce2168 | ||
|
|
86d8cbc4d2 | ||
|
|
6b63bd6a44 | ||
|
|
225707c877 | ||
|
|
66da6f18e3 | ||
|
|
bdf8bf391c | ||
|
|
f66d9e1a22 | ||
|
|
ccd3d96942 | ||
|
|
b203f2abaa | ||
|
|
5490a2f3ba | ||
|
|
ba44235471 | ||
|
|
be556f9e36 | ||
|
|
5e5ba11601 | ||
|
|
e384df30fa | ||
|
|
4635dacf7f | ||
|
|
18e4d270d9 | ||
|
|
3bcb91f6ae | ||
|
|
5e574a355d | ||
|
|
640c7c5fa3 | ||
|
|
eb6395a62c | ||
|
|
2b2845aa07 | ||
|
|
54d8c66f3e | ||
|
|
95bbf1b190 | ||
|
|
ee2dab51f3 | ||
|
|
f63409eed9 | ||
|
|
ad8940ad73 | ||
|
|
b8bfb5a56c | ||
|
|
3f58823430 | ||
|
|
e488220bfc | ||
|
|
e6e0771496 | ||
|
|
fe77d43fa0 | ||
|
|
737a7a2db2 | ||
|
|
6ae8d10132 | ||
|
|
7d249b6d3b | ||
|
|
a532b82771 | ||
|
|
e8756482aa | ||
|
|
35d6da785b | ||
|
|
dbdcbd4b9e | ||
|
|
a2b6f49c5c | ||
|
|
a31ed4a723 | ||
|
|
b2fc84c98e | ||
|
|
927369b06d | ||
|
|
401fd37e35 | ||
|
|
759cdf10c5 | ||
|
|
6e7ce1eec1 | ||
|
|
2a65955e88 | ||
|
|
00b6c6a437 | ||
|
|
21ef3b0ecf | ||
|
|
5b0d6a1375 | ||
|
|
0510da0853 | ||
|
|
365d22d076 | ||
|
|
1760169ef9 | ||
|
|
32ea224933 | ||
|
|
38a067e203 | ||
|
|
ab6f1b6df7 | ||
|
|
b36802edff | ||
|
|
1d2af0f291 | ||
|
|
40e8c5e2b0 | ||
|
|
e11d0d37ee | ||
|
|
ea4266538a | ||
|
|
aa7bf9169f | ||
|
|
a95e83ab6e | ||
|
|
86daaf4bf2 | ||
|
|
d5b4f02932 | ||
|
|
15deec6c53 | ||
|
|
17b1875151 | ||
|
|
bfda8f0b8a | ||
|
|
7bb0ff986b | ||
|
|
f3cfef4021 | ||
|
|
3184c3c21b | ||
|
|
9264737985 | ||
|
|
c9b353a689 | ||
|
|
fda6502f33 | ||
|
|
0fdac82b93 | ||
|
|
6b3d6d5211 | ||
|
|
cb89ee39f5 | ||
|
|
be827be742 | ||
|
|
8fcecd59a0 | ||
|
|
83b49b23e4 | ||
|
|
f5ee618986 | ||
|
|
8339b88180 | ||
|
|
f2958818c8 | ||
|
|
608547c62c | ||
|
|
20f604948f | ||
|
|
f72a4f966d | ||
|
|
bd00db4292 | ||
|
|
68debc474a | ||
|
|
6a7f993a9a | ||
|
|
ae25931b37 | ||
|
|
84fe6654cc | ||
|
|
d2d023cca7 | ||
|
|
64908e0080 | ||
|
|
d4e1899747 | ||
|
|
7f9b8d68ac | ||
|
|
5b1e849bde | ||
|
|
fba6de76b1 | ||
|
|
8419b42e83 | ||
|
|
5f38c15b1f | ||
|
|
c3b72baa8e | ||
|
|
16d0416f22 | ||
|
|
b9aa4f4478 | ||
|
|
8d3ad3a8c1 | ||
|
|
5a689ce897 | ||
|
|
35e22703af | ||
|
|
41a2d0e06c | ||
|
|
4d95e35c06 | ||
|
|
4e553f34ba | ||
|
|
b910726c43 | ||
|
|
64e3cab6ab | ||
|
|
f3196396a2 | ||
|
|
148336929d | ||
|
|
2f3ec3a77f | ||
|
|
8d7a487013 | ||
|
|
622464ff5e | ||
|
|
b45a44e405 | ||
|
|
a196958bd6 | ||
|
|
f101418658 | ||
|
|
aa05a1e81d | ||
|
|
384bc62f25 | ||
|
|
46e62f1a9a | ||
|
|
c5c2014081 | ||
|
|
c668c603cc | ||
|
|
8f3b7c179e | ||
|
|
ea18c47011 | ||
|
|
ced7110a78 | ||
|
|
92f13eb8bf | ||
|
|
1312ef7e50 | ||
|
|
e992979113 | ||
|
|
d317b49940 | ||
|
|
6be53468c5 | ||
|
|
046094bdcb | ||
|
|
1064c270d9 | ||
|
|
2447fccf1e | ||
|
|
c43c711f72 | ||
|
|
f354e6de69 | ||
|
|
08d60fcbf2 | ||
|
|
4cda54774a | ||
|
|
613475ac26 | ||
|
|
12615c46f8 | ||
|
|
20d23fcb92 | ||
|
|
b335840f97 | ||
|
|
6d84f59e6b | ||
|
|
c5efec678e | ||
|
|
83cb89e4f7 | ||
|
|
6bdf689d0f | ||
|
|
e164362069 | ||
|
|
72e3f33f28 | ||
|
|
ccfd907914 | ||
|
|
5a44e63cad | ||
|
|
d96cca3822 | ||
|
|
06580bf0e4 | ||
|
|
b21bd64764 | ||
|
|
6b97dc6734 | ||
|
|
4e3f328a02 | ||
|
|
8380ca2fdd | ||
|
|
ec678bc6d2 | ||
|
|
2b5e2d4760 | ||
|
|
89abad7980 | ||
|
|
d84da5bdbf | ||
|
|
5cc1d9521c | ||
|
|
a199fc6113 | ||
|
|
655e34b166 | ||
|
|
5ea3a02d6a | ||
|
|
15c68c9594 | ||
|
|
08438608d1 | ||
|
|
2da94e0fdf | ||
|
|
63165764dc | ||
|
|
103810ce20 | ||
|
|
f59356b96b | ||
|
|
4a8511f680 | ||
|
|
cd6698c688 | ||
|
|
c7ca9d7e36 | ||
|
|
d70b759cb9 | ||
|
|
ae3dda0f8f | ||
|
|
e229ba5945 | ||
|
|
dcc50093bb | ||
|
|
0831690f79 | ||
|
|
98ef51514f | ||
|
|
ac9993394c | ||
|
|
849c3fd9c9 | ||
|
|
da58fcbfce | ||
|
|
fa3cd9736f | ||
|
|
a0c5d17539 | ||
|
|
224cd04673 | ||
|
|
52243d0870 | ||
|
|
d519873fa4 | ||
|
|
a76dcd4ba1 | ||
|
|
2d07185300 | ||
|
|
518e1df257 | ||
|
|
e0def66959 | ||
|
|
772d970074 | ||
|
|
ba7d85145a | ||
|
|
fb73dceab0 | ||
|
|
13fd83e0ba | ||
|
|
719ba75fcc | ||
|
|
13ab98440c | ||
|
|
1752004301 | ||
|
|
536a5f7cff | ||
|
|
a69aece23a | ||
|
|
77d3815baa | ||
|
|
e225e17386 | ||
|
|
3290208749 | ||
|
|
3106187aac | ||
|
|
eae490b5b1 | ||
|
|
bcce77508a | ||
|
|
e9bdf02cfc | ||
|
|
77f659c9b9 | ||
|
|
c66e157a14 | ||
|
|
2c927277e2 | ||
|
|
29fe1c86da | ||
|
|
1ae9c48370 | ||
|
|
078a8b40e9 | ||
|
|
9daeae1695 | ||
|
|
014e016058 | ||
|
|
f41f93af3a | ||
|
|
51539521b1 | ||
|
|
522dec34a5 | ||
|
|
ea6d76cce6 | ||
|
|
7c7d61f61e | ||
|
|
184dde92a2 | ||
|
|
7e43c794fd | ||
|
|
c7285967d6 | ||
|
|
41f4baadb9 | ||
|
|
20082ec9fb | ||
|
|
8dae8c52c0 | ||
|
|
068076c0d5 | ||
|
|
c0ae44a41b | ||
|
|
593e8e1f63 | ||
|
|
707cf35f0a | ||
|
|
30f11d0e16 | ||
|
|
53d6ab6c23 | ||
|
|
280e44304a | ||
|
|
79fac4466e | ||
|
|
0e9f09e582 | ||
|
|
25d0fdf8ff | ||
|
|
1d089d4541 | ||
|
|
1c78663378 | ||
|
|
7666022840 | ||
|
|
946c8b498a | ||
|
|
406ca66c8d | ||
|
|
ed971df93a | ||
|
|
74c054b2a5 | ||
|
|
017a10189c | ||
|
|
f1c361855e | ||
|
|
3d72df4666 | ||
|
|
6f732a9957 | ||
|
|
447bf77dfe | ||
|
|
6621ef6a0b | ||
|
|
e327c8758e | ||
|
|
8a08de5691 | ||
|
|
3d7375be8b | ||
|
|
da754e9a71 | ||
|
|
cc9ec806b2 | ||
|
|
72cf037c0d | ||
|
|
8ae08b29e4 | ||
|
|
4967fa020f | ||
|
|
54f2640ef2 | ||
|
|
56246592c7 | ||
|
|
1ff5d71e12 | ||
|
|
67c42c5911 | ||
|
|
d0b514890a | ||
|
|
c0285fbc15 | ||
|
|
a438c841e1 | ||
|
|
19d7c2b336 | ||
|
|
afb6c70909 | ||
|
|
d9af496b13 | ||
|
|
a55cf40b1b | ||
|
|
b19008d1b8 | ||
|
|
6f88c81616 | ||
|
|
43cb230f19 | ||
|
|
fd6a59202d | ||
|
|
e353f66eaa | ||
|
|
9293bcfb1c | ||
|
|
d154118600 | ||
|
|
c2273d2c8e | ||
|
|
495ba01d8e | ||
|
|
f627a02886 | ||
|
|
5f9daa6640 | ||
|
|
9edda556de | ||
|
|
3891a52aeb | ||
|
|
1e5e3353f3 | ||
|
|
7aa4b8247c | ||
|
|
37339ddafc | ||
|
|
dcb51683c5 | ||
|
|
74a4a788b1 | ||
|
|
7909273a21 | ||
|
|
130e8dbd40 | ||
|
|
40e2ebed95 | ||
|
|
bf9b33acec | ||
|
|
7710a33b6c | ||
|
|
af3ea2d4fd | ||
|
|
52a3255936 | ||
|
|
5ab9ca1c0d | ||
|
|
461f602992 | ||
|
|
46180435cc | ||
|
|
7f2699c6da | ||
|
|
aede5c486b | ||
|
|
fb2407386f | ||
|
|
aa59c46c4c | ||
|
|
07afc4953a | ||
|
|
c5557fc488 | ||
|
|
8c14150536 | ||
|
|
c127903127 | ||
|
|
0881cf1379 | ||
|
|
d0a16b0ec0 | ||
|
|
290beb90a7 | ||
|
|
0a4ef17135 | ||
|
|
e0c32ce700 | ||
|
|
e65144a105 | ||
|
|
ae40445dba | ||
|
|
25a8240d12 | ||
|
|
1a4a180e8c | ||
|
|
aea10a3b93 | ||
|
|
f855862ade | ||
|
|
91d37c7875 | ||
|
|
5707b93110 | ||
|
|
81b2d0732f | ||
|
|
ec0538d251 | ||
|
|
eb0c629fad | ||
|
|
509d3f6d30 | ||
|
|
c576af7c6f | ||
|
|
f3682f0e8e | ||
|
|
1c58913eeb | ||
|
|
cfbc294832 | ||
|
|
64ad8b1dac | ||
|
|
fb079f9e50 | ||
|
|
39b09f8f87 | ||
|
|
53d26e5c5c | ||
|
|
e21f3e6c73 | ||
|
|
edd46eb3d1 | ||
|
|
e71238571a | ||
|
|
2867ec509e | ||
|
|
d853a9ebbe | ||
|
|
e0d2fa98f3 | ||
|
|
cc90f83463 | ||
|
|
9ea1238e1b | ||
|
|
1530abbd1a | ||
|
|
3bfcd18a03 | ||
|
|
6b7db22981 | ||
|
|
8adb8a6986 | ||
|
|
37e3e2f9c2 | ||
|
|
3c7be32ef5 | ||
|
|
051775b9b4 | ||
|
|
e0d5b91388 | ||
|
|
4dd2027428 | ||
|
|
42ab98b830 | ||
|
|
2b2bce6457 | ||
|
|
69ee816541 | ||
|
|
2cbf3f7e15 | ||
|
|
923eece3f5 | ||
|
|
d4fb313ff0 | ||
|
|
7dce465c06 | ||
|
|
5a30f5c00e | ||
|
|
fd406af962 | ||
|
|
9e5ae30372 | ||
|
|
0fe3538331 | ||
|
|
b0f5ad75ae | ||
|
|
a290f63a15 | ||
|
|
d078ce794e | ||
|
|
06f51a5c34 | ||
|
|
db96778064 | ||
|
|
d8918ea156 | ||
|
|
3503474bb8 | ||
|
|
eb1606b086 | ||
|
|
1330a092fa | ||
|
|
d3c74cfb45 | ||
|
|
d044545520 | ||
|
|
d1cdc1c6a0 | ||
|
|
dc8d91ea39 | ||
|
|
ae5a6d330d | ||
|
|
fe0bee21b0 | ||
|
|
c090c19bfe | ||
|
|
8fbec785e8 | ||
|
|
06fb3d9476 | ||
|
|
fe3e8a7bb6 | ||
|
|
ce2ff25edd | ||
|
|
65a1b892e3 | ||
|
|
e272fde95e | ||
|
|
d48bff0e20 | ||
|
|
6e61c34f0f | ||
|
|
2d9506eb54 | ||
|
|
b1988c7b67 | ||
|
|
fb5d72c29b | ||
|
|
ac148ce0e9 | ||
|
|
016dca654e | ||
|
|
6502bdecbe | ||
|
|
91a739af6e | ||
|
|
e232565971 | ||
|
|
472488ebe8 | ||
|
|
8de3698b23 | ||
|
|
a694b46914 | ||
|
|
c384ed960c | ||
|
|
2386d2e299 | ||
|
|
cc7e1a72c1 | ||
|
|
5cc0fa7c98 | ||
|
|
4ce848ab51 | ||
|
|
2e34e11b02 | ||
|
|
8eda5f36fb | ||
|
|
3dbe5d872b | ||
|
|
96a95ba9fe | ||
|
|
f594ed659e | ||
|
|
5c4bfbbd95 | ||
|
|
8733635638 | ||
|
|
928aa74e89 | ||
|
|
8fdfe673e8 | ||
|
|
494a6e6090 | ||
|
|
89bb7e6b0e | ||
|
|
52cfb9a041 | ||
|
|
3817ddef41 | ||
|
|
0f494c9dd6 | ||
|
|
3ff97ecf45 | ||
|
|
a10c0b516b | ||
|
|
5eb1469dbf | ||
|
|
9a90fe3794 | ||
|
|
1917c4b04a | ||
|
|
2b01d4a203 | ||
|
|
c5100219d1 | ||
|
|
a730a08161 | ||
|
|
4bb8e3a121 | ||
|
|
eacc00f786 | ||
|
|
17f5e557ed | ||
|
|
c33e5bc40f | ||
|
|
06d1a98ad2 | ||
|
|
afe6f4030e | ||
|
|
6e49c4ffe0 | ||
|
|
28d9f00610 | ||
|
|
8f688e5e13 | ||
|
|
5a182eddbf | ||
|
|
4b615cb3a9 | ||
|
|
b36247a091 | ||
|
|
6e77756d6a | ||
|
|
585c0c3818 | ||
|
|
6ff75f9a9f | ||
|
|
906ef43c00 | ||
|
|
4528957235 | ||
|
|
9d448a42a7 | ||
|
|
979e10f9d5 | ||
|
|
31f65b89bb | ||
|
|
86639dbc02 | ||
|
|
bf8c33703c | ||
|
|
ffe7ef4764 | ||
|
|
30dae70e2b | ||
|
|
557a747d55 | ||
|
|
fe273b3829 | ||
|
|
2439bb30e8 | ||
|
|
e48b6bd22d | ||
|
|
1de9ffacb0 | ||
|
|
93fd6170a3 | ||
|
|
b7c3e6099c | ||
|
|
d8163e9835 | ||
|
|
12b1916599 | ||
|
|
9d8cdb5976 | ||
|
|
29a7c1938a | ||
|
|
8636d3139e | ||
|
|
987571ce91 | ||
|
|
6f4c5fcc87 | ||
|
|
a7ca010d4e | ||
|
|
a836842a7e | ||
|
|
f8c11a324a | ||
|
|
052c9be111 | ||
|
|
854e520528 | ||
|
|
05737b85eb | ||
|
|
c8a2308739 | ||
|
|
ed46a078f9 | ||
|
|
4f7c2bf8c3 | ||
|
|
0e341726d2 | ||
|
|
a4f9746d3a | ||
|
|
556c546b2e | ||
|
|
aa479948f9 | ||
|
|
fa3cee9d58 | ||
|
|
0021fb8a33 | ||
|
|
c8c727e6c6 | ||
|
|
b8494ab3cc | ||
|
|
2eda03f5de | ||
|
|
3a38358946 | ||
|
|
e7fc697e57 | ||
|
|
6c9845b9f3 | ||
|
|
9e146a8a5a | ||
|
|
433d9bfb02 | ||
|
|
94917e315e | ||
|
|
ced6852735 | ||
|
|
8dc55f417d | ||
|
|
3d338bba3c | ||
|
|
631398f700 | ||
|
|
7984d8cdfb | ||
|
|
783a6110ef | ||
|
|
5d4d53c3a1 | ||
|
|
3014955ece | ||
|
|
0481f20c6b | ||
|
|
76309601eb | ||
|
|
84dd864886 | ||
|
|
7924e01b15 | ||
|
|
dadc70630b | ||
|
|
effc37a702 | ||
|
|
deac3fc918 | ||
|
|
e6dea4c92c | ||
|
|
075e992fa0 | ||
|
|
565ca81b30 | ||
|
|
58c4eaaf86 | ||
|
|
77e8008752 | ||
|
|
2ba10fcbc7 | ||
|
|
4956a58026 | ||
|
|
92332fc385 | ||
|
|
9366f4b40e | ||
|
|
f49e8ec5ad | ||
|
|
cd33647087 | ||
|
|
71ebcac7f2 | ||
|
|
f06aee21eb | ||
|
|
5fbbc17376 | ||
|
|
a9d8830106 | ||
|
|
d21e6235ad | ||
|
|
289f79bbb0 | ||
|
|
768e9f4c09 | ||
|
|
62c776d90c | ||
|
|
464022bea2 | ||
|
|
61549b4a74 | ||
|
|
18df3dc07a | ||
|
|
3725724c54 | ||
|
|
500a005aac | ||
|
|
584cc6de2e | ||
|
|
2e5a6e21cf | ||
|
|
b79f63db78 | ||
|
|
7ed7a57d92 | ||
|
|
1eaf7c89b7 | ||
|
|
1fff8dd306 | ||
|
|
c3a3d02bea | ||
|
|
a9261970dd | ||
|
|
b7a3fe05a4 | ||
|
|
ab6b9006b7 | ||
|
|
d9a8b057c3 | ||
|
|
2ec6215b1c | ||
|
|
c59a8c9644 | ||
|
|
10de4b6b7b | ||
|
|
0be214e79e | ||
|
|
d6083c68fd | ||
|
|
22f7ac22d5 | ||
|
|
a00046f9b2 | ||
|
|
8a24275ba9 | ||
|
|
ca841252bd | ||
|
|
54195b16ad | ||
|
|
cb90167c76 | ||
|
|
ac4ae85a4a | ||
|
|
48b2a271cc | ||
|
|
596807055e | ||
|
|
cee20c4eb9 | ||
|
|
15fb47cb3d | ||
|
|
2635dfef96 | ||
|
|
7d7789ae96 | ||
|
|
cc69285420 | ||
|
|
99d3a283ef | ||
|
|
9b532584d6 | ||
|
|
7576eb38d9 | ||
|
|
8440d013f8 | ||
|
|
174c87a192 | ||
|
|
32b62d6d4f | ||
|
|
a0c2d312e9 | ||
|
|
5127a9ae3c | ||
|
|
b5653a1c06 | ||
|
|
671bd1022e | ||
|
|
94bba4ac9c | ||
|
|
fe1136aa95 | ||
|
|
8950ffcc5e | ||
|
|
2bc627970e | ||
|
|
44615c6fa2 | ||
|
|
00f55ea0bc | ||
|
|
be43cebf7d | ||
|
|
f38317d01f | ||
|
|
da839aae66 | ||
|
|
768e00ff1a | ||
|
|
40631f465e | ||
|
|
f665c73bb1 | ||
|
|
be067466fe | ||
|
|
3c309df6dd | ||
|
|
b7e6d98647 | ||
|
|
48942de75e | ||
|
|
fbcbc10174 | ||
|
|
342b48105f | ||
|
|
5f3e7f02cc | ||
|
|
bfccf29ccf | ||
|
|
1e3bb1f02b | ||
|
|
0dcf6771e7 | ||
|
|
062503c523 | ||
|
|
c3d7f2f170 | ||
|
|
faf6c16717 | ||
|
|
3d79d78134 | ||
|
|
35cab4ee73 | ||
|
|
a0edb8f2ad | ||
|
|
5cfe5e312b | ||
|
|
13f6ec04d5 | ||
|
|
2edc4a79b9 | ||
|
|
4661185719 | ||
|
|
895da5cbf0 | ||
|
|
ef9147512b | ||
|
|
abe05456f7 | ||
|
|
f9dfd3b348 | ||
|
|
9449501537 | ||
|
|
f3b5d5ab7b | ||
|
|
8ee5726e0c | ||
|
|
effa7fd57d | ||
|
|
6c039d2ad0 | ||
|
|
2dd8527566 | ||
|
|
fe811ce32e | ||
|
|
9fee0805c4 | ||
|
|
1987c32761 | ||
|
|
0ed2659698 | ||
|
|
9878856dfe | ||
|
|
ed01fd4edf | ||
|
|
9474933070 | ||
|
|
f3987b453c | ||
|
|
dcb4cb3a1e | ||
|
|
198b840059 | ||
|
|
42583cf3bb | ||
|
|
2e15371d61 | ||
|
|
339ff8ca77 | ||
|
|
268eaddad8 | ||
|
|
bb3cc1130b | ||
|
|
64ae3280e1 | ||
|
|
95235d69c2 | ||
|
|
d639c7be39 | ||
|
|
d7cafe25ff | ||
|
|
996f53373e | ||
|
|
6f55370ad4 | ||
|
|
fd511966a7 | ||
|
|
c4ddddd434 | ||
|
|
83a4db3b31 | ||
|
|
e35ef75949 | ||
|
|
d9dec6fe6b | ||
|
|
69bdbaed41 | ||
|
|
30f359e642 | ||
|
|
29d0a1714e | ||
|
|
fcb97f802f | ||
|
|
9ad05e640d | ||
|
|
449f00f960 | ||
|
|
8e64329d05 | ||
|
|
4284777556 | ||
|
|
81c496d96c | ||
|
|
65c06da275 | ||
|
|
5d0657c49a | ||
|
|
f6f6550bfb | ||
|
|
e01fb50359 | ||
|
|
28ce1c1249 | ||
|
|
7db592d27a | ||
|
|
b8e5c0d898 | ||
|
|
067c1771d0 | ||
|
|
349429b76e | ||
|
|
cc8f2afce9 | ||
|
|
2e97b20f94 | ||
|
|
1a163243ec | ||
|
|
75660e6f21 | ||
|
|
199ca77c2a | ||
|
|
1e2d2abbdf | ||
|
|
11b980f574 | ||
|
|
3274f9f155 | ||
|
|
f90f8824bb | ||
|
|
c7116d40ca | ||
|
|
6ef66399f8 | ||
|
|
2b8561f27d | ||
|
|
b20d8f195b | ||
|
|
6cf0eb9e1d | ||
|
|
c349e9aabe | ||
|
|
6ee38ceaba | ||
|
|
c60613fbcb | ||
|
|
bcc1b7b48a | ||
|
|
b19cb0805c | ||
|
|
7dfc5a78ba | ||
|
|
a077132d82 | ||
|
|
8ed6be6307 | ||
|
|
c490dd1563 | ||
|
|
d866b3df1f | ||
|
|
97f3fb4496 | ||
|
|
7530266330 | ||
|
|
6a53f356d2 | ||
|
|
075dc1e4e9 | ||
|
|
97b87d4ce4 | ||
|
|
e203e98375 | ||
|
|
9fcd104065 | ||
|
|
178e0ba87c | ||
|
|
cc40110d7e | ||
|
|
d58fb2bbc0 | ||
|
|
812333e9ae | ||
|
|
92bbdce435 | ||
|
|
bc62d49fc9 | ||
|
|
fe54d5b8ae | ||
|
|
7cc30c268b | ||
|
|
df60a2248a | ||
|
|
aa85d0ffeb | ||
|
|
1db963361c | ||
|
|
8a55b20284 | ||
|
|
beec349bc5 | ||
|
|
b025ed6057 | ||
|
|
27a54bcbaa | ||
|
|
6b20993d2a | ||
|
|
9ab16bdbb3 | ||
|
|
23088bc897 | ||
|
|
054a62de60 | ||
|
|
5aff548794 | ||
|
|
ff8fe7e018 | ||
|
|
c6617ebc9f | ||
|
|
15fa0c264f | ||
|
|
25468f55ff | ||
|
|
2340c55d76 | ||
|
|
13c1f4ab19 | ||
|
|
a160b798ca | ||
|
|
71f00a9efd | ||
|
|
967096f01c | ||
|
|
7616e94fd3 | ||
|
|
27ec69fb97 | ||
|
|
182d150eaa | ||
|
|
098ef976f7 | ||
|
|
ea724e343b | ||
|
|
85736d697c | ||
|
|
576a146ed2 | ||
|
|
69c5291e52 | ||
|
|
4875ef045a | ||
|
|
369cfc2413 | ||
|
|
491842ea34 | ||
|
|
9801876a2f | ||
|
|
9c28a04c65 | ||
|
|
596a1764ef | ||
|
|
8e09e1b248 | ||
|
|
d5674c85d7 | ||
|
|
ed6649b1d3 | ||
|
|
02baa778c5 | ||
|
|
5fd0e5add2 | ||
|
|
23eccb2f20 | ||
|
|
3c523fb824 | ||
|
|
5dbfc2786d | ||
|
|
c61495df52 | ||
|
|
6ad5ea1696 | ||
|
|
39fa27a2dc | ||
|
|
348bae53fe | ||
|
|
6bc00fc5e5 | ||
|
|
54c0f015f9 | ||
|
|
ea3e6dae93 | ||
|
|
80e52c73b0 | ||
|
|
92dbe6cdf8 | ||
|
|
0781e8cf12 | ||
|
|
5c893f0f39 | ||
|
|
e05dc99006 | ||
|
|
f864416e39 | ||
|
|
472dbd641c | ||
|
|
05141b4f52 | ||
|
|
b71a088da7 | ||
|
|
d76fb566a2 | ||
|
|
24a40af103 | ||
|
|
4a60292f82 | ||
|
|
784b914e07 | ||
|
|
7eea866869 | ||
|
|
4ab5456a98 | ||
|
|
8881a9f40e | ||
|
|
4db7f6f59c | ||
|
|
edbf8509e1 | ||
|
|
a017fbadd3 | ||
|
|
201f4b7e4a | ||
|
|
c9ff536e24 | ||
|
|
238efb02c6 | ||
|
|
f7e12b629f | ||
|
|
7cbca7fc1e | ||
|
|
be7b87cda3 | ||
|
|
07979a13fb | ||
|
|
9073c4554f | ||
|
|
8694e0ad19 | ||
|
|
60fe987a5f | ||
|
|
86256162de | ||
|
|
db24ca3dc1 | ||
|
|
5e3aa2db1d | ||
|
|
b74a501fac | ||
|
|
490a7d4a78 | ||
|
|
b147195189 | ||
|
|
b561666d80 | ||
|
|
a0b5305e3e | ||
|
|
e7a6c17260 | ||
|
|
c8ee9e6447 | ||
|
|
f2d350002e | ||
|
|
fabd26f85b | ||
|
|
17dcf7d2e5 | ||
|
|
6f62995c96 | ||
|
|
f405f4bbc4 | ||
|
|
98124de362 | ||
|
|
1e4ea90021 | ||
|
|
053f4a9a2e | ||
|
|
2c7d2230b3 | ||
|
|
040e0d8387 | ||
|
|
b5c382f929 | ||
|
|
0c9c1ae673 | ||
|
|
da0e0bdaec | ||
|
|
1f5b6a6a35 | ||
|
|
d25b2890be | ||
|
|
d73438a397 | ||
|
|
e91538a554 | ||
|
|
8a3b514372 | ||
|
|
7eca955b79 | ||
|
|
4242354c03 | ||
|
|
8728389c88 | ||
|
|
6651801b3f | ||
|
|
9190ce3701 | ||
|
|
90e2064d72 | ||
|
|
943d419f98 | ||
|
|
551316bcb6 | ||
|
|
9dd5089940 | ||
|
|
06e7ebbdeb | ||
|
|
a83b16e12a | ||
|
|
91a8b97cf4 | ||
|
|
41754c92c3 | ||
|
|
e544995b83 | ||
|
|
45cf5c4c0f | ||
|
|
900eedfc2e | ||
|
|
faaa7bfa3a | ||
|
|
70366a98bd | ||
|
|
e88180b4d5 | ||
|
|
b639683ac1 | ||
|
|
918c8f9295 | ||
|
|
c2214cd4b5 | ||
|
|
7d7e5bac12 | ||
|
|
185b558561 | ||
|
|
4632035581 | ||
|
|
d7f81dff23 | ||
|
|
c849738c6f | ||
|
|
3cd7a2e6d6 | ||
|
|
0d4904f05d | ||
|
|
0b539a5977 | ||
|
|
395fdc9d61 | ||
|
|
3b3d7eff3c | ||
|
|
763c05313b | ||
|
|
9f80df3fcb | ||
|
|
f170ee9e59 | ||
|
|
339218508d | ||
|
|
f0c710b245 | ||
|
|
e66337a1db | ||
|
|
e087bccd33 | ||
|
|
8017774bf3 | ||
|
|
5f4d08ada5 | ||
|
|
1ad450d753 | ||
|
|
f1692b3436 | ||
|
|
2a9c56d9e3 | ||
|
|
39a5688464 | ||
|
|
e4e6173eff | ||
|
|
cf7334eb7d | ||
|
|
fdb96e91f1 | ||
|
|
844c5027ea | ||
|
|
8d0e485120 | ||
|
|
281ee1a853 | ||
|
|
a0bf29e600 | ||
|
|
d66c430e46 | ||
|
|
f511a52705 | ||
|
|
a674e410e0 | ||
|
|
fc63445c80 | ||
|
|
0c15655574 | ||
|
|
328b6d1cc6 | ||
|
|
b67d663a38 | ||
|
|
dd6c5c9eea | ||
|
|
2e87e64bd1 | ||
|
|
08c210d833 | ||
|
|
a9403013df | ||
|
|
05477c1a03 | ||
|
|
fcb6198a82 | ||
|
|
410d0bc125 | ||
|
|
abc62b9348 | ||
|
|
6fbf33c8f4 | ||
|
|
0ceb750dc7 | ||
|
|
a48c22d14f | ||
|
|
1521199e44 | ||
|
|
2910be82a4 | ||
|
|
07fdb087dc | ||
|
|
58150f5dcd | ||
|
|
e7a6ff39f9 | ||
|
|
b086afb272 | ||
|
|
7decf76883 | ||
|
|
d81369d63a | ||
|
|
c0fbe8237b | ||
|
|
58923b2846 | ||
|
|
d9f9477a52 | ||
|
|
966c744992 | ||
|
|
ddc91ce7c3 | ||
|
|
7e5107d90f | ||
|
|
c1668c9bdb | ||
|
|
7ddc2ccf1a | ||
|
|
3cd85fb395 | ||
|
|
b805ea9bf6 | ||
|
|
edd76f595a | ||
|
|
c131b63852 | ||
|
|
a70f377388 | ||
|
|
50f76446a9 | ||
|
|
15ce3ec41a | ||
|
|
b7b01999d9 | ||
|
|
956114fc42 | ||
|
|
bb3a986859 | ||
|
|
50fefc3bb0 | ||
|
|
c5eea2e7c5 | ||
|
|
19555a98ed | ||
|
|
9021f006f0 | ||
|
|
2d5f14388e | ||
|
|
ab47bf6451 | ||
|
|
d8bd45c2bd | ||
|
|
de692d3dcc | ||
|
|
4adb525513 | ||
|
|
72235a5f72 | ||
|
|
b6508cccec | ||
|
|
d9e7cf659e | ||
|
|
cf500cd817 | ||
|
|
f1338aca84 | ||
|
|
284de4ac5d | ||
|
|
19c4345162 | ||
|
|
d5d38b3331 | ||
|
|
bd04638d27 | ||
|
|
2f0d0e7c7a | ||
|
|
0b531e9fbc | ||
|
|
e3ebd582ec | ||
|
|
95ef046d0a | ||
|
|
21fd46d66b | ||
|
|
b3a801df11 | ||
|
|
a6d22e3b22 | ||
|
|
0415c050a5 | ||
|
|
014396cf11 | ||
|
|
70488f9c56 | ||
|
|
0052ab7148 | ||
|
|
f725040dd5 | ||
|
|
f131863642 | ||
|
|
8791047005 | ||
|
|
836a293f16 | ||
|
|
1177cc3f29 | ||
|
|
269847d19d | ||
|
|
df22f68088 | ||
|
|
d83d8552b8 | ||
|
|
365aa69afd | ||
|
|
578c338d40 | ||
|
|
389518e1b8 | ||
|
|
d42ff227f1 | ||
|
|
737e9e48ca | ||
|
|
f96d91cb6c | ||
|
|
85503655ab | ||
|
|
8d811760a9 | ||
|
|
4e0de22375 | ||
|
|
7cfbf100eb | ||
|
|
edbe026b49 | ||
|
|
1e967eceef | ||
|
|
7d20db93d3 | ||
|
|
fb05a42d2e | ||
|
|
9490ae1440 | ||
|
|
e932be0fb3 | ||
|
|
70b49980cb | ||
|
|
5e6ee5fd48 | ||
|
|
a2d872a9f0 | ||
|
|
e8bcde31b7 | ||
|
|
40cda9220a | ||
|
|
a494683bc8 | ||
|
|
2ba6a85eca | ||
|
|
0e58158a59 | ||
|
|
af740592c9 | ||
|
|
c9452c9f31 | ||
|
|
494a1603e4 | ||
|
|
5207e111e1 | ||
|
|
06995fb080 | ||
|
|
1f5cafc2d1 | ||
|
|
f190de39a6 | ||
|
|
272ab746a6 | ||
|
|
fce0bf6e59 | ||
|
|
5957786b2c | ||
|
|
4f3f4e23e4 | ||
|
|
5f5096e1d4 | ||
|
|
4b35aef728 | ||
|
|
6a0ed51f5e | ||
|
|
67360e93b8 | ||
|
|
f18f4c69f2 | ||
|
|
41435578d2 | ||
|
|
7f33ae3bee | ||
|
|
645135bf56 | ||
|
|
eb9005ad74 | ||
|
|
14089f8c6a | ||
|
|
49094120d9 | ||
|
|
fe4111a9f5 | ||
|
|
61613bee98 | ||
|
|
3ce967d8e5 | ||
|
|
aad9afad59 | ||
|
|
8cbbd022fc | ||
|
|
9b23cd6d19 | ||
|
|
d94f241d3c | ||
|
|
bb7c11adf1 | ||
|
|
5c295254bf | ||
|
|
236e8cc95c | ||
|
|
421973e0d9 | ||
|
|
e2a5af1cf7 | ||
|
|
65aa7b1084 | ||
|
|
f8c8330258 | ||
|
|
f5411ac9ab | ||
|
|
f31debc09c | ||
|
|
6654d7a919 | ||
|
|
44743b5f6f | ||
|
|
f80276584f | ||
|
|
cda41debfc | ||
|
|
2c50d01c26 | ||
|
|
01ebb6576d | ||
|
|
aaca0b6f76 | ||
|
|
84e1f3649f | ||
|
|
0ab2cfaf8b | ||
|
|
0545d6f083 | ||
|
|
d804228956 | ||
|
|
f7d70df2ba | ||
|
|
4daef52991 | ||
|
|
a329547682 | ||
|
|
f02af8d481 | ||
|
|
dd6c067832 | ||
|
|
dbc435506c | ||
|
|
f00e289014 | ||
|
|
1dffaba266 | ||
|
|
958a2f4274 | ||
|
|
21718a69d3 | ||
|
|
c650ae0e19 | ||
|
|
9ed435d04a | ||
|
|
fed6a0c24e | ||
|
|
44b9a8e7ed | ||
|
|
c16757b03a | ||
|
|
5d0dde5c15 | ||
|
|
f60356e8c7 | ||
|
|
cdf8f78962 | ||
|
|
4539d236df | ||
|
|
8718ac0c4b | ||
|
|
94787d537a | ||
|
|
bb8cff967e | ||
|
|
eca57beec1 | ||
|
|
58b4eb04f9 | ||
|
|
a9d46297c4 | ||
|
|
e9edecf34a | ||
|
|
71a5f0e84e | ||
|
|
fb22ee94d9 | ||
|
|
f03904ebce | ||
|
|
6cc9f49d97 | ||
|
|
f5f0680ec7 | ||
|
|
60e04b9065 | ||
|
|
025da92450 | ||
|
|
5de4aa091b | ||
|
|
1fe8235a85 | ||
|
|
0ab14399ae | ||
|
|
15dded712c | ||
|
|
8837f7e6e8 | ||
|
|
e8defd821a | ||
|
|
5d6effeff5 | ||
|
|
427c278012 | ||
|
|
a78a09f594 | ||
|
|
59fd48cfe2 | ||
|
|
cc78ab4855 | ||
|
|
063562261e | ||
|
|
70619dd0b7 | ||
|
|
63031fb278 | ||
|
|
7f924a56b3 | ||
|
|
114f2a1465 | ||
|
|
5d2777634a | ||
|
|
2d5b4a0003 | ||
|
|
adfa1704e2 | ||
|
|
ab3fd6be8f | ||
|
|
47702d075e | ||
|
|
341f000b9c | ||
|
|
0deea53931 | ||
|
|
8b3d792bec | ||
|
|
b82c48b66f | ||
|
|
fa91516dce | ||
|
|
4954b44d8e | ||
|
|
d132e51ac7 | ||
|
|
243b6ae985 | ||
|
|
8780ba3626 | ||
|
|
694194be2f | ||
|
|
c7f61f8b80 | ||
|
|
3a7c7fe4e8 | ||
|
|
668967a719 | ||
|
|
d15c14ab93 | ||
|
|
52b81608a1 | ||
|
|
048f754d83 | ||
|
|
748cb28017 | ||
|
|
d5ef3a3f8c | ||
|
|
e768e285ce | ||
|
|
a102d775b2 | ||
|
|
65e82b03ad | ||
|
|
80a636bd14 | ||
|
|
a4c57ee363 | ||
|
|
94bf54e7e0 | ||
|
|
b18ce5ade0 | ||
|
|
99793bb2c4 | ||
|
|
093936e594 | ||
|
|
036a37e351 | ||
|
|
d904df57ca | ||
|
|
d507979ec1 | ||
|
|
9bbcfead67 | ||
|
|
81036894c0 | ||
|
|
9044adecb5 | ||
|
|
9190fdd42c | ||
|
|
eab35605e4 | ||
|
|
28b65a7e7b | ||
|
|
5d1d2308b4 | ||
|
|
cf5952f508 | ||
|
|
3b0d7bc4ad | ||
|
|
22f8ab110e | ||
|
|
8f2d085d28 | ||
|
|
c3d7f5b28b | ||
|
|
45e6000619 | ||
|
|
7cd00a6760 | ||
|
|
cecc53fed3 | ||
|
|
58c2c70146 | ||
|
|
ad9f488df6 | ||
|
|
b19799bc72 | ||
|
|
1209b9b86e | ||
|
|
da957a3caf | ||
|
|
1bfd0f0149 | ||
|
|
3ff48b8559 | ||
|
|
6ba1eda96f | ||
|
|
dddfe07867 | ||
|
|
054f67eeb8 | ||
|
|
65c59c8c30 | ||
|
|
24925a1739 | ||
|
|
c49b40ee95 | ||
|
|
c06db30a65 | ||
|
|
0ed6fef49b | ||
|
|
9bad11ec79 | ||
|
|
74cdcde449 | ||
|
|
1613461504 | ||
|
|
0043f3558c | ||
|
|
a9c4b8dd1a | ||
|
|
5d00edc896 | ||
|
|
3bad815982 | ||
|
|
08cc7587ab | ||
|
|
458c0db3a8 | ||
|
|
a995333081 | ||
|
|
8eb608a839 | ||
|
|
1041c9f9fc | ||
|
|
1564742b76 | ||
|
|
b887fd153d | ||
|
|
d083674fb1 | ||
|
|
ed7a945261 | ||
|
|
ef5ffa939f | ||
|
|
d842ccb287 | ||
|
|
233893f122 | ||
|
|
2febdfc363 | ||
|
|
2c971a2598 | ||
|
|
b4c3c20e5e | ||
|
|
b6fbb012ad | ||
|
|
7f4db5a731 | ||
|
|
4dfdfa0b7d | ||
|
|
dbf659c575 | ||
|
|
cd8e04471c | ||
|
|
93de1e4903 | ||
|
|
2df43c9e2b | ||
|
|
5ace44493a | ||
|
|
a57ba3d81c | ||
|
|
6298112531 | ||
|
|
25afca55f6 | ||
|
|
52a16c917f | ||
|
|
eef9a60037 | ||
|
|
e158b5ccf6 | ||
|
|
f03d7efb5e | ||
|
|
da7b1fb014 | ||
|
|
62378d063e | ||
|
|
5fac282ee0 | ||
|
|
6eff873a07 | ||
|
|
1fb306c9d3 | ||
|
|
c064b3896a | ||
|
|
8400d1e60e | ||
|
|
5530e74382 | ||
|
|
47a38a8977 | ||
|
|
47883a94a6 | ||
|
|
ca19bbd366 | ||
|
|
7595808d26 | ||
|
|
37d22a144a | ||
|
|
dc697a6862 | ||
|
|
5398bac533 | ||
|
|
9984a168cb | ||
|
|
286f3713b0 | ||
|
|
ff9be30f86 | ||
|
|
34cebe8c0c | ||
|
|
200cd5972a | ||
|
|
808d1af578 | ||
|
|
d0995665a3 | ||
|
|
6132af8ecb | ||
|
|
2e9c4914a8 | ||
|
|
bf0d513e5b | ||
|
|
2be435ff32 | ||
|
|
20ba820253 | ||
|
|
44fd332965 | ||
|
|
0453d656d6 | ||
|
|
cb7e38577d | ||
|
|
4fa59ea04e | ||
|
|
bc2ed602e7 | ||
|
|
a1c4d159dd | ||
|
|
598f29b78e | ||
|
|
f61f2d6e5e | ||
|
|
f38df4df11 | ||
|
|
1e34ccbe2e | ||
|
|
2a8746f6b0 | ||
|
|
554e083f3d | ||
|
|
5d881a8b0f | ||
|
|
3d81641139 | ||
|
|
c25b4ba099 | ||
|
|
fd64c20807 | ||
|
|
80f1034dd6 | ||
|
|
15b841da06 | ||
|
|
902c08e9c9 | ||
|
|
ea652c023e | ||
|
|
3c79bb77db | ||
|
|
d8dbb85946 | ||
|
|
20702d26ec | ||
|
|
7d7e9501fa | ||
|
|
efef76d9cf | ||
|
|
e1e1ee31f0 | ||
|
|
142ca58d38 | ||
|
|
3b01bf7bda | ||
|
|
30416f54d1 | ||
|
|
f21ef0d2e9 | ||
|
|
39ced21a6f | ||
|
|
0f24417cb3 | ||
|
|
f84a87f2a2 | ||
|
|
dada57e5c4 | ||
|
|
d437d6fde9 | ||
|
|
69b11575e3 | ||
|
|
72e1a1b2e9 | ||
|
|
8ba573d196 | ||
|
|
4593231049 | ||
|
|
de9eac760b | ||
|
|
12ad8d52ae | ||
|
|
22f9a3b467 | ||
|
|
b64f0ba83f | ||
|
|
f73a494407 | ||
|
|
46ee74ed16 | ||
|
|
806b746fc0 | ||
|
|
cc820e97c6 | ||
|
|
283b04df73 | ||
|
|
6420d1239f | ||
|
|
04771e5a4a | ||
|
|
3d9608faa0 | ||
|
|
b7b4ae4262 | ||
|
|
b73b078705 | ||
|
|
887fa8649b | ||
|
|
f6172d7273 | ||
|
|
c70681712d | ||
|
|
80ca6de531 | ||
|
|
c6c395cd0f | ||
|
|
4831064623 | ||
|
|
d4660a23c0 | ||
|
|
e184a1b9e6 | ||
|
|
f8662c9bc2 | ||
|
|
463df9e4ba | ||
|
|
c768581829 | ||
|
|
754f7a7891 | ||
|
|
21450a08c2 | ||
|
|
c355b25bb1 | ||
|
|
1fe3d80838 | ||
|
|
930e16b64a | ||
|
|
2077a70d03 | ||
|
|
cbdb8bd9b9 | ||
|
|
5dcb417676 | ||
|
|
71bc993e3d | ||
|
|
c2812896f8 | ||
|
|
d43227ede4 | ||
|
|
8554ae38ed | ||
|
|
da656caf1e | ||
|
|
51fc853228 | ||
|
|
7a30cb9de7 | ||
|
|
1b475cf9f3 | ||
|
|
719b690451 | ||
|
|
3cdc523dec | ||
|
|
eb49127b9e | ||
|
|
d07172a528 | ||
|
|
79637097ba | ||
|
|
1259341095 | ||
|
|
5d3bc95ac5 | ||
|
|
de25232a73 | ||
|
|
95769de464 | ||
|
|
52a168b961 | ||
|
|
b3b00b6700 | ||
|
|
8e8cda132c | ||
|
|
6459ccb185 | ||
|
|
548f83c3ad | ||
|
|
555e0de9e4 | ||
|
|
cc4bce283f | ||
|
|
8189a34d14 | ||
|
|
5d88ad554f | ||
|
|
2cc50a2b65 | ||
|
|
33670a5bd0 | ||
|
|
64f8a222cb | ||
|
|
d9a9695fe0 | ||
|
|
8e6c4e1aca | ||
|
|
490fbfc13e | ||
|
|
4b45973361 | ||
|
|
4c27e08e3d | ||
|
|
94eb80597b | ||
|
|
d610eb15d8 | ||
|
|
4f53cd1704 | ||
|
|
d795fac37a | ||
|
|
ce6b71a58b | ||
|
|
09f74a9af8 | ||
|
|
552a49a680 | ||
|
|
97741398fb | ||
|
|
f8b225e70e | ||
|
|
57b4eda014 | ||
|
|
fc3a181779 | ||
|
|
9541ea6a9f | ||
|
|
f716f6060e | ||
|
|
dc0cca8c83 | ||
|
|
4f303de00c | ||
|
|
05aa26e619 | ||
|
|
1c4b831922 | ||
|
|
6613ae57b0 | ||
|
|
b6552aff75 | ||
|
|
3c98fae4f2 | ||
|
|
2028e4c8ad | ||
|
|
5c7feba77b | ||
|
|
23f2677052 | ||
|
|
6b67511748 | ||
|
|
36e0feea43 | ||
|
|
69392f67e8 | ||
|
|
e7d130cc11 | ||
|
|
6e3ba3ca45 | ||
|
|
0f54cf83f4 | ||
|
|
fb209cdfde | ||
|
|
431c53efcf | ||
|
|
79ad0ff56b | ||
|
|
d4dad58b0d | ||
|
|
baff032e3b | ||
|
|
26309f51e3 | ||
|
|
f8f53a6bd9 | ||
|
|
ac3667c765 | ||
|
|
96180e7555 | ||
|
|
024619676b | ||
|
|
5aa0f547cf | ||
|
|
b1ce6ffcc7 | ||
|
|
f01936ca4f | ||
|
|
70fdb1042f | ||
|
|
58cfc0d0cf | ||
|
|
4eff3b6a24 | ||
|
|
e5f69f0815 | ||
|
|
ef7b51beb7 | ||
|
|
8494ac8f3d | ||
|
|
8dea519235 | ||
|
|
0712e98904 | ||
|
|
c7ccddbcb9 | ||
|
|
efd3e8067b | ||
|
|
c6f7b7f35f | ||
|
|
f3dd1603db | ||
|
|
be7688a4df | ||
|
|
8e2f11389d | ||
|
|
346454c21b | ||
|
|
c822870cf8 | ||
|
|
9666cf680e | ||
|
|
a88622c1be | ||
|
|
c3fbc36ce7 | ||
|
|
f174d7dd39 | ||
|
|
38a8721a91 | ||
|
|
8aedf26a87 | ||
|
|
b2ff9240ac | ||
|
|
7a3c61b744 | ||
|
|
8dd1df71cc | ||
|
|
b59b0f0386 | ||
|
|
f59f484c01 | ||
|
|
a44ea0ddf0 | ||
|
|
2214507db0 | ||
|
|
a138425417 | ||
|
|
99c47dd50a | ||
|
|
594b83e7a6 | ||
|
|
76d0ef0851 | ||
|
|
549ebbb462 | ||
|
|
341656ddb9 | ||
|
|
5a3c3b4876 | ||
|
|
375b8dceb7 | ||
|
|
f37546e173 | ||
|
|
239d53426a | ||
|
|
3ccac629bc | ||
|
|
e8e6feeb0f | ||
|
|
c22705a593 | ||
|
|
ec7889dfa8 | ||
|
|
563d59526f | ||
|
|
0ffd5de6fc | ||
|
|
5014f83b86 | ||
|
|
953a9b1768 | ||
|
|
c641b61b26 | ||
|
|
d87e507865 | ||
|
|
ec1f9841b2 | ||
|
|
84b0f29d87 | ||
|
|
b23e05dbc5 | ||
|
|
37978b4fe5 | ||
|
|
ef15e55947 | ||
|
|
c282dd086f | ||
|
|
aac9f089d9 | ||
|
|
adce8f52e8 | ||
|
|
20b64c8900 | ||
|
|
66d781a226 | ||
|
|
867ec010ab | ||
|
|
ce0c6da9fa | ||
|
|
e22c5ea800 | ||
|
|
16bfb1727f | ||
|
|
f1f14040b8 | ||
|
|
9cb328966c | ||
|
|
0c76890572 | ||
|
|
18fc42e63b | ||
|
|
fc8d9df516 | ||
|
|
afdf8a78c0 | ||
|
|
0b3ae1f972 | ||
|
|
6a5ee72722 | ||
|
|
c6ec8bc0d9 | ||
|
|
51cfd996eb | ||
|
|
6d0e4bed4b | ||
|
|
05247dc4a4 | ||
|
|
43011f3bfa | ||
|
|
38854bd876 | ||
|
|
a9726bd52f | ||
|
|
4216c9e8f7 | ||
|
|
a8d670fc0d | ||
|
|
cbacc779fc | ||
|
|
896778cead | ||
|
|
04b0c62bf9 | ||
|
|
63a779baa8 | ||
|
|
2d1a776db7 | ||
|
|
3d2df3ba93 | ||
|
|
eb6238781d | ||
|
|
6140a3c26b | ||
|
|
6eaf2d67b7 | ||
|
|
35b34c43ed | ||
|
|
f67a9d2de1 | ||
|
|
05acf28e0d | ||
|
|
5698bec621 | ||
|
|
3ec495225f | ||
|
|
fee9baca89 | ||
|
|
bec26ce754 | ||
|
|
343d7df57c | ||
|
|
dca6a4bbd5 | ||
|
|
a32b95544b | ||
|
|
2422e0b481 | ||
|
|
3441bd0e7c | ||
|
|
05ced9fbc4 | ||
|
|
b7c3df455e | ||
|
|
d7affad059 | ||
|
|
34c3da9117 | ||
|
|
a22d3b2390 | ||
|
|
7c09bdc6e0 | ||
|
|
bc291141b1 | ||
|
|
c1b089d1c3 | ||
|
|
1271f97b66 | ||
|
|
582c77805c | ||
|
|
9eb5f65b8f | ||
|
|
00f01a9889 | ||
|
|
671edc33e1 | ||
|
|
1253357a39 | ||
|
|
6df31eb7f5 | ||
|
|
9299a83b17 | ||
|
|
a6614abd24 | ||
|
|
4c9d99040c | ||
|
|
6ef8adc863 | ||
|
|
a00300f88a | ||
|
|
274393ac64 | ||
|
|
1339b9422d | ||
|
|
ed9e196bf6 | ||
|
|
bba5376a36 | ||
|
|
df3575217a | ||
|
|
c504506455 | ||
|
|
2a28772312 | ||
|
|
d04c6dd3ac | ||
|
|
e48daffad9 | ||
|
|
58642286c9 | ||
|
|
6d62ae226a | ||
|
|
14f6f9ec94 | ||
|
|
e10f447b5b | ||
|
|
1d1f61613c | ||
|
|
b85c1a8861 | ||
|
|
430956d304 | ||
|
|
c0449a3ed2 | ||
|
|
18ad01533b | ||
|
|
288049dc95 | ||
|
|
573c8f3b13 | ||
|
|
e85d7a7be5 | ||
|
|
7015215f26 | ||
|
|
ffa5472b31 | ||
|
|
e00f0b4cf1 | ||
|
|
dc5c220e8f | ||
|
|
933d49b0b0 | ||
|
|
9af85f5a7e | ||
|
|
7ec52145e8 | ||
|
|
aa6112482d | ||
|
|
d035cdcff9 | ||
|
|
7ad3ddef2a | ||
|
|
38e08bb91f | ||
|
|
252a21e2ae | ||
|
|
ba7db3edda | ||
|
|
f64b061a28 | ||
|
|
f323ced4ca | ||
|
|
c06ec7c6ba | ||
|
|
835f9aad91 | ||
|
|
a4ec9f8b44 | ||
|
|
47c33d0344 | ||
|
|
f500c7abcb | ||
|
|
95dd7b5323 | ||
|
|
65c950e1a4 | ||
|
|
e484f32b1a | ||
|
|
bc396e7a90 | ||
|
|
ce182f43db | ||
|
|
9382d52d55 | ||
|
|
dd156d0689 | ||
|
|
ac9f6e3a41 | ||
|
|
1e7534b9d7 | ||
|
|
72d800ed10 | ||
|
|
54143ae6d4 | ||
|
|
8ef5daa807 | ||
|
|
fa47ea4196 | ||
|
|
683592fa86 | ||
|
|
477a04760c | ||
|
|
b4a62bfa30 | ||
|
|
10eec7d48c | ||
|
|
b8cc10ab5d | ||
|
|
be0df07dfb | ||
|
|
5244097e2d | ||
|
|
1ba4ab2bd1 | ||
|
|
2e855f8983 | ||
|
|
51b4a9e350 | ||
|
|
237d28cf83 | ||
|
|
5723fd112f | ||
|
|
73b89c554e | ||
|
|
950d024a11 | ||
|
|
815a3be48b | ||
|
|
bfa6e52470 | ||
|
|
ec654d2355 | ||
|
|
dfb4883c93 | ||
|
|
4bf1f579f5 | ||
|
|
b9994e52eb | ||
|
|
80d63dbb7c | ||
|
|
6b817d4563 | ||
|
|
1081d98bf9 | ||
|
|
91d82da497 | ||
|
|
0ca46774ac | ||
|
|
f0d6d46766 | ||
|
|
4a81205e04 | ||
|
|
0ac37981cb | ||
|
|
400c31d031 | ||
|
|
54b38086e5 | ||
|
|
e0deca33d0 | ||
|
|
0b2b8b960b | ||
|
|
9b173dcd71 | ||
|
|
a3361806ab | ||
|
|
5bdfdfefbe | ||
|
|
ee38cccad8 | ||
|
|
f82ff90f06 | ||
|
|
9826b8ae69 | ||
|
|
c7849a43e1 | ||
|
|
874bd093cb | ||
|
|
2cbc04e04d | ||
|
|
143eac092c | ||
|
|
d74dfb1f5c | ||
|
|
d42cf6daeb | ||
|
|
75191e7187 | ||
|
|
b9b2cd278b | ||
|
|
55dea4ee9d | ||
|
|
41c951811e | ||
|
|
72e7eb6777 | ||
|
|
9a733a57e7 | ||
|
|
45e8bb03e4 | ||
|
|
54e189616c | ||
|
|
c25947d544 | ||
|
|
80af3d6ada | ||
|
|
93d29a9733 | ||
|
|
ccc2142b45 | ||
|
|
28c153a0a2 | ||
|
|
b8e6287774 | ||
|
|
8d393ff137 | ||
|
|
5c09788ec4 | ||
|
|
5e970cdca4 | ||
|
|
28cadc5e06 | ||
|
|
3cdfa4051d | ||
|
|
28a9df669d | ||
|
|
5f94474330 | ||
|
|
42497028c4 | ||
|
|
57b16e3ac2 | ||
|
|
bd9af86de1 | ||
|
|
2ce9fb9760 | ||
|
|
ae66c6f0b4 | ||
|
|
fe5f34231b | ||
|
|
d694ee8651 | ||
|
|
89c0b67b8c | ||
|
|
0e9fbf3c5a | ||
|
|
ec40807c54 | ||
|
|
971a85a6f8 | ||
|
|
8e36695bc4 | ||
|
|
2e2e056198 | ||
|
|
e0d4115ed7 | ||
|
|
6ff3f5d1ff | ||
|
|
a2738e8599 | ||
|
|
468edfa6cc | ||
|
|
c83f2f98bd | ||
|
|
06f860c8ea | ||
|
|
24e574ee09 | ||
|
|
803b67af9f | ||
|
|
f803c6c0bf | ||
|
|
254feecf21 | ||
|
|
c6b6855131 | ||
|
|
9bc45563ee | ||
|
|
882ac74a0c | ||
|
|
d883a870e1 | ||
|
|
f2c6e3f65b | ||
|
|
1ff3dcf138 | ||
|
|
9b564431b0 | ||
|
|
65058db89f | ||
|
|
487d2a9221 | ||
|
|
6151debeab | ||
|
|
e05ef230a7 | ||
|
|
685755fe64 | ||
|
|
465ece5d25 | ||
|
|
10d1361a2c | ||
|
|
34617270a5 | ||
|
|
29aa370aa6 | ||
|
|
1ef7fd3659 | ||
|
|
6650072fe6 | ||
|
|
951bd3a517 | ||
|
|
2e3ddd3a61 | ||
|
|
dd628514c6 | ||
|
|
c74686a197 | ||
|
|
c42dbbfec8 | ||
|
|
ea3678f8c7 | ||
|
|
51099bf148 | ||
|
|
924e0261f9 | ||
|
|
dc5eda7ebb | ||
|
|
aec6636205 | ||
|
|
a18c3ff07d | ||
|
|
64928b28bc | ||
|
|
0bbaa51945 | ||
|
|
561803c0a7 | ||
|
|
1a5279bd6e | ||
|
|
534ddf01db | ||
|
|
937d5b5472 | ||
|
|
03a407d4df | ||
|
|
3508e1e6d5 | ||
|
|
145b1f4fb3 | ||
|
|
09bce5e6d6 | ||
|
|
51447961cb | ||
|
|
89e73594eb | ||
|
|
0cddc8a154 | ||
|
|
8152309435 | ||
|
|
a3089a719f | ||
|
|
2cb0b00e3a | ||
|
|
2aa219e150 | ||
|
|
93740c997c | ||
|
|
264ec5bab7 | ||
|
|
e340593ad1 | ||
|
|
05b6afcd17 | ||
|
|
10994d65be | ||
|
|
a18ce275ab | ||
|
|
66c39a953a | ||
|
|
09fb9dcd92 | ||
|
|
eb5c2e9823 | ||
|
|
50d5c4b9ca | ||
|
|
54708c6131 | ||
|
|
0e9ba9a004 | ||
|
|
08f10d3cea | ||
|
|
c2dd7e0f6e | ||
|
|
57fc5d28a9 | ||
|
|
49bdcad4b6 | ||
|
|
ef7d259bb7 | ||
|
|
688fe131c9 | ||
|
|
68b42a00e0 | ||
|
|
7368a790a3 | ||
|
|
527e1b8a16 | ||
|
|
f6d6658de7 | ||
|
|
6a929d6a1a | ||
|
|
ae380cb21e | ||
|
|
cf4c603362 | ||
|
|
d83c9da830 | ||
|
|
9a7c9e8d98 | ||
|
|
ace947e6b3 | ||
|
|
5f9378569b | ||
|
|
a180b95cca | ||
|
|
73bbe25d26 | ||
|
|
fc5e3a0aec | ||
|
|
7625d66259 | ||
|
|
30f2c2bd77 | ||
|
|
e3052c8c57 | ||
|
|
7b6ebc5c98 | ||
|
|
0093dc3d32 | ||
|
|
ccefd3be02 | ||
|
|
d509ef7581 | ||
|
|
5e165819a1 | ||
|
|
d180f01b45 | ||
|
|
91c09dd0a0 | ||
|
|
f6ca92337b | ||
|
|
ade9d662db | ||
|
|
baca196da6 | ||
|
|
10801bfb25 | ||
|
|
a89a62071b | ||
|
|
657051e4b6 | ||
|
|
f6f6d89e06 | ||
|
|
1a6af5d896 | ||
|
|
a4b83895a3 | ||
|
|
4f03548608 | ||
|
|
773e1d4e05 | ||
|
|
d9ef8c1779 | ||
|
|
f9e3a2132f | ||
|
|
9ab318cafc | ||
|
|
1a126b700f | ||
|
|
92dd5e1610 | ||
|
|
11ecbd27be | ||
|
|
585ef998d0 | ||
|
|
c297aff99b | ||
|
|
68142c9835 | ||
|
|
b8f4fa359c | ||
|
|
2b765fdedb | ||
|
|
0accdb9e34 | ||
|
|
5d468f7ca5 | ||
|
|
83768f0531 | ||
|
|
acae0ac2a6 | ||
|
|
0f86651089 | ||
|
|
a77f2fa424 | ||
|
|
6198e43fe6 | ||
|
|
621d4745b4 | ||
|
|
dac75a1dda | ||
|
|
f1c0f3d45f | ||
|
|
08b6a2c36d | ||
|
|
130b67821c | ||
|
|
b902769fa8 | ||
|
|
7503a58d1f | ||
|
|
096ce1a207 | ||
|
|
d1ef039e39 | ||
|
|
040ca5320d | ||
|
|
625c85291d | ||
|
|
b28835a604 | ||
|
|
6340704173 | ||
|
|
522b7c51f7 | ||
|
|
388ff75260 | ||
|
|
37ef0a0cb6 | ||
|
|
d7be2c5b8a | ||
|
|
b50e701cae | ||
|
|
b7a0443091 | ||
|
|
a8f0fd1fff | ||
|
|
a89d50d34e | ||
|
|
6489cfbce6 | ||
|
|
d10f40f109 | ||
|
|
175b56b43c | ||
|
|
9b68a3ef4a | ||
|
|
345d6c5687 | ||
|
|
5b1b5cc8f2 | ||
|
|
1b062ab929 | ||
|
|
a7420ca3d4 | ||
|
|
4dcd1f3e65 | ||
|
|
e46b392a8d | ||
|
|
47ff768b70 | ||
|
|
ba4bd3ed55 | ||
|
|
68428a5d5e | ||
|
|
28694e8afb | ||
|
|
c420a0ae2b | ||
|
|
a85e50f465 | ||
|
|
4962cc3da8 | ||
|
|
bb703281a2 | ||
|
|
52f5564122 | ||
|
|
1dc420ce51 | ||
|
|
20af1ceb7d | ||
|
|
ec982ccacb | ||
|
|
a97e74b2d4 | ||
|
|
fecc5b09f8 | ||
|
|
d1030eb0b2 | ||
|
|
fb749dc526 | ||
|
|
e6df1828d9 | ||
|
|
9ff53fea98 | ||
|
|
4f1888d2ea | ||
|
|
53dcd0dee9 | ||
|
|
e291ada371 | ||
|
|
9c9fed749a | ||
|
|
61556a54e2 | ||
|
|
79e2f8a2e5 | ||
|
|
d1f39e6217 | ||
|
|
4aa488f48b | ||
|
|
2d72b25c43 | ||
|
|
f23b0aacd7 | ||
|
|
98d27c4a6a | ||
|
|
d01ab227b8 | ||
|
|
0cfeee4ded | ||
|
|
c97e43dcd6 | ||
|
|
eda321954d | ||
|
|
64e5392788 | ||
|
|
987f95221c | ||
|
|
6e917d156c | ||
|
|
aeed287122 | ||
|
|
36e697b344 | ||
|
|
c2d0d4d28c | ||
|
|
ea6a3c0963 | ||
|
|
8902a5c5cd | ||
|
|
3021c5cfad | ||
|
|
16db9a7337 | ||
|
|
aec9c3c9a4 | ||
|
|
0daa225e26 | ||
|
|
85be2b85fd | ||
|
|
a7d6146169 | ||
|
|
978ec91107 | ||
|
|
297859c5bc | ||
|
|
bea52aa743 | ||
|
|
54f1be69c7 | ||
|
|
a4cc9ef2cc | ||
|
|
4f47594b6d | ||
|
|
189a7766d4 | ||
|
|
df9174577a | ||
|
|
bd1bb7a71b | ||
|
|
4b6e7e6c37 | ||
|
|
8896642e25 | ||
|
|
0ecb5a3fec | ||
|
|
d289b0b450 | ||
|
|
b8489464b3 | ||
|
|
ebaa3f39e4 | ||
|
|
e19753dcde | ||
|
|
13255a3762 | ||
|
|
15ce2a3d67 | ||
|
|
3bb97b81de | ||
|
|
9247780073 | ||
|
|
37161d3017 | ||
|
|
4532037e4f | ||
|
|
0fe08e1b33 | ||
|
|
236acbd6e8 | ||
|
|
16b0704acc | ||
|
|
61bcd67a5d | ||
|
|
0629c2a086 | ||
|
|
fbdc5a0eb5 | ||
|
|
68a8d81b6a | ||
|
|
a368301dbf | ||
|
|
89989adcad | ||
|
|
95cdb4b2bc | ||
|
|
228c835466 | ||
|
|
d604166194 | ||
|
|
d643a2ff13 | ||
|
|
b581a171f0 | ||
|
|
307336cfc4 | ||
|
|
fc30171725 | ||
|
|
34be7e99f0 | ||
|
|
7679df062c | ||
|
|
71cfd874ae | ||
|
|
08be0c374a | ||
|
|
7decce9718 | ||
|
|
22bab90a90 | ||
|
|
02882fb327 | ||
|
|
c74d597c84 | ||
|
|
653c77e852 | ||
|
|
2b36f4f57f | ||
|
|
82b0ebb787 | ||
|
|
3f35006c26 | ||
|
|
2ffd8637e1 | ||
|
|
44c1572b8f | ||
|
|
d0d749074e | ||
|
|
dbc44c08df | ||
|
|
46fbd7f1e1 | ||
|
|
5048c6c22a | ||
|
|
709d82e764 | ||
|
|
9d64b35ed8 | ||
|
|
0f00862e5e | ||
|
|
532e79c7d0 | ||
|
|
4ebad10557 | ||
|
|
0b04a7f17f | ||
|
|
77f96b386e | ||
|
|
ea86ddc693 | ||
|
|
e3e43d0ba0 | ||
|
|
b10929fe23 | ||
|
|
f512cb8e35 | ||
|
|
4f240f538d | ||
|
|
db6db6a4e9 | ||
|
|
f2add8de94 | ||
|
|
88c6621cfe | ||
|
|
c152b6f0ad | ||
|
|
53c0188248 | ||
|
|
725addafda | ||
|
|
19628c4732 | ||
|
|
04eaf7f175 | ||
|
|
f5850d0c08 | ||
|
|
855eb8355a | ||
|
|
fdbb7fd30f | ||
|
|
30d0ac0784 | ||
|
|
3d5c75420a | ||
|
|
1d5967d143 | ||
|
|
110a41d18d | ||
|
|
a3d8b9935a | ||
|
|
08357e3cb0 | ||
|
|
162a445a50 | ||
|
|
c7257bcf46 | ||
|
|
dfca8c09e0 | ||
|
|
7690f73e81 | ||
|
|
86fbb5952e | ||
|
|
78c92642e4 | ||
|
|
b3f6129718 | ||
|
|
2a52603b7e | ||
|
|
e6f9f258ec | ||
|
|
22e7b4c911 | ||
|
|
af5f7a7779 | ||
|
|
693d692a47 | ||
|
|
81f0189d23 | ||
|
|
e7f7e96d58 | ||
|
|
1dab2ac7d3 | ||
|
|
ec54074392 | ||
|
|
23b4c9c667 | ||
|
|
472ed721a3 | ||
|
|
ed3f2646f0 | ||
|
|
0499d2b5c4 | ||
|
|
81ba629b56 | ||
|
|
0bb746ba39 | ||
|
|
16a0f40ac2 | ||
|
|
f84103918a | ||
|
|
b5ca9bbab2 | ||
|
|
ff38d2bba6 | ||
|
|
8f2a8a0051 | ||
|
|
97147b594b | ||
|
|
9ff6d6e7b5 | ||
|
|
a0ec5b18e7 | ||
|
|
f2acdd27fd | ||
|
|
4ade446b55 | ||
|
|
ec6569fbea | ||
|
|
1ad6742dbc | ||
|
|
63ea3e8d27 | ||
|
|
9ace7db216 | ||
|
|
841513501a | ||
|
|
5c94147603 | ||
|
|
02f6d4cb66 | ||
|
|
ec5fad433c | ||
|
|
b4fa97fd54 | ||
|
|
d30b441ede | ||
|
|
dda29f7e2f | ||
|
|
952e281993 | ||
|
|
1fa026b9c7 | ||
|
|
e8c91e6e12 | ||
|
|
41425f7f74 | ||
|
|
2cf01c23a2 | ||
|
|
412f85b665 | ||
|
|
ebc90f6ab8 | ||
|
|
84d80e93bc | ||
|
|
b7e92dbced | ||
|
|
44dcb0d0a9 | ||
|
|
b3e3e080a9 | ||
|
|
1167cdcaec | ||
|
|
4eda39a31d | ||
|
|
759b75ca48 | ||
|
|
55e862a4a4 | ||
|
|
227547f826 | ||
|
|
7c41dd5e31 | ||
|
|
a964646803 | ||
|
|
9dac02ba5d | ||
|
|
43877d2647 | ||
|
|
cc6159b39b | ||
|
|
43ff787b04 | ||
|
|
3633598462 | ||
|
|
94922f2df6 | ||
|
|
75fe022f96 | ||
|
|
bc839569fb | ||
|
|
f62a4a0c0c | ||
|
|
8dacd51abb | ||
|
|
06302871bc | ||
|
|
c1ec2afeca | ||
|
|
e2f1338f94 | ||
|
|
5f9b57d300 | ||
|
|
ddf77f10e9 | ||
|
|
127532c226 | ||
|
|
572adbaad2 | ||
|
|
f4c7822bc9 | ||
|
|
89561b8d45 | ||
|
|
b0775f7a58 | ||
|
|
2dc50e6633 | ||
|
|
ad2a3d603e | ||
|
|
545f23551f | ||
|
|
b15c1ffedc | ||
|
|
56d70e4ea7 | ||
|
|
0b934232fd | ||
|
|
a4a53e1355 | ||
|
|
c445e70cff | ||
|
|
e8eec2cb41 | ||
|
|
dd6fa4af00 | ||
|
|
afdb9a63ff | ||
|
|
10ba2cd312 | ||
|
|
4c1fa9c242 | ||
|
|
3c6b707353 | ||
|
|
96efc8c7f0 | ||
|
|
0cd6afde6f | ||
|
|
82b11da4ca | ||
|
|
4ec39ab707 | ||
|
|
d2a60f3ca4 | ||
|
|
3bc6628227 | ||
|
|
27579e0701 | ||
|
|
f91bcfeb4b | ||
|
|
e19809d5b5 | ||
|
|
dd068467de | ||
|
|
3099c799b2 | ||
|
|
cd3ef8fa5a | ||
|
|
72ce37704b | ||
|
|
4420d073bb | ||
|
|
9cc9f519fc | ||
|
|
0483d841e3 | ||
|
|
5546120312 | ||
|
|
ad613e2437 | ||
|
|
c544759d36 | ||
|
|
20503d3c58 | ||
|
|
3fb17c5de8 | ||
|
|
a5e4bf16d3 | ||
|
|
68d9aad3a2 | ||
|
|
0aba1b4ad3 | ||
|
|
e12c7c8d27 | ||
|
|
83a040722e | ||
|
|
b7b504d43a | ||
|
|
b18804f57f | ||
|
|
550a5fb4c0 | ||
|
|
40f0238bb7 | ||
|
|
089823785e | ||
|
|
ecf7dded07 | ||
|
|
909aba27d1 | ||
|
|
cd4f29135b | ||
|
|
68c5c366f4 | ||
|
|
29a5311ae0 | ||
|
|
62d774a548 | ||
|
|
c9baca7910 | ||
|
|
86366ae157 | ||
|
|
c4094c68ee | ||
|
|
ec67a1b2c1 | ||
|
|
7ba9a5972d | ||
|
|
b32071ad04 | ||
|
|
b38c4e1a28 | ||
|
|
a13b2b4018 | ||
|
|
65a2f789dc | ||
|
|
8bd12ed040 | ||
|
|
6914662dd8 | ||
|
|
bcb11d9b7e | ||
|
|
920cab6f12 | ||
|
|
9756adb933 | ||
|
|
2671af13cd | ||
|
|
b7b94e38ac | ||
|
|
37792e9b38 | ||
|
|
f90bf756fb | ||
|
|
0b363a5c98 | ||
|
|
ebb1a8af1b | ||
|
|
f4ba7fcaf4 | ||
|
|
13964ac726 | ||
|
|
c8c1140f15 | ||
|
|
9a473640fb | ||
|
|
405173a0b4 | ||
|
|
8e43b86f06 | ||
|
|
eea9aaf940 | ||
|
|
67d3e8d049 | ||
|
|
f99ca918db | ||
|
|
79a0a66f1f | ||
|
|
08681f4a8b | ||
|
|
a58ef94a9c | ||
|
|
2b9ebd6662 | ||
|
|
6fdd2f40ed | ||
|
|
238990a285 | ||
|
|
1a77490969 | ||
|
|
5431d05168 | ||
|
|
eb97cdc33a | ||
|
|
b54d6589c3 | ||
|
|
463768fcf7 | ||
|
|
a43545c6ea | ||
|
|
9f067d7f56 | ||
|
|
8907e2d850 | ||
|
|
c84466b131 | ||
|
|
f62b956e74 | ||
|
|
9841063df9 | ||
|
|
803c8177d3 | ||
|
|
7ff525468f | ||
|
|
7c1c36f043 | ||
|
|
565e3f75c7 | ||
|
|
842f030355 | ||
|
|
f4ad42bb84 | ||
|
|
fb08b53f0b | ||
|
|
598becf619 | ||
|
|
9a27b38976 | ||
|
|
7917aa2a7c | ||
|
|
d0c97a589b | ||
|
|
8ec1ea7b7a | ||
|
|
71cb6d2bce | ||
|
|
ee258f1425 | ||
|
|
50278674f0 | ||
|
|
be5085f205 | ||
|
|
ca9476f72a | ||
|
|
d55c64c838 | ||
|
|
563de2cc90 | ||
|
|
6afe3ccc3b | ||
|
|
14ad5955b5 | ||
|
|
a6f2110141 | ||
|
|
393d8b9ded | ||
|
|
137dc1eac0 | ||
|
|
5fee82ce39 | ||
|
|
552710ac2a | ||
|
|
0a9a11636a | ||
|
|
e550631275 | ||
|
|
87d2f7f27a | ||
|
|
a40cd2b46f | ||
|
|
4b581f3720 | ||
|
|
12956679e7 | ||
|
|
7903fcb48c | ||
|
|
9672c6b885 | ||
|
|
55369d30a6 | ||
|
|
a207199879 | ||
|
|
a894b7cc9b | ||
|
|
46b3a9158c | ||
|
|
26421684dc | ||
|
|
fd536d373e | ||
|
|
4f59a821d3 | ||
|
|
b29e21efa8 | ||
|
|
a63dc75b43 | ||
|
|
9f6f721a13 | ||
|
|
fdb9d93b12 | ||
|
|
475e6e28eb | ||
|
|
4c1f70af4b | ||
|
|
454c90820d | ||
|
|
0a4e61c1dd | ||
|
|
31d9ba7e02 | ||
|
|
6a81b0f807 | ||
|
|
3322630732 | ||
|
|
d8885984ab | ||
|
|
4a18c45e4f | ||
|
|
5b7cac1002 | ||
|
|
5aef9266cb | ||
|
|
80b40c02b4 | ||
|
|
56d6079c4a | ||
|
|
3975792bf1 | ||
|
|
697e694de6 | ||
|
|
828d8eaadb | ||
|
|
30ee00ff50 | ||
|
|
7e130c2618 | ||
|
|
b859dd660c | ||
|
|
8868783476 | ||
|
|
dc0dd6588c | ||
|
|
f7e7e885a9 | ||
|
|
d813be1f48 | ||
|
|
0d03309c2f | ||
|
|
1dab353fdb | ||
|
|
8eb4efaddb | ||
|
|
12c900ea7d | ||
|
|
c31db83b26 | ||
|
|
98ce8f4c2f | ||
|
|
1f25b4a8a9 | ||
|
|
1756bbff84 | ||
|
|
21b2ffa42e | ||
|
|
b6efdac1db | ||
|
|
fc9d321ebe | ||
|
|
68a290c347 | ||
|
|
9133de50e9 | ||
|
|
73d04b976e | ||
|
|
a35d271669 | ||
|
|
72a7f932c6 | ||
|
|
2e74df2583 | ||
|
|
4fffb3c816 | ||
|
|
840b3a34cb | ||
|
|
dc267663a7 | ||
|
|
8d6443b25d | ||
|
|
e3c7fc8077 | ||
|
|
bbf2a15f27 | ||
|
|
5a326b82bd | ||
|
|
f1b0dd7836 | ||
|
|
c205777542 | ||
|
|
329a1e6f16 | ||
|
|
9c39121e99 | ||
|
|
e5e57e684e | ||
|
|
659a60aeec | ||
|
|
b0268adad9 | ||
|
|
71e4bbfc99 | ||
|
|
c6bd004d63 | ||
|
|
8b6986ba18 | ||
|
|
b23718f3ad | ||
|
|
8113548920 | ||
|
|
b401dbbf65 | ||
|
|
75dd0a770f | ||
|
|
6d8292cdd8 | ||
|
|
0575eb671a | ||
|
|
d06eea53ef | ||
|
|
9e96a93172 | ||
|
|
441f8f3ce8 | ||
|
|
1d4dec5510 | ||
|
|
167758003c | ||
|
|
0a3ac1f5c3 | ||
|
|
9173140ddf | ||
|
|
7aeb113c62 | ||
|
|
22cd408efb | ||
|
|
4fbd21da57 | ||
|
|
68f66ca101 | ||
|
|
ed95509a4f | ||
|
|
4162975f9f | ||
|
|
ac0cdcf70b | ||
|
|
63134fafec | ||
|
|
5494e88e08 | ||
|
|
86276ad17b | ||
|
|
c883ec40d7 | ||
|
|
d19bc328f3 | ||
|
|
f286f904dc | ||
|
|
150029a5e2 | ||
|
|
709a3fb06f | ||
|
|
d3c9d0b331 | ||
|
|
cc2d59468d | ||
|
|
91391fba5d | ||
|
|
4369402855 | ||
|
|
0bc556618f | ||
|
|
cb11580981 | ||
|
|
0366e8758c | ||
|
|
8d230dd798 | ||
|
|
411b342a27 | ||
|
|
b9b7032386 | ||
|
|
c38ef9023b | ||
|
|
9cecd525e2 | ||
|
|
4f5995abc0 | ||
|
|
2945b230e4 | ||
|
|
261cc448f7 | ||
|
|
616b0b6baa | ||
|
|
d99968ee6d | ||
|
|
4a65ff6ae2 | ||
|
|
94f91ae687 | ||
|
|
28e4bcf67f | ||
|
|
884461f1a6 | ||
|
|
26c669e42d | ||
|
|
f60dde4138 | ||
|
|
9c545059ae | ||
|
|
05dea7b22a | ||
|
|
5b3f915d90 | ||
|
|
d987d61ea9 | ||
|
|
dedb56d295 | ||
|
|
8697972d5d | ||
|
|
f90a2ae195 | ||
|
|
c34aadfbf7 | ||
|
|
084de9d8e0 | ||
|
|
69b780ee32 | ||
|
|
c7b904501c | ||
|
|
206be3c161 | ||
|
|
48e8022095 | ||
|
|
4715a1a5e0 | ||
|
|
ed2ba6bc3a | ||
|
|
ca1d62bec0 | ||
|
|
6a4aad1aa8 | ||
|
|
3f0462b68b | ||
|
|
f3a622d1a7 | ||
|
|
3cd5b9ca2e | ||
|
|
e9782c3219 | ||
|
|
8d348954a7 | ||
|
|
7d19d784df | ||
|
|
733b4e0a34 | ||
|
|
08d29a8342 | ||
|
|
675e2d25d6 | ||
|
|
ba20af48d3 | ||
|
|
707e053949 | ||
|
|
c47e67e52c | ||
|
|
3a95bfb699 | ||
|
|
6567bb4c12 | ||
|
|
39ba697e19 | ||
|
|
c9818ea2c4 | ||
|
|
afe5cb588d | ||
|
|
e9e999542d | ||
|
|
d9db90752e | ||
|
|
f7c3f52817 | ||
|
|
681e3785ef | ||
|
|
5f593994c7 | ||
|
|
ec73aeba16 | ||
|
|
7573e560b6 | ||
|
|
c8bc155cfe | ||
|
|
1eae73105a | ||
|
|
360dc140ea | ||
|
|
f85348ba94 | ||
|
|
2f15ad4be0 | ||
|
|
f254bb39a5 | ||
|
|
c58465d630 | ||
|
|
03a1386902 | ||
|
|
70b56eb527 | ||
|
|
4e05062def | ||
|
|
266333468b | ||
|
|
e32b3aac22 | ||
|
|
676402d918 | ||
|
|
edb4d066a9 | ||
|
|
03f4518da9 | ||
|
|
8259e82787 | ||
|
|
838d3ddc17 | ||
|
|
66686de4e4 | ||
|
|
ce8dca7afe | ||
|
|
9f5ef4c1cb | ||
|
|
f0a87da375 | ||
|
|
263e30d25d | ||
|
|
15ffc30d88 | ||
|
|
8a5c4979ad | ||
|
|
e36fbd6af5 | ||
|
|
3216806fae | ||
|
|
f8526f027c | ||
|
|
ed3066aae7 | ||
|
|
98a7e72f0a | ||
|
|
a2259865b3 | ||
|
|
63f3283591 | ||
|
|
d670ea4f59 | ||
|
|
d0d10bc6e7 | ||
|
|
4fea06c9fa | ||
|
|
09fed60dec | ||
|
|
75b9c39b0e | ||
|
|
5957a1068f | ||
|
|
df5229c7c8 | ||
|
|
ed817c81de | ||
|
|
dd72f7638d | ||
|
|
a77e4aa6fa | ||
|
|
792f3775ce | ||
|
|
4c7700ec3b | ||
|
|
eee296c4c2 | ||
|
|
499f745732 | ||
|
|
446388e0ba | ||
|
|
e1628bcdd8 | ||
|
|
6d5874fc45 | ||
|
|
3d563dea87 | ||
|
|
09304c33c1 | ||
|
|
521d8c4b1f | ||
|
|
b7d573a4b8 | ||
|
|
4a62385dcc | ||
|
|
98e15f658e | ||
|
|
0a3fa35c5d | ||
|
|
02e095bec2 | ||
|
|
696d9c6bd3 | ||
|
|
50dee5d464 | ||
|
|
87a8dda955 | ||
|
|
d7c73f590c | ||
|
|
d1b197e339 | ||
|
|
8f5ee989ba | ||
|
|
ce9c227425 | ||
|
|
e32c2b84ee | ||
|
|
ce9f77afed | ||
|
|
2bc38b2063 | ||
|
|
b4f4c28871 | ||
|
|
b14ef537e1 | ||
|
|
84ac386481 | ||
|
|
f0365d32aa | ||
|
|
795764f22f | ||
|
|
986f61ac92 | ||
|
|
09576f2f4f | ||
|
|
bba474dc6b | ||
|
|
fde971fe81 | ||
|
|
882e1db1d6 | ||
|
|
6b0333e919 | ||
|
|
914808b867 | ||
|
|
8d38cf4d1f | ||
|
|
2d1d512d0f | ||
|
|
37bc099d39 | ||
|
|
9e3c931b34 | ||
|
|
f8fb0e67b4 | ||
|
|
86ef6e6987 | ||
|
|
e8fd373e6c | ||
|
|
7efa546665 | ||
|
|
4d2a0697ed | ||
|
|
c6023782a4 | ||
|
|
30283282d2 | ||
|
|
7588fc0989 | ||
|
|
d698c1093a | ||
|
|
7b327d47c0 | ||
|
|
e629985cf4 | ||
|
|
aa875f1147 | ||
|
|
fe843bc466 | ||
|
|
5b355c6ca7 | ||
|
|
a3f7ff90e3 | ||
|
|
1f3f8a5073 | ||
|
|
9c88971bc1 | ||
|
|
aad309ee4f | ||
|
|
e80ca4ddbc | ||
|
|
28ccad28c2 | ||
|
|
ba9e7fbf64 | ||
|
|
28c85cf8e7 | ||
|
|
526b5a8d25 | ||
|
|
6b15cf3f72 | ||
|
|
fbd8ab47ea | ||
|
|
893917a25d | ||
|
|
af5ff2bb93 | ||
|
|
8995d3434f | ||
|
|
5f9b0675e2 | ||
|
|
46ac97a3ff | ||
|
|
db3264ab8c | ||
|
|
a0923622ae | ||
|
|
aa9975ad0d | ||
|
|
6787c81abe | ||
|
|
72205176e1 | ||
|
|
480742cc15 | ||
|
|
48bdfa2377 | ||
|
|
2ff6f4d3cf | ||
|
|
ae32938531 | ||
|
|
d3da603292 | ||
|
|
912bcf9487 | ||
|
|
413f071861 | ||
|
|
668c43abf3 | ||
|
|
43e9553ebc | ||
|
|
e8b54a5087 | ||
|
|
39852662a6 | ||
|
|
6a66ba8a21 | ||
|
|
36a7a84080 | ||
|
|
7e0b334b38 | ||
|
|
5d8d217a13 | ||
|
|
f2aa5c0235 | ||
|
|
323febe8c7 | ||
|
|
32d8f349c9 | ||
|
|
3910495cce | ||
|
|
fe69afdefb | ||
|
|
9082862b9d | ||
|
|
cb4a2cf029 | ||
|
|
26b5180bf7 | ||
|
|
11bfb1e5fd | ||
|
|
e431df06ab | ||
|
|
cbf0ceacd5 | ||
|
|
8c634d8323 | ||
|
|
4ae108009c | ||
|
|
a5c1c30368 | ||
|
|
8cd3086be0 | ||
|
|
dd37ae26a5 | ||
|
|
fdaebc7365 | ||
|
|
a8b62261f6 | ||
|
|
47eb913c22 | ||
|
|
7a46293f7a | ||
|
|
6b26d2b62d | ||
|
|
cac3b3ea35 | ||
|
|
dff4d03bd4 | ||
|
|
28d83d42e2 | ||
|
|
38f1b4d205 | ||
|
|
931d19eece | ||
|
|
88bbe55b85 | ||
|
|
dd17124ec6 | ||
|
|
674b50889e | ||
|
|
263c38caec | ||
|
|
3881f22192 | ||
|
|
664446631f | ||
|
|
c5f1cca3a0 | ||
|
|
a7407097e1 | ||
|
|
14bb60c61f | ||
|
|
749c0e51e6 | ||
|
|
0f120c41f1 | ||
|
|
65a7d56957 | ||
|
|
8ab8a6eefb | ||
|
|
52351d7dc8 | ||
|
|
224e0c298a | ||
|
|
a5a0e564dd | ||
|
|
b8c94fc7cf | ||
|
|
62dd3a5380 | ||
|
|
e3ddb677e1 | ||
|
|
32d7bd5ab1 | ||
|
|
861df49670 | ||
|
|
716f727753 | ||
|
|
7e381f8e5d | ||
|
|
183063a244 | ||
|
|
ef871775b7 | ||
|
|
45e386b26d | ||
|
|
e1db5db8ac | ||
|
|
c3a289cebc | ||
|
|
20e51f0b4d | ||
|
|
464dc93751 | ||
|
|
7445a3be59 | ||
|
|
2bbc25c1eb | ||
|
|
fd56fe6eb2 | ||
|
|
92dfa8becc | ||
|
|
ae8f9561ad | ||
|
|
3052ba433a | ||
|
|
508012342d | ||
|
|
9fa207e613 | ||
|
|
2d7b9817cb | ||
|
|
e8d808d708 | ||
|
|
d71595fc75 | ||
|
|
399d6592b8 | ||
|
|
628a6ffa07 | ||
|
|
3e1a94cbcd | ||
|
|
a6c2d4b0e2 | ||
|
|
19277aec87 | ||
|
|
14c2755436 | ||
|
|
50a9145386 | ||
|
|
9046509b95 | ||
|
|
61eaa44cf8 | ||
|
|
2d1e9abb60 | ||
|
|
d064260bf1 | ||
|
|
a19ad3fd1d | ||
|
|
3bc59a0327 | ||
|
|
5a883889a2 | ||
|
|
fbaa7a4d67 | ||
|
|
6baa6d488b | ||
|
|
e26f9b8095 | ||
|
|
f92fae7625 | ||
|
|
3d0e269241 | ||
|
|
c17c47f18a | ||
|
|
98b8bfb3fa | ||
|
|
83b1a98db1 | ||
|
|
f49f55f4a5 | ||
|
|
e58d19b420 | ||
|
|
58f753136a | ||
|
|
22d827adf1 | ||
|
|
d3de50e0f9 | ||
|
|
432037d20d | ||
|
|
86ef0a2609 | ||
|
|
bae50da799 | ||
|
|
2c45f27356 | ||
|
|
759f4f2c62 | ||
|
|
28355335f8 | ||
|
|
9f6832d636 | ||
|
|
5309afc347 | ||
|
|
693627a858 | ||
|
|
48eaa0e5bf | ||
|
|
9ad7ac632a | ||
|
|
41e3ecad46 | ||
|
|
d6f8d63742 | ||
|
|
28b0929554 | ||
|
|
55787ff7b9 | ||
|
|
b00919c692 | ||
|
|
d43392628b | ||
|
|
aa831fee5b | ||
|
|
5c568d6999 | ||
|
|
d84665cb64 | ||
|
|
4bb5d27c59 | ||
|
|
11a9f1d1f0 | ||
|
|
123e8f21b5 | ||
|
|
6d6b2efdb5 | ||
|
|
94b925f5ef | ||
|
|
c86755f1ab | ||
|
|
b8418ced44 | ||
|
|
fac0beaa0a | ||
|
|
c1f5229906 | ||
|
|
d5865989cf | ||
|
|
90e587a974 | ||
|
|
4a6b31fbe2 | ||
|
|
a63766a005 | ||
|
|
47359c1a3b | ||
|
|
abd0dad2bf | ||
|
|
54a52f7048 | ||
|
|
f213215c81 | ||
|
|
96cde120b4 | ||
|
|
0096ef4ddb | ||
|
|
ce6c7d4b59 | ||
|
|
b51ed9bbb7 | ||
|
|
5c6af92a0d | ||
|
|
d8eb08e214 | ||
|
|
c6f5c7f1a3 | ||
|
|
a6b6e31cda | ||
|
|
28145a9deb | ||
|
|
fa991c8501 | ||
|
|
5f345d2089 | ||
|
|
520c454f22 | ||
|
|
cc81668c8f | ||
|
|
6426e044d6 | ||
|
|
7b92371a03 | ||
|
|
6105d4a4e6 | ||
|
|
84649e9d20 | ||
|
|
1f7df33e28 | ||
|
|
3c394f08b0 | ||
|
|
64f07d9bf3 | ||
|
|
849a6c12be | ||
|
|
9144ce746e | ||
|
|
64821ad4f5 | ||
|
|
2655e726c9 | ||
|
|
de2970d7ef | ||
|
|
012dd6986b | ||
|
|
2ef9904d00 | ||
|
|
2c83224f07 | ||
|
|
0c63090a23 | ||
|
|
0159277dbf | ||
|
|
6ca5f3d8f6 | ||
|
|
875625b147 | ||
|
|
da0bd5a9dc | ||
|
|
ce02ad641b | ||
|
|
78915896d5 | ||
|
|
4a5d2e16d0 | ||
|
|
3e3161c747 | ||
|
|
694af4aeb1 | ||
|
|
7f59d7ea48 | ||
|
|
52b945164c | ||
|
|
584fb2904b | ||
|
|
71013b372d | ||
|
|
338b3ba590 | ||
|
|
6b798b01a8 | ||
|
|
7df20e5049 | ||
|
|
7128d79935 | ||
|
|
e27dfbb0bb | ||
|
|
a51f109930 | ||
|
|
3e101521dd | ||
|
|
726c7a4d32 | ||
|
|
767f05cfa7 | ||
|
|
47b49f1be9 | ||
|
|
a4964b9073 | ||
|
|
40e2ec3ae9 | ||
|
|
120cde169b | ||
|
|
1c35f46b45 | ||
|
|
eea713eed2 | ||
|
|
8c88757451 | ||
|
|
dd171ca44a | ||
|
|
cfd086a140 | ||
|
|
72fe7396d6 | ||
|
|
03140865f0 | ||
|
|
a4fc802d1b | ||
|
|
b6d760b903 | ||
|
|
e4b24d20ac | ||
|
|
91607bb2a1 | ||
|
|
fce8223663 | ||
|
|
d1067c60bf | ||
|
|
441c26dd32 | ||
|
|
00781dd4e1 | ||
|
|
c7b8debb6e | ||
|
|
d51c383866 | ||
|
|
f7d4698ef0 | ||
|
|
3fdbbafcb5 | ||
|
|
c82cd90ed6 | ||
|
|
b6fc8398cf | ||
|
|
e90f3b84c1 | ||
|
|
9134b6ea98 | ||
|
|
4a380ad7fc | ||
|
|
c05eb0b1b2 | ||
|
|
66feebfc0e | ||
|
|
04a609b51f | ||
|
|
258cf20c92 | ||
|
|
41c8d88217 | ||
|
|
37f39c0870 | ||
|
|
6ba4f8b54c | ||
|
|
c1f8ffa386 | ||
|
|
97893d293b | ||
|
|
78d1cfb464 | ||
|
|
b5fdfe27d5 | ||
|
|
cd9fb3b635 | ||
|
|
9e9f839d96 | ||
|
|
60814ecfe1 | ||
|
|
0170c20e9a | ||
|
|
01cc2e13d8 | ||
|
|
f823f170e6 | ||
|
|
be186bd39b | ||
|
|
ea25492c28 | ||
|
|
79a2bed640 | ||
|
|
cd8fc35968 | ||
|
|
d2cde379ad | ||
|
|
506c41cb15 | ||
|
|
72f54ca6c1 | ||
|
|
f8d22c486e | ||
|
|
c1151b0d45 | ||
|
|
0e65fdd6f7 | ||
|
|
3164b5ab13 | ||
|
|
e6cda79ee8 | ||
|
|
45e21d5000 | ||
|
|
7e212c4d40 | ||
|
|
775aae7082 | ||
|
|
a01da2fd92 | ||
|
|
cd2fe698bb | ||
|
|
4f209e8992 | ||
|
|
eb207322d3 | ||
|
|
06a2e5fc82 | ||
|
|
a8ae23d0a2 | ||
|
|
2befb5e784 | ||
|
|
6d5e7826ae | ||
|
|
c99d4948b7 | ||
|
|
ee6f78805f | ||
|
|
9a419bd63f | ||
|
|
4a139934f6 | ||
|
|
2823306810 | ||
|
|
4f3b3a273f | ||
|
|
6ae3911972 | ||
|
|
136aebc009 | ||
|
|
6541492a55 | ||
|
|
59e9750602 | ||
|
|
8ea800205c | ||
|
|
59bb9268a1 | ||
|
|
a582e7c2fb | ||
|
|
6e2669ed1d | ||
|
|
7b8a82ce90 | ||
|
|
a8bad622ff | ||
|
|
9c4f7aa688 | ||
|
|
e75b56073b | ||
|
|
a95ccc7e4c | ||
|
|
8101aceab5 | ||
|
|
1c9b19833c | ||
|
|
488745f378 | ||
|
|
b140e2553b | ||
|
|
ae29929714 | ||
|
|
9f80909f6a | ||
|
|
8201458332 | ||
|
|
7a88d80a10 | ||
|
|
3ced411769 | ||
|
|
f7c346de09 | ||
|
|
731ed6952f | ||
|
|
454ad6f8bd | ||
|
|
8a3b6bf0e6 | ||
|
|
fb6e0658cf | ||
|
|
f9b8d7a9d8 | ||
|
|
f763e1edd7 | ||
|
|
dbc3ad1304 | ||
|
|
4249e13eb4 | ||
|
|
ca7ebd9333 | ||
|
|
3f1e6c128f | ||
|
|
1f635b90e7 | ||
|
|
db3043553c | ||
|
|
f87890cb4b | ||
|
|
5911594906 | ||
|
|
9a1f769828 | ||
|
|
b91c0a0616 | ||
|
|
4a9f607d31 | ||
|
|
a00169451f | ||
|
|
ecba959dd9 | ||
|
|
a8202d4b37 | ||
|
|
657334fb67 | ||
|
|
78712245f7 | ||
|
|
70702e41e9 | ||
|
|
0ca3141088 | ||
|
|
ac0970abba | ||
|
|
9eeebb147f | ||
|
|
dcf8457f4d | ||
|
|
534a5ad688 | ||
|
|
529cbc0379 | ||
|
|
b6aff65997 | ||
|
|
b615cce92d | ||
|
|
aea631d9d2 | ||
|
|
bf942a4cb3 | ||
|
|
ceafe389af | ||
|
|
f62457a24e | ||
|
|
bab4f691c5 | ||
|
|
c7becddb78 | ||
|
|
cc3660e259 | ||
|
|
6e93ff8bca | ||
|
|
212d0f24d8 | ||
|
|
114003406d | ||
|
|
4c99c0127b | ||
|
|
c809b33161 | ||
|
|
3f1c7da15e | ||
|
|
a46695581e | ||
|
|
7902d10a3a | ||
|
|
8aff2bd74c | ||
|
|
352dd907ac | ||
|
|
43f195160e | ||
|
|
872bfe4757 | ||
|
|
70bd493a25 | ||
|
|
bd065838fa | ||
|
|
5f6e3da766 | ||
|
|
ee56b9cd4e | ||
|
|
1a27172f20 | ||
|
|
4ef1159666 | ||
|
|
c924e7c537 | ||
|
|
814bd7cb0d | ||
|
|
5f2d8c0155 | ||
|
|
372f691fd6 | ||
|
|
5f05a452fc | ||
|
|
afe3283c53 | ||
|
|
641a2895a6 | ||
|
|
c73c33f94c | ||
|
|
6c7da215e7 | ||
|
|
754a4a7c8b | ||
|
|
0427e8bbb4 | ||
|
|
c47f6ed30a | ||
|
|
3bdc317fc8 | ||
|
|
20cce349e4 | ||
|
|
5261162fdf | ||
|
|
acf117584b | ||
|
|
7b4bbed553 | ||
|
|
270ce87582 | ||
|
|
2fc0225bc9 | ||
|
|
3536cd336d | ||
|
|
86dd290c1d | ||
|
|
95949b6519 | ||
|
|
6499a7298d | ||
|
|
042e09d29f | ||
|
|
36309e6dbc | ||
|
|
e1ac201de1 | ||
|
|
f0c4e44d2f | ||
|
|
1dc3036822 | ||
|
|
a6b399286e | ||
|
|
856811bd2e | ||
|
|
53273a15bf | ||
|
|
3eeb090578 | ||
|
|
a9726fde19 | ||
|
|
f81d4033fa | ||
|
|
5e864ea3b5 | ||
|
|
8148bfeacf | ||
|
|
1e30718df6 | ||
|
|
72e1eb88d9 | ||
|
|
8ee5ede834 | ||
|
|
bd8b1a2501 | ||
|
|
a098167bdb | ||
|
|
7790208126 | ||
|
|
e52304edb4 | ||
|
|
afdd02a80d | ||
|
|
c73fdd4022 | ||
|
|
4356eefbb1 | ||
|
|
6104680caa | ||
|
|
4373fdf48c | ||
|
|
f9cdfd3e5b | ||
|
|
b6c2fc5a69 | ||
|
|
cc6610edc2 | ||
|
|
7b8ddfdd96 | ||
|
|
443a5ca0c2 | ||
|
|
3794b5cb58 | ||
|
|
6cb5377d73 | ||
|
|
a3a92ff1df | ||
|
|
2068efdb38 | ||
|
|
258ca1b434 | ||
|
|
103fa959cb | ||
|
|
b6d48b7a14 | ||
|
|
412e4e6cf9 | ||
|
|
e6f81173a3 | ||
|
|
d50281453d | ||
|
|
4bb488258d | ||
|
|
f6da19ba83 | ||
|
|
88bb7b780d | ||
|
|
3805e5d37e | ||
|
|
a2d6daaef4 | ||
|
|
48e9006cd1 | ||
|
|
a25a4b5d11 | ||
|
|
309bec474f | ||
|
|
d36440a06d | ||
|
|
2a2f772412 | ||
|
|
6a524bff9d | ||
|
|
5def1169db | ||
|
|
a07395fb56 | ||
|
|
e25a375f43 | ||
|
|
96801e3478 | ||
|
|
90100aa169 | ||
|
|
415f375ce6 | ||
|
|
94e9844179 | ||
|
|
50d238b60f | ||
|
|
5c3b41bd93 | ||
|
|
3e0971c159 | ||
|
|
57740fbd18 | ||
|
|
16bc8f7040 | ||
|
|
c145f24621 | ||
|
|
cd3a4573f2 | ||
|
|
8eab77f3c6 | ||
|
|
4af339424a | ||
|
|
4dd69a8b1a | ||
|
|
baa1160594 | ||
|
|
f438ff4bab | ||
|
|
a25b2af66c | ||
|
|
31b67ab92e | ||
|
|
5303b36913 | ||
|
|
a5c56c547d | ||
|
|
ccf9a9976c | ||
|
|
ab1efd923b | ||
|
|
d6780f9e49 | ||
|
|
8534e3b2f7 | ||
|
|
8e6cf669ad | ||
|
|
577380e98e | ||
|
|
235b5b0c15 | ||
|
|
aefed1d1b9 | ||
|
|
d39649f30d | ||
|
|
1546b7e5a9 | ||
|
|
ff74778dea | ||
|
|
29b21b828b | ||
|
|
a6a0495392 | ||
|
|
3281043e27 | ||
|
|
f7299403f7 | ||
|
|
a0d251a336 | ||
|
|
8ca45d3d03 | ||
|
|
d988542afc | ||
|
|
4285d81ca9 | ||
|
|
4286b2917e | ||
|
|
d09b5cb80e | ||
|
|
90fd18bf42 | ||
|
|
cae6c8e5f5 | ||
|
|
473340c53b | ||
|
|
ae302ee600 | ||
|
|
27934ac4ca | ||
|
|
283ef9adb7 | ||
|
|
377fe5ecde | ||
|
|
c848d3ee22 | ||
|
|
eb6be88fac | ||
|
|
fceb728501 | ||
|
|
a0df46258d | ||
|
|
57d1db58db | ||
|
|
d61b687853 | ||
|
|
42ab7a5d72 | ||
|
|
18cb11dcbf | ||
|
|
e9d5407792 | ||
|
|
a09b2c0074 | ||
|
|
a460ac021f | ||
|
|
bdee66fe29 | ||
|
|
84a251c8c7 | ||
|
|
cea763bb11 | ||
|
|
accbda9d2f | ||
|
|
7d64e141e4 | ||
|
|
6a9b4db448 | ||
|
|
e6a95ecd08 | ||
|
|
10cb7585a7 | ||
|
|
a577c7215f | ||
|
|
e5244cf3c0 | ||
|
|
c4d0aec536 | ||
|
|
796647158e | ||
|
|
266e9d0619 | ||
|
|
9ec54ef89b | ||
|
|
256cb90f3c | ||
|
|
8f3a3b293d | ||
|
|
975a7359a2 | ||
|
|
ac7361a55e | ||
|
|
bd41f50ba5 | ||
|
|
a2038ab07e | ||
|
|
6445a7674b | ||
|
|
3bf2e89a1a | ||
|
|
e6a13bf386 | ||
|
|
42b2adc03e | ||
|
|
f2b9af01e8 | ||
|
|
9bd2d92755 | ||
|
|
babe884b7c | ||
|
|
200287254b | ||
|
|
0b797f5964 | ||
|
|
13a8c309f5 | ||
|
|
528d2f29d3 | ||
|
|
0aba5dc8de | ||
|
|
72fcf5ab85 | ||
|
|
dd9df068b0 | ||
|
|
81772fb703 | ||
|
|
6304566603 | ||
|
|
5f8b60a0e5 | ||
|
|
4964e075df | ||
|
|
e2accaf70e | ||
|
|
7b32bbfc26 | ||
|
|
6963f3880d | ||
|
|
6c3430b6e5 | ||
|
|
c53f36a777 | ||
|
|
bb4d378733 | ||
|
|
333090a967 | ||
|
|
c8f5646d53 | ||
|
|
3002f6dfd5 | ||
|
|
3f8a50e2ae | ||
|
|
1a504118e5 | ||
|
|
2e602ef6b0 | ||
|
|
d55c3faaeb | ||
|
|
6b185d20c0 | ||
|
|
1bbc33a0cf | ||
|
|
a71eba07a1 | ||
|
|
422dd1fa4f | ||
|
|
c4b2e5829e | ||
|
|
1be222f6ed | ||
|
|
5463f459e6 | ||
|
|
14d7bfdab2 | ||
|
|
ea722da3de | ||
|
|
9aed1e2d17 | ||
|
|
63c6ed3fd0 | ||
|
|
744bfc1982 | ||
|
|
d04434e3ec | ||
|
|
dc3b7b5775 | ||
|
|
17fbfd14db | ||
|
|
d0f5aece5f | ||
|
|
eeda3062e1 | ||
|
|
168d712dec | ||
|
|
e64ad5176e | ||
|
|
0e1469f359 | ||
|
|
7d2b6cfeaf | ||
|
|
8e7d029946 | ||
|
|
9dd62ae0f8 | ||
|
|
e7dff4756f | ||
|
|
fa98d72f3a | ||
|
|
ccf0492890 | ||
|
|
eb0ef6bd3d | ||
|
|
19aa2ccf4d | ||
|
|
841b762796 | ||
|
|
63fb90806c | ||
|
|
88ada80686 | ||
|
|
7d5ddf5e6a | ||
|
|
8f01919f62 | ||
|
|
98394f99b5 | ||
|
|
cae50e16a7 | ||
|
|
4a2ac7bd2e | ||
|
|
a3bdaa85f2 | ||
|
|
40ef86f475 | ||
|
|
4cee14f3c5 | ||
|
|
48d9a8c180 | ||
|
|
1d384e3192 | ||
|
|
87f3dc4558 | ||
|
|
8afec596aa | ||
|
|
26e3263aec | ||
|
|
08b4e1a744 | ||
|
|
d68f0999a4 | ||
|
|
2c9ed4c565 | ||
|
|
be4f87c760 | ||
|
|
b963dadc14 | ||
|
|
26e7fd8b80 | ||
|
|
b9a972bccd | ||
|
|
bb7b9280d3 | ||
|
|
395fbbfd14 | ||
|
|
896dfe3def | ||
|
|
6c4f33910c | ||
|
|
0a301cdd21 | ||
|
|
ce59fc6c10 | ||
|
|
cbb74c984f | ||
|
|
287623df58 | ||
|
|
f8c1d97a25 | ||
|
|
2a188905da | ||
|
|
c2b1e38d7f | ||
|
|
fcc0aef7f4 | ||
|
|
eaa3de2dce | ||
|
|
f3dc5dd12f | ||
|
|
d2f0178fab | ||
|
|
326c386b2e | ||
|
|
6e68c4e2d6 | ||
|
|
a79e96802f | ||
|
|
65b22b493c | ||
|
|
b73f5a4e94 | ||
|
|
3b74ac841e | ||
|
|
253bf776b5 | ||
|
|
eef4acd07d | ||
|
|
b6f00ea241 | ||
|
|
0bd4a4f98f | ||
|
|
0b52645bb6 | ||
|
|
8e845d9f21 | ||
|
|
d29aa43ba4 | ||
|
|
450efea191 | ||
|
|
241cfc4342 | ||
|
|
7c67e3d7e2 | ||
|
|
674790a511 | ||
|
|
4e3c82e329 | ||
|
|
df711b0ea2 | ||
|
|
1019fd9a9d | ||
|
|
8a420dd853 | ||
|
|
f8bcfeb2ab | ||
|
|
34e5beda6a | ||
|
|
6185244754 | ||
|
|
60d9509e39 | ||
|
|
ded4469efe | ||
|
|
1f95d8eedf | ||
|
|
aa66dfff57 | ||
|
|
25263ce40f | ||
|
|
e85deb54e1 | ||
|
|
4750fd159e | ||
|
|
7eea9533e8 | ||
|
|
ec675b9ad2 | ||
|
|
486e77f474 | ||
|
|
048059ba1f | ||
|
|
ed3dda7da9 | ||
|
|
fa93d68b08 | ||
|
|
6093a4f9f8 | ||
|
|
4e20d89d9c | ||
|
|
b420ec6cb9 | ||
|
|
375f6101e9 | ||
|
|
2844d73dc7 | ||
|
|
6c1176f853 | ||
|
|
df037db0bb | ||
|
|
949cc7d21b | ||
|
|
9244529007 | ||
|
|
319d49ddbe | ||
|
|
96fcfdb6c6 | ||
|
|
d61ef6b49a | ||
|
|
804a6c8d47 | ||
|
|
c487cd6af2 | ||
|
|
6a2592a9d9 | ||
|
|
0f48b15695 | ||
|
|
4320b8a5a5 | ||
|
|
a20707cd73 | ||
|
|
5da1d3b73b | ||
|
|
be15e63d41 | ||
|
|
9bc5f686eb | ||
|
|
5bed21dace | ||
|
|
a97e651582 | ||
|
|
dff641a665 | ||
|
|
47a25cc3e8 | ||
|
|
5e3a5f627a | ||
|
|
9201e0a5b9 | ||
|
|
4c80ed3208 | ||
|
|
f34579e921 | ||
|
|
cc1d3b20b6 | ||
|
|
8c56356459 | ||
|
|
21f728f0ea | ||
|
|
8051b6e8b6 | ||
|
|
4c38fec3b5 | ||
|
|
c4cdcf44c5 | ||
|
|
347dab0c14 | ||
|
|
a3d3ea2b4b | ||
|
|
5332387125 | ||
|
|
5b21cbe0de | ||
|
|
2f4111a2e2 | ||
|
|
326ac485b3 | ||
|
|
f9b419d1e4 | ||
|
|
f4e81953ce | ||
|
|
2b09253961 | ||
|
|
1994c6828e | ||
|
|
f5c381d5b4 | ||
|
|
7e2af8364f | ||
|
|
0a2ab2aed2 | ||
|
|
2310a9bbc0 | ||
|
|
5b3e3d9cf4 | ||
|
|
c97c79ab2f | ||
|
|
1231b71245 | ||
|
|
824ffa24f4 | ||
|
|
148f869bec | ||
|
|
c140fe9bae | ||
|
|
4b02ee5b46 | ||
|
|
de3bac53bf | ||
|
|
3f1a76d9e4 | ||
|
|
0138e167e9 | ||
|
|
bcd2ee6204 | ||
|
|
4c1d521711 | ||
|
|
90c70fa5bf | ||
|
|
8e15c48092 | ||
|
|
9cf65e31cd | ||
|
|
a6e5876d96 | ||
|
|
937e723036 | ||
|
|
3a439063a6 | ||
|
|
12d876a005 | ||
|
|
4e2426a2b4 | ||
|
|
020f9cd2a6 | ||
|
|
d7eebe9df0 | ||
|
|
cebc5bf9fc | ||
|
|
dbe68684a0 | ||
|
|
27a05ff271 | ||
|
|
1489ddc49a | ||
|
|
9be2c1beb9 | ||
|
|
9964e6eba3 | ||
|
|
8bcc19d91e | ||
|
|
3c07f57aad | ||
|
|
3262a916e0 | ||
|
|
7883cc5891 | ||
|
|
ded7a5438c | ||
|
|
cd98951001 | ||
|
|
8470c60e06 | ||
|
|
394b1002b3 | ||
|
|
9a61d6293d | ||
|
|
192ad27f8f | ||
|
|
eb0fc67461 | ||
|
|
adbe5e9048 | ||
|
|
fcdf41ba29 | ||
|
|
7b40cbe8c1 | ||
|
|
0933929cfe | ||
|
|
7f618e7ecc | ||
|
|
482cb73702 | ||
|
|
fd1598017a | ||
|
|
96f79475f1 | ||
|
|
3576754c21 | ||
|
|
6c3a0bc72c | ||
|
|
49d75a0cd4 | ||
|
|
756dbac39f | ||
|
|
7df062b7d7 | ||
|
|
e485e8f60d | ||
|
|
fde8ea081a | ||
|
|
f7217c5f26 | ||
|
|
fd77e463a1 | ||
|
|
6d7f6750e9 | ||
|
|
695482ded7 | ||
|
|
fc9649dbc4 | ||
|
|
afb67d375f | ||
|
|
66e38ae69e | ||
|
|
1f4e64f81d | ||
|
|
ad541f713d | ||
|
|
f3b434397b | ||
|
|
1d5d49312c | ||
|
|
232c7361a9 | ||
|
|
721543653b | ||
|
|
d8ba26e664 | ||
|
|
7ecabeac97 | ||
|
|
6cf7be4b7e | ||
|
|
5f2e56674c | ||
|
|
58d4c74b0b | ||
|
|
e137792efd | ||
|
|
b7b934913e | ||
|
|
cd8fcbf9c6 | ||
|
|
d0300d4443 | ||
|
|
40acd9a4c3 | ||
|
|
e66b4d5390 | ||
|
|
f94433e504 | ||
|
|
7f32488b78 | ||
|
|
8f73e24175 | ||
|
|
f3c984281c | ||
|
|
00777a10ae | ||
|
|
0d6ce9f977 | ||
|
|
997c517ba2 | ||
|
|
849bed4bef | ||
|
|
61a48a5b9f | ||
|
|
df14085ec8 | ||
|
|
00b34eb2a4 | ||
|
|
84a6730b1a | ||
|
|
357b514bc9 | ||
|
|
d39b108274 | ||
|
|
39f3239682 | ||
|
|
506f36b26d | ||
|
|
4cedbf80df | ||
|
|
b54ce31078 | ||
|
|
9b2aa974ba | ||
|
|
291c97dc81 | ||
|
|
bcbecff6f6 | ||
|
|
3d22708f67 | ||
|
|
8afd31902f | ||
|
|
89f66ebf6d | ||
|
|
d1a2208196 | ||
|
|
42d1fe5422 | ||
|
|
1424e8a2de | ||
|
|
7dc548b4b8 | ||
|
|
2c37d94611 | ||
|
|
b1e4a7c615 | ||
|
|
b0561058c6 | ||
|
|
d039295070 | ||
|
|
4ddafb8e84 | ||
|
|
a536231ded | ||
|
|
13fe54c938 | ||
|
|
d54ffdd187 | ||
|
|
5c539af7d7 | ||
|
|
47d9a9cf20 | ||
|
|
7f25205aeb | ||
|
|
688973fa44 | ||
|
|
400661d432 | ||
|
|
7ebecf3851 | ||
|
|
0a5eaec0f2 | ||
|
|
4bdab73dd5 | ||
|
|
f19f21007c | ||
|
|
e4e60ed654 | ||
|
|
f21dd9117d | ||
|
|
2aa75f034f | ||
|
|
5c78e0a462 | ||
|
|
52cdedcba0 | ||
|
|
c87cd0de73 | ||
|
|
12530655df | ||
|
|
7479706b29 | ||
|
|
070a141601 | ||
|
|
7044236824 | ||
|
|
3e9478b58d | ||
|
|
5ffca2d138 | ||
|
|
a8d4a98621 | ||
|
|
905f7f4ecc | ||
|
|
819d2bc560 | ||
|
|
c0b2027588 | ||
|
|
136d1b04b5 | ||
|
|
f063dd195e | ||
|
|
be972fc0b5 | ||
|
|
ff90a5d321 | ||
|
|
ac11ba3d60 | ||
|
|
3d8598654c | ||
|
|
55a5da2102 | ||
|
|
f881d6c44f | ||
|
|
5df2ca3ef3 | ||
|
|
a15f87ae39 | ||
|
|
0dd6377fe6 | ||
|
|
c7257e0a3c | ||
|
|
3b7fbcd0c3 | ||
|
|
5fe91af6c3 | ||
|
|
4dd646a424 | ||
|
|
dcbd90ce04 | ||
|
|
04683338a2 | ||
|
|
b778f9c40e | ||
|
|
e0a96be378 | ||
|
|
ac690fceaf | ||
|
|
1e5e03cc46 | ||
|
|
d3c4cd8270 | ||
|
|
17361df66b | ||
|
|
7d0452c7e3 | ||
|
|
cee0ab87fc | ||
|
|
dd0b0cae93 | ||
|
|
c4bf5eef73 | ||
|
|
2587639914 | ||
|
|
bfa1ae59cc | ||
|
|
9e8575c315 | ||
|
|
e36340ce64 | ||
|
|
63ec05a66c | ||
|
|
6f1c72f5b4 | ||
|
|
27924ffd5b | ||
|
|
527029574c | ||
|
|
cf3aeafcdb | ||
|
|
f08a79d372 | ||
|
|
eb2a26cfaa | ||
|
|
5378d9ca26 | ||
|
|
bce11af09a | ||
|
|
158abf5c6c | ||
|
|
b30c1daf72 | ||
|
|
0649206796 | ||
|
|
f589a1d245 | ||
|
|
8a604bd2a1 | ||
|
|
68eb627d62 | ||
|
|
9c87a5890d | ||
|
|
b8a8e2280d | ||
|
|
e538a13e28 | ||
|
|
6fb2a1ed39 | ||
|
|
6b00787f45 | ||
|
|
e735d8d4e5 | ||
|
|
8cd4fd0b50 | ||
|
|
8f4b0559ce | ||
|
|
5288c54aad | ||
|
|
51f8bec81b | ||
|
|
49d3e5d324 | ||
|
|
1fff0e5592 | ||
|
|
8d53ec5353 | ||
|
|
a1e1bfc71b | ||
|
|
29992f54a3 | ||
|
|
4ab6786163 | ||
|
|
eaad34a69a | ||
|
|
839f18d052 | ||
|
|
58ef6d8385 | ||
|
|
a0037c9333 | ||
|
|
177b57e1c0 | ||
|
|
810c129ca9 | ||
|
|
f731a4c704 | ||
|
|
39a1f1ef64 | ||
|
|
d24a87caf1 | ||
|
|
9683ffe13a | ||
|
|
fab2d9dc6a | ||
|
|
59f7a2f6ef | ||
|
|
9b12407028 | ||
|
|
96c4bb7fd0 | ||
|
|
81532f375e | ||
|
|
79eb8e2b35 | ||
|
|
7c2e875494 | ||
|
|
c715b4637d | ||
|
|
c719a61ea7 | ||
|
|
58e4d337e4 | ||
|
|
509c802045 | ||
|
|
4fd63f4e30 | ||
|
|
f5c28c72fd | ||
|
|
342128a457 | ||
|
|
b1931828e1 | ||
|
|
4410226db1 | ||
|
|
945bd4b1b0 | ||
|
|
77f1ea40cd | ||
|
|
98c8c7ce0d | ||
|
|
795bf9e101 | ||
|
|
851fedf751 | ||
|
|
5be6ab8c89 | ||
|
|
3670412c7c | ||
|
|
e6cd596dc9 | ||
|
|
abf4278d09 | ||
|
|
2b2b65fe18 | ||
|
|
3d6a125bdc | ||
|
|
9efd40a366 | ||
|
|
383fa8401d | ||
|
|
bf8ffade29 | ||
|
|
1633d14547 | ||
|
|
e55605dbe9 | ||
|
|
cdec38ba12 | ||
|
|
1cb6e9e7d0 | ||
|
|
d1d2f6f451 | ||
|
|
743f821f1e | ||
|
|
b95a99e0c2 | ||
|
|
3ebbeb103c | ||
|
|
0d4035e996 | ||
|
|
88ed5e506a | ||
|
|
76a3371b40 | ||
|
|
7b2fa1edb4 | ||
|
|
1965035166 | ||
|
|
76d4ff056a | ||
|
|
a239a9efd5 | ||
|
|
4747e7c5b9 | ||
|
|
35ca729cb9 | ||
|
|
a0fa7421d1 | ||
|
|
ddf293bbcd | ||
|
|
45d6e00ff1 | ||
|
|
6dfc8fe0ea | ||
|
|
96f106d6aa | ||
|
|
044da37c95 | ||
|
|
e0b00ee11a | ||
|
|
086444c73a | ||
|
|
1efb2085e9 | ||
|
|
e1c42eb6cc | ||
|
|
a2da26cbdd | ||
|
|
8796adfd63 | ||
|
|
95a6c28d98 | ||
|
|
85e1f4ea13 | ||
|
|
d9c9114b3b | ||
|
|
02140ce763 | ||
|
|
af1cc3b331 | ||
|
|
e852044b64 | ||
|
|
f845b371ce | ||
|
|
3a1bd3114b | ||
|
|
a6d2e3a1e6 | ||
|
|
93bce1b24c | ||
|
|
7b87f29c9c | ||
|
|
cb6f622957 | ||
|
|
43d3b51bde | ||
|
|
57e015155a | ||
|
|
2436d7e0ba | ||
|
|
e6e85b0c55 | ||
|
|
e43fd39594 | ||
|
|
52765466c1 | ||
|
|
fa9afb0860 | ||
|
|
e02bede4f5 | ||
|
|
113089be5d | ||
|
|
712b895170 | ||
|
|
dca163f54d | ||
|
|
fc6cf4d963 | ||
|
|
4126c7e188 | ||
|
|
db50462920 | ||
|
|
9310b44cef | ||
|
|
6480250221 | ||
|
|
710ce7c2e9 | ||
|
|
b88e64f0d0 | ||
|
|
c70432996a | ||
|
|
c6dcf2a0e2 | ||
|
|
2aec627503 | ||
|
|
b7924ce58b | ||
|
|
0bc745f68f | ||
|
|
8acdf823a2 | ||
|
|
52f8b787c9 | ||
|
|
ad153ae041 | ||
|
|
2527f8f599 | ||
|
|
72af092cc1 | ||
|
|
2751060b91 | ||
|
|
b3449db2f8 | ||
|
|
7db28745c8 | ||
|
|
6921211461 | ||
|
|
9f90618a70 | ||
|
|
302c41edc9 | ||
|
|
5d943a35f8 | ||
|
|
03f8d6e946 | ||
|
|
68a35155e4 | ||
|
|
9d725af602 | ||
|
|
d04ccb7a3f | ||
|
|
76c1ed6628 | ||
|
|
6f4abe95cb | ||
|
|
e35e313240 | ||
|
|
3a77a6eded | ||
|
|
0a7a1b9bfb | ||
|
|
3e1418d662 | ||
|
|
884f70fb39 | ||
|
|
9499a1142b | ||
|
|
6e8dcdce78 | ||
|
|
ce9fae82bd | ||
|
|
afa3fc8bf9 | ||
|
|
09eccf6fc0 | ||
|
|
edfefb6763 | ||
|
|
98b3dcbf37 | ||
|
|
2698ef6c5f | ||
|
|
46b2ee3bae | ||
|
|
e7ee3a7dd5 | ||
|
|
0085e6f83b | ||
|
|
c90fa3bcfc | ||
|
|
0ec9b9823f | ||
|
|
50d7b8452d | ||
|
|
d88f8e862b | ||
|
|
fd72cced13 | ||
|
|
18a90734d9 | ||
|
|
a4b2cebef6 | ||
|
|
68d708e56d | ||
|
|
e925ab0999 | ||
|
|
a6feb0a887 | ||
|
|
cd9c3a79e5 | ||
|
|
f3685bdef9 | ||
|
|
e2edf20833 | ||
|
|
31e39f1f6d | ||
|
|
d47c5eaf60 | ||
|
|
b8e515a738 | ||
|
|
0984585d58 | ||
|
|
f3e0eb705b | ||
|
|
04e0f87c03 | ||
|
|
8ccfcd272f | ||
|
|
ba69062a36 | ||
|
|
cea24b5f70 | ||
|
|
f3a71f62a1 | ||
|
|
4e4a6d8397 | ||
|
|
d8beaf727f | ||
|
|
c961c865ee | ||
|
|
34f25fa590 | ||
|
|
7af1155c11 | ||
|
|
c9d7daab70 | ||
|
|
e3feac3fd8 | ||
|
|
1a5989350f | ||
|
|
3365df7778 | ||
|
|
3be5a68e12 | ||
|
|
f158caa2eb | ||
|
|
e02c94eb00 | ||
|
|
981077516b | ||
|
|
7c488b5913 | ||
|
|
0e44f587a5 | ||
|
|
e593d86b80 | ||
|
|
339a8ad610 | ||
|
|
d1190febec | ||
|
|
5d2c5b01a8 | ||
|
|
0f954c20ff | ||
|
|
d6edff3182 | ||
|
|
dba26c3240 | ||
|
|
0ca5b7996c | ||
|
|
9507b121ac | ||
|
|
3812b275e9 | ||
|
|
6c1561f415 | ||
|
|
9ab6353d73 | ||
|
|
ff60dc4d24 | ||
|
|
62e7d904b4 | ||
|
|
5d3de4b670 | ||
|
|
7d75ad4c56 | ||
|
|
63e1d3610c | ||
|
|
b4325026b1 | ||
|
|
b072ef2e82 | ||
|
|
562a4c056e | ||
|
|
bb8248556d | ||
|
|
b22b085b50 | ||
|
|
e82ea94bb6 | ||
|
|
a50252382a | ||
|
|
9bdb799b41 | ||
|
|
989651c23b | ||
|
|
259cefab72 | ||
|
|
09ed421948 | ||
|
|
87f5ec5be5 | ||
|
|
f78b656f5f | ||
|
|
e6b940e247 | ||
|
|
50a9680f17 | ||
|
|
f2a6dc4dfd | ||
|
|
67e3dd36b3 | ||
|
|
6c4cc357b5 | ||
|
|
94965a418f | ||
|
|
40e6ba1100 | ||
|
|
0aed065a75 | ||
|
|
70b63a5ed4 | ||
|
|
1476a9ecf1 | ||
|
|
600a235140 | ||
|
|
ffa9f104db | ||
|
|
b807ec0956 | ||
|
|
5f8daeeb6d | ||
|
|
aec8307417 | ||
|
|
e96a1b01b9 | ||
|
|
d78ba322bf | ||
|
|
ab5c1b0a3a | ||
|
|
53fa16d39f | ||
|
|
05cf405cb5 | ||
|
|
5d833336d3 | ||
|
|
13be9d8d79 | ||
|
|
bb6326f4d4 | ||
|
|
c070407ab2 | ||
|
|
6b27bdadcd | ||
|
|
1c22c2f76a | ||
|
|
aa262d4124 | ||
|
|
646c0bfcb9 | ||
|
|
da68775472 | ||
|
|
bcf96608d1 | ||
|
|
dd17ac5045 | ||
|
|
59182dbc97 | ||
|
|
11df7187b3 | ||
|
|
0899803294 | ||
|
|
4742e0951c | ||
|
|
c3b1eb0837 | ||
|
|
542d7977db | ||
|
|
11927a768e | ||
|
|
ecd5bcec0c | ||
|
|
911a2084d4 | ||
|
|
839bf0e2c9 | ||
|
|
25555b8c3e | ||
|
|
cd7ac94f35 | ||
|
|
68aea3af9e | ||
|
|
cdb3216cc9 | ||
|
|
78f0201dfa | ||
|
|
2cd47b0240 | ||
|
|
be83a6a37a | ||
|
|
8ca99e85b0 | ||
|
|
f5b546b3c8 | ||
|
|
50c9d17830 | ||
|
|
d87af4788e | ||
|
|
0f21537f14 | ||
|
|
0533bde9f2 | ||
|
|
32b3717c32 | ||
|
|
d950f801af | ||
|
|
a8ab028b84 | ||
|
|
2aff36e74b | ||
|
|
db7e4bf940 | ||
|
|
ee20015d44 | ||
|
|
80941f8413 | ||
|
|
cf909db159 | ||
|
|
27dbe77fad | ||
|
|
38f2334360 | ||
|
|
6489a48e1f | ||
|
|
ca24d1762e | ||
|
|
2f4b84c074 | ||
|
|
6ae810a1fa | ||
|
|
3e3f695536 | ||
|
|
3ca93f4a4c | ||
|
|
f2d9930773 | ||
|
|
2fbf399156 | ||
|
|
5fe91d6577 | ||
|
|
753d0e7df7 | ||
|
|
e2cc350fbc | ||
|
|
07feb87dee | ||
|
|
1699e94f0f | ||
|
|
8a1e335bf5 | ||
|
|
5413bf8753 | ||
|
|
c4c5ecd03d | ||
|
|
3b67cf4378 | ||
|
|
f3c937f9ed | ||
|
|
671a699472 | ||
|
|
cc4fa1c6bd | ||
|
|
d11d476126 | ||
|
|
1ce06c7cdd | ||
|
|
800f02ba38 | ||
|
|
79964b0e85 | ||
|
|
0924d11bbf | ||
|
|
3cf85634eb | ||
|
|
ec01175d37 | ||
|
|
5bb518ff0f | ||
|
|
5415381cf4 | ||
|
|
129ae06354 | ||
|
|
058e5d5f4b | ||
|
|
2ec493f033 | ||
|
|
a33e8a8509 | ||
|
|
7b4be7be40 | ||
|
|
b90917a529 | ||
|
|
180f05f6f0 | ||
|
|
b2686e5b6d | ||
|
|
831b95f13a | ||
|
|
331b599a81 | ||
|
|
3ef817ebc2 | ||
|
|
d0f7c309ab | ||
|
|
e5079b9dad | ||
|
|
9e773c9143 | ||
|
|
fe600441c9 | ||
|
|
291b56db93 | ||
|
|
ec9975c3d9 | ||
|
|
9e57161941 | ||
|
|
20444bf253 | ||
|
|
e428c4402e | ||
|
|
15af89d51c | ||
|
|
ed95865696 | ||
|
|
8c71bd57e7 | ||
|
|
d204be4b2c | ||
|
|
4743171b4f | ||
|
|
1fb800f7a7 | ||
|
|
3a3b0dd5c1 | ||
|
|
2959281d42 | ||
|
|
e2c939fb97 | ||
|
|
51d0e4325e | ||
|
|
08b6cf0231 | ||
|
|
059147b74a | ||
|
|
c96ceeb1bd | ||
|
|
0a3b6c4813 | ||
|
|
8636a15695 | ||
|
|
96db9362c5 | ||
|
|
f81f93e9c3 | ||
|
|
a3a04b5f76 | ||
|
|
329174b6d9 | ||
|
|
b171aff418 | ||
|
|
1f6ffa3e88 | ||
|
|
24d2a8b9d5 | ||
|
|
4d8b99a307 | ||
|
|
f9085fedd7 | ||
|
|
c4236e58d1 | ||
|
|
eb91f9575a | ||
|
|
947d14ffeb | ||
|
|
b3f915b5b2 | ||
|
|
03d3f3afc3 | ||
|
|
2728170c01 | ||
|
|
c107f3632a | ||
|
|
7d1c5fca0b | ||
|
|
e71887e1a8 | ||
|
|
39d1eeda23 | ||
|
|
b482f3ce94 | ||
|
|
122cc48c29 | ||
|
|
c7c57cfa0e | ||
|
|
1f9ca20dfd | ||
|
|
5efbaa4849 | ||
|
|
c6f55bc4ab | ||
|
|
b28a3db3d6 | ||
|
|
b9ece28f68 | ||
|
|
79b4907cb9 | ||
|
|
dfbc244b00 | ||
|
|
b23d5e26e2 | ||
|
|
f1f3074306 | ||
|
|
f162ad193f | ||
|
|
5254f300f9 | ||
|
|
c0d0100ca8 | ||
|
|
21f201e371 | ||
|
|
bbc378ed8f | ||
|
|
e2d494321c | ||
|
|
c2df8043db | ||
|
|
df62150b5a | ||
|
|
eea52a5fa6 | ||
|
|
2d9015b840 | ||
|
|
187ef29914 | ||
|
|
c12be766e9 | ||
|
|
2168458f11 | ||
|
|
8cf0725593 | ||
|
|
8379f015d7 | ||
|
|
f08c01800f | ||
|
|
a62706678e | ||
|
|
e4468562d2 | ||
|
|
7316bf7bc5 | ||
|
|
3747b7d930 | ||
|
|
5be3f22d06 | ||
|
|
c572ce946b | ||
|
|
d69f0289ca | ||
|
|
048e5210f7 | ||
|
|
219e9115c0 | ||
|
|
b43416af97 | ||
|
|
6d84da588b | ||
|
|
569d6c557c | ||
|
|
58bb94d7c7 | ||
|
|
4f8f775e69 | ||
|
|
5eed02f7e9 | ||
|
|
983f1f28ca | ||
|
|
192ede5e64 | ||
|
|
803a7aa878 | ||
|
|
93f3098aec | ||
|
|
dbd94d047b | ||
|
|
be39ab32d1 | ||
|
|
8b1fb3cb0c | ||
|
|
9e04222ee6 | ||
|
|
72349507c4 | ||
|
|
79db8daddd | ||
|
|
b967f83f20 | ||
|
|
74a7592b4b | ||
|
|
486f23538f | ||
|
|
df86ff2191 | ||
|
|
41266f05e9 | ||
|
|
d2aa331838 | ||
|
|
670cb9d223 | ||
|
|
a205762bf0 | ||
|
|
2ffab66d97 | ||
|
|
10a6aec998 | ||
|
|
7cbe31baad | ||
|
|
8a09dc1b9b | ||
|
|
fa6e174651 | ||
|
|
8dee328eae | ||
|
|
340155e6a6 | ||
|
|
7fe19a030a | ||
|
|
11e0ccdc91 | ||
|
|
e55b2f4f8d | ||
|
|
d78c1f695e | ||
|
|
cfbb3e86b3 | ||
|
|
fa6234e417 | ||
|
|
9eeae9ad7e | ||
|
|
28688488ff | ||
|
|
41d804719f | ||
|
|
bc18168662 | ||
|
|
1fadae82c7 | ||
|
|
791c62ca64 | ||
|
|
4441a6ff59 | ||
|
|
63a7002477 | ||
|
|
a746139c53 | ||
|
|
697a060132 | ||
|
|
7834c25253 | ||
|
|
2e7cbfcff5 | ||
|
|
5b771039fc | ||
|
|
2f1bc5864f | ||
|
|
16d79ebaac | ||
|
|
e009ec8b93 | ||
|
|
738ece513f | ||
|
|
5a7b7b51c5 | ||
|
|
5bba9bddfd | ||
|
|
a3c0c7546d | ||
|
|
05d7ae4efa | ||
|
|
44483dba21 | ||
|
|
3498a5856a | ||
|
|
cd79b0343a | ||
|
|
abba00d595 | ||
|
|
88a8d7d901 | ||
|
|
d5c00071d3 | ||
|
|
c7a6cf5463 | ||
|
|
2ce2a15fc6 | ||
|
|
ce4be4e91e | ||
|
|
82dc2244c0 | ||
|
|
ab45b7783f | ||
|
|
4a56b2406b | ||
|
|
e0cd2cdb9c | ||
|
|
7575094cf3 | ||
|
|
40a3ae04b6 | ||
|
|
ac26f84170 | ||
|
|
fa574fe833 | ||
|
|
9a6e18ce80 | ||
|
|
8f9034fc8b | ||
|
|
3e5b102445 | ||
|
|
d8b1a8d439 | ||
|
|
54ae008dd7 | ||
|
|
9c1747581f | ||
|
|
ae4f7b6d7a | ||
|
|
c947322a69 | ||
|
|
14db45215f | ||
|
|
d20831e41a | ||
|
|
1bb902984b | ||
|
|
09f1c58872 | ||
|
|
7f944c2c8b | ||
|
|
1c02b85802 | ||
|
|
a2801649b4 | ||
|
|
15777732d3 | ||
|
|
b875037150 | ||
|
|
a6d583cb5e | ||
|
|
9882f3429a | ||
|
|
29d47c4de2 | ||
|
|
a9b15f1c36 | ||
|
|
ce38ecb966 | ||
|
|
13ffa17048 | ||
|
|
a387682dfb | ||
|
|
f6ed197cd3 | ||
|
|
243593cdaa | ||
|
|
1e2d559859 | ||
|
|
20ea859183 | ||
|
|
86b24ea059 | ||
|
|
07af42476d | ||
|
|
87edf71e93 | ||
|
|
e3ea2ed420 | ||
|
|
8f9a1881a4 | ||
|
|
a8c6111197 | ||
|
|
3de8570022 | ||
|
|
a0311b0134 | ||
|
|
158a628c0e | ||
|
|
422e5026d6 | ||
|
|
797cbb9b20 | ||
|
|
be68fbd4f5 | ||
|
|
e440223b40 | ||
|
|
5766250288 | ||
|
|
870274ad9d | ||
|
|
9d6abcd9be | ||
|
|
432771dfe3 | ||
|
|
69925ce823 | ||
|
|
e9f9f515bd | ||
|
|
efd96153d8 | ||
|
|
c7b8f223ee | ||
|
|
a0636d5a87 | ||
|
|
c7b16249b8 | ||
|
|
031e885e4d | ||
|
|
796e2cc156 | ||
|
|
7af784adce | ||
|
|
a988a91e2e | ||
|
|
a8b564fa64 | ||
|
|
b97e140389 | ||
|
|
e51bef6d12 | ||
|
|
4bd31f4967 | ||
|
|
7ff7a7c527 | ||
|
|
c4a375b3a5 | ||
|
|
e3698edd19 | ||
|
|
e591d5cfe4 | ||
|
|
a2e62f8e1d | ||
|
|
764963e986 | ||
|
|
4da493f3f3 | ||
|
|
dec90f7e5e | ||
|
|
2ea5b283a8 | ||
|
|
eb23549cd6 | ||
|
|
cd90062850 | ||
|
|
3ec72fcee9 | ||
|
|
b9091e14b3 | ||
|
|
5daefc0194 | ||
|
|
44edb2bd2f | ||
|
|
201aa24448 | ||
|
|
e799ef2977 | ||
|
|
0c538f7527 | ||
|
|
ca7202eb0a | ||
|
|
d5ec5f80ff | ||
|
|
0c944a03fe | ||
|
|
79a267ab08 | ||
|
|
f9a6988ece | ||
|
|
95e06de5de | ||
|
|
f530a5074b | ||
|
|
439580b91b | ||
|
|
4c2a384159 | ||
|
|
22b83d7630 | ||
|
|
cfd0b040e4 | ||
|
|
b001840dee | ||
|
|
0b5bff01e1 | ||
|
|
4fe7b6cd65 | ||
|
|
49e1f7d8bf | ||
|
|
803fb243bf | ||
|
|
20a6ab3d1a | ||
|
|
21f16b50f3 | ||
|
|
f58e83ee87 | ||
|
|
a9b4a2a1ac | ||
|
|
5be1449db5 | ||
|
|
37d7f89c6d | ||
|
|
57e58ce76c | ||
|
|
1d9f76e2c8 | ||
|
|
56e0269e5e | ||
|
|
662df85e54 | ||
|
|
2be4a5e486 | ||
|
|
a43d697225 | ||
|
|
f06c1e6c78 | ||
|
|
b5d1918401 | ||
|
|
d018be5d36 | ||
|
|
67184d7b20 | ||
|
|
dfdd48b990 | ||
|
|
cae9cee295 | ||
|
|
067d586c1c | ||
|
|
d22b7938da | ||
|
|
29b7510957 | ||
|
|
9d548d81ac | ||
|
|
3c33cdfa3d | ||
|
|
288485b209 | ||
|
|
1cbf416b10 | ||
|
|
3b46d3dd74 | ||
|
|
c243829234 | ||
|
|
fd80faa389 | ||
|
|
e7d4352292 | ||
|
|
610e0f21d6 | ||
|
|
7e512bad96 | ||
|
|
98c4ce0c45 | ||
|
|
93fc48a2db | ||
|
|
d7eea12a6f | ||
|
|
2a1e06f8a9 | ||
|
|
02d54a783a | ||
|
|
5961d44339 | ||
|
|
fb3be8509d | ||
|
|
97be53741d | ||
|
|
0d2c26735e | ||
|
|
2d0a8ddb58 | ||
|
|
72518d4827 | ||
|
|
c6cd744186 | ||
|
|
ae2db62f1c | ||
|
|
8c76b8bc36 | ||
|
|
18256c4923 | ||
|
|
f345cc66cf | ||
|
|
5dbf664a6b | ||
|
|
d7c6679d70 | ||
|
|
cae203be71 | ||
|
|
8a29fbc850 | ||
|
|
00bcbd367f | ||
|
|
1befee5aca | ||
|
|
5982f4bcf0 | ||
|
|
2399476a21 | ||
|
|
9e45ac939b | ||
|
|
34f231f9f3 | ||
|
|
f940b2a58e | ||
|
|
0383c33558 | ||
|
|
3afa4b210d | ||
|
|
998783eb9d | ||
|
|
54d61bdc4a | ||
|
|
2d279c4c5c | ||
|
|
0fb206fe15 | ||
|
|
5d6fd8099f | ||
|
|
800e3f4599 | ||
|
|
35c61f52fe | ||
|
|
3db446633c | ||
|
|
e3b4a0213c | ||
|
|
9910ff5fa1 | ||
|
|
049be10406 | ||
|
|
9aa3be7f9f | ||
|
|
aba5c634ae | ||
|
|
9774b01b0e | ||
|
|
ecf0a710e1 | ||
|
|
c9febbdd87 | ||
|
|
caa2e45a8c | ||
|
|
d9130c9852 | ||
|
|
661f05837c | ||
|
|
a61fe418b2 | ||
|
|
b15cfc2c5a | ||
|
|
c575c9af3d | ||
|
|
81f27e9077 | ||
|
|
f78babfaa0 | ||
|
|
66990cf872 | ||
|
|
095fe2ed1b | ||
|
|
483ebc8141 | ||
|
|
b9311282eb | ||
|
|
d6f0c2b52b | ||
|
|
bb25febd70 | ||
|
|
19ab2a29ce | ||
|
|
24b4fe9867 | ||
|
|
aa7b82de04 | ||
|
|
3d826bed3a | ||
|
|
d2ae7e78ef | ||
|
|
656bd330f8 | ||
|
|
c2c8f32010 | ||
|
|
9f43c270e6 | ||
|
|
50827188ff | ||
|
|
6ae0f7f5c6 | ||
|
|
c583d6bb49 | ||
|
|
fdcb6b721c | ||
|
|
ef858ef062 | ||
|
|
fe04faf675 | ||
|
|
ad752b317d | ||
|
|
59649e9b1e | ||
|
|
6bf281f905 | ||
|
|
4dfc8b262c | ||
|
|
f08ffe9feb | ||
|
|
65de3110a9 | ||
|
|
f6dcd98995 | ||
|
|
950172dc01 | ||
|
|
5c48e139d4 | ||
|
|
150e9c8a48 | ||
|
|
b19ba13aff | ||
|
|
30bfc2cea7 | ||
|
|
30c2d84c6d | ||
|
|
fbd2038fa7 | ||
|
|
e4b8d9b9d6 | ||
|
|
6b50003697 | ||
|
|
a73c5b3355 | ||
|
|
eb59817e81 | ||
|
|
d404e92d16 | ||
|
|
86c017ec9d | ||
|
|
0407c4e0f7 | ||
|
|
0ba95a3dd4 | ||
|
|
0463b5d6cd | ||
|
|
3f4513b3a9 | ||
|
|
fb2029e717 | ||
|
|
48f02fb61b | ||
|
|
0c00e870c6 | ||
|
|
7da50703fb | ||
|
|
cfdaff5a46 | ||
|
|
18e46962c2 | ||
|
|
319e0ae3cf | ||
|
|
e69a7c38d9 | ||
|
|
ecd685755b | ||
|
|
276b51d927 | ||
|
|
251d1c5c9f | ||
|
|
028e17475f | ||
|
|
1ab630435d | ||
|
|
89002ed298 | ||
|
|
d0871bdae3 | ||
|
|
5ea6e9c9c0 |
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: acmesh
|
||||
ko_fi: neilpang
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
27
.github/ISSUE_TEMPLATE.md
vendored
Normal file
27
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<!--
|
||||
我很忙, 每天可能只有 几秒钟 时间看你的 issue, 如果不按照我的要求写 issue, 你可能不会得到任何回复, 石沉大海.
|
||||
|
||||
请确保已经更新到最新的代码, 然后贴上来 `--debug 2` 的调试输出. 没有调试信息. 我做不了什么.
|
||||
如何调试 https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh
|
||||
|
||||
If it is a bug report:
|
||||
- make sure you are able to repro it on the latest released version.
|
||||
You can install the latest version by: `acme.sh --upgrade`
|
||||
|
||||
- Search the existing issues.
|
||||
- Refer to the [WIKI](https://wiki.acme.sh).
|
||||
- Debug info [Debug](https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh).
|
||||
|
||||
-->
|
||||
|
||||
Steps to reproduce
|
||||
------------------
|
||||
|
||||
Debug log
|
||||
-----------------
|
||||
|
||||
```
|
||||
acme.sh --issue ..... --debug 2
|
||||
```
|
||||
|
||||
|
||||
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
<!--
|
||||
1. Do NOT send pull request to `master` branch.
|
||||
Please send to `dev` branch instead.
|
||||
Any PR to `master` branch will NOT be merged.
|
||||
|
||||
2. For dns api support, read this guide first: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide
|
||||
You will NOT get any review without passing this guide. You also need to fix the CI errors.
|
||||
|
||||
-->
|
||||
190
.github/copilot-instructions.md
vendored
Normal file
190
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
# GitHub Copilot Shell Scripting (sh) Review Instructions for acme.sh
|
||||
|
||||
## Overall Goal
|
||||
|
||||
Your role is to act as a rigorous yet helpful senior engineer, reviewing Shell script code (`.sh` files) for the [acme.sh](https://github.com/acmesh-official/acme.sh) project. Ensure the code exhibits the highest levels of robustness, security, and portability.
|
||||
The review must focus on risks unique to Shell scripting, such as proper quoting, robust error handling, and the secure execution of external commands.
|
||||
|
||||
## Required Output Format
|
||||
|
||||
Organize the feedback into a single, structured report, using the three-level marking system:
|
||||
|
||||
1. **Critical Issues (Must Fix Before Merge)**
|
||||
2. **Suggestions (Improvements to Consider)**
|
||||
3. **Good Practices (Points to Commend)**
|
||||
|
||||
---
|
||||
|
||||
## Shell Compatibility
|
||||
|
||||
- **POSIX sh only** -- all scripts must target `sh`, not `bash`. No bash-isms allowed.
|
||||
- **Shebang**: always use `#!/usr/bin/env sh` (not `#!/bin/sh`, not `#!/usr/bin/env bash`).
|
||||
- **Use `return`, never `exit`** -- scripts are sourced, not executed as subprocesses. `exit` would kill the parent shell.
|
||||
- **Cross-platform**: code must work on Linux, macOS, FreeBSD, Solaris, and BusyBox environments.
|
||||
|
||||
---
|
||||
|
||||
## Robustness and Error Handling
|
||||
|
||||
- **(Critical)** Enforce the use of the following combination at the start of the script for safety and robustness:
|
||||
- `set -e`: Exit immediately if a command exits with a non-zero status.
|
||||
- `set -u`: Treat unset variables as an error and exit.
|
||||
- `set -o pipefail`: Ensure the whole pipeline fails if any command in the pipe fails.
|
||||
- **Always check return values** of function calls. If an error occurs, there must be a way to stop execution.
|
||||
- **Return 1** after `_err` messages:
|
||||
```sh
|
||||
if [ -z "$VARIABLE" ]; then
|
||||
_err "VARIABLE is required"
|
||||
return 1
|
||||
fi
|
||||
```
|
||||
- Check for the use of `mktemp` when creating temporary files to prevent race conditions and security risks.
|
||||
|
||||
---
|
||||
|
||||
## Security and Quoting
|
||||
|
||||
- **(Critical)** Check that all variable expansions (like `$VAR` and `$(COMMAND)`) are properly enclosed in **double quotes** (i.e., `"$VAR"` and `"$(COMMAND)"`) to prevent **Word Splitting** and **Globbing**.
|
||||
- **(Critical)** Find and flag any hardcoded passwords, keys, tokens, or authentication details.
|
||||
- Verify that all user input, command-line arguments (`$1`, `$2`, etc.), or environment variables are rigorously validated and sanitized before use.
|
||||
- Avoid `eval` -- warn against and suggest alternatives, as it can lead to arbitrary code execution.
|
||||
|
||||
---
|
||||
|
||||
## Use Built-in Helper Functions
|
||||
|
||||
Never use raw shell commands when acme.sh provides a wrapper function. This is the most critical rule for portability.
|
||||
|
||||
| Instead of | Use |
|
||||
|---|---|
|
||||
| `tr '[:upper:]' '[:lower:]'` | `_lower_case()` |
|
||||
| `tr '[:lower:]' '[:upper:]'` | `_upper_case()` |
|
||||
| `head -n 1` | `_head_n 1` |
|
||||
| `openssl dgst` / `openssl` | `_digest()` / `_hmac()` |
|
||||
| `date` | `_utc_date()` with `sed`/`tr` |
|
||||
| `curl` / `wget` | `_get()` or `_post()` |
|
||||
| `sleep` | `_sleep` |
|
||||
| `base64` / `openssl base64` | `_base64()` |
|
||||
| `$(( ))` arithmetic | `_math()` |
|
||||
| `grep -E` / `grep -Po` | `_egrep_o()` |
|
||||
| `printf` | `echo` |
|
||||
| `idn` command | `_idn()` / `_is_idn()` |
|
||||
| `mktemp` | `_mktemp()` |
|
||||
| `[:space:]` | ` ` |
|
||||
| `[:alnum:]` | `A-Za-z0-9` |
|
||||
| `[:alpha:]` | `A-Za-z` |
|
||||
| `[:digit:]` | `0-9` |
|
||||
| `awk` | `cut` / `sed` / `while read` loops |
|
||||
|
||||
|
||||
|
||||
When fixing a pattern issue, fix **all instances** in the file, not just the one highlighted.
|
||||
|
||||
---
|
||||
|
||||
## Forbidden External Tools
|
||||
|
||||
Do not use these commands -- they are not portable across all target platforms:
|
||||
|
||||
- `jq` (parse JSON with built-in string manipulation)
|
||||
- `grep -A` (removed throughout the project)
|
||||
- `grep -Po` (Perl regex not available everywhere)
|
||||
- `rev`, `xargs`, `iconv`
|
||||
- If you must depend on an external tool, check with `_exists` first:
|
||||
```sh
|
||||
if ! _exists jq; then
|
||||
_err "jq is required"
|
||||
return 1
|
||||
fi
|
||||
```
|
||||
- Warn against patterns like `for i in $(cat file)` or `for i in $(ls)` and recommend the more robust `while IFS= read -r line` pattern for safely processing file contents or filenames that might contain spaces.
|
||||
|
||||
---
|
||||
|
||||
## Configuration Management
|
||||
|
||||
Use the correct save/read functions depending on hook type:
|
||||
|
||||
- **DNS hooks**: `_readaccountconf_mutable` to read API keys, `_saveaccountconf_mutable` to save them. Do not use `_saveaccountconf` or `_readaccountconf`.
|
||||
- **Deploy hooks**: `_savedeployconf` / `_getdeployconf`
|
||||
- **Notification hooks**: use account conf functions.
|
||||
- Save operations should only happen in the correct lifecycle function (e.g., `_issue()`).
|
||||
- Use environment variables for all configurable values -- do not introduce hardcoded config files.
|
||||
- Do not clear account conf without a clear reason.
|
||||
|
||||
---
|
||||
|
||||
## DNS API Conventions
|
||||
|
||||
- Read the [DNS API Dev Guide](https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide) before writing a DNS plugin.
|
||||
- Each file under `dnsapi/` must contain a `{filename}_add` function for adding DNS TXT records.
|
||||
- The `_get_root()` loop counter `i` must start from `1` (not `2`) to support DNS alias mode.
|
||||
- The `dns_*_rm()` function must remove records **by TXT value**, not by replacing/updating. See [#1261](https://github.com/acmesh-official/acme.sh/issues/1261).
|
||||
- Preserve the `dns_*_info` metadata variable block in each DNS script header.
|
||||
|
||||
---
|
||||
|
||||
## Variable Naming
|
||||
|
||||
- Use CamelCase with provider prefix: `KINGHOST_Username` (not `KINGHOST_username`).
|
||||
- Variable names should use uppercase letters and underscores (e.g., `MY_VARIABLE`), or follow established project conventions.
|
||||
- Avoid confusingly similar names. Prefer one variable with comma-separated values over multiple variables (e.g., `CZ_Zones` with comma support instead of separate `CZ_Zone` and `CZ_Zones`).
|
||||
- Do not define variables with the same name in different scopes.
|
||||
- Variables inside functions should be declared using the `local` keyword to avoid unintentionally modifying global state.
|
||||
|
||||
---
|
||||
|
||||
## Code Style
|
||||
|
||||
- Use `shfmt` for formatting -- CI enforces it.
|
||||
- Reduce indentation where possible.
|
||||
- Single space, not double spaces.
|
||||
- No trailing semicolons after `return` statements.
|
||||
- Add a newline at the end of every file.
|
||||
- Use `$(command)` over backticks `` `command` `` for command substitution.
|
||||
|
||||
---
|
||||
|
||||
## Simplicity
|
||||
|
||||
- Prefer hardcoded sensible defaults over unnecessary configuration variables (e.g., use `3600` for TTL instead of a `DESEC_TTL` variable).
|
||||
- Reject over-engineered solutions. If it can be done in one line, do it in one line.
|
||||
- Follow existing patterns in the codebase -- new hooks should look like existing hooks.
|
||||
- Respect user choices: do not `chmod` files that already exist; the user's permissions take priority.
|
||||
|
||||
---
|
||||
|
||||
## Documentation Requirements
|
||||
|
||||
Before a PR can be merged, the following documentation must be provided:
|
||||
|
||||
- **Wiki page**: add or update the relevant page:
|
||||
- DNS APIs: [dnsapi](https://github.com/acmesh-official/acme.sh/wiki/dnsapi) or [dnsapi2](https://github.com/acmesh-official/acme.sh/wiki/dnsapi2)
|
||||
- Deploy hooks: [deployhooks](https://github.com/acmesh-official/acme.sh/wiki/deployhooks)
|
||||
- Notification hooks: [notify](https://github.com/acmesh-official/acme.sh/wiki/notify)
|
||||
- Options: [Options-and-Params](https://github.com/acmesh-official/acme.sh/wiki/Options-and-Params)
|
||||
- **In-code usage**: add usage examples in the help text of `acme.sh` itself.
|
||||
- **README**: add website URLs for new DNS providers.
|
||||
|
||||
---
|
||||
|
||||
## CI and Merge Hygiene
|
||||
|
||||
- All CI checks must pass before merge.
|
||||
- Rebase to the latest `dev` branch frequently -- do not use merge commits.
|
||||
- Enable GitHub Actions on your fork to catch errors early.
|
||||
- Run the [DNS API Test](https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test) workflow for DNS plugins.
|
||||
- For Docker changes, ensure the Dockerfile includes any required dependencies.
|
||||
|
||||
---
|
||||
|
||||
## Debug Logging
|
||||
|
||||
- Use `_debug2` (not `_debug3` or other levels) unless there is a specific reason for a different level.
|
||||
|
||||
---
|
||||
|
||||
## Things to Avoid in Reviews
|
||||
|
||||
- Do not comment on purely stylistic issues like spacing or indentation, which should be handled by tools like ShellCheck or `shfmt`.
|
||||
- Do not be overly verbose unless a significant issue is found. Keep feedback concise and actionable.
|
||||
50
.github/workflows/Apache.yml
vendored
Normal file
50
.github/workflows/Apache.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name: Apache
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Apache.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Apache.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
Apache:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TestingDomain: example.com
|
||||
TEST_ACME_Server: https://localhost:14000/dir
|
||||
HTTPS_INSECURE: 1
|
||||
TEST_LOCAL: 1
|
||||
TEST_CA: "Pebble Intermediate CA"
|
||||
TEST_APACHE: 1
|
||||
CASE: le_test_apache
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install tools
|
||||
run: sudo apt-get install -y socat apache2
|
||||
- name: Run Pebble
|
||||
run: cd .. && curl https://raw.githubusercontent.com/letsencrypt/pebble/master/docker-compose.yml >docker-compose.yml && docker compose up -d
|
||||
- name: Set up Pebble
|
||||
run: curl --request POST --data '{"ip":"10.30.50.1"}' http://localhost:8055/set-default-ipv4
|
||||
- name: Set up Apache
|
||||
# Apache serves on 5002, which is the HTTP-01 validation port in
|
||||
# Pebble's default config; acme.sh appends the challenge Alias to
|
||||
# the main config itself
|
||||
run: |
|
||||
echo "Listen 5002" | sudo tee /etc/apache2/ports.conf
|
||||
sudo sed -i "s/\*:80/*:5002/" /etc/apache2/sites-available/000-default.conf
|
||||
sudo apache2ctl configtest
|
||||
sudo systemctl restart apache2
|
||||
curl -s -o /dev/null -w "%{http_code}" -H "Host: example.com" http://127.0.0.1:5002/ | grep -E "200|403|404"
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- name: Run acmetest
|
||||
run: cd ../acmetest && sudo --preserve-env ./letest.sh
|
||||
975
.github/workflows/DNS.yml
vendored
Normal file
975
.github/workflows/DNS.yml
vendored
Normal file
@@ -0,0 +1,975 @@
|
||||
name: DNS
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- 'dnsapi/*.sh'
|
||||
- '.github/workflows/DNS.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'dev'
|
||||
paths:
|
||||
- 'dnsapi/*.sh'
|
||||
- '.github/workflows/DNS.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
CheckToken:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
hasToken: ${{ steps.step_one.outputs.hasToken }}
|
||||
steps:
|
||||
- name: Set the value
|
||||
id: step_one
|
||||
run: |
|
||||
if [ "${{secrets.TokenName1}}" ] ; then
|
||||
echo "hasToken=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "hasToken=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
- name: Check the value
|
||||
run: echo ${{ steps.step_one.outputs.hasToken }}
|
||||
|
||||
Fail:
|
||||
runs-on: ubuntu-latest
|
||||
needs: CheckToken
|
||||
if: "contains(needs.CheckToken.outputs.hasToken, 'false')"
|
||||
steps:
|
||||
- name: "Read this: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test"
|
||||
run: |
|
||||
echo "Read this: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test"
|
||||
if [ "${{github.repository_owner}}" != "acmesh-official" ]; then
|
||||
false
|
||||
fi
|
||||
|
||||
Docker:
|
||||
runs-on: ubuntu-latest
|
||||
needs: CheckToken
|
||||
if: "contains(needs.CheckToken.outputs.hasToken, 'true')"
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||
CASE: le_test_dnsapi
|
||||
TEST_LOCAL: 1
|
||||
DEBUG: ${{ secrets.DEBUG }}
|
||||
http_proxy: ${{ secrets.http_proxy }}
|
||||
https_proxy: ${{ secrets.https_proxy }}
|
||||
TokenName1: ${{ secrets.TokenName1}}
|
||||
TokenName2: ${{ secrets.TokenName2}}
|
||||
TokenName3: ${{ secrets.TokenName3}}
|
||||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- name: Set env file
|
||||
run: |
|
||||
cd ../acmetest
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
echo "${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}" >> docker.env
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
echo "${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}" >> docker.env
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
echo "${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}" >> docker.env
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
echo "${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}" >> docker.env
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
echo "${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}" >> docker.env
|
||||
fi
|
||||
|
||||
- name: Run acmetest
|
||||
run: cd ../acmetest && ./rundocker.sh testall
|
||||
|
||||
|
||||
|
||||
|
||||
MacOS:
|
||||
runs-on: macos-latest
|
||||
needs: Docker
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||
CASE: le_test_dnsapi
|
||||
TEST_LOCAL: 1
|
||||
DEBUG: ${{ secrets.DEBUG }}
|
||||
http_proxy: ${{ secrets.http_proxy }}
|
||||
https_proxy: ${{ secrets.https_proxy }}
|
||||
TokenName1: ${{ secrets.TokenName1}}
|
||||
TokenName2: ${{ secrets.TokenName2}}
|
||||
TokenName3: ${{ secrets.TokenName3}}
|
||||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Install tools
|
||||
run: |
|
||||
brew untap aws/tap || true
|
||||
brew install socat
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- name: Run acmetest
|
||||
run: |
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
fi
|
||||
cd ../acmetest
|
||||
./letest.sh
|
||||
|
||||
|
||||
|
||||
|
||||
Windows:
|
||||
runs-on: windows-latest
|
||||
needs: MacOS
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||
CASE: le_test_dnsapi
|
||||
TEST_LOCAL: 1
|
||||
DEBUG: ${{ secrets.DEBUG }}
|
||||
http_proxy: ${{ secrets.http_proxy }}
|
||||
https_proxy: ${{ secrets.https_proxy }}
|
||||
TokenName1: ${{ secrets.TokenName1}}
|
||||
TokenName2: ${{ secrets.TokenName2}}
|
||||
TokenName3: ${{ secrets.TokenName3}}
|
||||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- name: Set git to use LF
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
- uses: actions/checkout@v7
|
||||
- name: Install cygwin base packages with chocolatey
|
||||
run: |
|
||||
choco config get cacheLocation
|
||||
choco install --no-progress cygwin
|
||||
shell: cmd
|
||||
- name: Install cygwin additional packages
|
||||
run: |
|
||||
C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s https://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git
|
||||
shell: cmd
|
||||
- name: Set ENV
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'PATH=C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin' >> "$GITHUB_ENV"
|
||||
# cygwin git sees the runner workspace as owned by another user and
|
||||
# fails with "dubious ownership" (exit 128) in the checkout post step
|
||||
echo 'GIT_CONFIG_COUNT=1' >> "$GITHUB_ENV"
|
||||
echo 'GIT_CONFIG_KEY_0=safe.directory' >> "$GITHUB_ENV"
|
||||
echo 'GIT_CONFIG_VALUE_0=*' >> "$GITHUB_ENV"
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- name: Run acmetest
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
fi
|
||||
cd ../acmetest
|
||||
./letest.sh
|
||||
|
||||
|
||||
|
||||
FreeBSD:
|
||||
runs-on: ubuntu-latest
|
||||
needs: Windows
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||
CASE: le_test_dnsapi
|
||||
TEST_LOCAL: 1
|
||||
DEBUG: ${{ secrets.DEBUG }}
|
||||
http_proxy: ${{ secrets.http_proxy }}
|
||||
https_proxy: ${{ secrets.https_proxy }}
|
||||
TokenName1: ${{ secrets.TokenName1}}
|
||||
TokenName2: ${{ secrets.TokenName2}}
|
||||
TokenName3: ${{ secrets.TokenName3}}
|
||||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
|
||||
prepare: pkg install -y socat curl
|
||||
usesh: true
|
||||
sync: nfs
|
||||
run: |
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
fi
|
||||
cd ../acmetest
|
||||
./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
|
||||
|
||||
GhostBSD:
|
||||
runs-on: ubuntu-latest
|
||||
needs: FreeBSD
|
||||
# GhostBSD VM frequently flakes on boot/ssh; don't let it fail the whole run
|
||||
continue-on-error: true
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||
CASE: le_test_dnsapi
|
||||
TEST_LOCAL: 1
|
||||
DEBUG: ${{ secrets.DEBUG }}
|
||||
http_proxy: ${{ secrets.http_proxy }}
|
||||
https_proxy: ${{ secrets.https_proxy }}
|
||||
TokenName1: ${{ secrets.TokenName1}}
|
||||
TokenName2: ${{ secrets.TokenName2}}
|
||||
TokenName3: ${{ secrets.TokenName3}}
|
||||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/ghostbsd-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
|
||||
prepare: pkg install -y socat curl
|
||||
usesh: true
|
||||
sync: nfs
|
||||
run: |
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
fi
|
||||
cd ../acmetest
|
||||
./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
|
||||
|
||||
OpenBSD:
|
||||
runs-on: ubuntu-latest
|
||||
needs: GhostBSD
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||
CASE: le_test_dnsapi
|
||||
TEST_LOCAL: 1
|
||||
DEBUG: ${{ secrets.DEBUG }}
|
||||
http_proxy: ${{ secrets.http_proxy }}
|
||||
https_proxy: ${{ secrets.https_proxy }}
|
||||
TokenName1: ${{ secrets.TokenName1}}
|
||||
TokenName2: ${{ secrets.TokenName2}}
|
||||
TokenName3: ${{ secrets.TokenName3}}
|
||||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/openbsd-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
|
||||
prepare: pkg_add socat curl libiconv
|
||||
usesh: true
|
||||
sync: nfs
|
||||
run: |
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
fi
|
||||
cd ../acmetest
|
||||
./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
|
||||
|
||||
NetBSD:
|
||||
runs-on: ubuntu-latest
|
||||
needs: OpenBSD
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||
CASE: le_test_dnsapi
|
||||
TEST_LOCAL: 1
|
||||
DEBUG: ${{ secrets.DEBUG }}
|
||||
http_proxy: ${{ secrets.http_proxy }}
|
||||
https_proxy: ${{ secrets.https_proxy }}
|
||||
TokenName1: ${{ secrets.TokenName1}}
|
||||
TokenName2: ${{ secrets.TokenName2}}
|
||||
TokenName3: ${{ secrets.TokenName3}}
|
||||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/netbsd-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
|
||||
prepare: |
|
||||
/usr/sbin/pkg_add curl socat
|
||||
usesh: true
|
||||
sync: nfs
|
||||
run: |
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
fi
|
||||
cd ../acmetest
|
||||
./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
|
||||
|
||||
DragonFlyBSD:
|
||||
runs-on: ubuntu-latest
|
||||
needs: NetBSD
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||
CASE: le_test_dnsapi
|
||||
TEST_LOCAL: 1
|
||||
DEBUG: ${{ secrets.DEBUG }}
|
||||
http_proxy: ${{ secrets.http_proxy }}
|
||||
https_proxy: ${{ secrets.https_proxy }}
|
||||
TokenName1: ${{ secrets.TokenName1}}
|
||||
TokenName2: ${{ secrets.TokenName2}}
|
||||
TokenName3: ${{ secrets.TokenName3}}
|
||||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/dragonflybsd-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
|
||||
prepare: |
|
||||
pkg install -y libnghttp2
|
||||
pkg install -y curl socat
|
||||
usesh: true
|
||||
sync: nfs
|
||||
run: |
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
fi
|
||||
cd ../acmetest
|
||||
./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
MidnightBSD:
|
||||
runs-on: ubuntu-latest
|
||||
needs: DragonFlyBSD
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||
CASE: le_test_dnsapi
|
||||
TEST_LOCAL: 1
|
||||
DEBUG: ${{ secrets.DEBUG }}
|
||||
http_proxy: ${{ secrets.http_proxy }}
|
||||
https_proxy: ${{ secrets.https_proxy }}
|
||||
TokenName1: ${{ secrets.TokenName1}}
|
||||
TokenName2: ${{ secrets.TokenName2}}
|
||||
TokenName3: ${{ secrets.TokenName3}}
|
||||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/midnightbsd-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
|
||||
prepare: mport install socat curl || true
|
||||
usesh: true
|
||||
sync: nfs
|
||||
run: |
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
fi
|
||||
cd ../acmetest
|
||||
./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
|
||||
|
||||
Solaris:
|
||||
runs-on: ubuntu-latest
|
||||
needs: MidnightBSD
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||
CASE: le_test_dnsapi
|
||||
TEST_LOCAL: 1
|
||||
DEBUG: ${{ secrets.DEBUG }}
|
||||
http_proxy: ${{ secrets.http_proxy }}
|
||||
https_proxy: ${{ secrets.https_proxy }}
|
||||
HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since Solaris doesn't accept the expired ISRG X1 root
|
||||
TokenName1: ${{ secrets.TokenName1}}
|
||||
TokenName2: ${{ secrets.TokenName2}}
|
||||
TokenName3: ${{ secrets.TokenName3}}
|
||||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/solaris-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
|
||||
sync: nfs
|
||||
prepare: |
|
||||
pkgutil -U
|
||||
pkgutil -y -i socat
|
||||
run: |
|
||||
pkg set-mediator -v -I default@1.1 openssl
|
||||
export PATH=/usr/gnu/bin:$PATH
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
fi
|
||||
cd ../acmetest
|
||||
./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
|
||||
Omnios:
|
||||
runs-on: ubuntu-latest
|
||||
needs: Solaris
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||
CASE: le_test_dnsapi
|
||||
TEST_LOCAL: 1
|
||||
DEBUG: ${{ secrets.DEBUG }}
|
||||
http_proxy: ${{ secrets.http_proxy }}
|
||||
https_proxy: ${{ secrets.https_proxy }}
|
||||
HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since Omnios doesn't accept the expired ISRG X1 root
|
||||
TokenName1: ${{ secrets.TokenName1}}
|
||||
TokenName2: ${{ secrets.TokenName2}}
|
||||
TokenName3: ${{ secrets.TokenName3}}
|
||||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/omnios-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
|
||||
sync: nfs
|
||||
prepare: pkg install socat
|
||||
run: |
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
fi
|
||||
cd ../acmetest
|
||||
./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
|
||||
|
||||
OpenIndiana:
|
||||
runs-on: ubuntu-latest
|
||||
needs: Omnios
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||
CASE: le_test_dnsapi
|
||||
TEST_LOCAL: 1
|
||||
DEBUG: ${{ secrets.DEBUG }}
|
||||
http_proxy: ${{ secrets.http_proxy }}
|
||||
https_proxy: ${{ secrets.https_proxy }}
|
||||
HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since OpenIndiana doesn't accept the expired ISRG X1 root
|
||||
TokenName1: ${{ secrets.TokenName1}}
|
||||
TokenName2: ${{ secrets.TokenName2}}
|
||||
TokenName3: ${{ secrets.TokenName3}}
|
||||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/openindiana-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
|
||||
sync: nfs
|
||||
prepare: pkg install socat
|
||||
run: |
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
fi
|
||||
cd ../acmetest
|
||||
./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
|
||||
|
||||
Tribblix:
|
||||
runs-on: ubuntu-latest
|
||||
needs: OpenIndiana
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||
CASE: le_test_dnsapi
|
||||
TEST_LOCAL: 1
|
||||
DEBUG: ${{ secrets.DEBUG }}
|
||||
http_proxy: ${{ secrets.http_proxy }}
|
||||
https_proxy: ${{ secrets.https_proxy }}
|
||||
HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since Tribblix doesn't accept the expired ISRG X1 root
|
||||
TokenName1: ${{ secrets.TokenName1}}
|
||||
TokenName2: ${{ secrets.TokenName2}}
|
||||
TokenName3: ${{ secrets.TokenName3}}
|
||||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/tribblix-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
|
||||
sync: nfs
|
||||
prepare: zap install socat
|
||||
run: |
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
fi
|
||||
cd ../acmetest
|
||||
./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
|
||||
|
||||
Haiku:
|
||||
runs-on: ubuntu-latest
|
||||
needs: Tribblix
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||
CASE: le_test_dnsapi
|
||||
TEST_LOCAL: 1
|
||||
DEBUG: ${{ secrets.DEBUG }}
|
||||
http_proxy: ${{ secrets.http_proxy }}
|
||||
https_proxy: ${{ secrets.https_proxy }}
|
||||
HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since OpenIndiana doesn't accept the expired ISRG X1 root
|
||||
TokenName1: ${{ secrets.TokenName1}}
|
||||
TokenName2: ${{ secrets.TokenName2}}
|
||||
TokenName3: ${{ secrets.TokenName3}}
|
||||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/haiku-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
|
||||
sync: rsync
|
||||
copyback: false
|
||||
prepare: |
|
||||
mkdir -p /boot/home/.cache
|
||||
pkgman install -y cronie
|
||||
|
||||
run: |
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
fi
|
||||
cd ../acmetest
|
||||
./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
|
||||
|
||||
Hurd:
|
||||
runs-on: ubuntu-latest
|
||||
needs: Haiku
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||
CASE: le_test_dnsapi
|
||||
TEST_LOCAL: 1
|
||||
DEBUG: ${{ secrets.DEBUG }}
|
||||
http_proxy: ${{ secrets.http_proxy }}
|
||||
https_proxy: ${{ secrets.https_proxy }}
|
||||
HTTPS_INSECURE: 1 # always set to 1 to ignore https error
|
||||
TokenName1: ${{ secrets.TokenName1}}
|
||||
TokenName2: ${{ secrets.TokenName2}}
|
||||
TokenName3: ${{ secrets.TokenName3}}
|
||||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/hurd-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
|
||||
sync: rsync
|
||||
copyback: false
|
||||
usesh: true
|
||||
prepare: |
|
||||
apt-get update -y
|
||||
apt-get install -y curl cron
|
||||
run: |
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
fi
|
||||
cd ../acmetest
|
||||
./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
|
||||
|
||||
OpenEuler:
|
||||
runs-on: ubuntu-latest
|
||||
needs: Hurd
|
||||
env:
|
||||
TEST_DNS : ${{ secrets.TEST_DNS }}
|
||||
TestingDomain: ${{ secrets.TestingDomain }}
|
||||
TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
|
||||
TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
|
||||
TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
|
||||
CASE: le_test_dnsapi
|
||||
TEST_LOCAL: 1
|
||||
DEBUG: ${{ secrets.DEBUG }}
|
||||
http_proxy: ${{ secrets.http_proxy }}
|
||||
https_proxy: ${{ secrets.https_proxy }}
|
||||
HTTPS_INSECURE: 1 # always set to 1 to ignore https error
|
||||
TokenName1: ${{ secrets.TokenName1}}
|
||||
TokenName2: ${{ secrets.TokenName2}}
|
||||
TokenName3: ${{ secrets.TokenName3}}
|
||||
TokenName4: ${{ secrets.TokenName4}}
|
||||
TokenName5: ${{ secrets.TokenName5}}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/openeuler-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
|
||||
sync: rsync
|
||||
copyback: false
|
||||
usesh: true
|
||||
prepare: dnf install -y curl socat cronie tar gzip
|
||||
run: |
|
||||
if [ "${{ secrets.TokenName1}}" ] ; then
|
||||
export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName2}}" ] ; then
|
||||
export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName3}}" ] ; then
|
||||
export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName4}}" ] ; then
|
||||
export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}"
|
||||
fi
|
||||
if [ "${{ secrets.TokenName5}}" ] ; then
|
||||
export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}"
|
||||
fi
|
||||
cd ../acmetest
|
||||
./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
|
||||
|
||||
78
.github/workflows/DragonFlyBSD.yml
vendored
Normal file
78
.github/workflows/DragonFlyBSD.yml
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
name: DragonFlyBSD
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/DragonFlyBSD.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/DragonFlyBSD.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
DragonFlyBSD:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: anyvm-org/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
port: 8080
|
||||
- name: Set envs
|
||||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/dragonflybsd-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
|
||||
nat: |
|
||||
"8080": "80"
|
||||
prepare: |
|
||||
pkg install -y libnghttp2
|
||||
pkg install -y curl socat
|
||||
usesh: true
|
||||
sync: nfs
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
82
.github/workflows/FreeBSD.yml
vendored
Normal file
82
.github/workflows/FreeBSD.yml
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
name: FreeBSD
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/FreeBSD.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/FreeBSD.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
FreeBSD:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
ACME_USE_WGET: 1
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: anyvm-org/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
port: 8080
|
||||
- name: Set envs
|
||||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
|
||||
nat: |
|
||||
"8080": "80"
|
||||
prepare: pkg install -y socat curl wget
|
||||
usesh: true
|
||||
sync: nfs
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
83
.github/workflows/GhostBSD.yml
vendored
Normal file
83
.github/workflows/GhostBSD.yml
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
name: GhostBSD
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/GhostBSD.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/GhostBSD.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
GhostBSD:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
ACME_USE_WGET: 1
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: ubuntu-latest
|
||||
# GhostBSD VM frequently flakes on boot/ssh; don't let it fail the whole run
|
||||
continue-on-error: true
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: anyvm-org/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
port: 8080
|
||||
- name: Set envs
|
||||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/ghostbsd-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
|
||||
nat: |
|
||||
"8080": "80"
|
||||
prepare: pkg install -y socat curl wget
|
||||
usesh: true
|
||||
sync: nfs
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
85
.github/workflows/Haiku.yml
vendored
Normal file
85
.github/workflows/Haiku.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
name: Haiku
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Haiku.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Haiku.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
Haiku:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
ACME_USE_WGET: 1
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: anyvm-org/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
port: 8080
|
||||
- name: Set envs
|
||||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/haiku-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
|
||||
nat: |
|
||||
"8080": "80"
|
||||
prepare: |
|
||||
mkdir -p /boot/home/.cache
|
||||
pkgman install -y cronie
|
||||
sync: rsync
|
||||
copyback: false
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
76
.github/workflows/Hurd.yml
vendored
Normal file
76
.github/workflows/Hurd.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
name: Hurd
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Hurd.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Hurd.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
Hurd:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: anyvm-org/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
port: 8080
|
||||
- name: Set envs
|
||||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/hurd-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN'
|
||||
nat: |
|
||||
"8080": "80"
|
||||
# Do NOT install socat: socat's SYSTEM: address is broken on GNU Hurd
|
||||
# (the child shell output goes to socat's stdout instead of the socket,
|
||||
# so clients get an empty reply). Without socat, acme.sh standalone
|
||||
# mode falls back to its python3 server, which works on Hurd.
|
||||
prepare: |
|
||||
apt-get update -y
|
||||
apt-get install -y curl cron
|
||||
usesh: true
|
||||
sync: rsync
|
||||
copyback: false
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
55
.github/workflows/Linux.yml
vendored
Normal file
55
.github/workflows/Linux.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
name: Linux
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Linux.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Linux.yml'
|
||||
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
Linux:
|
||||
strategy:
|
||||
matrix:
|
||||
os: ["ubuntu:latest", "debian:latest", "almalinux:latest", "fedora:latest", "opensuse/leap:latest", "alpine:latest", "oraclelinux:8", "kalilinux/kali", "archlinux:latest", "gentoo/stage3"]
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: anyvm-org/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
port: 80
|
||||
- name: Set envs
|
||||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- name: Clone acmetest
|
||||
run: |
|
||||
cd .. \
|
||||
&& git clone --depth=1 https://github.com/acmesh-official/acmetest.git \
|
||||
&& cp -r acme.sh acmetest/
|
||||
- name: Run acmetest
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./rundocker.sh testplat ${{ matrix.os }}
|
||||
|
||||
|
||||
|
||||
67
.github/workflows/MacOS.yml
vendored
Normal file
67
.github/workflows/MacOS.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
name: MacOS
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/MacOS.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/MacOS.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
MacOS:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: macos-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install tools
|
||||
run: brew install socat
|
||||
- uses: anyvm-org/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
port: 80
|
||||
- name: Set envs
|
||||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- name: Clone acmetest
|
||||
run: |
|
||||
cd .. \
|
||||
&& git clone --depth=1 https://github.com/acmesh-official/acmetest.git \
|
||||
&& cp -r acme.sh acmetest/
|
||||
- name: Run acmetest
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& sudo --preserve-env ./letest.sh
|
||||
|
||||
|
||||
75
.github/workflows/MidnightBSD.yml
vendored
Normal file
75
.github/workflows/MidnightBSD.yml
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
name: MidnightBSD
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/MidnightBSD.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/MidnightBSD.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
MidnightBSD:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: anyvm-org/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
port: 8080
|
||||
- name: Set envs
|
||||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/midnightbsd-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
|
||||
nat: |
|
||||
"8080": "80"
|
||||
prepare: mport install socat curl wget || true
|
||||
usesh: true
|
||||
sync: nfs
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
77
.github/workflows/NetBSD.yml
vendored
Normal file
77
.github/workflows/NetBSD.yml
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
name: NetBSD
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/NetBSD.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/NetBSD.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
NetBSD:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: anyvm-org/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
port: 8080
|
||||
- name: Set envs
|
||||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/netbsd-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
|
||||
nat: |
|
||||
"8080": "80"
|
||||
prepare: |
|
||||
/usr/sbin/pkg_add curl socat
|
||||
usesh: true
|
||||
sync: nfs
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
66
.github/workflows/Nginx.yml
vendored
Normal file
66
.github/workflows/Nginx.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
name: Nginx
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Nginx.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Nginx.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
Nginx:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TestingDomain: example.com
|
||||
TEST_ACME_Server: https://localhost:14000/dir
|
||||
HTTPS_INSECURE: 1
|
||||
TEST_LOCAL: 1
|
||||
TEST_CA: "Pebble Intermediate CA"
|
||||
TEST_NGINX: 1
|
||||
CASE: le_test_nginx
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install tools
|
||||
run: sudo apt-get install -y socat nginx
|
||||
- name: Run Pebble
|
||||
run: cd .. && curl https://raw.githubusercontent.com/letsencrypt/pebble/master/docker-compose.yml >docker-compose.yml && docker compose up -d
|
||||
- name: Set up Pebble
|
||||
run: curl --request POST --data '{"ip":"10.30.50.1"}' http://localhost:8055/set-default-ipv4
|
||||
- name: Set up nginx
|
||||
# a backend on 8081 plus a site with an aaPanel/BT style
|
||||
# "location ^~ /" proxy block that shadows plain regex locations
|
||||
# (regression for #6125); the site listens on 5002, which is the
|
||||
# HTTP-01 validation port in Pebble's default config
|
||||
run: |
|
||||
sudo tee /etc/nginx/sites-available/default >/dev/null <<'EOF'
|
||||
server {
|
||||
listen 127.0.0.1:8081;
|
||||
location / {
|
||||
default_type text/plain;
|
||||
return 200 "backend";
|
||||
}
|
||||
}
|
||||
server {
|
||||
listen 5002 default_server;
|
||||
server_name example.com;
|
||||
location ^~ / {
|
||||
proxy_pass http://127.0.0.1:8081;
|
||||
proxy_set_header Host $http_host;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
sudo nginx -t
|
||||
sudo systemctl restart nginx
|
||||
curl -s -H "Host: example.com" http://127.0.0.1:5002/ | grep backend
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- name: Run acmetest
|
||||
run: cd ../acmetest && sudo --preserve-env ./letest.sh
|
||||
81
.github/workflows/Omnios.yml
vendored
Normal file
81
.github/workflows/Omnios.yml
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
name: Omnios
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Omnios.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Omnios.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
Omnios:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
ACME_USE_WGET: 1
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: anyvm-org/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
port: 8080
|
||||
- name: Set envs
|
||||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/omnios-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
|
||||
nat: |
|
||||
"8080": "80"
|
||||
prepare: pkg install socat wget
|
||||
sync: nfs
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
82
.github/workflows/OpenBSD.yml
vendored
Normal file
82
.github/workflows/OpenBSD.yml
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
name: OpenBSD
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/OpenBSD.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/OpenBSD.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
OpenBSD:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
ACME_USE_WGET: 1
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: anyvm-org/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
port: 8080
|
||||
- name: Set envs
|
||||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/openbsd-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
|
||||
nat: |
|
||||
"8080": "80"
|
||||
prepare: pkg_add socat curl wget libnghttp2
|
||||
usesh: true
|
||||
sync: nfs
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
70
.github/workflows/OpenEuler.yml
vendored
Normal file
70
.github/workflows/OpenEuler.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: OpenEuler
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/OpenEuler.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/OpenEuler.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
OpenEuler:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: anyvm-org/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
port: 8080
|
||||
- name: Set envs
|
||||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/openeuler-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN'
|
||||
nat: |
|
||||
"8080": "80"
|
||||
prepare: dnf install -y curl socat cronie tar gzip
|
||||
usesh: true
|
||||
sync: rsync
|
||||
copyback: false
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
81
.github/workflows/OpenIndiana.yml
vendored
Normal file
81
.github/workflows/OpenIndiana.yml
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
name: OpenIndiana
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/OpenIndiana.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/OpenIndiana.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
OpenIndiana:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
ACME_USE_WGET: 1
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: anyvm-org/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
port: 8080
|
||||
- name: Set envs
|
||||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/openindiana-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
|
||||
nat: |
|
||||
"8080": "80"
|
||||
prepare: pkg install socat curl
|
||||
sync: nfs
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
72
.github/workflows/PebbleStrict.yml
vendored
Normal file
72
.github/workflows/PebbleStrict.yml
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
name: PebbleStrict
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/PebbleStrict.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/PebbleStrict.yml'
|
||||
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
PebbleStrict:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TestingDomain: example.com
|
||||
TestingAltDomains: www.example.com
|
||||
TEST_ACME_Server: https://localhost:14000/dir
|
||||
HTTPS_INSECURE: 1
|
||||
Le_HTTPPort: 5002
|
||||
TEST_LOCAL: 1
|
||||
TEST_CA: "Pebble Intermediate CA"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install tools
|
||||
run: sudo apt-get install -y socat
|
||||
- name: Run Pebble
|
||||
run: cd .. && curl https://raw.githubusercontent.com/letsencrypt/pebble/master/docker-compose.yml >docker-compose.yml && docker compose up -d
|
||||
- name: Set up Pebble
|
||||
run: curl --request POST --data '{"ip":"10.30.50.1"}' http://localhost:8055/set-default-ipv4
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- name: Run acmetest
|
||||
run: cd ../acmetest && ./letest.sh
|
||||
|
||||
PebbleStrict_IPCert:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TestingDomain: 1.23.45.67
|
||||
TEST_ACME_Server: https://localhost:14000/dir
|
||||
HTTPS_INSECURE: 1
|
||||
Le_HTTPPort: 5002
|
||||
Le_TLSPort: 5001
|
||||
TEST_LOCAL: 1
|
||||
TEST_CA: "Pebble Intermediate CA"
|
||||
TEST_IPCERT: 1
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install tools
|
||||
run: sudo apt-get install -y socat
|
||||
- name: Run Pebble
|
||||
run: |
|
||||
docker run --rm -itd --name=pebble \
|
||||
-e PEBBLE_VA_ALWAYS_VALID=1 \
|
||||
-p 14000:14000 -p 15000:15000 ghcr.io/letsencrypt/pebble:latest -config /test/config/pebble-config.json -strict
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- name: Run acmetest
|
||||
run: cd ../acmetest && ./letest.sh
|
||||
83
.github/workflows/Solaris.yml
vendored
Normal file
83
.github/workflows/Solaris.yml
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
name: Solaris
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Solaris.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Solaris.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
Solaris:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
ACME_USE_WGET: 1
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: anyvm-org/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
port: 8080
|
||||
- name: Set envs
|
||||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/solaris-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
|
||||
nat: |
|
||||
"8080": "80"
|
||||
prepare: |
|
||||
pkgutil -U
|
||||
pkgutil -y -i socat curl wget
|
||||
sync: nfs
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
|
||||
80
.github/workflows/Tribblix.yml
vendored
Normal file
80
.github/workflows/Tribblix.yml
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
name: Tribblix
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Tribblix.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Tribblix.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
Tribblix:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
ACME_USE_WGET: 1
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: anyvm-org/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
port: 8080
|
||||
- name: Set envs
|
||||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- name: Clone acmetest
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- uses: vmactions/tribblix-vm@v1
|
||||
with:
|
||||
debug-on-error: ${{ vars.DEBUG_ON_ERROR }}
|
||||
cache-after-prepare: true
|
||||
envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
|
||||
nat: |
|
||||
"8080": "80"
|
||||
prepare: zap install socat curl wget
|
||||
sync: nfs
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& ./letest.sh
|
||||
- name: DebugOnError
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
echo "See how to debug in VM:"
|
||||
echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM"
|
||||
103
.github/workflows/Ubuntu.yml
vendored
Normal file
103
.github/workflows/Ubuntu.yml
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
name: Ubuntu
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Ubuntu.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Ubuntu.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
|
||||
jobs:
|
||||
Ubuntu:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
ACME_USE_WGET: 1
|
||||
- TEST_ACME_Server: "ZeroSSL.com"
|
||||
CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
CA_EMAIL: "githubtest@acme.sh"
|
||||
TEST_PREFERRED_CHAIN: ""
|
||||
- TEST_ACME_Server: "https://localhost:9000/acme/acme/directory"
|
||||
CA_ECDSA: "Smallstep Intermediate CA"
|
||||
CA: "Smallstep Intermediate CA"
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: ""
|
||||
NO_REVOKE: 1
|
||||
- TEST_ACME_Server: "https://localhost:9000/acme/acme/directory"
|
||||
CA_ECDSA: "Smallstep Intermediate CA"
|
||||
CA: "Smallstep Intermediate CA"
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: ""
|
||||
NO_REVOKE: 1
|
||||
TEST_IPCERT: 1
|
||||
TestingDomain: "172.17.0.1"
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_LOCAL: 1
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
NO_ECC_384: ${{ matrix.NO_ECC_384 }}
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
NO_REVOKE: ${{ matrix.NO_REVOKE }}
|
||||
TEST_IPCERT: ${{ matrix.TEST_IPCERT }}
|
||||
TestingDomain: ${{ matrix.TestingDomain }}
|
||||
ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install tools
|
||||
run: sudo apt-get install -y socat wget
|
||||
- name: Start StepCA
|
||||
if: ${{ matrix.TEST_ACME_Server=='https://localhost:9000/acme/acme/directory' }}
|
||||
run: |
|
||||
docker run --rm -d \
|
||||
-p 9000:9000 \
|
||||
-e "DOCKER_STEPCA_INIT_NAME=Smallstep" \
|
||||
-e "DOCKER_STEPCA_INIT_DNS_NAMES=localhost,$(hostname -f)" \
|
||||
-e "DOCKER_STEPCA_INIT_REMOTE_MANAGEMENT=true" \
|
||||
-e "DOCKER_STEPCA_INIT_PASSWORD=test" \
|
||||
--name stepca \
|
||||
smallstep/step-ca:0.23.1
|
||||
|
||||
sleep 5
|
||||
docker exec stepca bash -c "echo test >test" \
|
||||
&& docker exec stepca step ca provisioner add acme --type ACME --admin-subject step --admin-password-file=/home/step/test \
|
||||
&& docker exec stepca kill -1 1 \
|
||||
&& docker exec stepca cat /home/step/certs/root_ca.crt | sudo bash -c "cat - >>/etc/ssl/certs/ca-certificates.crt"
|
||||
- name: Clone acmetest
|
||||
run: |
|
||||
cd .. \
|
||||
&& git clone --depth=1 https://github.com/acmesh-official/acmetest.git \
|
||||
&& cp -r acme.sh acmetest/
|
||||
- name: Run acmetest
|
||||
run: |
|
||||
cd ../acmetest \
|
||||
&& sudo --preserve-env ./letest.sh
|
||||
|
||||
|
||||
85
.github/workflows/Windows.yml
vendored
Normal file
85
.github/workflows/Windows.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
name: Windows
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Windows.yml'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '*.sh'
|
||||
- '.github/workflows/Windows.yml'
|
||||
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
jobs:
|
||||
Windows:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- TEST_ACME_Server: "LetsEncrypt.org_test"
|
||||
CA_ECDSA: ""
|
||||
CA: ""
|
||||
CA_EMAIL: ""
|
||||
TEST_PREFERRED_CHAIN: (STAGING)
|
||||
#- TEST_ACME_Server: "ZeroSSL.com"
|
||||
# CA_ECDSA: "ZeroSSL ECC DV SSL CA 2"
|
||||
# CA: "ZeroSSL RSA DV SSL CA 2"
|
||||
# CA_EMAIL: "githubtest@acme.sh"
|
||||
# TEST_PREFERRED_CHAIN: ""
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
|
||||
CA_ECDSA: ${{ matrix.CA_ECDSA }}
|
||||
CA: ${{ matrix.CA }}
|
||||
CA_EMAIL: ${{ matrix.CA_EMAIL }}
|
||||
TEST_LOCAL: 1
|
||||
#The 80 port is used by Windows server, we have to use a custom port, tunnel will also use this port.
|
||||
Le_HTTPPort: 8888
|
||||
TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
|
||||
steps:
|
||||
- name: Set git to use LF
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install cygwin base packages with chocolatey
|
||||
run: |
|
||||
choco config get cacheLocation
|
||||
choco install --no-progress cygwin
|
||||
shell: cmd
|
||||
- name: Install cygwin additional packages
|
||||
run: |
|
||||
C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s https://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git,xxd
|
||||
shell: cmd
|
||||
- name: Set ENV
|
||||
shell: cmd
|
||||
run: |
|
||||
echo PATH=C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin;%PATH% >> %GITHUB_ENV%
|
||||
- name: Check ENV
|
||||
shell: cmd
|
||||
run: |
|
||||
echo "PATH=%PATH%"
|
||||
- uses: anyvm-org/cf-tunnel@v0
|
||||
id: tunnel
|
||||
with:
|
||||
protocol: http
|
||||
port: 80
|
||||
- name: Set envs
|
||||
run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
|
||||
- name: Clone acmetest
|
||||
shell: cmd
|
||||
run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/
|
||||
- name: Run acmetest
|
||||
shell: cmd
|
||||
run: cd ../acmetest && bash.exe -c ./letest.sh
|
||||
|
||||
|
||||
|
||||
114
.github/workflows/blacklist-command.yml
vendored
Normal file
114
.github/workflows/blacklist-command.yml
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
name: Blacklist Command
|
||||
|
||||
# An issue titled "blacklist: <login-or-email>" opened by the maintainer
|
||||
# or a write-access member adds that identity to the Blacklist wiki page
|
||||
# (see wiki-guard.yml) and closes the issue. The wiki-monitor notification
|
||||
# embeds a prefilled link that opens such an issue in one click.
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
|
||||
# Share the wiki-guard concurrency group so we never push to the wiki
|
||||
# at the same time as the guard.
|
||||
concurrency:
|
||||
group: wiki-guard
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
blacklist:
|
||||
# Upstream only: forks have no <fork>.wiki repository to push to.
|
||||
if: github.repository == 'acmesh-official/acme.sh' && startsWith(github.event.issue.title, 'blacklist:')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check authorization
|
||||
id: auth
|
||||
run: |
|
||||
assoc="${{ github.event.issue.author_association }}"
|
||||
case "$assoc" in
|
||||
OWNER|MEMBER|COLLABORATOR)
|
||||
echo "ok=true" >> "$GITHUB_OUTPUT"
|
||||
;;
|
||||
*)
|
||||
echo "issue author is not authorized ($assoc); ignoring"
|
||||
echo "ok=false" >> "$GITHUB_OUTPUT"
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Checkout wiki repository
|
||||
if: steps.auth.outputs.ok == 'true'
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: ${{ github.repository }}.wiki
|
||||
path: wiki
|
||||
|
||||
- name: Add the identity to the blacklist page
|
||||
if: steps.auth.outputs.ok == 'true'
|
||||
id: add
|
||||
env:
|
||||
TITLE: ${{ github.event.issue.title }}
|
||||
run: |
|
||||
target="$(printf '%s' "$TITLE" \
|
||||
| sed 's/^blacklist:[[:space:]]*//; s/^@//; s/[[:space:]].*$//' \
|
||||
| tr 'A-Z' 'a-z')"
|
||||
case "$target" in
|
||||
''|*[!a-z0-9._+@-]*)
|
||||
echo "invalid target: '$target'"
|
||||
echo "result=invalid" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
echo "target=$target" >> "$GITHUB_OUTPUT"
|
||||
cd wiki
|
||||
if [ ! -e Blacklist.md ]; then
|
||||
echo "result=nopage" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
if grep -Fxiq -- "- $target" Blacklist.md; then
|
||||
echo "result=already" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
if [ -n "$(tail -c1 Blacklist.md)" ]; then
|
||||
echo >> Blacklist.md
|
||||
fi
|
||||
printf -- '- %s\n' "$target" >> Blacklist.md
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add Blacklist.md
|
||||
git commit -m "blacklist $target (requested in #${{ github.event.issue.number }})"
|
||||
git push origin HEAD || { git pull --rebase && git push origin HEAD; }
|
||||
echo "result=added" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Reply and close
|
||||
if: steps.auth.outputs.ok == 'true'
|
||||
uses: actions/github-script@v9
|
||||
env:
|
||||
RESULT: ${{ steps.add.outputs.result }}
|
||||
TARGET: ${{ steps.add.outputs.target }}
|
||||
with:
|
||||
script: |
|
||||
const result = process.env.RESULT;
|
||||
const target = process.env.TARGET;
|
||||
const messages = {
|
||||
added: `\`${target}\` has been added to the [Blacklist](https://github.com/${context.repo.owner}/${context.repo.repo}/wiki/Blacklist). The wiki guard will revert their recent wiki changes on its next run.`,
|
||||
already: `\`${target}\` is already on the blacklist.`,
|
||||
invalid: "Could not parse a valid login or email from the issue title.",
|
||||
nopage: "The Blacklist wiki page does not exist."
|
||||
};
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: messages[result] || "No action taken."
|
||||
});
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
state: "closed",
|
||||
state_reason: result === "added" ? "completed" : "not_planned"
|
||||
});
|
||||
100
.github/workflows/dockerhub.yml
vendored
Normal file
100
.github/workflows/dockerhub.yml
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
|
||||
name: Build DockerHub
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
tags:
|
||||
- '*'
|
||||
paths:
|
||||
- '**.sh'
|
||||
- "Dockerfile"
|
||||
- '.github/workflows/dockerhub.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
DOCKER_IMAGE: neilpang/acme.sh
|
||||
|
||||
jobs:
|
||||
CheckToken:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
hasToken: ${{ steps.step_one.outputs.hasToken }}
|
||||
env:
|
||||
DOCKER_PASSWORD : ${{ secrets.DOCKER_PASSWORD }}
|
||||
steps:
|
||||
- name: Set the value
|
||||
id: step_one
|
||||
run: |
|
||||
if [ "$DOCKER_PASSWORD" ] ; then
|
||||
echo "hasToken=true" >>$GITHUB_OUTPUT
|
||||
else
|
||||
echo "hasToken=false" >>$GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Check the value
|
||||
run: echo ${{ steps.step_one.outputs.hasToken }}
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
needs: CheckToken
|
||||
if: "contains(needs.CheckToken.outputs.hasToken, 'true')"
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ${DOCKER_IMAGE}
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
- name: login to docker hub
|
||||
run: |
|
||||
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
|
||||
- name: login to ghcr
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
|
||||
- name: build and push the image
|
||||
run: |
|
||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
DOCKER_IMAGE_TAG=${GITHUB_REF#refs/tags/}
|
||||
fi
|
||||
|
||||
if [[ $GITHUB_REF == refs/heads/* ]]; then
|
||||
DOCKER_IMAGE_TAG=${GITHUB_REF#refs/heads/}
|
||||
|
||||
if [[ $DOCKER_IMAGE_TAG == master ]]; then
|
||||
DOCKER_IMAGE_TAG=latest
|
||||
AUTO_UPGRADE=1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "DOCKER_IMAGE_TAG=${DOCKER_IMAGE_TAG}" >>"$GITHUB_ENV"
|
||||
|
||||
DOCKER_LABELS=()
|
||||
while read -r label; do
|
||||
DOCKER_LABELS+=(--label "${label}")
|
||||
done <<<"${DOCKER_METADATA_OUTPUT_LABELS}"
|
||||
|
||||
docker buildx build \
|
||||
--tag ${DOCKER_IMAGE}:${DOCKER_IMAGE_TAG} \
|
||||
"${DOCKER_LABELS[@]}" \
|
||||
--output "type=image,push=true" \
|
||||
--build-arg AUTO_UPGRADE=${AUTO_UPGRADE} \
|
||||
--platform linux/arm64/v8,linux/amd64,linux/arm/v6,linux/arm/v7,linux/386,linux/ppc64le,linux/s390x .
|
||||
- name: mirror the image to ghcr (best-effort)
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
--tag ghcr.io/${{ github.repository }}:${DOCKER_IMAGE_TAG} \
|
||||
${DOCKER_IMAGE}:${DOCKER_IMAGE_TAG} \
|
||||
|| echo "::warning::GHCR mirror failed; Docker Hub publish unaffected"
|
||||
129
.github/workflows/issue.yml
vendored
Normal file
129
.github/workflows/issue.yml
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
name: "Update issues"
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
comment:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const item = context.payload.issue || context.payload.pull_request;
|
||||
|
||||
// Close on sight anything opened by a user on the wiki Blacklist
|
||||
// page (maintained by the Wiki Guard workflow).
|
||||
let blacklist = [];
|
||||
try {
|
||||
const res = await fetch(`https://raw.githubusercontent.com/wiki/${context.repo.owner}/${context.repo.repo}/Blacklist.md`);
|
||||
if (res.ok) {
|
||||
blacklist = (await res.text()).split("\n")
|
||||
.filter(l => l.startsWith("- "))
|
||||
.map(l => l.slice(2).trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
} catch (e) {
|
||||
core.warning(`Failed to fetch the blacklist: ${e}`);
|
||||
}
|
||||
// A comment on a closed tracking issue reopens it (the standard
|
||||
// closing note promises this). Bots, blacklisted users and the
|
||||
// maintainer's own comments don't reopen.
|
||||
if (context.eventName === "issue_comment") {
|
||||
const issue = context.payload.issue;
|
||||
const commenter = context.payload.comment.user;
|
||||
if (issue.pull_request || issue.state !== "closed") {
|
||||
return;
|
||||
}
|
||||
if (!/^report\s+(bugs?|issues?)\b/i.test(issue.title)) {
|
||||
return;
|
||||
}
|
||||
if (commenter.type === "Bot" ||
|
||||
commenter.login.toLowerCase() === "neilpang" ||
|
||||
blacklist.includes(commenter.login.toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
state: "open"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (blacklist.includes(item.user.login.toLowerCase())) {
|
||||
if (context.payload.pull_request) {
|
||||
await github.rest.pulls.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: item.number,
|
||||
state: "closed"
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: item.number,
|
||||
state: "closed",
|
||||
state_reason: "not_planned"
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.payload.pull_request) {
|
||||
return;
|
||||
}
|
||||
|
||||
const issue = context.payload.issue;
|
||||
if (issue.title.startsWith("blacklist:") || issue.title.startsWith("revert:")) {
|
||||
// Handled by the Blacklist / Revert Command workflows.
|
||||
return;
|
||||
}
|
||||
if (/^report\s+(bugs?|issues?)\b/i.test(issue.title)) {
|
||||
// Tracking issue for a third-party dns/deploy/notify api:
|
||||
// no upgrade boilerplate; assign it to the opener, label it,
|
||||
// then close it right away to keep the issue list clean. Any
|
||||
// later comment reopens it (see the issue_comment handler).
|
||||
await github.rest.issues.addAssignees({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
assignees: [issue.user.login]
|
||||
});
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: ["3rd party api"]
|
||||
});
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: "Closing this tracking issue for now to keep the issue list clean. It remains the place to report problems with this provider -- if you hit a bug, comment here and the issue will be reopened."
|
||||
});
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
state: "closed",
|
||||
state_reason: "completed"
|
||||
});
|
||||
return;
|
||||
}
|
||||
await github.rest.issues.createComment({
|
||||
issue_number: issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: "Please upgrade to the latest code and try again first. Maybe it's already fixed. ```acme.sh --upgrade``` If it's still not working, please provide the log with `--debug 2`, otherwise, nobody can help you. Before posting the log, review it and REDACT any secrets: private keys (`-----BEGIN ... PRIVATE KEY-----` blocks), API tokens and passwords."
|
||||
})
|
||||
34
.github/workflows/pr_dns.yml
vendored
Normal file
34
.github/workflows/pr_dns.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Check dns api
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
paths:
|
||||
- 'dnsapi/*.sh'
|
||||
|
||||
|
||||
jobs:
|
||||
welcome:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor != 'neilpang'
|
||||
steps:
|
||||
- uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `**Welcome**
|
||||
READ ME !!!!!
|
||||
Read me !!!!!!
|
||||
First thing: don't send PR to the master branch, please send to the dev branch instead.
|
||||
Please read the [DNS API Dev Guide](../wiki/DNS-API-Dev-Guide).
|
||||
You MUST pass the [DNS-API-Test](../wiki/DNS-API-Test).
|
||||
Then reply on this message, otherwise, your code will not be reviewed or merged.
|
||||
Please also make sure to add/update the usage here: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2
|
||||
注意: 必须通过了 [DNS-API-Test](../wiki/DNS-API-Test) 才会被 review. 无论是修改, 还是新加的 dns api, 都必须确保通过这个测试.
|
||||
`
|
||||
})
|
||||
|
||||
31
.github/workflows/pr_notify.yml
vendored
Normal file
31
.github/workflows/pr_notify.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: Check notify api
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
branches:
|
||||
- 'dev'
|
||||
paths:
|
||||
- 'notify/*.sh'
|
||||
|
||||
|
||||
jobs:
|
||||
welcome:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor != 'neilpang'
|
||||
steps:
|
||||
- uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `**Welcome**
|
||||
Please make sure you've read our [Code-of-conduct](../wiki/Code-of-conduct) and add the usage here: [notify](../wiki/notify).
|
||||
Then reply on this message, otherwise, your code will not be reviewed or merged.
|
||||
We look forward to reviewing your Pull request shortly ✨
|
||||
`
|
||||
})
|
||||
|
||||
110
.github/workflows/revert-command.yml
vendored
Normal file
110
.github/workflows/revert-command.yml
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
name: Revert Command
|
||||
|
||||
# An issue titled "revert: <wiki-commit-sha>" opened by the maintainer or
|
||||
# a write-access member reverts that commit in the wiki repository and
|
||||
# closes the issue. The wiki-monitor notification embeds a prefilled link
|
||||
# that opens such an issue in one click.
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
|
||||
# Share the wiki-guard concurrency group so we never push to the wiki
|
||||
# at the same time as the guard.
|
||||
concurrency:
|
||||
group: wiki-guard
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
revert:
|
||||
# Upstream only: forks have no <fork>.wiki repository to push to.
|
||||
if: github.repository == 'acmesh-official/acme.sh' && startsWith(github.event.issue.title, 'revert:')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check authorization
|
||||
id: auth
|
||||
run: |
|
||||
assoc="${{ github.event.issue.author_association }}"
|
||||
case "$assoc" in
|
||||
OWNER|MEMBER|COLLABORATOR)
|
||||
echo "ok=true" >> "$GITHUB_OUTPUT"
|
||||
;;
|
||||
*)
|
||||
echo "issue author is not authorized ($assoc); ignoring"
|
||||
echo "ok=false" >> "$GITHUB_OUTPUT"
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Checkout wiki repository
|
||||
if: steps.auth.outputs.ok == 'true'
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: ${{ github.repository }}.wiki
|
||||
path: wiki
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Revert the wiki commit
|
||||
if: steps.auth.outputs.ok == 'true'
|
||||
id: revert
|
||||
env:
|
||||
TITLE: ${{ github.event.issue.title }}
|
||||
run: |
|
||||
target="$(printf '%s' "$TITLE" \
|
||||
| sed 's/^revert:[[:space:]]*//; s/[[:space:]].*$//' \
|
||||
| tr 'A-Z' 'a-z')"
|
||||
case "$target" in
|
||||
*[!0-9a-f]*|"")
|
||||
echo "invalid commit sha: '$target'"
|
||||
echo "result=invalid" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
echo "target=$target" >> "$GITHUB_OUTPUT"
|
||||
cd wiki
|
||||
if ! git cat-file -e "$target^{commit}" 2>/dev/null; then
|
||||
echo "result=notfound" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
if git revert --no-edit "$target"; then
|
||||
git push origin HEAD || { git pull --rebase && git push origin HEAD; }
|
||||
echo "result=reverted" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
git revert --abort || true
|
||||
echo "result=conflict" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Reply and close
|
||||
if: steps.auth.outputs.ok == 'true'
|
||||
uses: actions/github-script@v9
|
||||
env:
|
||||
RESULT: ${{ steps.revert.outputs.result }}
|
||||
TARGET: ${{ steps.revert.outputs.target }}
|
||||
with:
|
||||
script: |
|
||||
const result = process.env.RESULT;
|
||||
const target = process.env.TARGET;
|
||||
const messages = {
|
||||
reverted: `Wiki commit \`${target}\` has been reverted.`,
|
||||
conflict: `Reverting \`${target}\` conflicts with later edits; please revert manually from the page history.`,
|
||||
notfound: `Commit \`${target}\` was not found in the wiki repository.`,
|
||||
invalid: "Could not parse a commit sha from the issue title."
|
||||
};
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: messages[result] || "No action taken."
|
||||
});
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
state: "closed",
|
||||
state_reason: result === "reverted" ? "completed" : "not_planned"
|
||||
});
|
||||
38
.github/workflows/shellcheck.yml
vendored
Normal file
38
.github/workflows/shellcheck.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Shellcheck
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- '**.sh'
|
||||
- '.github/workflows/shellcheck.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- '**.sh'
|
||||
- '.github/workflows/shellcheck.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
jobs:
|
||||
ShellCheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install Shellcheck
|
||||
run: sudo apt-get install -y shellcheck
|
||||
- name: DoShellcheck
|
||||
run: shellcheck -V && shellcheck -e SC2181 -e SC2089 **/*.sh && echo "shellcheck OK"
|
||||
|
||||
shfmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install shfmt
|
||||
run: curl -sSL https://github.com/mvdan/sh/releases/download/v3.1.2/shfmt_v3.1.2_linux_amd64 -o ~/shfmt && chmod +x ~/shfmt
|
||||
- name: shfmt
|
||||
run: ~/shfmt -l -w -i 2 . ; git diff --exit-code && echo "shfmt OK"
|
||||
32
.github/workflows/vtag.yml
vendored
Normal file
32
.github/workflows/vtag.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Mirror version tag
|
||||
|
||||
# Historical release tags are plain version numbers ("3.1.3") and cannot be
|
||||
# renamed. When a plain version tag is pushed (including the tag created by
|
||||
# publishing a GitHub release), mirror it as a "v"-prefixed tag ("v3.1.3")
|
||||
# pointing to the same object, so both forms exist.
|
||||
# No retrigger loop: the tag filter never matches a "v"-prefixed tag, and
|
||||
# refs created with GITHUB_TOKEN do not fire workflows anyway.
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '[0-9]*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
vtag:
|
||||
if: github.repository == 'acmesh-official/acme.sh'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Create the v-prefixed tag
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
if gh api "repos/${{ github.repository }}/git/ref/tags/v${{ github.ref_name }}" >/dev/null 2>&1; then
|
||||
echo "Tag v${{ github.ref_name }} already exists, nothing to do."
|
||||
exit 0
|
||||
fi
|
||||
gh api "repos/${{ github.repository }}/git/refs" -f ref="refs/tags/v${{ github.ref_name }}" -f sha="${{ github.sha }}"
|
||||
echo "Created tag v${{ github.ref_name }} -> ${{ github.sha }}"
|
||||
325
.github/workflows/wiki-guard.yml
vendored
Normal file
325
.github/workflows/wiki-guard.yml
vendored
Normal file
@@ -0,0 +1,325 @@
|
||||
name: Wiki Guard
|
||||
|
||||
# Rules enforced here:
|
||||
# - Only the maintainer and write-access members may delete or rename wiki
|
||||
# pages. Anyone else doing so gets blacklisted and the page restored to
|
||||
# its last good revision.
|
||||
# - Only the maintainer and write-access members may edit the Blacklist
|
||||
# wiki page. Anyone else touching it gets blacklisted and the page
|
||||
# reverted.
|
||||
# - Any wiki change made by a blacklisted identity is reverted.
|
||||
# A "good" revision is one authored by the maintainer, by this bot, or by
|
||||
# a non-blacklisted user -- restoring from the deleted commit's parent is
|
||||
# NOT safe, because vandals replace a page before destroying it and the
|
||||
# parent would launder their version into a bot commit.
|
||||
# The gollum event only fires on page create/update, never on deletion,
|
||||
# so violations are caught by polling the wiki git history.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "*/10 * * * *"
|
||||
gollum:
|
||||
# Piggyback on frequent repo activity, because the cron schedule is
|
||||
# best-effort and often delayed well beyond its interval.
|
||||
issues:
|
||||
types: [opened]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
|
||||
concurrency:
|
||||
group: wiki-guard
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
guard:
|
||||
# Forks have no <fork>.wiki repository, so the checkout below would
|
||||
# fail there -- run only in the upstream repository.
|
||||
if: github.repository == 'acmesh-official/acme.sh'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout wiki repository
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: ${{ github.repository }}.wiki
|
||||
path: wiki
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Enforce wiki rules
|
||||
id: guard
|
||||
env:
|
||||
# WIKI_GUARD_TOKEN: a PAT with read:org, needed to enumerate
|
||||
# members whose write access comes via the organization -- the
|
||||
# repo-scoped GITHUB_TOKEN only sees direct collaborators.
|
||||
GH_TOKEN: ${{ secrets.WIKI_GUARD_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
# Logins with write (push) access to the repository, including
|
||||
# organization members -- they may delete/rename pages and edit
|
||||
# the blacklist just like the maintainer. If the API call fails,
|
||||
# the list stays empty and enforcement falls back to
|
||||
# maintainer-only, which is the safe direction.
|
||||
gh api "repos/${GITHUB_REPOSITORY}/collaborators?per_page=100" --paginate \
|
||||
-q '.[] | select(.permissions.push) | .login' 2>/dev/null \
|
||||
| tr 'A-Z' 'a-z' | sort -u > writers.txt || true
|
||||
echo "write-access members loaded: $(wc -l < writers.txt)"
|
||||
cd wiki
|
||||
git config core.quotePath false
|
||||
|
||||
# Any author email under this domain is the maintainer.
|
||||
OWNER_DOMAIN="neilpang.com"
|
||||
# Our own enforcement commits.
|
||||
BOT_EMAIL="41898282+github-actions[bot]@users.noreply.github.com"
|
||||
BL_PAGE="Blacklist.md"
|
||||
# Rolling window; the cron runs every 10 minutes, so 7 days gives
|
||||
# ample overlap without re-judging old changes the maintainer
|
||||
# already accepted.
|
||||
WINDOW="7 days ago"
|
||||
|
||||
: > ../actions.txt
|
||||
: > ../bl_new.txt
|
||||
|
||||
is_owner() {
|
||||
case "$1" in
|
||||
*@"$OWNER_DOMAIN") return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
is_bot() {
|
||||
[ "$1" = "$BOT_EMAIL" ]
|
||||
}
|
||||
|
||||
author_email() {
|
||||
git show -s --format=%ae "$1" | tr 'A-Z' 'a-z'
|
||||
}
|
||||
|
||||
# Identity of a commit author: the GitHub login when the email is a
|
||||
# users.noreply.github.com address, otherwise the email itself.
|
||||
identity_of() {
|
||||
case "$1" in
|
||||
*+*@users.noreply.github.com)
|
||||
printf '%s\n' "$1" | sed 's/^[^+]*+//; s/@users\.noreply\.github\.com$//'
|
||||
;;
|
||||
*@users.noreply.github.com)
|
||||
printf '%s\n' "$1" | sed 's/@users\.noreply\.github\.com$//'
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' "$1"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
is_blacklisted() {
|
||||
grep -Fxq "$1" ../bl_all.txt
|
||||
}
|
||||
|
||||
# Trusted committers: the maintainer (by email domain), this bot,
|
||||
# and anyone whose GitHub login has write access to the repo.
|
||||
is_trusted() {
|
||||
if is_owner "$1" || is_bot "$1"; then
|
||||
return 0
|
||||
fi
|
||||
grep -Fxq "$(identity_of "$1")" ../writers.txt
|
||||
}
|
||||
|
||||
# Newest commit on file $1 authored by a non-blacklisted user.
|
||||
last_good_for() {
|
||||
for g in $(git log --format=%H --no-renames -- "$1"); do
|
||||
gae="$(author_email "$g")"
|
||||
if is_trusted "$gae"; then
|
||||
printf '%s\n' "$g"
|
||||
return 0
|
||||
fi
|
||||
gid="$(identity_of "$gae")"
|
||||
if ! is_blacklisted "$gid" && ! is_blacklisted "$gae"; then
|
||||
printf '%s\n' "$g"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
if [ -e "$BL_PAGE" ]; then
|
||||
page_existed=1
|
||||
else
|
||||
page_existed=""
|
||||
fi
|
||||
|
||||
# ---- 1. Last good version of the blacklist page: the newest
|
||||
# revision authored by the maintainer or by this bot. Everything
|
||||
# else on that page is tampering and is discarded.
|
||||
bl_good_commit=""
|
||||
for c in $(git log --format=%H --no-renames -- "$BL_PAGE"); do
|
||||
ae="$(author_email "$c")"
|
||||
if is_trusted "$ae"; then
|
||||
bl_good_commit="$c"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -n "$bl_good_commit" ] && git cat-file -e "$bl_good_commit:$BL_PAGE" 2>/dev/null; then
|
||||
git show "$bl_good_commit:$BL_PAGE" > ../bl_page.txt
|
||||
else
|
||||
{
|
||||
echo "# Blacklist"
|
||||
echo ""
|
||||
echo "Users listed below violated the wiki rules (deleted or renamed"
|
||||
echo "pages, or tampered with this page). Their new issues and pull"
|
||||
echo "requests are closed on sight and their wiki edits are reverted"
|
||||
echo "automatically. Only the maintainer and write-access members"
|
||||
echo "may edit this page."
|
||||
echo ""
|
||||
echo "To pardon a user while their violation is still inside the"
|
||||
echo "scan window, replace their entry with: pardon: username"
|
||||
echo ""
|
||||
} > ../bl_page.txt
|
||||
fi
|
||||
sed -n 's/^- *//p' ../bl_page.txt | tr -d '\r' | tr 'A-Z' 'a-z' | sort -u > ../bl_good.txt
|
||||
sed -n 's/^[Pp]ardon: *//p' ../bl_page.txt | tr -d '\r' | tr 'A-Z' 'a-z' | sort -u > ../bl_pardon.txt
|
||||
|
||||
bl_add() {
|
||||
if grep -Fxq "$1" ../bl_pardon.txt; then
|
||||
return 0
|
||||
fi
|
||||
if ! grep -Fxq "$1" ../bl_good.txt && ! grep -Fxq "$1" ../bl_new.txt; then
|
||||
printf '%s\n' "$1" >> ../bl_new.txt
|
||||
printf '%s\n' "- blacklisted \`$1\`: $2" >> ../actions.txt
|
||||
fi
|
||||
}
|
||||
|
||||
# ---- 2. Blacklist everyone who deleted or renamed a page.
|
||||
# --no-renames makes a rename count as a deletion of the old path.
|
||||
for c in $(git log --since="$WINDOW" --diff-filter=D --no-renames --format=%H); do
|
||||
ae="$(author_email "$c")"
|
||||
if is_trusted "$ae"; then
|
||||
continue
|
||||
fi
|
||||
an="$(git show -s --format=%an "$c")"
|
||||
bl_add "$(identity_of "$ae")" "deleted or renamed pages in $c ($an <$ae>)"
|
||||
done
|
||||
|
||||
# ---- 3. Blacklist everyone else who touched the blacklist page.
|
||||
# The revert of their tampering falls out of steps 5 and 6.
|
||||
for c in $(git log --since="$WINDOW" --format=%H --no-renames -- "$BL_PAGE"); do
|
||||
ae="$(author_email "$c")"
|
||||
if is_trusted "$ae"; then
|
||||
continue
|
||||
fi
|
||||
an="$(git show -s --format=%an "$c")"
|
||||
bl_add "$(identity_of "$ae")" "tampered with \`$BL_PAGE\` in $c ($an <$ae>)"
|
||||
done
|
||||
|
||||
sort -u ../bl_new.txt > ../bl_new_u.txt
|
||||
cat ../bl_good.txt ../bl_new_u.txt | sort -u > ../bl_all.txt
|
||||
|
||||
# ---- 4. Restore pages that are currently missing because a
|
||||
# non-maintainer deleted them, using the last good revision.
|
||||
git log --since="$WINDOW" --diff-filter=D --no-renames --name-only --format= \
|
||||
| sort -u \
|
||||
| while IFS= read -r f; do
|
||||
if [ -z "$f" ] || [ "$f" = "$BL_PAGE" ] || [ -e "$f" ]; then
|
||||
continue
|
||||
fi
|
||||
del="$(git log -1 --diff-filter=D --no-renames --format=%H -- "$f")"
|
||||
if [ -z "$del" ]; then
|
||||
continue
|
||||
fi
|
||||
ae="$(author_email "$del")"
|
||||
if is_trusted "$ae"; then
|
||||
continue
|
||||
fi
|
||||
good="$(last_good_for "$f")"
|
||||
if [ -n "$good" ] && git cat-file -e "$good:$f" 2>/dev/null; then
|
||||
git checkout "$good" -- "$f"
|
||||
printf '%s\n' "- restored \`$f\` (deleted in $del) from its last good revision $good" >> ../actions.txt
|
||||
fi
|
||||
done
|
||||
|
||||
# ---- 5. Revert every recent change made by a blacklisted
|
||||
# identity: each touched file goes back to its newest revision
|
||||
# authored by a non-blacklisted user; a file that has no such
|
||||
# revision (they created it) is removed.
|
||||
if [ -s ../bl_all.txt ]; then
|
||||
for c in $(git log --since="$WINDOW" --format=%H --no-renames); do
|
||||
ae="$(author_email "$c")"
|
||||
if is_trusted "$ae"; then
|
||||
continue
|
||||
fi
|
||||
id="$(identity_of "$ae")"
|
||||
if ! is_blacklisted "$id" && ! is_blacklisted "$ae"; then
|
||||
continue
|
||||
fi
|
||||
git show --name-only --no-renames --format= "$c" \
|
||||
| while IFS= read -r f; do
|
||||
if [ -z "$f" ] || [ "$f" = "$BL_PAGE" ]; then
|
||||
continue
|
||||
fi
|
||||
good="$(last_good_for "$f")"
|
||||
if [ -n "$good" ] && git cat-file -e "$good:$f" 2>/dev/null; then
|
||||
want="$(git rev-parse "$good:$f")"
|
||||
have="$(git hash-object -- "$f" 2>/dev/null || echo missing)"
|
||||
if [ "$want" != "$have" ]; then
|
||||
git checkout "$good" -- "$f"
|
||||
printf '%s\n' "- reverted \`$f\` to its last good revision $good (undoing change by \`$id\` in $c)" >> ../actions.txt
|
||||
fi
|
||||
elif [ -e "$f" ]; then
|
||||
git rm -q -- "$f"
|
||||
printf '%s\n' "- removed \`$f\` created by blacklisted \`$id\` in $c" >> ../actions.txt
|
||||
fi
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
# ---- 6. Regenerate the blacklist page: the last good text plus
|
||||
# any newly blacklisted identities. This both reverts tampering
|
||||
# and records new violators; manual edits by the maintainer are
|
||||
# preserved as the new good text.
|
||||
cp ../bl_page.txt ../bl_page_new.txt
|
||||
if [ -s ../bl_page_new.txt ] && [ -n "$(tail -c1 ../bl_page_new.txt)" ]; then
|
||||
echo >> ../bl_page_new.txt
|
||||
fi
|
||||
while IFS= read -r id; do
|
||||
if [ -n "$id" ] && ! grep -Fxiq -- "- $id" ../bl_page_new.txt; then
|
||||
printf -- '- %s\n' "$id" >> ../bl_page_new.txt
|
||||
fi
|
||||
done < ../bl_new_u.txt
|
||||
if ! cmp -s ../bl_page_new.txt "$BL_PAGE" 2>/dev/null; then
|
||||
cp ../bl_page_new.txt "$BL_PAGE"
|
||||
git add -- "$BL_PAGE"
|
||||
if [ -n "$page_existed" ] || [ -s ../bl_new_u.txt ]; then
|
||||
printf '%s\n' "- updated \`$BL_PAGE\`" >> ../actions.txt
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---- 7. Commit, push, notify.
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "$BOT_EMAIL"
|
||||
git commit -m "wiki-guard: restore pages and enforce blacklist"
|
||||
git push origin HEAD || { git pull --rebase && git push origin HEAD; }
|
||||
fi
|
||||
if [ -s ../actions.txt ]; then
|
||||
{
|
||||
echo "The wiki guard handled the following rule violations:"
|
||||
echo ""
|
||||
cat ../actions.txt
|
||||
echo ""
|
||||
echo "Blacklist: https://github.com/${GITHUB_REPOSITORY}/wiki/Blacklist"
|
||||
echo "Wiki: https://github.com/${GITHUB_REPOSITORY}/wiki"
|
||||
} > ../guard-msg.txt
|
||||
echo "acted=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "No rule violations found."
|
||||
echo "acted=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Create issue to notify Neilpang
|
||||
if: steps.guard.outputs.acted == 'true'
|
||||
uses: peter-evans/create-issue-from-file@v6
|
||||
with:
|
||||
title: "Wiki guard: rule violations handled"
|
||||
content-filepath: ./guard-msg.txt
|
||||
assignees: Neilpang
|
||||
82
.github/workflows/wiki-monitor.yml
vendored
Normal file
82
.github/workflows/wiki-monitor.yml
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
name: Notify via Issue on Wiki Edit
|
||||
|
||||
on:
|
||||
gollum:
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor != 'neilpang'
|
||||
steps:
|
||||
- name: Checkout wiki repository
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: ${{ github.repository }}.wiki
|
||||
path: wiki
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Generate wiki change message
|
||||
id: msg
|
||||
run: |
|
||||
actor="${{ github.actor }}"
|
||||
sender_url=$(jq -r '.sender.html_url' "$GITHUB_EVENT_PATH")
|
||||
page_name=$(jq -r '.pages[0].page_name' "$GITHUB_EVENT_PATH")
|
||||
page_sha=$(jq -r '.pages[0].sha' "$GITHUB_EVENT_PATH")
|
||||
page_url=$(jq -r '.pages[0].html_url' "$GITHUB_EVENT_PATH")
|
||||
page_action=$(jq -r '.pages[0].action' "$GITHUB_EVENT_PATH")
|
||||
page_summary=$(jq -r '.pages[0].summary' "$GITHUB_EVENT_PATH")
|
||||
now="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
|
||||
cd wiki
|
||||
# Skip notification when the change was authored by the
|
||||
# maintainer himself (any author email under neilpang.com),
|
||||
# e.g. a direct git push to the wiki repository.
|
||||
author_email=$(git show -s --format=%ae "$page_sha" 2>/dev/null | tr 'A-Z' 'a-z')
|
||||
case "$author_email" in
|
||||
*@neilpang.com)
|
||||
echo "Change authored by maintainer ($author_email); skipping notification."
|
||||
echo "notify=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
prev_sha=$(git rev-list $page_sha^ -- "$page_name.md" | head -n 1)
|
||||
if [ -n "$prev_sha" ]; then
|
||||
git diff $prev_sha $page_sha -- "$page_name.md" > ../wiki.diff || echo "(No diff found)" > ../wiki.diff
|
||||
else
|
||||
echo "(no diff)" > ../wiki.diff
|
||||
fi
|
||||
cd ..
|
||||
{
|
||||
echo "Wiki edited"
|
||||
echo -n "User: "
|
||||
echo "@$actor [$actor]($sender_url)"
|
||||
echo "Time: $now"
|
||||
echo "Page: [$page_name]($page_url) (Action: $page_action)"
|
||||
echo "Comment: $page_summary"
|
||||
echo "[Click here to Revert](https://github.com/${GITHUB_REPOSITORY}/issues/new?title=revert%3A+${page_sha}&body=Revert+wiki+commit+${page_sha}+by+@${actor}.)"
|
||||
echo ""
|
||||
echo "[Click here to Blacklist @$actor](https://github.com/${GITHUB_REPOSITORY}/issues/new?title=blacklist%3A+${actor}&body=Blacklist+@${actor},+requested+from+the+wiki+monitor.)"
|
||||
echo ""
|
||||
echo "----"
|
||||
echo "### diff:"
|
||||
echo '```diff'
|
||||
cat wiki.diff
|
||||
echo '```'
|
||||
} > wiki-change-msg.txt
|
||||
echo "notify=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create issue to notify Neilpang
|
||||
if: steps.msg.outputs.notify == 'true'
|
||||
uses: peter-evans/create-issue-from-file@v6
|
||||
with:
|
||||
title: "Wiki edited"
|
||||
content-filepath: ./wiki-change-msg.txt
|
||||
assignees: Neilpang
|
||||
env:
|
||||
TZ: Asia/Shanghai
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
8
CONTRIBUTING.md
Normal file
8
CONTRIBUTING.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Contributing
|
||||
|
||||
1. Do NOT send pull request to `master` branch.
|
||||
Please send to `dev` branch instead.
|
||||
Any PR to `master` branch will NOT be merged.
|
||||
|
||||
2. For dns api support, read this guide first: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide
|
||||
You will NOT get any review without passing this guide. You also need to fix the CI errors.
|
||||
96
Dockerfile
Normal file
96
Dockerfile
Normal file
@@ -0,0 +1,96 @@
|
||||
FROM alpine:3.23
|
||||
|
||||
RUN apk --no-cache add -f \
|
||||
openssl \
|
||||
openssh-client \
|
||||
coreutils \
|
||||
bind-tools \
|
||||
curl \
|
||||
sed \
|
||||
socat \
|
||||
tzdata \
|
||||
oath-toolkit-oathtool \
|
||||
tar \
|
||||
libidn \
|
||||
jq \
|
||||
yq-go \
|
||||
supercronic
|
||||
|
||||
ENV LE_WORKING_DIR=/acmebin
|
||||
|
||||
ENV LE_CONFIG_HOME=/acme.sh
|
||||
|
||||
ENV HOME=/acme.sh
|
||||
|
||||
ARG AUTO_UPGRADE=1
|
||||
|
||||
ENV AUTO_UPGRADE=$AUTO_UPGRADE
|
||||
|
||||
#Install
|
||||
COPY ./acme.sh /install_acme.sh/acme.sh
|
||||
COPY ./deploy /install_acme.sh/deploy
|
||||
COPY ./dnsapi /install_acme.sh/dnsapi
|
||||
COPY ./notify /install_acme.sh/notify
|
||||
|
||||
RUN addgroup -g 1000 acme && adduser -h $LE_CONFIG_HOME -s /bin/sh -G acme -D -H -u 1000 acme
|
||||
|
||||
RUN cd /install_acme.sh && ([ -f /install_acme.sh/acme.sh ] && /install_acme.sh/acme.sh --install || curl https://get.acme.sh | sh) && rm -rf /install_acme.sh/
|
||||
|
||||
RUN ln -s $LE_WORKING_DIR/acme.sh /usr/local/bin/acme.sh
|
||||
|
||||
RUN chown -R acme:acme $LE_CONFIG_HOME
|
||||
|
||||
RUN for verb in help \
|
||||
version \
|
||||
install \
|
||||
uninstall \
|
||||
upgrade \
|
||||
issue \
|
||||
signcsr \
|
||||
deploy \
|
||||
install-cert \
|
||||
renew \
|
||||
renew-all \
|
||||
revoke \
|
||||
remove \
|
||||
list \
|
||||
info \
|
||||
showcsr \
|
||||
install-cronjob \
|
||||
uninstall-cronjob \
|
||||
cron \
|
||||
toPkcs \
|
||||
toPkcs8 \
|
||||
update-account \
|
||||
register-account \
|
||||
create-account-key \
|
||||
create-domain-key \
|
||||
createCSR \
|
||||
deactivate \
|
||||
deactivate-account \
|
||||
set-notify \
|
||||
set-default-ca \
|
||||
set-default-chain \
|
||||
; do \
|
||||
printf -- "%b" "#!/usr/bin/env sh\n$LE_WORKING_DIR/acme.sh --${verb} --config-home $LE_CONFIG_HOME \"\$@\"" >/usr/local/bin/--${verb} && chmod +x /usr/local/bin/--${verb} \
|
||||
; done
|
||||
|
||||
RUN printf "%b" '#!'"/usr/bin/env sh\n \
|
||||
if [ \"\$1\" = \"daemon\" ]; then \n \
|
||||
if [ ! -f \"\$LE_CONFIG_HOME/crontab\" ]; then \n \
|
||||
echo \"\$LE_CONFIG_HOME/crontab not found, generating one\" \n \
|
||||
time=\$(date -u \"+%s\") \n \
|
||||
random_minute=\$((\$time % 60)) \n \
|
||||
random_hour=\$((\$time / 60 % 6)) \n \
|
||||
echo \"\$random_minute \$random_hour,\$((\$random_hour + 6)),\$((\$random_hour + 12)),\$((\$random_hour + 18)) * * * \\\"\$LE_WORKING_DIR\\\"/acme.sh --cron --home \\\"\$LE_WORKING_DIR\\\" --config-home \\\"\$LE_CONFIG_HOME\\\"\" > \"\$LE_CONFIG_HOME\"/crontab \n \
|
||||
fi \n \
|
||||
echo \"Running Supercronic using crontab at \$LE_CONFIG_HOME/crontab\" \n \
|
||||
exec -- /usr/bin/supercronic \"\$LE_CONFIG_HOME/crontab\" \n \
|
||||
else \n \
|
||||
exec -- \"\$@\"\n \
|
||||
fi\n" >/entry.sh && chmod +x /entry.sh && chmod -R o+rwx $LE_WORKING_DIR && chmod -R o+rwx $LE_CONFIG_HOME
|
||||
|
||||
VOLUME /acme.sh
|
||||
|
||||
ENTRYPOINT ["/entry.sh"]
|
||||
CMD ["--help"]
|
||||
674
LICENSE.md
Normal file
674
LICENSE.md
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
724
README.md
724
README.md
@@ -1,92 +1,215 @@
|
||||
# An ACME Shell script: acme.sh
|
||||
- An ACME protocol client written purely in Shell (Unix shell) language.
|
||||
- Fully ACME protocol implementation.
|
||||
- Simple, powerful and very easy to use. You only need 3 minutes to learn.
|
||||
- Bash, dash and sh compatible.
|
||||
- Simplest shell script for Let's Encrypt free certificate client.
|
||||
- Purely written in Shell with no dependencies on python or Let's Encrypt official client.
|
||||
- Just one script, to issue, renew and install your certificates automatically.
|
||||
- DOES NOT require `root/sudoer` access.
|
||||
<p align="center">
|
||||
<a href="https://zerossl.com?utm_source=acme-sh">
|
||||
<picture>
|
||||
<!-- Dark mode -->
|
||||
<source
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcset="https://github.com/user-attachments/assets/1308516b-e0cc-496d-b5df-e3932423ead6" />
|
||||
<!-- Light mode -->
|
||||
<source
|
||||
media="(prefers-color-scheme: light)"
|
||||
srcset="https://github.com/user-attachments/assets/4ba7a79e-8cc9-4d49-87fc-02d44fb7b043" />
|
||||
<!-- Fallback for environments without media queries -->
|
||||
<img
|
||||
alt="ZeroSSL"
|
||||
src="https://github.com/user-attachments/assets/4ba7a79e-8cc9-4d49-87fc-02d44fb7b043"
|
||||
height="auto" />
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
It's probably the `easiest&smallest&smartest` shell script to automatically issue & renew the free certificates from Let's Encrypt.
|
||||
<h1 align="center">🔐 acme.sh</h1>
|
||||
<h3 align="center">An ACME Protocol Client Written Purely in Shell</h3>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml/badge.svg" alt="FreeBSD"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml/badge.svg" alt="OpenBSD"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml/badge.svg" alt="NetBSD"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml/badge.svg" alt="MacOS"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml/badge.svg" alt="Ubuntu"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml/badge.svg" alt="Windows"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml/badge.svg" alt="Solaris"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml/badge.svg" alt="DragonFlyBSD"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/MidnightBSD.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/MidnightBSD.yml/badge.svg" alt="MidnightBSD"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/GhostBSD.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/GhostBSD.yml/badge.svg" alt="GhostBSD"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml/badge.svg" alt="Omnios"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/OpenIndiana.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/OpenIndiana.yml/badge.svg" alt="OpenIndiana"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Tribblix.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Tribblix.yml/badge.svg" alt="Tribblix"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml/badge.svg" alt="Haiku"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/Hurd.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/Hurd.yml/badge.svg" alt="Hurd"></a>
|
||||
<a href="https://github.com/acmesh-official/acme.sh/actions/workflows/OpenEuler.yml"><img src="https://github.com/acmesh-official/acme.sh/actions/workflows/OpenEuler.yml/badge.svg" alt="OpenEuler"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/acmesh-official/acme.sh/workflows/Shellcheck/badge.svg" alt="Shellcheck">
|
||||
<img src="https://github.com/acmesh-official/acme.sh/workflows/PebbleStrict/badge.svg" alt="PebbleStrict">
|
||||
<img src="https://github.com/acmesh-official/acme.sh/workflows/Build%20DockerHub/badge.svg" alt="DockerHub">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://opencollective.com/acmesh"><img src="https://opencollective.com/acmesh/all/badge.svg?label=financial+contributors" alt="Financial Contributors on Open Collective"></a>
|
||||
<a href="https://gitter.im/acme-sh/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img src="https://badges.gitter.im/acme-sh/Lobby.svg" alt="Join the chat at Gitter"></a>
|
||||
<a href="https://hub.docker.com/r/neilpang/acme.sh" title="Click to view the image on Docker Hub"><img src="https://img.shields.io/docker/stars/neilpang/acme.sh.svg" alt="Docker stars"></a>
|
||||
<a href="https://hub.docker.com/r/neilpang/acme.sh" title="Click to view the image on Docker Hub"><img src="https://img.shields.io/docker/pulls/neilpang/acme.sh.svg" alt="Docker pulls"></a>
|
||||
</p>
|
||||
|
||||
|
||||
Wiki: https://github.com/Neilpang/acme.sh/wiki
|
||||
---
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- 🐚 An ACME protocol client written **purely in Shell** (Unix shell) language
|
||||
- 📜 Full ACME protocol implementation
|
||||
- 🔑 Support **ECDSA** certificates
|
||||
- 🌐 Support **SAN** and **wildcard** certificates
|
||||
- ⚡ Simple, powerful and very easy to use — only **3 minutes** to learn!
|
||||
- 🔧 Compatible with **Bash**, **dash** and **sh**
|
||||
- 🚫 No dependencies on Python
|
||||
- 🔄 One script to issue, renew and install your certificates automatically
|
||||
- 👤 **DOES NOT** require `root/sudoer` access
|
||||
- 🐳 Docker ready
|
||||
- 🌍 IPv6 ready
|
||||
- 📧 Cron job notifications for renewal or error
|
||||
|
||||
> 💡 It's probably the **easiest & smartest** shell script to automatically issue & renew free certificates.
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/acmesh-official/acme.sh/wiki"><strong>📚 Wiki</strong></a> •
|
||||
<a href="https://github.com/acmesh-official/acme.sh/wiki/Run-acme.sh-in-docker"><strong>🐳 Docker Guide</strong></a> •
|
||||
<a href="https://twitter.com/neilpangxa"><strong>🐦 Twitter</strong></a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## 🌏 [中文说明](https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E)
|
||||
|
||||
---
|
||||
|
||||
## 🏆 Who Uses acme.sh?
|
||||
- [FreeBSD.org](https://blog.crashed.org/letsencrypt-in-freebsd-org/)
|
||||
- [ruby-china.org](https://ruby-china.org/topics/31983)
|
||||
- [Proxmox](https://pve.proxmox.com/wiki/Certificate_Management)
|
||||
- [pfsense](https://github.com/pfsense/FreeBSD-ports/pull/89)
|
||||
- [Loadbalancer.org](https://www.loadbalancer.org/blog/loadbalancer-org-with-lets-encrypt-quick-and-dirty)
|
||||
- [discourse.org](https://meta.discourse.org/t/setting-up-lets-encrypt/40709)
|
||||
- [Centminmod](https://centminmod.com/letsencrypt-acmetool-https.html)
|
||||
- [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297)
|
||||
- [opnsense.org](https://github.com/opnsense/plugins/tree/master/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient)
|
||||
- [CentOS Web Panel](https://control-webpanel.com)
|
||||
- [lnmp.org](https://lnmp.org/)
|
||||
- [more...](https://github.com/acmesh-official/acme.sh/wiki/Blogs-and-tutorials)
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ Tested OS
|
||||
|
||||
#Tested OS
|
||||
| NO | Status| Platform|
|
||||
|----|-------|---------|
|
||||
|1|[](https://github.com/Neilpang/letest#here-are-the-latest-status)| Ubuntu
|
||||
|2|[](https://github.com/Neilpang/letest#here-are-the-latest-status)| Debian
|
||||
|3|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|CentOS
|
||||
|4|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|Windows (cygwin with curl, openssl and crontab included)
|
||||
|5|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|FreeBSD
|
||||
|6|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|pfsense
|
||||
|7|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|openSUSE
|
||||
|8|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|Alpine Linux (with curl)
|
||||
|9|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|Archlinux
|
||||
|10|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|fedora
|
||||
|11|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|Kali Linux
|
||||
|12|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|Oracle Linux
|
||||
|13|[](https://github.com/Neilpang/letest#here-are-the-latest-status)| Proxmox https://pve.proxmox.com/wiki/HTTPSCertificateConfiguration#Let.27s_Encrypt_using_acme.sh
|
||||
|14|-----| Cloud Linux https://github.com/Neilpang/le/issues/111
|
||||
|15|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|OpenBSD
|
||||
|16|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|Mageia
|
||||
|17|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/Neilpang/acme.sh/wiki/How-to-run-on-OpenWRT)
|
||||
|18|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|SunOS/Solaris
|
||||
|
||||
For all build statuses, check our [daily build project](https://github.com/Neilpang/acmetest):
|
||||
|
||||
https://github.com/Neilpang/acmetest
|
||||
|
||||
# Supported Mode
|
||||
|
||||
1. Webroot mode
|
||||
2. Standalone mode
|
||||
3. Apache mode
|
||||
4. Dns mode
|
||||
|1|[](https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml)|Mac OSX
|
||||
|2|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml)|Windows (cygwin with curl, openssl and crontab included)
|
||||
|3|[](https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml)|FreeBSD
|
||||
|4|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml)|Solaris
|
||||
|5|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml)| Ubuntu
|
||||
|6|NA|pfsense
|
||||
|7|[](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml)|OpenBSD
|
||||
|8|[](https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml)|NetBSD
|
||||
|9|[](https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml)|DragonFlyBSD
|
||||
|10|[](https://github.com/acmesh-official/acme.sh/actions/workflows/MidnightBSD.yml)|MidnightBSD
|
||||
|11|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml)|Omnios
|
||||
|12|[](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenIndiana.yml)|OpenIndiana
|
||||
|13|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)| Debian
|
||||
|14|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|openSUSE
|
||||
|15|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Alpine Linux (with curl)
|
||||
|16|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Archlinux
|
||||
|17|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|fedora
|
||||
|18|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Kali Linux
|
||||
|19|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Oracle Linux
|
||||
|20|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Mageia
|
||||
|21|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Gentoo Linux
|
||||
|22|-----| Cloud Linux https://github.com/acmesh-official/acme.sh/issues/111
|
||||
|23|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/acmesh-official/acme.sh/wiki/How-to-run-on-OpenWRT)
|
||||
|24|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)| Proxmox: See Proxmox VE Wiki. Version [4.x, 5.0, 5.1](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x,_5.0_and_5.1)#Let.27s_Encrypt_using_acme.sh), version [5.2 and up](https://pve.proxmox.com/wiki/Certificate_Management)
|
||||
|25|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml)|Haiku OS
|
||||
|26|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Tribblix.yml)|Tribblix
|
||||
|27|[](https://github.com/acmesh-official/acme.sh/actions/workflows/GhostBSD.yml)|GhostBSD
|
||||
|28|[](https://github.com/acmesh-official/acme.sh/actions/workflows/Hurd.yml)|GNU Hurd
|
||||
|29|[](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenEuler.yml)|openEuler
|
||||
|
||||
|
||||
> 🧪 Check our [testing project](https://github.com/acmesh-official/acmetest)
|
||||
>
|
||||
> 🖥️ The testing VMs are supported by [vmactions.org](https://vmactions.org)
|
||||
|
||||
# 1. How to install
|
||||
---
|
||||
|
||||
### 1. Install online:
|
||||
## 🏛️ Supported CA
|
||||
|
||||
Check this project: https://github.com/Neilpang/get.acme.sh
|
||||
| CA | Status |
|
||||
|---|---|
|
||||
| [ZeroSSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA) | ⭐ **Default** |
|
||||
| Letsencrypt.org CA | ✅ Supported |
|
||||
| [SSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/SSL.com-CA) | ✅ Supported |
|
||||
| [Google.com Public CA](https://github.com/acmesh-official/acme.sh/wiki/Google-Public-CA) | ✅ Supported |
|
||||
| [Actalis.com CA](https://github.com/acmesh-official/acme.sh/wiki/Actalis.com-CA) | ✅ Supported |
|
||||
| [Pebble strict Mode](https://github.com/letsencrypt/pebble) | ✅ Supported |
|
||||
| Any [RFC8555](https://tools.ietf.org/html/rfc8555)-compliant CA | ✅ Supported |
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Supported Modes
|
||||
|
||||
| Mode | Description |
|
||||
|------|-------------|
|
||||
| 📁 Webroot mode | Use existing webroot directory |
|
||||
| 🖥️ Standalone mode | Built-in webserver on port 80 |
|
||||
| 🔐 Standalone tls-alpn mode | Built-in webserver on port 443 |
|
||||
| 🪶 Apache mode | Use Apache for verification |
|
||||
| ⚡ Nginx mode | Use Nginx for verification |
|
||||
| 🌐 DNS mode | Use DNS TXT records |
|
||||
| 🔗 [DNS alias mode](https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode) | Use DNS alias for verification |
|
||||
| 📡 [Stateless mode](https://github.com/acmesh-official/acme.sh/wiki/Stateless-Mode) | Stateless verification |
|
||||
| 📌 [DNS persist mode](https://github.com/acmesh-official/acme.sh/wiki/DNS-persist-mode) | Persistent DNS TXT record ([draft-ietf-acme-dns-persist-01](https://datatracker.ietf.org/doc/draft-ietf-acme-dns-persist/)) |
|
||||
|
||||
---
|
||||
|
||||
## 📖 Usage Guide
|
||||
|
||||
### 1️⃣ How to Install
|
||||
|
||||
#### 📥 Install Online
|
||||
|
||||
> Check this project: https://github.com/acmesh-official/get.acme.sh
|
||||
|
||||
```bash
|
||||
curl https://get.acme.sh | sh
|
||||
|
||||
curl https://get.acme.sh | sh -s email=my@example.com
|
||||
```
|
||||
|
||||
Or:
|
||||
**Or:**
|
||||
|
||||
```bash
|
||||
wget -O - https://get.acme.sh | sh
|
||||
|
||||
wget -O - https://get.acme.sh | sh -s email=my@example.com
|
||||
```
|
||||
|
||||
#### 📦 Install from Git
|
||||
|
||||
### 2. Or, Install from git:
|
||||
|
||||
Clone this project:
|
||||
Clone this project and launch installation:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Neilpang/acme.sh.git
|
||||
git clone https://github.com/acmesh-official/acme.sh.git
|
||||
cd ./acme.sh
|
||||
./acme.sh --install
|
||||
./acme.sh --install -m my@example.com
|
||||
```
|
||||
|
||||
You `don't have to be root` then, although `it is recommended`.
|
||||
> 💡 You `don't have to be root` then, although `it is recommended`.
|
||||
|
||||
Advanced Installation: https://github.com/Neilpang/acme.sh/wiki/How-to-install
|
||||
📚 **Advanced Installation:** https://github.com/acmesh-official/acme.sh/wiki/How-to-install
|
||||
|
||||
The installer will perform 3 actions:
|
||||
**The installer will perform 3 actions:**
|
||||
|
||||
1. Create and copy `acme.sh` to your home dir (`$HOME`): `~/.acme.sh/`.
|
||||
All certs will be placed in this folder.
|
||||
2. Create alias for: `acme.sh=~/.acme.sh/acme.sh`.
|
||||
3. Create everyday cron job to check and renew the cert if needed.
|
||||
1. Create and copy `acme.sh` to your home dir (`$HOME`): `~/.acme.sh/`.
|
||||
All certs will be placed in this folder too.
|
||||
2. Create alias for: `acme.sh=~/.acme.sh/acme.sh`.
|
||||
3. Create daily cron job to check and renew the certs if needed.
|
||||
|
||||
Cron entry example:
|
||||
|
||||
@@ -94,241 +217,484 @@ Cron entry example:
|
||||
0 0 * * * "/home/user/.acme.sh"/acme.sh --cron --home "/home/user/.acme.sh" > /dev/null
|
||||
```
|
||||
|
||||
After the installation, you must close current terminal and reopen again to make the alias take effect.
|
||||
> ⚠️ After the installation, you must close the current terminal and reopen it to make the alias take effect.
|
||||
|
||||
Ok, you are ready to issue cert now.
|
||||
Show help message:
|
||||
✅ **You are ready to issue certs now!**
|
||||
|
||||
**Show help message:**
|
||||
|
||||
```sh
|
||||
acme.sh -h
|
||||
```
|
||||
|
||||
root@v1:~# acme.sh -h
|
||||
---
|
||||
|
||||
```
|
||||
|
||||
# 2. Just issue a cert:
|
||||
### 2️⃣ Issue a Certificate
|
||||
|
||||
**Example 1:** Single domain.
|
||||
|
||||
```bash
|
||||
acme.sh --issue -d aa.com -w /home/wwwroot/aa.com
|
||||
acme.sh --issue -d example.com -w /home/wwwroot/example.com
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```bash
|
||||
acme.sh --issue -d example.com -w /home/username/public_html
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```bash
|
||||
acme.sh --issue -d example.com -w /var/www/html
|
||||
```
|
||||
|
||||
**Example 2:** Multiple domains in the same cert.
|
||||
|
||||
```bash
|
||||
acme.sh --issue -d aa.com -d www.aa.com -d cp.aa.com -w /home/wwwroot/aa.com
|
||||
acme.sh --issue -d example.com -d www.example.com -d cp.example.com -w /home/wwwroot/example.com
|
||||
```
|
||||
|
||||
The parameter `/home/wwwroot/aa.com` is the web root folder. You **MUST** have `write access` to this folder.
|
||||
The parameter `/home/wwwroot/example.com` or `/home/username/public_html` or `/var/www/html` is the web root folder where you host your website files. You **MUST** have `write access` to this folder.
|
||||
|
||||
Second argument **"aa.com"** is the main domain you want to issue cert for.
|
||||
You must have at least a domain there.
|
||||
Second argument **"example.com"** is the main domain you want to issue the cert for.
|
||||
You must have at least one domain there.
|
||||
|
||||
You must point and bind all the domains to the same webroot dir: `/home/wwwroot/aa.com`.
|
||||
You must point and bind all the domains to the same webroot dir: `/home/wwwroot/example.com`.
|
||||
|
||||
Generate/issued certs will be placed in `~/.acme.sh/aa.com/`
|
||||
The certs will be placed in `~/.acme.sh/example.com/`
|
||||
|
||||
The issued cert will be renewed every 80 days automatically.
|
||||
> 🔄 The certs will be renewed automatically every **30** days.
|
||||
|
||||
More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
|
||||
> 🔐 The certs will default to **ECC** certificates.
|
||||
|
||||
📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
|
||||
|
||||
# 3. Install issued cert to apache/nginx etc.
|
||||
---
|
||||
|
||||
After you issue a cert, you probably want to install the cert with your nginx/apache or other servers you may be using.
|
||||
### 3️⃣ Install the Certificate to Apache/Nginx
|
||||
|
||||
After the cert is generated, you probably want to install/copy the cert to your Apache/Nginx or other servers.
|
||||
|
||||
> ⚠️ **IMPORTANT:** You **MUST** use this command to copy the certs to the target files. **DO NOT** use the certs files in `~/.acme.sh/` folder — they are for internal use only, the folder structure may change in the future.
|
||||
|
||||
#### 🪶 Apache Example:
|
||||
```bash
|
||||
acme.sh --installcert -d aa.com \
|
||||
--certpath /path/to/certfile/in/apache/nginx \
|
||||
--keypath /path/to/keyfile/in/apache/nginx \
|
||||
--capath /path/to/ca/certfile/apache/nginx \
|
||||
--fullchainpath path/to/fullchain/certfile/apache/nginx \
|
||||
--reloadcmd "service apache2|nginx reload"
|
||||
acme.sh --install-cert -d example.com \
|
||||
--cert-file /path/to/certfile/in/apache/cert.pem \
|
||||
--key-file /path/to/keyfile/in/apache/key.pem \
|
||||
--fullchain-file /path/to/fullchain/certfile/apache/fullchain.pem \
|
||||
--reloadcmd "service apache2 force-reload"
|
||||
```
|
||||
|
||||
#### ⚡ Nginx Example:
|
||||
```bash
|
||||
acme.sh --install-cert -d example.com \
|
||||
--key-file /path/to/keyfile/in/nginx/key.pem \
|
||||
--fullchain-file /path/to/fullchain/nginx/cert.pem \
|
||||
--reloadcmd "service nginx force-reload"
|
||||
```
|
||||
|
||||
Only the domain is required, all the other parameters are optional.
|
||||
|
||||
Install the issued cert/key to the production apache or nginx path.
|
||||
The ownership and permission info of existing files are preserved. You can pre-create the files to define the ownership and permission.
|
||||
|
||||
The cert will be `renewed every 80 days by default` (which is configurable). Once the cert is renewed, the apache/nginx will be automatically reloaded by the command: `service apache2 reload` or `service nginx reload`.
|
||||
Install/copy the cert/key to the production Apache or Nginx path.
|
||||
|
||||
# 4. Use Standalone server to issue cert
|
||||
> 🔄 The cert will be renewed every **30** days by default (configurable). Once renewed, the Apache/Nginx service will be reloaded automatically.
|
||||
|
||||
**(requires you be root/sudoer, or you have permission to listen tcp 80 port)**
|
||||
> ⚠️ **IMPORTANT:** The `reloadcmd` is very important. The cert can be automatically renewed, but without a correct `reloadcmd`, the cert may not be flushed to your server (like nginx or apache), then your website will not be able to show the renewed cert.
|
||||
|
||||
The tcp `80` port **MUST** be free to listen, otherwise you will be prompted to free the `80` port and try again.
|
||||
---
|
||||
|
||||
### 4️⃣ Use Standalone Server to Issue Certificate
|
||||
|
||||
> 🔐 Requires root/sudoer or permission to listen on port **80** (TCP)
|
||||
|
||||
> ⚠️ Port `80` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again.
|
||||
|
||||
```bash
|
||||
acme.sh --issue --standalone -d aa.com -d www.aa.com -d cp.aa.com
|
||||
acme.sh --issue --standalone -d example.com -d www.example.com -d cp.example.com
|
||||
```
|
||||
|
||||
More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
|
||||
📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
|
||||
|
||||
# 5. Use Standalone tls server to issue cert
|
||||
---
|
||||
|
||||
**(requires you be root/sudoer, or you have permission to listen tcp 443 port)**
|
||||
### 5️⃣ Use Standalone TLS Server to Issue Certificate
|
||||
|
||||
acme.sh supports `tls-sni-01` validation.
|
||||
> 🔐 Requires root/sudoer or permission to listen on port **443** (TCP)
|
||||
|
||||
The tcp `443` port **MUST** be free to listen, otherwise you will be prompted to free the `443` port and try again.
|
||||
> ⚠️ Port `443` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again.
|
||||
|
||||
```bash
|
||||
acme.sh --issue --tls -d aa.com -d www.aa.com -d cp.aa.com
|
||||
acme.sh --issue --alpn -d example.com -d www.example.com -d cp.example.com
|
||||
```
|
||||
|
||||
More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
|
||||
📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
|
||||
|
||||
# 6. Use Apache mode
|
||||
---
|
||||
|
||||
**(requires you be root/sudoer, since it is required to interact with apache server)**
|
||||
### 6️⃣ Use Apache Mode
|
||||
|
||||
If you are running a web server, apache or nginx, it is recommended to use the `Webroot mode`.
|
||||
> 🔐 Requires root/sudoer to interact with Apache server
|
||||
|
||||
Particularly, if you are running an apache server, you should use apache mode instead. This mode doesn't write any files to your web root folder.
|
||||
If you are running a web server, it is recommended to use the `Webroot mode`.
|
||||
|
||||
Just set string "apache" as the second argument, it will force use of apache plugin automatically.
|
||||
Particularly, if you are running an Apache server, you can use Apache mode instead. This mode doesn't write any files to your web root folder.
|
||||
|
||||
```
|
||||
acme.sh --issue --apache -d aa.com -d www.aa.com -d user.aa.com
|
||||
```sh
|
||||
acme.sh --issue --apache -d example.com -d www.example.com -d cp.example.com
|
||||
```
|
||||
|
||||
More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
|
||||
> 💡 **Note:** This Apache mode is only to issue the cert, it will **not** change your Apache config files. You will need to configure your website config files to use the cert by yourself. We don't want to mess with your Apache server, don't worry!
|
||||
|
||||
# 7. Use DNS mode:
|
||||
📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
|
||||
|
||||
Support the `dns-01` challenge.
|
||||
---
|
||||
|
||||
### 7️⃣ Use Nginx Mode
|
||||
|
||||
> 🔐 Requires root/sudoer to interact with Nginx server
|
||||
|
||||
If you are running a web server, it is recommended to use the `Webroot mode`.
|
||||
|
||||
Particularly, if you are running an Nginx server, you can use Nginx mode instead. This mode doesn't write any files to your web root folder.
|
||||
|
||||
It will configure Nginx server automatically to verify the domain and then restore the Nginx config to the original version. So, the config is not changed.
|
||||
|
||||
```sh
|
||||
acme.sh --issue --nginx -d example.com -d www.example.com -d cp.example.com
|
||||
```
|
||||
|
||||
> 💡 **Note:** This Nginx mode is only to issue the cert, it will **not** change your Nginx config files. You will need to configure your website config files to use the cert by yourself. We don't want to mess with your Nginx server, don't worry!
|
||||
|
||||
📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
|
||||
|
||||
---
|
||||
|
||||
### 8️⃣ Automatic DNS API Integration
|
||||
|
||||
If your DNS provider supports API access, we can use that API to automatically issue the certs.
|
||||
|
||||
> ✨ **You don't have to do anything manually!**
|
||||
|
||||
📚 **Currently acme.sh supports most DNS providers:** https://github.com/acmesh-official/acme.sh/wiki/dnsapi
|
||||
|
||||
---
|
||||
|
||||
### 9️⃣ Use DNS Manual Mode
|
||||
|
||||
See: https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode first.
|
||||
|
||||
If your dns provider doesn't support any api access, you can add the txt record by hand.
|
||||
|
||||
```bash
|
||||
acme.sh --issue --dns -d aa.com -d www.aa.com -d user.aa.com
|
||||
acme.sh --issue --dns -d example.com -d www.example.com -d cp.example.com
|
||||
```
|
||||
|
||||
You should get the output like below:
|
||||
You should get an output like below:
|
||||
|
||||
```
|
||||
```sh
|
||||
Add the following txt record:
|
||||
Domain:_acme-challenge.aa.com
|
||||
Domain:_acme-challenge.example.com
|
||||
Txt value:9ihDbjYfTExAYeDs4DBUeuTo18KBzwvTEjUnSwd32-c
|
||||
|
||||
Add the following txt record:
|
||||
Domain:_acme-challenge.www.aa.com
|
||||
Domain:_acme-challenge.www.example.com
|
||||
Txt value:9ihDbjxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
|
||||
Please add those txt records to the domains. Waiting for the dns to take effect.
|
||||
|
||||
```
|
||||
|
||||
Then just rerun with `renew` argument:
|
||||
|
||||
```bash
|
||||
acme.sh --renew -d aa.com
|
||||
acme.sh --renew -d example.com
|
||||
```
|
||||
|
||||
Ok, it's finished.
|
||||
✅ **Done!**
|
||||
|
||||
# 8. Automatic DNS API integration
|
||||
> ⚠️ **WARNING:** This is DNS manual mode — it **cannot** be renewed automatically. You will have to add a new TXT record to your domain manually when you renew your cert. **Please use DNS API mode instead.**
|
||||
|
||||
If your DNS provider supports API access, we can use API to automatically issue the certs.
|
||||
---
|
||||
|
||||
You don't have do anything manually!
|
||||
### 🔟 Use DNS Persist Mode
|
||||
|
||||
### Currently acme.sh supports:
|
||||
📖 Wiki: https://github.com/acmesh-official/acme.sh/wiki/DNS-persist-mode
|
||||
|
||||
1. Cloudflare.com API
|
||||
2. Dnspod.cn API
|
||||
3. Cloudxns.com API
|
||||
4. Godaddy.com API
|
||||
5. OVH, kimsufi, soyoustart and runabove API
|
||||
6. AWS Route 53, see: https://github.com/Neilpang/acme.sh/issues/65
|
||||
7. lexicon dns api: https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api
|
||||
(DigitalOcean, DNSimple, DnsMadeEasy, DNSPark, EasyDNS, Namesilo, NS1, PointHQ, Rage4 and Vultr etc.)
|
||||
📚 Spec: [draft-ietf-acme-dns-persist-01](https://datatracker.ietf.org/doc/draft-ietf-acme-dns-persist/)
|
||||
|
||||
##### More APIs are coming soon...
|
||||
DNS persist mode lets you place a **single, long‑lived `_validation-persist` TXT record** in your zone and reuse it for every subsequent issuance and renewal. There is no per-issuance challenge token, so renewals require **no DNS edits** — useful when DNS API access is not available but you still want unattended renewals.
|
||||
|
||||
If your DNS provider is not on the supported list above, you can write your own script API easily. If you do please consider submitting a [Pull Request](https://github.com/Neilpang/acme.sh/pulls) and contribute to the project.
|
||||
|
||||
For more details: [How to use dns api](dnsapi)
|
||||
|
||||
# 9. Issue ECC certificate:
|
||||
|
||||
`Let's Encrypt` now can issue **ECDSA** certificates.
|
||||
|
||||
And we also support it.
|
||||
|
||||
Just set the `length` parameter with a prefix `ec-`.
|
||||
|
||||
For example:
|
||||
|
||||
### Single domain ECC cerfiticate:
|
||||
#### 🪄 Step 1: Print the TXT record value
|
||||
|
||||
```bash
|
||||
acme.sh --issue -w /home/wwwroot/aa.com -d aa.com --keylength ec-256
|
||||
acme.sh --make-dns-persist-value -d example.com [--server letsencrypt] [--dns-persist-wildcard] [--dns-persist-ca-name "sectigo.com"] [--dns-persist-days 365]
|
||||
```
|
||||
|
||||
SAN multi domain ECC certificate:
|
||||
Options:
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--server <ca>` | Pick the CA (default is your configured default). The account is registered automatically if you have not used this CA before. |
|
||||
| `--dns-persist-wildcard` | Adds `policy=wildcard` to the record so it also authorizes wildcard / subdomain certs. |
|
||||
| `--dns-persist-ca-name <name>` | Use a specific CA identity domain (e.g. `sectigo.com`). If omitted, identities are read from the ACME directory's `caaIdentities` field and one record per identity is printed — you only need to add **any one** of them. |
|
||||
| `--dns-persist-days <N>` | Adds `persistUntil=<unix-timestamp>` to the record, set to N days from now. The CA will refuse new validations against the record after that time. Omit for a record with no expiry. |
|
||||
|
||||
You should get an output like:
|
||||
|
||||
```sh
|
||||
TXT persist domain:_validation-persist.example.com
|
||||
TXT persist value :"letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/123456789"
|
||||
```
|
||||
|
||||
#### ✍️ Step 2: Add the TXT record to your DNS
|
||||
|
||||
Add the printed `TXT persist domain` / `TXT persist value` pair as a TXT record at your DNS provider, then wait for it to propagate.
|
||||
|
||||
#### 📜 Step 3: Issue the certificate
|
||||
|
||||
```bash
|
||||
acme.sh --issue -w /home/wwwroot/aa.com -d aa.com -d www.aa.com --keylength ec-256
|
||||
acme.sh --issue -d example.com --dns-persist
|
||||
```
|
||||
|
||||
Please look at the last parameter above.
|
||||
✅ **Done!** No challenge token is provisioned during issuance — the CA reads the persistent TXT record directly.
|
||||
|
||||
Valid values are:
|
||||
> 🔄 Renewals just work: `acme.sh --renew -d example.com` (or the cron job) reuses the same TXT record automatically — no further DNS edits needed.
|
||||
|
||||
1. **ec-256 (prime256v1, "ECDSA P-256")**
|
||||
2. **ec-384 (secp384r1, "ECDSA P-384")**
|
||||
3. **ec-521 (secp521r1, "ECDSA P-521", which is not supported by Let's Encrypt yet.)**
|
||||
---
|
||||
|
||||
### 1️⃣1️⃣ Issue Certificates of Different Key Types (ECC or RSA)
|
||||
|
||||
# 10. How to renew the cert
|
||||
Just set the `keylength` to a valid, supported value.
|
||||
|
||||
No, you don't need to renew the certs manually. All the certs will be renewed automatically every 80 days.
|
||||
**Valid values for the `keylength` parameter:**
|
||||
|
||||
However, you can also force to renew any cert:
|
||||
| Key Length | Description |
|
||||
|------------|-------------|
|
||||
| `ec-256` | prime256v1, "ECDSA P-256" ⭐ **Default** |
|
||||
| `ec-384` | secp384r1, "ECDSA P-384" |
|
||||
| `ec-521` | secp521r1, "ECDSA P-521" ⚠️ Not supported by Let's Encrypt yet |
|
||||
| `2048` | RSA 2048-bit |
|
||||
| `3072` | RSA 3072-bit |
|
||||
| `4096` | RSA 4096-bit |
|
||||
|
||||
```
|
||||
acme.sh --renew -d aa.com --force
|
||||
**Examples:**
|
||||
|
||||
#### Single domain with ECDSA P-384 certificate
|
||||
|
||||
```bash
|
||||
acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-384
|
||||
```
|
||||
|
||||
or, for ECC cert:
|
||||
```
|
||||
acme.sh --renew -d aa.com --force --ecc
|
||||
#### SAN multi domain with RSA4096 certificate
|
||||
|
||||
```bash
|
||||
acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength 4096
|
||||
```
|
||||
|
||||
# 11. How to upgrade `acme.sh`
|
||||
acme.sh is in developing, it's strongly recommended to use the latest code.
|
||||
---
|
||||
|
||||
You can update acme.sh to the latest code:
|
||||
### 1️⃣2️⃣ Issue Wildcard Certificates
|
||||
|
||||
It's simple! Just give a wildcard domain as the `-d` parameter:
|
||||
|
||||
```sh
|
||||
acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf
|
||||
```
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
### 1️⃣3️⃣ How to Renew Certificates
|
||||
|
||||
> 🔄 No need to renew manually! All certs will be renewed automatically every **30** days, **or earlier when the CA's ARI says so** (see below).
|
||||
|
||||
However, you can force a renewal:
|
||||
|
||||
```sh
|
||||
acme.sh --renew -d example.com --force
|
||||
```
|
||||
|
||||
**For ECC cert:**
|
||||
|
||||
```sh
|
||||
acme.sh --renew -d example.com --force --ecc
|
||||
```
|
||||
|
||||
#### 📡 ACME Renewal Information (ARI) — RFC 9773
|
||||
|
||||
📖 Wiki: https://github.com/acmesh-official/acme.sh/wiki/ARI
|
||||
|
||||
If the CA exposes a `renewalInfo` endpoint in its ACME directory (Let's Encrypt, ZeroSSL, etc.), `acme.sh` follows [RFC 9773](https://www.rfc-editor.org/rfc/rfc9773.html) automatically — **no flag needed, no opt-in**:
|
||||
|
||||
| What | When | Why |
|
||||
|------|------|-----|
|
||||
| 🔍 **Polls `suggestedWindow`** | Every cron run, before deciding to skip | Lets the CA shift the renewal time forward in case of an incident (key compromise, mass revocation, etc.) |
|
||||
| 🎯 **Picks a random renewal time** inside the window | Right after a successful issuance/renewal | Disperses renewals across the network so all clients don't hit the CA at the same instant |
|
||||
| 🔗 **Sends `replaces=<certID>`** in `newOrder` | On renewal | Lets the CA correlate the new order with the certificate it supersedes (RFC 9773 §5) |
|
||||
| ↩️ **Retries without `replaces`** | If the CA rejects with `alreadyReplaced` or an ARI validation error | Robust against edge cases (e.g. switching CAs, retired issuers) |
|
||||
|
||||
**Renewal trigger logic:** the cert is renewed if **any one** of the following becomes true:
|
||||
|
||||
1. `--force` is given
|
||||
2. The CA's **ARI `suggestedWindow` has started**
|
||||
3. The cached `Le_NextRenewTime` has passed (default fallback for CAs without ARI)
|
||||
|
||||
You can see the resulting next renewal time (already ARI-picked when applicable) in:
|
||||
|
||||
```sh
|
||||
acme.sh --info -d example.com
|
||||
# Look for: Le_NextRenewTimeStr=...
|
||||
```
|
||||
|
||||
For the live ARI window the CA is currently advertising, run with `--debug 2`:
|
||||
|
||||
```sh
|
||||
acme.sh --renew -d example.com --debug 2 2>&1 | grep -i 'ARI suggestedWindow'
|
||||
```
|
||||
|
||||
> 💡 If your CA does not advertise `renewalInfo`, `acme.sh` falls back to the classic 30-day rule — no behavior change.
|
||||
|
||||
---
|
||||
|
||||
### 1️⃣4️⃣ How to Stop Certificate Renewal
|
||||
|
||||
To stop renewal of a cert, you can execute the following to remove the cert from the renewal list:
|
||||
|
||||
```sh
|
||||
acme.sh --remove -d example.com [--ecc]
|
||||
```
|
||||
|
||||
The cert/key file is not removed from the disk.
|
||||
|
||||
> 💡 You can remove the respective directory (e.g. `~/.acme.sh/example.com`) manually.
|
||||
|
||||
---
|
||||
|
||||
### 1️⃣5️⃣ How to Upgrade acme.sh
|
||||
|
||||
> 🚀 acme.sh is in constant development — it's strongly recommended to use the latest code.
|
||||
|
||||
**Update to latest:**
|
||||
|
||||
```sh
|
||||
acme.sh --upgrade
|
||||
```
|
||||
|
||||
# 12. Issue a cert from existing CSR
|
||||
**Enable auto upgrade:**
|
||||
|
||||
https://github.com/Neilpang/acme.sh/wiki/Issue-a-cert-from-existing-CSR
|
||||
```sh
|
||||
acme.sh --upgrade --auto-upgrade
|
||||
```
|
||||
|
||||
**Disable auto upgrade:**
|
||||
|
||||
# Under the Hood
|
||||
```sh
|
||||
acme.sh --upgrade --auto-upgrade 0
|
||||
```
|
||||
|
||||
Speak ACME language using shell, directly to "Let's Encrypt".
|
||||
---
|
||||
|
||||
TODO:
|
||||
### 1️⃣6️⃣ Issue a Certificate from an Existing CSR
|
||||
|
||||
# Acknowledgment
|
||||
1. Acme-tiny: https://github.com/diafygi/acme-tiny
|
||||
2. ACME protocol: https://github.com/ietf-wg-acme/acme
|
||||
3. Certbot: https://github.com/certbot/certbot
|
||||
📚 https://github.com/acmesh-official/acme.sh/wiki/Issue-a-cert-from-existing-CSR
|
||||
|
||||
# License & Other
|
||||
---
|
||||
|
||||
License is GPLv3
|
||||
### 1️⃣7️⃣ Send Notifications in Cronjob
|
||||
|
||||
Please Star and Fork me.
|
||||
📚 https://github.com/acmesh-official/acme.sh/wiki/notify
|
||||
|
||||
[Issues](https://github.com/Neilpang/acme.sh/issues) and [pull requests](https://github.com/Neilpang/acme.sh/pulls) are welcomed.
|
||||
---
|
||||
|
||||
### 1️⃣8️⃣ Under the Hood
|
||||
|
||||
# Donate
|
||||
1. PayPal: donate@acme.sh
|
||||
> 🔧 Speak ACME language using shell, directly to "Let's Encrypt".
|
||||
|
||||
[Donate List](https://github.com/Neilpang/acme.sh/wiki/Donate-list)
|
||||
---
|
||||
|
||||
### 1️⃣9️⃣ Acknowledgments
|
||||
|
||||
| Project | Link |
|
||||
|---------|------|
|
||||
| 🙏 Acme-tiny | https://github.com/diafygi/acme-tiny |
|
||||
| 📜 ACME protocol | https://github.com/ietf-wg-acme/acme |
|
||||
|
||||
---
|
||||
|
||||
## 👥 Contributors
|
||||
|
||||
### 💻 Code Contributors
|
||||
|
||||
This project exists thanks to all the people who contribute.
|
||||
|
||||
<a href="https://github.com/acmesh-official/acme.sh/graphs/contributors"><img src="https://opencollective.com/acmesh/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
If you want to become a contributor make sure to read [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||
|
||||
### 💰 Financial Contributors
|
||||
|
||||
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/acmesh/contribute)]
|
||||
|
||||
#### 👤 Individuals
|
||||
|
||||
<a href="https://opencollective.com/acmesh"><img src="https://opencollective.com/acmesh/individuals.svg?width=890"></a>
|
||||
|
||||
#### 🏢 Organizations
|
||||
|
||||
Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/acmesh/contribute)]
|
||||
|
||||
<a href="https://opencollective.com/acmesh/organization/0/website"><img src="https://opencollective.com/acmesh/organization/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/acmesh/organization/1/website"><img src="https://opencollective.com/acmesh/organization/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/acmesh/organization/2/website"><img src="https://opencollective.com/acmesh/organization/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/acmesh/organization/3/website"><img src="https://opencollective.com/acmesh/organization/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/acmesh/organization/4/website"><img src="https://opencollective.com/acmesh/organization/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/acmesh/organization/5/website"><img src="https://opencollective.com/acmesh/organization/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/acmesh/organization/6/website"><img src="https://opencollective.com/acmesh/organization/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/acmesh/organization/7/website"><img src="https://opencollective.com/acmesh/organization/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/acmesh/organization/8/website"><img src="https://opencollective.com/acmesh/organization/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/acmesh/organization/9/website"><img src="https://opencollective.com/acmesh/organization/9/avatar.svg"></a>
|
||||
|
||||
---
|
||||
|
||||
### 2️⃣0️⃣ License & Others
|
||||
|
||||
📄 **License:** GPLv3
|
||||
|
||||
⭐ Please **Star** and **Fork** this project!
|
||||
|
||||
🐛 [Issues](https://github.com/acmesh-official/acme.sh/issues) and 🔀 [Pull Requests](https://github.com/acmesh-official/acme.sh/pulls) are welcome.
|
||||
|
||||
---
|
||||
|
||||
### 2️⃣1️⃣ Donate
|
||||
|
||||
> 💝 Your donation makes **acme.sh** better!
|
||||
|
||||
| Method | Link |
|
||||
|--------|------|
|
||||
| PayPal / Alipay(支付宝) / Wechat(微信) | [https://donate.acme.sh/](https://donate.acme.sh/) |
|
||||
|
||||
📜 [Donate List](https://github.com/acmesh-official/acme.sh/wiki/Donate-list)
|
||||
|
||||
---
|
||||
|
||||
### 2️⃣2️⃣ About This Repository
|
||||
|
||||
> [!NOTE]
|
||||
> This repository is officially maintained by <strong>ZeroSSL</strong> as part of our commitment to providing secure and reliable SSL/TLS solutions. We welcome contributions and feedback from the community!
|
||||
> For more information about our services, including free and paid SSL/TLS certificates, visit https://zerossl.com.
|
||||
>
|
||||
> All donations made through this repository go directly to the original independent maintainer (Neil Pang), not to ZeroSSL.
|
||||
<p align="center">
|
||||
<a href="https://zerossl.com">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://zerossl.com/assets/images/zerossl_logo_white.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://zerossl.com/assets/images/zerossl_logo.svg">
|
||||
<img src="https://zerossl.com/assets/images/zerossl_logo.svg" alt="ZeroSSL" width="256">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
341
acme.sh.completion
Normal file
341
acme.sh.completion
Normal file
@@ -0,0 +1,341 @@
|
||||
# Bash completion for acme.sh: https://github.com/acmesh-official/acme.sh
|
||||
#
|
||||
# "acme.sh --install" copies this file to the acme.sh home dir and wires
|
||||
# it into acme.sh.env, so the completion is loaded automatically in new
|
||||
# bash sessions after installation.
|
||||
#
|
||||
# To use it without installing acme.sh, source it from ~/.bashrc, or copy
|
||||
# it to /usr/share/bash-completion/completions/acme.sh
|
||||
#
|
||||
# Zsh users can load it with:
|
||||
# autoload -U +X bashcompinit && bashcompinit
|
||||
# . /path/to/acme.sh.completion
|
||||
|
||||
# This file may also be sourced by non-bash shells via acme.sh.env,
|
||||
# so silently do nothing if the "complete" builtin is not available.
|
||||
if ! command -v complete >/dev/null 2>&1; then
|
||||
return 0 2>/dev/null || exit 0
|
||||
fi
|
||||
|
||||
# Add each word of $1 that starts with $cur to COMPREPLY.
|
||||
# The words are read line by line, so that candidates like a wildcard
|
||||
# domain "*.example.com" are never glob-expanded against the cwd.
|
||||
_acme_sh_add_matches() {
|
||||
local _word
|
||||
while read -r _word; do
|
||||
[ -n "$_word" ] || continue
|
||||
case "$_word" in
|
||||
"$cur"*) COMPREPLY=("${COMPREPLY[@]}" "$_word") ;;
|
||||
esac
|
||||
done <<EOF
|
||||
$(printf '%s\n' "$1" | tr ' ' '\n')
|
||||
EOF
|
||||
return 0
|
||||
}
|
||||
|
||||
_acme_sh_files() {
|
||||
local _file
|
||||
while IFS= read -r _file; do
|
||||
[ -n "$_file" ] || continue
|
||||
COMPREPLY=("${COMPREPLY[@]}" "$_file")
|
||||
done <<EOF
|
||||
$(compgen -f -- "$cur")
|
||||
EOF
|
||||
if command -v compopt >/dev/null 2>&1; then
|
||||
compopt -o filenames 2>/dev/null
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
_acme_sh_dirs() {
|
||||
local _dir
|
||||
while IFS= read -r _dir; do
|
||||
[ -n "$_dir" ] || continue
|
||||
COMPREPLY=("${COMPREPLY[@]}" "$_dir")
|
||||
done <<EOF
|
||||
$(compgen -d -- "$cur")
|
||||
EOF
|
||||
if command -v compopt >/dev/null 2>&1; then
|
||||
compopt -o filenames 2>/dev/null
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Complete the domains that already have a cert: every directory in the
|
||||
# config home that contains a "<domain>.conf" file ("_ecc" suffix stripped).
|
||||
_acme_sh_domains() {
|
||||
local _dir _name _domains=""
|
||||
[ -n "${ZSH_VERSION:-}" ] && setopt localoptions nonomatch 2>/dev/null
|
||||
for _dir in "$_acme_conf_home"/*/; do
|
||||
[ -d "$_dir" ] || continue
|
||||
_name="${_dir%/}"
|
||||
_name="${_name##*/}"
|
||||
_name="${_name%_ecc}"
|
||||
if [ -f "${_dir}${_name}.conf" ]; then
|
||||
case " $_domains " in
|
||||
*" $_name "*) ;;
|
||||
*) _domains="$_domains $_name" ;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
_acme_sh_add_matches "$_domains"
|
||||
}
|
||||
|
||||
# Complete hook names from a subfolder of the acme.sh home dir.
|
||||
# $1: subfolder (dnsapi/deploy/notify), $2: file name prefix or empty.
|
||||
_acme_sh_hooks() {
|
||||
local _file _hooks=""
|
||||
[ -n "${ZSH_VERSION:-}" ] && setopt localoptions nonomatch 2>/dev/null
|
||||
for _file in "$_acme_home/$1/$2"*.sh; do
|
||||
[ -f "$_file" ] || continue
|
||||
_file="${_file##*/}"
|
||||
_hooks="$_hooks ${_file%.sh}"
|
||||
done
|
||||
_acme_sh_add_matches "$_hooks"
|
||||
}
|
||||
|
||||
_acme_sh_completion() {
|
||||
local cur prev _acme_home _acme_conf_home
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev=""
|
||||
if [ "$COMP_CWORD" -gt 0 ]; then
|
||||
prev="${COMP_WORDS[COMP_CWORD - 1]}"
|
||||
fi
|
||||
_acme_home="${LE_WORKING_DIR:-$HOME/.acme.sh}"
|
||||
_acme_conf_home="${LE_CONFIG_HOME:-$_acme_home}"
|
||||
|
||||
# The first argument is the command.
|
||||
if [ "$COMP_CWORD" -eq 1 ]; then
|
||||
_acme_sh_add_matches "
|
||||
--help
|
||||
--version
|
||||
--install
|
||||
--install-online
|
||||
--uninstall
|
||||
--upgrade
|
||||
--issue
|
||||
--deploy
|
||||
--sign-csr
|
||||
--show-csr
|
||||
--install-cert
|
||||
--renew
|
||||
--renew-all
|
||||
--revoke
|
||||
--remove
|
||||
--list
|
||||
--list-profiles
|
||||
--info
|
||||
--to-pkcs12
|
||||
--to-pkcs8
|
||||
--create-account-key
|
||||
--create-domain-key
|
||||
--create-csr
|
||||
--deactivate
|
||||
--update-account
|
||||
--register-account
|
||||
--deactivate-account
|
||||
--make-dns-persist-value
|
||||
--install-cronjob
|
||||
--uninstall-cronjob
|
||||
--cron
|
||||
--set-notify
|
||||
--set-default-ca
|
||||
--set-default-chain
|
||||
"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Complete the value of the previous option.
|
||||
case "$prev" in
|
||||
-d | --domain | --challenge-alias | --domain-alias)
|
||||
_acme_sh_domains
|
||||
return 0
|
||||
;;
|
||||
--dns)
|
||||
# The dns hook argument is optional, keep completing options if the
|
||||
# current word already looks like one.
|
||||
case "$cur" in
|
||||
-*) ;;
|
||||
*)
|
||||
_acme_sh_hooks "dnsapi" "dns_"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--deploy-hook)
|
||||
_acme_sh_hooks "deploy" ""
|
||||
return 0
|
||||
;;
|
||||
--notify-hook)
|
||||
_acme_sh_hooks "notify" ""
|
||||
return 0
|
||||
;;
|
||||
--server)
|
||||
_acme_sh_add_matches "letsencrypt letsencrypt_test zerossl sslcom google google_test actalis"
|
||||
return 0
|
||||
;;
|
||||
-k | --keylength | -ak | --accountkeylength)
|
||||
_acme_sh_add_matches "2048 3072 4096 8192 ec-256 ec-384 ec-521"
|
||||
return 0
|
||||
;;
|
||||
--debug)
|
||||
# Optional argument.
|
||||
case "$cur" in
|
||||
-*) ;;
|
||||
*)
|
||||
_acme_sh_add_matches "0 1 2 3"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--log)
|
||||
# Optional argument.
|
||||
case "$cur" in
|
||||
-*) ;;
|
||||
*)
|
||||
_acme_sh_files
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--nginx)
|
||||
# Optional argument.
|
||||
case "$cur" in
|
||||
-*) ;;
|
||||
*)
|
||||
_acme_sh_files
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--auto-upgrade | --always-force-new-domain-key)
|
||||
# Optional argument.
|
||||
case "$cur" in
|
||||
-*) ;;
|
||||
*)
|
||||
_acme_sh_add_matches "0 1"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
--log-level)
|
||||
_acme_sh_add_matches "1 2"
|
||||
return 0
|
||||
;;
|
||||
--syslog)
|
||||
_acme_sh_add_matches "0 3 6 7"
|
||||
return 0
|
||||
;;
|
||||
--notify-level)
|
||||
_acme_sh_add_matches "0 1 2 3"
|
||||
return 0
|
||||
;;
|
||||
--notify-mode)
|
||||
_acme_sh_add_matches "0 1"
|
||||
return 0
|
||||
;;
|
||||
--revoke-reason)
|
||||
_acme_sh_add_matches "0 1 2 3 4 5 6 7 8 9 10"
|
||||
return 0
|
||||
;;
|
||||
--cert-file | --key-file | --ca-file | --fullchain-file | --csr | --accountconf | --accountkey | --ca-bundle | --openssl-bin)
|
||||
_acme_sh_files
|
||||
return 0
|
||||
;;
|
||||
-w | --webroot | --home | --cert-home | --config-home | --ca-path)
|
||||
_acme_sh_dirs
|
||||
return 0
|
||||
;;
|
||||
-m | --email | --password | --useragent | --days | --valid-from | --valid-to | --httpport | --tlsport | --local-address | --dnssleep | --pre-hook | --post-hook | --renew-hook | --reloadcmd | --extended-key-usage | -b | --branch | --notify-source | --eab-kid | --eab-hmac-key | --preferred-chain | --cert-profile | --certificate-profile | --dns-persist-ca-name | --dns-persist-days)
|
||||
# These options take a free-form value, offer nothing.
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# Complete the parameters.
|
||||
_acme_sh_add_matches "
|
||||
--accountconf
|
||||
--accountkey
|
||||
--accountkeylength
|
||||
--alpn
|
||||
--always-force-new-domain-key
|
||||
--apache
|
||||
--auto-upgrade
|
||||
--branch
|
||||
--ca-bundle
|
||||
--ca-file
|
||||
--ca-path
|
||||
--cert-file
|
||||
--cert-home
|
||||
--cert-profile
|
||||
--challenge-alias
|
||||
--config-home
|
||||
--csr
|
||||
--days
|
||||
--debug
|
||||
--deploy-hook
|
||||
--dns
|
||||
--dns-persist
|
||||
--dns-persist-ca-name
|
||||
--dns-persist-days
|
||||
--dns-persist-wildcard
|
||||
--dnssleep
|
||||
--domain
|
||||
--domain-alias
|
||||
--eab-hmac-key
|
||||
--eab-kid
|
||||
--ecc
|
||||
--email
|
||||
--extended-key-usage
|
||||
--force
|
||||
--force-color
|
||||
--fullchain-file
|
||||
--home
|
||||
--httpport
|
||||
--insecure
|
||||
--key-file
|
||||
--keylength
|
||||
--listen-v4
|
||||
--listen-v6
|
||||
--listraw
|
||||
--local-address
|
||||
--log
|
||||
--log-level
|
||||
--nginx
|
||||
--no-color
|
||||
--no-cron
|
||||
--no-profile
|
||||
--notify-hook
|
||||
--notify-level
|
||||
--notify-mode
|
||||
--notify-source
|
||||
--ocsp-must-staple
|
||||
--openssl-bin
|
||||
--output-insecure
|
||||
--password
|
||||
--post-hook
|
||||
--pre-hook
|
||||
--preferred-chain
|
||||
--reloadcmd
|
||||
--renew-hook
|
||||
--revoke-reason
|
||||
--server
|
||||
--staging
|
||||
--standalone
|
||||
--stateless
|
||||
--stop-renew-on-error
|
||||
--syslog
|
||||
--tlsport
|
||||
--treat-skip-as-success
|
||||
--use-wget
|
||||
--useragent
|
||||
--valid-from
|
||||
--valid-to
|
||||
--webroot
|
||||
--yes-I-know-dns-manual-mode-enough-go-ahead-please
|
||||
"
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -F _acme_sh_completion acme.sh
|
||||
6
deploy/README.md
Normal file
6
deploy/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# Using deploy api
|
||||
|
||||
deploy hook usage:
|
||||
|
||||
https://github.com/acmesh-official/acme.sh/wiki/deployhooks
|
||||
|
||||
88
deploy/ali_cdn.sh
Normal file
88
deploy/ali_cdn.sh
Normal file
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034,SC2154
|
||||
|
||||
# Script to create certificate to Alibaba Cloud CDN
|
||||
#
|
||||
# Docs: https://github.com/acmesh-official/acme.sh/wiki/deployhooks#33-deploy-your-certificate-to-cdn-or-dcdn-of-alibaba-cloud-aliyun
|
||||
#
|
||||
# This deployment required following variables
|
||||
# export Ali_Key="ALIACCESSKEY"
|
||||
# export Ali_Secret="ALISECRETKEY"
|
||||
# The credentials are shared with all the Alibaba Cloud deploy hooks and dnsapi
|
||||
#
|
||||
# To specify the CDN domain that is different from the certificate CN, usually used for multi-domain or wildcard certificates
|
||||
# export DEPLOY_ALI_CDN_DOMAIN="cdn.example.com"
|
||||
# If you have multiple CDN domains using the same certificate, just
|
||||
# export DEPLOY_ALI_CDN_DOMAIN="cdn1.example.com cdn2.example.com"
|
||||
#
|
||||
# For DCDN, see ali_dcdn deploy hook
|
||||
|
||||
Ali_CDN_API="https://cdn.aliyuncs.com/"
|
||||
|
||||
ali_cdn_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
# Load dnsapi/dns_ali.sh to reduce the duplicated codes
|
||||
# https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276
|
||||
dnsapi_ali="$(_findHook "$_cdomain" "$_SUB_FOLDER_DNSAPI" dns_ali)"
|
||||
# shellcheck source=/dev/null
|
||||
if ! . "$dnsapi_ali"; then
|
||||
_err "Error loading file $dnsapi_ali. Please check your API file and try again."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_prepare_ali_credentials || return 1
|
||||
|
||||
_getdeployconf DEPLOY_ALI_CDN_DOMAIN
|
||||
if [ "$DEPLOY_ALI_CDN_DOMAIN" ]; then
|
||||
_savedeployconf DEPLOY_ALI_CDN_DOMAIN "$DEPLOY_ALI_CDN_DOMAIN"
|
||||
else
|
||||
DEPLOY_ALI_CDN_DOMAIN="$_cdomain"
|
||||
fi
|
||||
|
||||
# read cert and key files and urlencode both
|
||||
_cert=$(_url_encode upper-hex <"$_cfullchain")
|
||||
_key=$(_url_encode upper-hex <"$_ckey")
|
||||
|
||||
_debug2 _cert "$_cert"
|
||||
_debug2 _key "$_key"
|
||||
|
||||
## update domain ssl config
|
||||
for domain in $DEPLOY_ALI_CDN_DOMAIN; do
|
||||
_set_cdn_domain_ssl_certificate_query "$domain" "$_cert" "$_key"
|
||||
if _ali_rest "Set CDN domain SSL certificate for $domain" "" POST; then
|
||||
_info "Domain $domain certificate has been deployed successfully"
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# domain pub pri
|
||||
_set_cdn_domain_ssl_certificate_query() {
|
||||
endpoint=$Ali_CDN_API
|
||||
query=''
|
||||
query=$query'AccessKeyId='$Ali_Key
|
||||
query=$query'&Action=SetCdnDomainSSLCertificate'
|
||||
query=$query'&CertType=upload'
|
||||
query=$query'&DomainName='$1
|
||||
query=$query'&Format=json'
|
||||
query=$query'&SSLPri='$3
|
||||
query=$query'&SSLProtocol=on'
|
||||
query=$query'&SSLPub='$2
|
||||
query=$query'&SignatureMethod=HMAC-SHA1'
|
||||
query=$query"&SignatureNonce=$(_ali_nonce)"
|
||||
query=$query'&SignatureVersion=1.0'
|
||||
query=$query'&Timestamp='$(_ali_timestamp)
|
||||
query=$query'&Version=2018-05-10'
|
||||
}
|
||||
88
deploy/ali_dcdn.sh
Normal file
88
deploy/ali_dcdn.sh
Normal file
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034,SC2154
|
||||
|
||||
# Script to create certificate to Alibaba Cloud DCDN
|
||||
#
|
||||
# Docs: https://github.com/acmesh-official/acme.sh/wiki/deployhooks#33-deploy-your-certificate-to-cdn-or-dcdn-of-alibaba-cloud-aliyun
|
||||
#
|
||||
# This deployment required following variables
|
||||
# export Ali_Key="ALIACCESSKEY"
|
||||
# export Ali_Secret="ALISECRETKEY"
|
||||
# The credentials are shared with all the Alibaba Cloud deploy hooks and dnsapi
|
||||
#
|
||||
# To specify the DCDN domain that is different from the certificate CN, usually used for multi-domain or wildcard certificates
|
||||
# export DEPLOY_ALI_DCDN_DOMAIN="dcdn.example.com"
|
||||
# If you have multiple CDN domains using the same certificate, just
|
||||
# export DEPLOY_ALI_DCDN_DOMAIN="dcdn1.example.com dcdn2.example.com"
|
||||
#
|
||||
# For regular CDN, see ali_cdn deploy hook
|
||||
|
||||
Ali_DCDN_API="https://dcdn.aliyuncs.com/"
|
||||
|
||||
ali_dcdn_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
# Load dnsapi/dns_ali.sh to reduce the duplicated codes
|
||||
# https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276
|
||||
dnsapi_ali="$(_findHook "$_cdomain" "$_SUB_FOLDER_DNSAPI" dns_ali)"
|
||||
# shellcheck source=/dev/null
|
||||
if ! . "$dnsapi_ali"; then
|
||||
_err "Error loading file $dnsapi_ali. Please check your API file and try again."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_prepare_ali_credentials || return 1
|
||||
|
||||
_getdeployconf DEPLOY_ALI_DCDN_DOMAIN
|
||||
if [ "$DEPLOY_ALI_DCDN_DOMAIN" ]; then
|
||||
_savedeployconf DEPLOY_ALI_DCDN_DOMAIN "$DEPLOY_ALI_DCDN_DOMAIN"
|
||||
else
|
||||
DEPLOY_ALI_DCDN_DOMAIN="$_cdomain"
|
||||
fi
|
||||
|
||||
# read cert and key files and urlencode both
|
||||
_cert=$(_url_encode upper-hex <"$_cfullchain")
|
||||
_key=$(_url_encode upper-hex <"$_ckey")
|
||||
|
||||
_debug2 _cert "$_cert"
|
||||
_debug2 _key "$_key"
|
||||
|
||||
## update domain ssl config
|
||||
for domain in $DEPLOY_ALI_DCDN_DOMAIN; do
|
||||
_set_dcdn_domain_ssl_certificate_query "$domain" "$_cert" "$_key"
|
||||
if _ali_rest "Set DCDN domain SSL certificate for $domain" "" POST; then
|
||||
_info "Domain $domain certificate has been deployed successfully"
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# domain pub pri
|
||||
_set_dcdn_domain_ssl_certificate_query() {
|
||||
endpoint=$Ali_DCDN_API
|
||||
query=''
|
||||
query=$query'AccessKeyId='$Ali_Key
|
||||
query=$query'&Action=SetDcdnDomainSSLCertificate'
|
||||
query=$query'&CertType=upload'
|
||||
query=$query'&DomainName='$1
|
||||
query=$query'&Format=json'
|
||||
query=$query'&SSLPri='$3
|
||||
query=$query'&SSLProtocol=on'
|
||||
query=$query'&SSLPub='$2
|
||||
query=$query'&SignatureMethod=HMAC-SHA1'
|
||||
query=$query"&SignatureNonce=$(_ali_nonce)"
|
||||
query=$query'&SignatureVersion=1.0'
|
||||
query=$query'&Timestamp='$(_ali_timestamp)
|
||||
query=$query'&Version=2018-01-15'
|
||||
}
|
||||
26
deploy/apache.sh
Normal file
26
deploy/apache.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#Here is a script to deploy cert to apache server.
|
||||
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
apache_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
_err "Deploy cert to apache server, Not implemented yet"
|
||||
return 1
|
||||
|
||||
}
|
||||
222
deploy/baidu_cdn.sh
Normal file
222
deploy/baidu_cdn.sh
Normal file
@@ -0,0 +1,222 @@
|
||||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034,SC2154
|
||||
|
||||
# Deploy hook: Baidu Cloud CDN
|
||||
#
|
||||
# Code generated by GitHub Copilot with Claude Sonnet 4.6 and OpenAI Codex with GPT-5.6 Sol
|
||||
#
|
||||
# API Doc: https://cloud.baidu.com/doc/CDN/s/Zkna2r57w
|
||||
#
|
||||
# Uses the same credential variables as dnsapi/dns_baidu.sh:
|
||||
# export Baidu_AK="your-access-key-id"
|
||||
# export Baidu_SK="your-secret-access-key"
|
||||
#
|
||||
# To deploy to a CDN domain different from the certificate CN
|
||||
# (e.g. wildcard or multi-domain certs):
|
||||
# export DEPLOY_BAIDU_CDN_DOMAIN="cdn.example.com"
|
||||
#
|
||||
# Multiple CDN domains sharing the same certificate:
|
||||
# export DEPLOY_BAIDU_CDN_DOMAIN="cdn1.example.com cdn2.example.com"
|
||||
|
||||
BAIDU_CDN_HOST="cdn.baidubce.com"
|
||||
_BAIDU_CDN_BCE_AUTH_RESULT=""
|
||||
|
||||
baidu_cdn_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
if ! _baidu_cdn_load_credentials; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
_getdeployconf DEPLOY_BAIDU_CDN_DOMAIN
|
||||
if [ "$DEPLOY_BAIDU_CDN_DOMAIN" ]; then
|
||||
_savedeployconf DEPLOY_BAIDU_CDN_DOMAIN "$DEPLOY_BAIDU_CDN_DOMAIN"
|
||||
else
|
||||
DEPLOY_BAIDU_CDN_DOMAIN="$_cdomain"
|
||||
fi
|
||||
|
||||
# Build JSON "domains" array from space-separated domain list
|
||||
_domains_json=""
|
||||
for _d in $DEPLOY_BAIDU_CDN_DOMAIN; do
|
||||
_d_e="$(_baidu_cdn_json_escape "$_d")"
|
||||
if [ -z "$_domains_json" ]; then
|
||||
_domains_json="\"${_d_e}\""
|
||||
else
|
||||
_domains_json="${_domains_json},\"${_d_e}\""
|
||||
fi
|
||||
done
|
||||
|
||||
# Build a valid cert name: must start with a letter, allow [A-Za-z0-9-/.], max 65 chars
|
||||
_cert_name="$(printf "%s" "$_cdomain" | sed 's/\*\./wildcard./g;s/[^A-Za-z0-9./]/-/g' | cut -c 1-65)"
|
||||
case "$_cert_name" in
|
||||
[A-Za-z]*) ;;
|
||||
*) _cert_name="c${_cert_name}" ;;
|
||||
esac
|
||||
|
||||
# PEM content is already Base64 inside the -----BEGIN/END----- wrappers.
|
||||
# The API expects the raw PEM as a JSON string, so newlines must be escaped as \n.
|
||||
_cert_pem="$(sed 's/$/\\n/' "$_cfullchain" | tr -d '\n')"
|
||||
_key_pem="$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')"
|
||||
|
||||
_debug2 _cert_name "$_cert_name"
|
||||
_debug2 _domains_json "[$_domains_json]"
|
||||
|
||||
# Build JSON payload
|
||||
_payload="{\"domains\":[${_domains_json}],\"certificate\":{\"certName\":\"${_cert_name}\",\"certServerData\":\"${_cert_pem}\",\"certPrivateData\":\"${_key_pem}\"}}"
|
||||
|
||||
# Generate BCE v1 authorization header (query string included in canonical request)
|
||||
_cdn_path="/v2/domain/certificate"
|
||||
_cdn_query="action=put"
|
||||
_ts="$(_utc_date | sed 's/ /T/')Z"
|
||||
_content_type="application/json; charset=utf-8"
|
||||
_payload_hash="$(printf "%s" "$_payload" | _digest sha256 hex)"
|
||||
|
||||
if ! _baidu_cdn_bce_auth "POST" "$_cdn_path" "$_cdn_query" "$BAIDU_CDN_HOST" "$_ts" "3600" "$_content_type" "$_payload_hash"; then
|
||||
_err "Failed to sign request"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_H1="Authorization: $_BAIDU_CDN_BCE_AUTH_RESULT"
|
||||
_H2="x-bce-date: $_ts"
|
||||
_H3="x-bce-content-sha256: $_payload_hash"
|
||||
_H4="Host: $BAIDU_CDN_HOST"
|
||||
_H5=""
|
||||
|
||||
_url="https://${BAIDU_CDN_HOST}${_cdn_path}?${_cdn_query}"
|
||||
response="$(_post "$_payload" "$_url" "" "POST" "$_content_type")"
|
||||
if [ "$?" != "0" ]; then
|
||||
_err "Failed to call Baidu Cloud CDN API"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug2 response "$response"
|
||||
|
||||
if _contains "$response" "\"certId\""; then
|
||||
_info "Certificate deployed to Baidu Cloud CDN for: $DEPLOY_BAIDU_CDN_DOMAIN"
|
||||
return 0
|
||||
fi
|
||||
|
||||
_err "Failed to deploy certificate to Baidu Cloud CDN: $response"
|
||||
return 1
|
||||
}
|
||||
|
||||
# BCE v1 signing with canonical query string support.
|
||||
# The CDN endpoint uses ?action=put so it must be included in the canonical request.
|
||||
_baidu_cdn_bce_auth() {
|
||||
_method="$1"
|
||||
_uri="$2"
|
||||
_query="$3"
|
||||
_host="$4"
|
||||
_ts="$5"
|
||||
_expire="$6"
|
||||
_ct="$7"
|
||||
_payload_hash="$8"
|
||||
|
||||
_BAIDU_CDN_BCE_AUTH_RESULT=""
|
||||
|
||||
_auth_prefix="bce-auth-v1/${Baidu_AK}/${_ts}/${_expire}"
|
||||
_signed_headers="content-type;host;x-bce-content-sha256;x-bce-date"
|
||||
_canonical_uri="$(_baidu_cdn_bce_encode_path "$_uri")"
|
||||
|
||||
_host_e="$(printf "%s" "$_host" | _url_encode upper-hex)"
|
||||
_date_e="$(printf "%s" "$_ts" | _url_encode upper-hex)"
|
||||
_ct_e="$(printf "%s" "$_ct" | _url_encode upper-hex)"
|
||||
_hash_e="$(printf "%s" "$_payload_hash" | _url_encode upper-hex)"
|
||||
|
||||
_canonical_headers="content-type:${_ct_e}
|
||||
host:${_host_e}
|
||||
x-bce-content-sha256:${_hash_e}
|
||||
x-bce-date:${_date_e}"
|
||||
|
||||
_canonical_request="${_method}
|
||||
${_canonical_uri}
|
||||
${_query}
|
||||
${_canonical_headers}"
|
||||
|
||||
_sk_hex="$(printf "%s" "$Baidu_SK" | _hex_dump | tr -d " ")"
|
||||
_signing_key="$(_baidu_cdn_hmac_sha256_hexkey "$_sk_hex" "$_auth_prefix")"
|
||||
_signing_key_hex="$(printf "%s" "$_signing_key" | _hex_dump | tr -d " ")"
|
||||
_signature="$(_baidu_cdn_hmac_sha256_hexkey "$_signing_key_hex" "$_canonical_request")"
|
||||
|
||||
_BAIDU_CDN_BCE_AUTH_RESULT="${_auth_prefix}/${_signed_headers}/${_signature}"
|
||||
}
|
||||
|
||||
_baidu_cdn_load_credentials() {
|
||||
Baidu_AK="${Baidu_AK:-$(_readaccountconf_mutable Baidu_AK)}"
|
||||
Baidu_SK="${Baidu_SK:-$(_readaccountconf_mutable Baidu_SK)}"
|
||||
|
||||
Baidu_AK="$(_baidu_cdn_trim_ws "$Baidu_AK")"
|
||||
Baidu_SK="$(_baidu_cdn_trim_ws "$Baidu_SK")"
|
||||
|
||||
if [ -z "$Baidu_AK" ] || [ -z "$Baidu_SK" ]; then
|
||||
_err "Baidu_AK and Baidu_SK are required"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_saveaccountconf_mutable Baidu_AK "$Baidu_AK"
|
||||
_saveaccountconf_mutable Baidu_SK "$Baidu_SK"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
_baidu_cdn_bce_encode_path() {
|
||||
_p="$1"
|
||||
_out=""
|
||||
if [ "${_p#"/"}" != "$_p" ]; then
|
||||
_out="/"
|
||||
fi
|
||||
|
||||
_rest="${_p#/}"
|
||||
while [ -n "$_rest" ]; do
|
||||
_seg="${_rest%%/*}"
|
||||
if [ "$_seg" ]; then
|
||||
if [ -z "$_out" ] || [ "$_out" = "/" ]; then
|
||||
_out="${_out}$(printf "%s" "$_seg" | _url_encode upper-hex)"
|
||||
else
|
||||
_out="${_out}/$(printf "%s" "$_seg" | _url_encode upper-hex)"
|
||||
fi
|
||||
fi
|
||||
if [ "${_rest#*/}" = "$_rest" ]; then
|
||||
break
|
||||
fi
|
||||
_rest="${_rest#*/}"
|
||||
done
|
||||
|
||||
if [ -z "$_out" ]; then
|
||||
_out="/"
|
||||
fi
|
||||
printf "%s" "$_out"
|
||||
}
|
||||
|
||||
_baidu_cdn_trim_ws() {
|
||||
printf "%s" "$1" | tr '\r\n\t' ' ' | tr -s ' ' | sed 's/^ *//;s/ *$//'
|
||||
}
|
||||
|
||||
_baidu_cdn_json_escape() {
|
||||
_s="$1"
|
||||
_s="$(printf "%s" "$_s" | tr -d '\r\n')"
|
||||
printf "%s" "$_s" |
|
||||
sed 's/\\/\\\\/g; s/ /\\t/g' |
|
||||
_baidu_cdn_json_encode
|
||||
}
|
||||
|
||||
_baidu_cdn_json_encode() {
|
||||
_j_str="$(sed 's/"/\\"/g' | sed "s/\r/\\r/g")"
|
||||
printf "%s" "$_j_str" | _hex_dump | _lower_case | sed 's/0a/5c 6e/g' | tr -d ' ' | _h2b | tr -d "\r\n"
|
||||
}
|
||||
|
||||
_baidu_cdn_hmac_sha256_hexkey() {
|
||||
_key_hex="$1"
|
||||
_msg="$2"
|
||||
printf "%s" "$_msg" | _hmac sha256 "$_key_hex" hex
|
||||
}
|
||||
394
deploy/byteplus_alb.sh
Normal file
394
deploy/byteplus_alb.sh
Normal file
@@ -0,0 +1,394 @@
|
||||
#!/usr/bin/env sh
|
||||
# shellcheck disable=SC2034,SC2154
|
||||
#
|
||||
# acme.sh deploy hook: BytePlus Application Load Balancer (ALB)
|
||||
# https://github.com/acmesh-official/acme.sh/wiki/deployhooks
|
||||
#
|
||||
# Deploys SSL/TLS certificates issued by acme.sh to BytePlus ALB.
|
||||
# Supports automatic renewal with zero-downtime certificate rotation
|
||||
# for certificates that have already been uploaded and have a saved
|
||||
# BytePlus CertificateId.
|
||||
#
|
||||
# ┌─────────────────────────────────────────────────────────────────────┐
|
||||
# │ FIRST TIME (new domain) │
|
||||
# │ 1. acme.sh --issue -d example.com -w /var/www/html/ │
|
||||
# │ 2. Upload/import the certificate to BytePlus ALB manually │
|
||||
# │ 3. Save/configure the existing CertificateId for this hook │
|
||||
# │ 4. Manually assign cert to ALB Listener (one-time only) │
|
||||
# │ │
|
||||
# │ RENEWAL (fully automatic after CertificateId is configured) │
|
||||
# │ acme.sh cron triggers renew → deploy hook runs automatically │
|
||||
# │ → ReplaceCertificate (UpdateMode=new) — single API call │
|
||||
# │ → All attached listeners updated, old cert auto-deleted │
|
||||
# └─────────────────────────────────────────────────────────────────────┘
|
||||
#
|
||||
# Required environment variables:
|
||||
# export BYTEPLUS_ACCESS_KEY="AKAPxxxxxxxxxx"
|
||||
# export BYTEPLUS_SECRET_KEY="your-secret-key"
|
||||
#
|
||||
# Optional environment variables:
|
||||
# export BYTEPLUS_REGION="ap-southeast-3" # default: ap-southeast-3
|
||||
# export BYTEPLUS_HOST="alb.ap-southeast-3.byteplusapi.com" # custom API host
|
||||
# export BYTEPLUS_PROJECT_NAME="live" # default: "default" project
|
||||
# export BYTEPLUS_CERT_NAME="" # default: acme-{domain}-{YYYYMMDD-HHMM}
|
||||
# export BYTEPLUS_CERT_DESCRIPTION="" # default: empty
|
||||
# export BYTEPLUS_DELETE_OLD_CERT="true" # default: true — auto-delete after replace
|
||||
#
|
||||
# API notes:
|
||||
# - All BytePlus ALB APIs use GET with query string parameters
|
||||
# - Request signing: HMAC-SHA256 with signed headers host;x-date
|
||||
# - PublicKey/PrivateKey are URL-encoded (RFC 3986) in query string
|
||||
# - ReplaceCertificate with UpdateMode=new uploads + replaces in 1 call
|
||||
#
|
||||
# Dependencies: curl, openssl, awk (standard on most Linux)
|
||||
#
|
||||
# Docs:
|
||||
# Signing — https://docs.byteplus.com/en/docs/byteplus-platform/reference-how-to-calculate-a-signature
|
||||
# ALB API — https://docs.byteplus.com/en/docs/byteplus-alb
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# Constants
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# SHA-256 hash of empty string (used for GET requests with no body)
|
||||
_BYTEPLUS_EMPTY_HASH="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# Main deploy function — called by acme.sh
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
byteplus_alb_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
# ── 1. Load & validate credentials ──────────────────────────────────────────
|
||||
|
||||
# Preserve environment values before _getdeployconf (which may reset them)
|
||||
_env_project_name="${BYTEPLUS_PROJECT_NAME:-}"
|
||||
_env_delete_old="${BYTEPLUS_DELETE_OLD_CERT:-}"
|
||||
|
||||
_getdeployconf BYTEPLUS_ACCESS_KEY
|
||||
_getdeployconf BYTEPLUS_SECRET_KEY
|
||||
_getdeployconf BYTEPLUS_REGION
|
||||
_getdeployconf BYTEPLUS_HOST
|
||||
_getdeployconf BYTEPLUS_PROJECT_NAME
|
||||
_getdeployconf BYTEPLUS_DELETE_OLD_CERT
|
||||
_getdeployconf BYTEPLUS_CERT_NAME
|
||||
|
||||
# Restore from environment if _getdeployconf cleared them
|
||||
if [ -z "$BYTEPLUS_PROJECT_NAME" ] && [ -n "$_env_project_name" ]; then
|
||||
_debug "Restoring BYTEPLUS_PROJECT_NAME from environment"
|
||||
BYTEPLUS_PROJECT_NAME="$_env_project_name"
|
||||
fi
|
||||
if [ -z "$BYTEPLUS_DELETE_OLD_CERT" ] && [ -n "$_env_delete_old" ]; then
|
||||
BYTEPLUS_DELETE_OLD_CERT="$_env_delete_old"
|
||||
fi
|
||||
|
||||
# Validate required credentials
|
||||
if [ -z "$BYTEPLUS_ACCESS_KEY" ]; then
|
||||
_err "BYTEPLUS_ACCESS_KEY is not set."
|
||||
_err "Please run: export BYTEPLUS_ACCESS_KEY=\"your-access-key\""
|
||||
return 1
|
||||
fi
|
||||
if [ -z "$BYTEPLUS_SECRET_KEY" ]; then
|
||||
_err "BYTEPLUS_SECRET_KEY is not set."
|
||||
_err "Please run: export BYTEPLUS_SECRET_KEY=\"your-secret-key\""
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Save credentials for future runs
|
||||
_savedeployconf BYTEPLUS_ACCESS_KEY "$BYTEPLUS_ACCESS_KEY"
|
||||
_savedeployconf BYTEPLUS_SECRET_KEY "$BYTEPLUS_SECRET_KEY"
|
||||
|
||||
# Region (default: ap-southeast-3)
|
||||
BYTEPLUS_REGION="${BYTEPLUS_REGION:-ap-southeast-3}"
|
||||
_savedeployconf BYTEPLUS_REGION "$BYTEPLUS_REGION"
|
||||
|
||||
# Project name
|
||||
if [ -n "$BYTEPLUS_PROJECT_NAME" ]; then
|
||||
_savedeployconf BYTEPLUS_PROJECT_NAME "$BYTEPLUS_PROJECT_NAME"
|
||||
_info "Using project: $BYTEPLUS_PROJECT_NAME"
|
||||
else
|
||||
_info "WARNING: BYTEPLUS_PROJECT_NAME is not set. Cert will go to 'default' project."
|
||||
fi
|
||||
|
||||
# Delete old cert toggle (default: true)
|
||||
BYTEPLUS_DELETE_OLD_CERT="${BYTEPLUS_DELETE_OLD_CERT:-true}"
|
||||
_savedeployconf BYTEPLUS_DELETE_OLD_CERT "$BYTEPLUS_DELETE_OLD_CERT"
|
||||
|
||||
# API host — custom override or auto-build from region
|
||||
if [ -n "$BYTEPLUS_HOST" ]; then
|
||||
_BYTEPLUS_HOST="$BYTEPLUS_HOST"
|
||||
_savedeployconf BYTEPLUS_HOST "$BYTEPLUS_HOST"
|
||||
else
|
||||
_BYTEPLUS_HOST="alb.${BYTEPLUS_REGION}.byteplusapi.com"
|
||||
fi
|
||||
_info "Using API host: $_BYTEPLUS_HOST"
|
||||
_BYTEPLUS_SERVICE="alb"
|
||||
|
||||
# ── 2. Build certificate name ────────────────────────────────────────────────
|
||||
|
||||
_date_tag=$(date -u +%Y%m%d-%H%M)
|
||||
# Replace wildcard * and dots for a valid cert name
|
||||
_safe_domain=$(echo "$_cdomain" | sed 's/\*\.//g' | sed 's/\./-/g')
|
||||
# Safe identifier version for deployconf keys: map all non [A-Za-z0-9_] to _
|
||||
_conf_key=$(echo "$_cdomain" | sed 's/^\*\.//' | sed 's/[^A-Za-z0-9_]/_/g')
|
||||
|
||||
if [ -z "$BYTEPLUS_CERT_NAME" ]; then
|
||||
BYTEPLUS_CERT_NAME="acme-${_safe_domain}-${_date_tag}"
|
||||
fi
|
||||
|
||||
# Enforce BytePlus naming rules: start with letter, max 128 chars
|
||||
BYTEPLUS_CERT_NAME=$(echo "$BYTEPLUS_CERT_NAME" | sed 's/[^A-Za-z0-9._-]/-/g')
|
||||
case "$BYTEPLUS_CERT_NAME" in
|
||||
[A-Za-z]*) ;;
|
||||
|
||||
*)
|
||||
BYTEPLUS_CERT_NAME="a$BYTEPLUS_CERT_NAME"
|
||||
;;
|
||||
esac
|
||||
BYTEPLUS_CERT_NAME=$(echo "$BYTEPLUS_CERT_NAME" | cut -c1-128)
|
||||
|
||||
_info "Certificate name: $BYTEPLUS_CERT_NAME"
|
||||
|
||||
# ── 3. Read cert and key ─────────────────────────────────────────────────────
|
||||
# BytePlus requires NO blank lines between PEM blocks in the certificate chain
|
||||
|
||||
_public_key=$(_strip_blank_lines <"$_cfullchain" | tr -d '\r')
|
||||
_private_key=$(_strip_blank_lines <"$_ckey" | tr -d '\r')
|
||||
|
||||
if [ -z "$_public_key" ] || [ -z "$_private_key" ]; then
|
||||
_err "Failed to read certificate or key file."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# ── 4. Deploy: first-time upload or renewal replace ─────────────────────────
|
||||
|
||||
_getdeployconf "BYTEPLUS_CERT_ID_${_conf_key}"
|
||||
_old_cert_id=$(eval echo "\$BYTEPLUS_CERT_ID_${_conf_key}")
|
||||
|
||||
if [ -z "$_old_cert_id" ]; then
|
||||
_byteplus_first_time_deploy
|
||||
else
|
||||
_byteplus_renewal_deploy
|
||||
fi
|
||||
|
||||
# Check if deploy step set _new_cert_id
|
||||
if [ -z "$_new_cert_id" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# ── 5. Save new CertificateId for next renewal ───────────────────────────────
|
||||
|
||||
_savedeployconf "BYTEPLUS_CERT_ID_${_conf_key}" "$_new_cert_id"
|
||||
_info "Saved CertificateId '$_new_cert_id' for domain '$_cdomain'."
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# Deploy: First time — UploadCertificate
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
_byteplus_first_time_deploy() {
|
||||
_info "No previous CertificateId found."
|
||||
_err "Refusing to upload certificate material because this hook passes PublicKey/PrivateKey as request parameters."
|
||||
_err "Uploading a private key in the request URL can leak it via logs, proxies, and process listings."
|
||||
_err "Please upload the certificate to BytePlus manually for the initial deployment, set BYTEPLUS_CERT_ID_${_conf_key} to that CertificateId, and rerun."
|
||||
_err "This hook stores CertificateId values per domain using deployconf, so the variable name must include the current domain-specific suffix."
|
||||
_err "This hook must be updated to send PublicKey and PrivateKey in a POST body before automatic first-time upload can be enabled safely."
|
||||
return 1
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# Deploy: Renewal — ReplaceCertificate (UpdateMode=new)
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
_byteplus_renewal_deploy() {
|
||||
_info "Replacing old certificate '$_old_cert_id' (UpdateMode=new)..."
|
||||
_err "Refusing to replace certificate material because this hook passes PublicKey/PrivateKey as request parameters."
|
||||
_err "Uploading a private key in the request URL can leak it via logs, proxies, and process listings."
|
||||
_err "Please replace the certificate in BytePlus manually for renewal until this hook is updated to send PublicKey and PrivateKey in a POST body safely."
|
||||
return 1
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# Delete old certificate (with retry)
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
_byteplus_delete_old_cert() {
|
||||
_del_cert_id="$1"
|
||||
|
||||
_info "Waiting 5s for cert status to settle..."
|
||||
_sleep 5
|
||||
|
||||
_info "Deleting old certificate '$_del_cert_id'..."
|
||||
_del_response=$(_byteplus_alb_api "DeleteCertificate" "CertificateId=${_del_cert_id}")
|
||||
|
||||
if echo "$_del_response" | grep -q '"Error"'; then
|
||||
_info "Delete failed, retrying in 10s..."
|
||||
_sleep 10
|
||||
_del_response=$(_byteplus_alb_api "DeleteCertificate" "CertificateId=${_del_cert_id}")
|
||||
|
||||
if echo "$_del_response" | grep -q '"Error"'; then
|
||||
_info "Warning: Could not delete old certificate '$_del_cert_id'."
|
||||
_info "Error: $(_byteplus_extract_error "$_del_response")"
|
||||
_info "Please remove it manually from BytePlus Console."
|
||||
else
|
||||
_info "Old certificate '$_del_cert_id' deleted (retry succeeded)."
|
||||
fi
|
||||
else
|
||||
_info "Old certificate '$_del_cert_id' deleted."
|
||||
fi
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# JSON response helpers
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# Extract CertificateId from API response JSON
|
||||
_byteplus_extract_cert_id() {
|
||||
echo "$1" | _egrep_o '"CertificateId"\s*:\s*"[^"]*"' | head -1 | _egrep_o '"[^"]*"$' | tr -d '"'
|
||||
}
|
||||
|
||||
# Extract error message from API response JSON
|
||||
_byteplus_extract_error() {
|
||||
_code=$(echo "$1" | _egrep_o '"Code"\s*:\s*"[^"]*"' | head -1 | _egrep_o '"[^"]*"$' | tr -d '"')
|
||||
_msg=$(echo "$1" | _egrep_o '"Message"\s*:\s*"[^"]*"' | head -1 | _egrep_o '"[^"]*"$' | tr -d '"')
|
||||
if [ -n "$_code" ]; then
|
||||
printf '%s — %s' "$_code" "$_msg"
|
||||
else
|
||||
printf '%s' "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# BytePlus ALB API caller
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# Usage: _byteplus_alb_api ACTION [param1=val1] [param2=val2] ...
|
||||
# All parameters sent via GET query string. Signing: HMAC-SHA256, host;x-date.
|
||||
_byteplus_alb_api() {
|
||||
_action="$1"
|
||||
shift
|
||||
|
||||
# Build query string — all params go in URL
|
||||
_query_params="Action=${_action}&Version=2020-04-01"
|
||||
|
||||
for _param in "$@"; do
|
||||
_pname="${_param%%=*}"
|
||||
_pval="${_param#*=}"
|
||||
_query_params="${_query_params}&${_pname}=$(_byteplus_urlencode "$_pval")"
|
||||
done
|
||||
|
||||
# Timestamps
|
||||
_x_date=$(date -u +%Y%m%dT%H%M%SZ)
|
||||
_date_only=$(date -u +%Y%m%d)
|
||||
|
||||
# Sort query params for canonical request
|
||||
_sorted_query=$(echo "$_query_params" | tr '&' '\n' | LC_ALL=C sort | tr '\n' '&' | sed 's/&$//')
|
||||
|
||||
# Canonical headers — only host and x-date
|
||||
_canonical_headers="host:${_BYTEPLUS_HOST}
|
||||
x-date:${_x_date}
|
||||
"
|
||||
_signed_headers="host;x-date"
|
||||
|
||||
# Canonical request
|
||||
_canonical_request="GET
|
||||
/
|
||||
${_sorted_query}
|
||||
${_canonical_headers}
|
||||
${_signed_headers}
|
||||
${_BYTEPLUS_EMPTY_HASH}"
|
||||
|
||||
# Do not log _canonical_request because the query string may contain
|
||||
# URL-encoded certificate or private key material.
|
||||
|
||||
# Hash of canonical request
|
||||
# _digest is provided by acme.sh and works across OpenSSL versions.
|
||||
_cr_hash=$(printf '%s' "$_canonical_request" | _digest sha256 hex)
|
||||
|
||||
# Credential scope
|
||||
_credential_scope="${_date_only}/${BYTEPLUS_REGION}/${_BYTEPLUS_SERVICE}/request"
|
||||
|
||||
# String to sign
|
||||
_string_to_sign="HMAC-SHA256
|
||||
${_x_date}
|
||||
${_credential_scope}
|
||||
${_cr_hash}"
|
||||
|
||||
_debug2 _string_to_sign "$_string_to_sign"
|
||||
|
||||
# Signing key derivation (HMAC chain)
|
||||
# _hmac <algo> <hex-key> reads data from stdin and returns a hex digest.
|
||||
# acme.sh's _hmac abstracts away OpenSSL version differences, so this works
|
||||
# on both modern (-mac HMAC -macopt hexkey:) and older (-hmac) OpenSSL builds.
|
||||
#
|
||||
# The first step seeds the chain from the raw secret key, so we convert it
|
||||
# to hex first with _hex_dump (also an acme.sh built-in).
|
||||
_secret_hex=$(printf '%s' "$BYTEPLUS_SECRET_KEY" | _hex_dump | tr -d ' \n')
|
||||
_k_date=$(printf '%s' "$_date_only" | _hmac sha256 "$_secret_hex" hex)
|
||||
_k_region=$(printf '%s' "$BYTEPLUS_REGION" | _hmac sha256 "$_k_date" hex)
|
||||
_k_service=$(printf '%s' "$_BYTEPLUS_SERVICE" | _hmac sha256 "$_k_region" hex)
|
||||
_k_signing=$(printf '%s' "request" | _hmac sha256 "$_k_service" hex)
|
||||
|
||||
# Final signature
|
||||
_signature=$(printf '%s' "$_string_to_sign" | _hmac sha256 "$_k_signing" hex)
|
||||
|
||||
# Authorization header
|
||||
_auth="HMAC-SHA256 Credential=${BYTEPLUS_ACCESS_KEY}/${_credential_scope}, SignedHeaders=${_signed_headers}, Signature=${_signature}"
|
||||
|
||||
_secure_debug2 _auth "$_auth"
|
||||
|
||||
# Send request parameters in the POST body instead of the URL query string.
|
||||
# This avoids exposing sensitive or large values in debug-logged URLs and
|
||||
# reduces the risk of exceeding URL length limits.
|
||||
_url="https://${_BYTEPLUS_HOST}/"
|
||||
_body="$_sorted_query"
|
||||
|
||||
_saved_H1="${_H1:-}"
|
||||
_saved_H2="${_H2:-}"
|
||||
_saved_H3="${_H3:-}"
|
||||
_saved_H4="${_H4:-}"
|
||||
_saved_H5="${_H5:-}"
|
||||
|
||||
_H1="Authorization: ${_auth}"
|
||||
_H2="X-Date: ${_x_date}"
|
||||
_H3="Host: ${_BYTEPLUS_HOST}"
|
||||
_H4="Content-Type: application/x-www-form-urlencoded"
|
||||
_H5=""
|
||||
|
||||
_response="$(_post "$_body" "$_url" "" "POST")"
|
||||
_request_ret="$?"
|
||||
|
||||
_H1="$_saved_H1"
|
||||
_H2="$_saved_H2"
|
||||
_H3="$_saved_H3"
|
||||
_H4="$_saved_H4"
|
||||
_H5="$_saved_H5"
|
||||
|
||||
if [ "$_request_ret" != "0" ]; then
|
||||
_err "byteplus_alb_api request failed for [$_action]"
|
||||
return 1
|
||||
fi
|
||||
_debug2 "_byteplus_alb_api response [$_action]" "$_response"
|
||||
printf '%s' "$_response"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# URL encode (RFC 3986)
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
_byteplus_urlencode() {
|
||||
printf '%s' "$1" | _url_encode
|
||||
}
|
||||
56
deploy/cachefly.sh
Normal file
56
deploy/cachefly.sh
Normal file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Script to deploy certificate to CacheFly
|
||||
# https://api.cachefly.com/api/2.5/docs#tag/Certificates/paths/~1certificates/post
|
||||
|
||||
# This deployment required following variables
|
||||
# export CACHEFLY_TOKEN="Your CacheFly API Token"
|
||||
|
||||
# returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
CACHEFLY_API_BASE="https://api.cachefly.com/api/2.5"
|
||||
|
||||
cachefly_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
if [ -z "$CACHEFLY_TOKEN" ]; then
|
||||
_err "CACHEFLY_TOKEN is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf CACHEFLY_TOKEN "$CACHEFLY_TOKEN"
|
||||
fi
|
||||
|
||||
_info "Deploying certificate to CacheFly..."
|
||||
|
||||
## upload certificate
|
||||
string_fullchain=$(sed 's/$/\\n/' "$_cfullchain" | tr -d '\n')
|
||||
string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')
|
||||
|
||||
_request_body="{\"certificate\":\"$string_fullchain\",\"certificateKey\":\"$string_key\"}"
|
||||
_debug _request_body "$_request_body"
|
||||
_debug CACHEFLY_TOKEN "$CACHEFLY_TOKEN"
|
||||
export _H1="Authorization: Bearer $CACHEFLY_TOKEN"
|
||||
_response=$(_post "$_request_body" "$CACHEFLY_API_BASE/certificates" "" "POST" "application/json")
|
||||
|
||||
if _contains "$_response" "message"; then
|
||||
_err "Error in deploying $_cdomain certificate to CacheFly."
|
||||
_err "$_response"
|
||||
return 1
|
||||
fi
|
||||
_debug response "$_response"
|
||||
_info "Domain $_cdomain certificate successfully deployed to CacheFly."
|
||||
return 0
|
||||
}
|
||||
92
deploy/cleverreach.sh
Normal file
92
deploy/cleverreach.sh
Normal file
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env sh
|
||||
# Here is the script to deploy the cert to your CleverReach Account using the CleverReach REST API.
|
||||
# Your OAuth needs the right scope, please contact CleverReach support for that.
|
||||
#
|
||||
# Written by Jan-Philipp Benecke <github@bnck.me>
|
||||
# Public domain, 2020
|
||||
#
|
||||
# Following environment variables must be set:
|
||||
#
|
||||
#export DEPLOY_CLEVERREACH_CLIENT_ID=myid
|
||||
#export DEPLOY_CLEVERREACH_CLIENT_SECRET=mysecret
|
||||
|
||||
cleverreach_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_rest_endpoint="https://rest.cleverreach.com"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
_getdeployconf DEPLOY_CLEVERREACH_CLIENT_ID
|
||||
_getdeployconf DEPLOY_CLEVERREACH_CLIENT_SECRET
|
||||
_getdeployconf DEPLOY_CLEVERREACH_SUBCLIENT_ID
|
||||
|
||||
if [ -z "${DEPLOY_CLEVERREACH_CLIENT_ID}" ]; then
|
||||
_err "CleverReach Client ID is not found, please define DEPLOY_CLEVERREACH_CLIENT_ID."
|
||||
return 1
|
||||
fi
|
||||
if [ -z "${DEPLOY_CLEVERREACH_CLIENT_SECRET}" ]; then
|
||||
_err "CleverReach client secret is not found, please define DEPLOY_CLEVERREACH_CLIENT_SECRET."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_savedeployconf DEPLOY_CLEVERREACH_CLIENT_ID "${DEPLOY_CLEVERREACH_CLIENT_ID}"
|
||||
_savedeployconf DEPLOY_CLEVERREACH_CLIENT_SECRET "${DEPLOY_CLEVERREACH_CLIENT_SECRET}"
|
||||
_savedeployconf DEPLOY_CLEVERREACH_SUBCLIENT_ID "${DEPLOY_CLEVERREACH_SUBCLIENT_ID}"
|
||||
|
||||
_info "Obtaining a CleverReach access token"
|
||||
|
||||
_data="{\"grant_type\": \"client_credentials\", \"client_id\": \"${DEPLOY_CLEVERREACH_CLIENT_ID}\", \"client_secret\": \"${DEPLOY_CLEVERREACH_CLIENT_SECRET}\"}"
|
||||
_auth_result="$(_post "$_data" "$_rest_endpoint/oauth/token.php" "" "POST" "application/json")"
|
||||
|
||||
_debug _data "$_data"
|
||||
_debug _auth_result "$_auth_result"
|
||||
|
||||
_regex=".*\"access_token\":\"\([-._0-9A-Za-z]*\)\".*$"
|
||||
_debug _regex "$_regex"
|
||||
_access_token=$(echo "$_auth_result" | _json_decode | sed -n "s/$_regex/\1/p")
|
||||
|
||||
_debug _subclient "${DEPLOY_CLEVERREACH_SUBCLIENT_ID}"
|
||||
|
||||
if [ -n "${DEPLOY_CLEVERREACH_SUBCLIENT_ID}" ]; then
|
||||
_info "Obtaining token for sub-client ${DEPLOY_CLEVERREACH_SUBCLIENT_ID}"
|
||||
export _H1="Authorization: Bearer ${_access_token}"
|
||||
_subclient_token_result="$(_get "$_rest_endpoint/v3/clients/$DEPLOY_CLEVERREACH_SUBCLIENT_ID/token")"
|
||||
_access_token=$(echo "$_subclient_token_result" | sed -n "s/\"//p")
|
||||
|
||||
_debug _subclient_token_result "$_access_token"
|
||||
|
||||
_info "Destroying parent token at CleverReach, as it not needed anymore"
|
||||
_destroy_result="$(_post "" "$_rest_endpoint/v3/oauth/token.json" "" "DELETE" "application/json")"
|
||||
_debug _destroy_result "$_destroy_result"
|
||||
fi
|
||||
|
||||
_info "Uploading certificate and key to CleverReach"
|
||||
|
||||
_certData="{\"cert\":\"$(_json_encode <"$_cfullchain")\", \"key\":\"$(_json_encode <"$_ckey")\"}"
|
||||
export _H1="Authorization: Bearer ${_access_token}"
|
||||
_add_cert_result="$(_post "$_certData" "$_rest_endpoint/v3/ssl" "" "POST" "application/json")"
|
||||
|
||||
if [ -z "${DEPLOY_CLEVERREACH_SUBCLIENT_ID}" ]; then
|
||||
_info "Destroying token at CleverReach, as it not needed anymore"
|
||||
_destroy_result="$(_post "" "$_rest_endpoint/v3/oauth/token.json" "" "DELETE" "application/json")"
|
||||
_debug _destroy_result "$_destroy_result"
|
||||
fi
|
||||
|
||||
if ! echo "$_add_cert_result" | grep '"error":' >/dev/null; then
|
||||
_info "Uploaded certificate successfully"
|
||||
return 0
|
||||
else
|
||||
_debug _add_cert_result "$_add_cert_result"
|
||||
_err "Unable to update certificate"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
98
deploy/consul.sh
Normal file
98
deploy/consul.sh
Normal file
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Here is a script to deploy cert to hashicorp consul using curl
|
||||
# (https://www.consul.io/)
|
||||
#
|
||||
# it requires following environment variables:
|
||||
#
|
||||
# CONSUL_PREFIX - this contains the prefix path in consul
|
||||
# CONSUL_HTTP_ADDR - consul requires this to find your consul server
|
||||
#
|
||||
# additionally, you need to ensure that CONSUL_HTTP_TOKEN is available
|
||||
# to access the consul server
|
||||
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
consul_deploy() {
|
||||
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
# validate required env vars
|
||||
_getdeployconf CONSUL_PREFIX
|
||||
if [ -z "$CONSUL_PREFIX" ]; then
|
||||
_err "CONSUL_PREFIX needs to be defined (contains prefix path in vault)"
|
||||
return 1
|
||||
fi
|
||||
_savedeployconf CONSUL_PREFIX "$CONSUL_PREFIX"
|
||||
|
||||
_getdeployconf CONSUL_HTTP_ADDR
|
||||
if [ -z "$CONSUL_HTTP_ADDR" ]; then
|
||||
_err "CONSUL_HTTP_ADDR needs to be defined (contains consul connection address)"
|
||||
return 1
|
||||
fi
|
||||
_savedeployconf CONSUL_HTTP_ADDR "$CONSUL_HTTP_ADDR"
|
||||
|
||||
CONSUL_CMD=$(command -v consul)
|
||||
|
||||
# force CLI, but the binary does not exist => error
|
||||
if [ -n "$USE_CLI" ] && [ -z "$CONSUL_CMD" ]; then
|
||||
_err "Cannot find the consul binary!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# use the CLI first
|
||||
if [ -n "$USE_CLI" ] || [ -n "$CONSUL_CMD" ]; then
|
||||
_info "Found consul binary, deploying with CLI"
|
||||
consul_deploy_cli "$CONSUL_CMD" "$CONSUL_PREFIX"
|
||||
else
|
||||
_info "Did not find consul binary, deploying with API"
|
||||
consul_deploy_api "$CONSUL_HTTP_ADDR" "$CONSUL_PREFIX" "$CONSUL_HTTP_TOKEN"
|
||||
fi
|
||||
}
|
||||
|
||||
consul_deploy_api() {
|
||||
CONSUL_HTTP_ADDR="$1"
|
||||
CONSUL_PREFIX="$2"
|
||||
CONSUL_HTTP_TOKEN="$3"
|
||||
|
||||
URL="$CONSUL_HTTP_ADDR/v1/kv/$CONSUL_PREFIX"
|
||||
export _H1="X-Consul-Token: $CONSUL_HTTP_TOKEN"
|
||||
|
||||
if [ -n "$FABIO" ]; then
|
||||
_post "$(cat "$_cfullchain")" "$URL/${_cdomain}-cert.pem" '' "PUT" || return 1
|
||||
_post "$(cat "$_ckey")" "$URL/${_cdomain}-key.pem" '' "PUT" || return 1
|
||||
else
|
||||
_post "$(cat "$_ccert")" "$URL/${_cdomain}/cert.pem" '' "PUT" || return 1
|
||||
_post "$(cat "$_ckey")" "$URL/${_cdomain}/cert.key" '' "PUT" || return 1
|
||||
_post "$(cat "$_cca")" "$URL/${_cdomain}/chain.pem" '' "PUT" || return 1
|
||||
_post "$(cat "$_cfullchain")" "$URL/${_cdomain}/fullchain.pem" '' "PUT" || return 1
|
||||
fi
|
||||
}
|
||||
|
||||
consul_deploy_cli() {
|
||||
CONSUL_CMD="$1"
|
||||
CONSUL_PREFIX="$2"
|
||||
|
||||
if [ -n "$FABIO" ]; then
|
||||
$CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}-cert.pem" @"$_cfullchain" || return 1
|
||||
$CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}-key.pem" @"$_ckey" || return 1
|
||||
else
|
||||
$CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}/cert.pem" value=@"$_ccert" || return 1
|
||||
$CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}/cert.key" value=@"$_ckey" || return 1
|
||||
$CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}/chain.pem" value=@"$_cca" || return 1
|
||||
$CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}/fullchain.pem" value=@"$_cfullchain" || return 1
|
||||
fi
|
||||
}
|
||||
224
deploy/cpanel_uapi.sh
Normal file
224
deploy/cpanel_uapi.sh
Normal file
@@ -0,0 +1,224 @@
|
||||
#!/usr/bin/env sh
|
||||
# Here is the script to deploy the cert to your cpanel using the cpanel API.
|
||||
# Uses command line uapi. --user option is needed only if run as root.
|
||||
# Returns 0 when success.
|
||||
#
|
||||
# Configure DEPLOY_CPANEL_AUTO_<...> options to enable or restrict automatic
|
||||
# detection of deployment targets through UAPI (if not set, defaults below are used.)
|
||||
# - ENABLED : 'true' for multi-site / wildcard capability; otherwise single-site mode.
|
||||
# - NOMATCH : 'true' to allow deployment to sites that do not match the certificate.
|
||||
# - INCLUDE : Comma-separated list - sites must match this field.
|
||||
# - EXCLUDE : Comma-separated list - sites must NOT match this field.
|
||||
# INCLUDE/EXCLUDE both support non-lexical, glob-style matches using '*'
|
||||
#
|
||||
# Please note that I am no longer using Github. If you want to report an issue
|
||||
# or contact me, visit https://forum.webseodesigners.com/web-design-seo-and-hosting-f16/
|
||||
#
|
||||
# Written by Santeri Kannisto <santeri.kannisto@webseodesigners.com>
|
||||
# Public domain, 2017-2018
|
||||
#
|
||||
# export DEPLOY_CPANEL_USER=myusername
|
||||
# export DEPLOY_CPANEL_AUTO_ENABLED='true'
|
||||
# export DEPLOY_CPANEL_AUTO_NOMATCH='false'
|
||||
# export DEPLOY_CPANEL_AUTO_INCLUDE='*'
|
||||
# export DEPLOY_CPANEL_AUTO_EXCLUDE=''
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
cpanel_uapi_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
# re-declare vars inherited from acme.sh but not passed to make ShellCheck happy
|
||||
: "${Le_Alt:=""}"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
if ! _exists uapi; then
|
||||
_err "The command uapi is not found."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# declare useful constants
|
||||
uapi_error_response='status: 0'
|
||||
|
||||
# read cert and key files and urlencode both
|
||||
_cert=$(_url_encode <"$_ccert")
|
||||
# with --signcsr the private key was never handed to acme.sh, so the key
|
||||
# file does not exist; skip it instead of spilling a shell redirection
|
||||
# error on every renewal (cPanel keeps using the already-installed key)
|
||||
if [ -f "$_ckey" ]; then
|
||||
_key=$(_url_encode <"$_ckey")
|
||||
else
|
||||
_debug "Key file $_ckey does not exist (csr mode), not sending a key."
|
||||
_key=""
|
||||
fi
|
||||
|
||||
_debug2 _cert "$_cert"
|
||||
_debug2 _key "$_key"
|
||||
|
||||
if [ "$(id -u)" = 0 ]; then
|
||||
_getdeployconf DEPLOY_CPANEL_USER
|
||||
# fallback to _readdomainconf for old installs
|
||||
if [ -z "${DEPLOY_CPANEL_USER:=$(_readdomainconf DEPLOY_CPANEL_USER)}" ]; then
|
||||
_err "It seems that you are root, please define the target user name: export DEPLOY_CPANEL_USER=username"
|
||||
return 1
|
||||
fi
|
||||
_debug DEPLOY_CPANEL_USER "$DEPLOY_CPANEL_USER"
|
||||
_savedeployconf DEPLOY_CPANEL_USER "$DEPLOY_CPANEL_USER"
|
||||
|
||||
_uapi_user="$DEPLOY_CPANEL_USER"
|
||||
fi
|
||||
|
||||
# Load all AUTO envars and set defaults - see above for usage
|
||||
__cpanel_initautoparam ENABLED 'true'
|
||||
__cpanel_initautoparam NOMATCH 'false'
|
||||
__cpanel_initautoparam INCLUDE '*'
|
||||
__cpanel_initautoparam EXCLUDE ''
|
||||
|
||||
# Auto mode
|
||||
if [ "$DEPLOY_CPANEL_AUTO_ENABLED" = "true" ]; then
|
||||
# call API for site config
|
||||
if [ -n "$_uapi_user" ]; then
|
||||
_response=$(uapi --user="$_uapi_user" DomainInfo list_domains)
|
||||
else
|
||||
_response=$(uapi DomainInfo list_domains)
|
||||
fi
|
||||
# exit if error in response
|
||||
if [ -z "$_response" ] || [ "${_response#*"$uapi_error_response"}" != "$_response" ]; then
|
||||
_err "Error in deploying certificate - cannot retrieve sitelist:"
|
||||
_err "\n$_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# parse response to create site list
|
||||
sitelist=$(__cpanel_parse_response "$_response")
|
||||
_debug "UAPI sites found: $sitelist"
|
||||
|
||||
# filter sitelist using configured domains
|
||||
# skip if NOMATCH is "true"
|
||||
if [ "$DEPLOY_CPANEL_AUTO_NOMATCH" = "true" ]; then
|
||||
_debug "DEPLOY_CPANEL_AUTO_NOMATCH is true"
|
||||
_info "UAPI nomatch mode is enabled - Will not validate sites are valid for the certificate"
|
||||
else
|
||||
_debug "DEPLOY_CPANEL_AUTO_NOMATCH is false"
|
||||
d="$(echo "${Le_Alt}," | sed -e "s/^$_cdomain,//" -e "s/,$_cdomain,/,/")"
|
||||
d="$(echo "$_cdomain,$d" | tr ',' '\n' | sed -e 's/\./\\./g' -e 's/\*/\[\^\.\]\*/g')"
|
||||
sitelist="$(echo "$sitelist" | grep -ix "$d")"
|
||||
_debug2 "Matched UAPI sites: $sitelist"
|
||||
fi
|
||||
|
||||
# filter sites that do not match $DEPLOY_CPANEL_AUTO_INCLUDE
|
||||
_info "Applying sitelist filter DEPLOY_CPANEL_AUTO_INCLUDE: $DEPLOY_CPANEL_AUTO_INCLUDE"
|
||||
sitelist="$(echo "$sitelist" | grep -ix "$(echo "$DEPLOY_CPANEL_AUTO_INCLUDE" | tr ',' '\n' | sed -e 's/\./\\./g' -e 's/\*/\.\*/g')")"
|
||||
_debug2 "Remaining sites: $sitelist"
|
||||
|
||||
# filter sites that match $DEPLOY_CPANEL_AUTO_EXCLUDE
|
||||
_info "Applying sitelist filter DEPLOY_CPANEL_AUTO_EXCLUDE: $DEPLOY_CPANEL_AUTO_EXCLUDE"
|
||||
sitelist="$(echo "$sitelist" | grep -vix "$(echo "$DEPLOY_CPANEL_AUTO_EXCLUDE" | tr ',' '\n' | sed -e 's/\./\\./g' -e 's/\*/\.\*/g')")"
|
||||
_debug2 "Remaining sites: $sitelist"
|
||||
|
||||
# counter for success / failure check
|
||||
successes=0
|
||||
if [ -n "$sitelist" ]; then
|
||||
sitetotal="$(echo "$sitelist" | wc -l)"
|
||||
_debug "$sitetotal sites to deploy"
|
||||
else
|
||||
sitetotal=0
|
||||
_debug "No sites to deploy"
|
||||
fi
|
||||
|
||||
# for each site: call uapi to publish cert and log result. Only return failure if all fail
|
||||
for site in $sitelist; do
|
||||
# call uapi to publish cert, check response for errors and log them.
|
||||
if [ -n "$_uapi_user" ]; then
|
||||
_response=$(uapi --user="$_uapi_user" SSL install_ssl domain="$site" cert="$_cert" key="$_key")
|
||||
else
|
||||
_response=$(uapi SSL install_ssl domain="$site" cert="$_cert" key="$_key")
|
||||
fi
|
||||
if [ "${_response#*"$uapi_error_response"}" != "$_response" ]; then
|
||||
_err "Error in deploying certificate to $site:"
|
||||
_err "$_response"
|
||||
else
|
||||
successes=$((successes + 1))
|
||||
_debug "$_response"
|
||||
_info "Succcessfully deployed to $site"
|
||||
fi
|
||||
done
|
||||
|
||||
# Raise error if all updates fail
|
||||
if [ "$sitetotal" -gt 0 ] && [ "$successes" -eq 0 ]; then
|
||||
_err "Could not deploy to any of $sitetotal sites via UAPI"
|
||||
_debug "successes: $successes, sitetotal: $sitetotal"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Successfully deployed certificate to $successes of $sitetotal sites via UAPI"
|
||||
return 0
|
||||
else
|
||||
# "classic" mode - will only try to deploy to the primary domain; will not check UAPI first
|
||||
if [ -n "$_uapi_user" ]; then
|
||||
_response=$(uapi --user="$_uapi_user" SSL install_ssl domain="$_cdomain" cert="$_cert" key="$_key")
|
||||
else
|
||||
_response=$(uapi SSL install_ssl domain="$_cdomain" cert="$_cert" key="$_key")
|
||||
fi
|
||||
|
||||
if [ "${_response#*"$uapi_error_response"}" != "$_response" ]; then
|
||||
_err "Error in deploying certificate:"
|
||||
_err "$_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug response "$_response"
|
||||
_info "Certificate successfully deployed"
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
######## Private functions #####################
|
||||
|
||||
# Internal utility to process YML from UAPI - looks at main_domain, sub_domains, addon domains and parked domains
|
||||
#[response]
|
||||
__cpanel_parse_response() {
|
||||
if [ $# -gt 0 ]; then resp="$*"; else resp="$(cat)"; fi
|
||||
|
||||
echo "$resp" |
|
||||
sed -En \
|
||||
-e 's/\r$//' \
|
||||
-e 's/^( *)([_.[:alnum:]]+) *: *(.*)/\1,\2,\3/p' \
|
||||
-e 's/^( *)- (.*)/\1,-,\2/p' |
|
||||
awk -F, '{
|
||||
level = length($1)/2;
|
||||
section[level] = $2;
|
||||
for (i in section) {if (i > level) {delete section[i]}}
|
||||
if (length($3) > 0) {
|
||||
prefix="";
|
||||
for (i=0; i < level; i++)
|
||||
{ prefix = (prefix)(section[i])("/") }
|
||||
printf("%s%s=%s\n", prefix, $2, $3);
|
||||
}
|
||||
}' |
|
||||
sed -En -e 's/^result\/data\/(main_domain|sub_domains\/-|addon_domains\/-|parked_domains\/-)=(.*)$/\2/p' |
|
||||
sed -e 's/^"//' -e 's/"$//' # YAML double-quotes values starting with '*' (wildcard subdomains)
|
||||
}
|
||||
|
||||
# Load parameter by prefix+name - fallback to default if not set, and save to config
|
||||
#pname pdefault
|
||||
__cpanel_initautoparam() {
|
||||
pname="$1"
|
||||
pdefault="$2"
|
||||
pkey="DEPLOY_CPANEL_AUTO_$pname"
|
||||
|
||||
_getdeployconf "$pkey"
|
||||
[ -n "$(eval echo "\"\$$pkey\"")" ] || eval "$pkey=\"$pdefault\""
|
||||
_debug2 "$pkey" "$(eval echo "\"\$$pkey\"")"
|
||||
_savedeployconf "$pkey" "$(eval echo "\"\$$pkey\"")"
|
||||
}
|
||||
86
deploy/directadmin.sh
Normal file
86
deploy/directadmin.sh
Normal file
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Script to deploy certificate to DirectAdmin
|
||||
# https://docs.directadmin.com/directadmin/customizing-workflow/api-all-about.html#creating-a-login-key
|
||||
# https://docs.directadmin.com/changelog/version-1.24.4.html#cmd-api-catch-all-pop-passwords-frontpage-protected-dirs-ssl-certs
|
||||
|
||||
# This deployment required following variables
|
||||
# export DirectAdmin_SCHEME="https" # Optional, https or http, defaults to https
|
||||
# export DirectAdmin_ENDPOINT="example.com:2222"
|
||||
# export DirectAdmin_USERNAME="Your DirectAdmin Username"
|
||||
# export DirectAdmin_KEY="Your DirectAdmin Login Key or Password"
|
||||
# export DirectAdmin_MAIN_DOMAIN="Your DirectAdmin Main Domain, NOT Subdomain"
|
||||
|
||||
# returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
directadmin_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
if [ -z "$DirectAdmin_ENDPOINT" ]; then
|
||||
_err "DirectAdmin_ENDPOINT is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf DirectAdmin_ENDPOINT "$DirectAdmin_ENDPOINT"
|
||||
fi
|
||||
if [ -z "$DirectAdmin_USERNAME" ]; then
|
||||
_err "DirectAdmin_USERNAME is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf DirectAdmin_USERNAME "$DirectAdmin_USERNAME"
|
||||
fi
|
||||
if [ -z "$DirectAdmin_KEY" ]; then
|
||||
_err "DirectAdmin_KEY is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf DirectAdmin_KEY "$DirectAdmin_KEY"
|
||||
fi
|
||||
if [ -z "$DirectAdmin_MAIN_DOMAIN" ]; then
|
||||
_err "DirectAdmin_MAIN_DOMAIN is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf DirectAdmin_MAIN_DOMAIN "$DirectAdmin_MAIN_DOMAIN"
|
||||
fi
|
||||
|
||||
# Optional SCHEME
|
||||
_getdeployconf DirectAdmin_SCHEME
|
||||
# set default values for DirectAdmin_SCHEME
|
||||
[ -n "${DirectAdmin_SCHEME}" ] || DirectAdmin_SCHEME="https"
|
||||
|
||||
_info "Deploying certificate to DirectAdmin..."
|
||||
|
||||
# upload certificate
|
||||
string_cfullchain=$(sed 's/$/\\n/' "$_cfullchain" | tr -d '\n')
|
||||
string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')
|
||||
|
||||
_request_body="{\"domain\":\"$DirectAdmin_MAIN_DOMAIN\",\"action\":\"save\",\"type\":\"paste\",\"certificate\":\"$string_key\n$string_cfullchain\n\"}"
|
||||
_debug _request_body "$_request_body"
|
||||
_debug DirectAdmin_ENDPOINT "$DirectAdmin_ENDPOINT"
|
||||
_debug DirectAdmin_USERNAME "$DirectAdmin_USERNAME"
|
||||
_debug DirectAdmin_KEY "$DirectAdmin_KEY"
|
||||
_debug DirectAdmin_MAIN_DOMAIN "$DirectAdmin_MAIN_DOMAIN"
|
||||
_response=$(_post "$_request_body" "$DirectAdmin_SCHEME://$DirectAdmin_USERNAME:$DirectAdmin_KEY@$DirectAdmin_ENDPOINT/CMD_API_SSL" "" "POST" "application/json")
|
||||
|
||||
if _contains "$_response" "error=1"; then
|
||||
_err "Error in deploying $_cdomain certificate to DirectAdmin Domain $DirectAdmin_MAIN_DOMAIN."
|
||||
_err "$_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "$_response"
|
||||
_info "Domain $_cdomain certificate successfully deployed to DirectAdmin Domain $DirectAdmin_MAIN_DOMAIN."
|
||||
|
||||
return 0
|
||||
}
|
||||
352
deploy/docker.sh
Executable file
352
deploy/docker.sh
Executable file
@@ -0,0 +1,352 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#DEPLOY_DOCKER_CONTAINER_LABEL="xxxxxxx"
|
||||
|
||||
#DEPLOY_DOCKER_CONTAINER_KEY_FILE="/path/to/key.pem"
|
||||
#DEPLOY_DOCKER_CONTAINER_KEY_MODE="0640"
|
||||
#DEPLOY_DOCKER_CONTAINER_KEY_OWNER="1000:1000"
|
||||
#DEPLOY_DOCKER_CONTAINER_CERT_FILE="/path/to/cert.pem"
|
||||
#DEPLOY_DOCKER_CONTAINER_CA_FILE="/path/to/ca.pem"
|
||||
#DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE="/path/to/fullchain.pem"
|
||||
#DEPLOY_DOCKER_CONTAINER_RELOAD_CMD="service nginx force-reload"
|
||||
|
||||
_DEPLOY_DOCKER_WIKI="https://github.com/acmesh-official/acme.sh/wiki/deploy-to-docker-containers"
|
||||
|
||||
_DOCKER_HOST_DEFAULT="/var/run/docker.sock"
|
||||
|
||||
docker_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
_cpfx="$6"
|
||||
_debug _cdomain "$_cdomain"
|
||||
_getdeployconf DEPLOY_DOCKER_CONTAINER_LABEL
|
||||
_debug2 DEPLOY_DOCKER_CONTAINER_LABEL "$DEPLOY_DOCKER_CONTAINER_LABEL"
|
||||
if [ -z "$DEPLOY_DOCKER_CONTAINER_LABEL" ]; then
|
||||
_err "The DEPLOY_DOCKER_CONTAINER_LABEL variable is not defined, we use this label to find the container."
|
||||
_err "See: $_DEPLOY_DOCKER_WIKI"
|
||||
fi
|
||||
|
||||
_savedeployconf DEPLOY_DOCKER_CONTAINER_LABEL "$DEPLOY_DOCKER_CONTAINER_LABEL"
|
||||
|
||||
if [ "$DOCKER_HOST" ]; then
|
||||
_saveaccountconf DOCKER_HOST "$DOCKER_HOST"
|
||||
fi
|
||||
|
||||
if _exists docker && docker version | grep -i docker >/dev/null; then
|
||||
_info "Using docker command"
|
||||
export _USE_DOCKER_COMMAND=1
|
||||
else
|
||||
export _USE_DOCKER_COMMAND=
|
||||
fi
|
||||
|
||||
export _USE_UNIX_SOCKET=
|
||||
if [ -z "$_USE_DOCKER_COMMAND" ]; then
|
||||
export _USE_REST=
|
||||
if [ "$DOCKER_HOST" ]; then
|
||||
_debug "Try use docker host: $DOCKER_HOST"
|
||||
export _USE_REST=1
|
||||
else
|
||||
export _DOCKER_SOCK="$_DOCKER_HOST_DEFAULT"
|
||||
_debug "Try use $_DOCKER_SOCK"
|
||||
if [ ! -e "$_DOCKER_SOCK" ] || [ ! -w "$_DOCKER_SOCK" ]; then
|
||||
_err "$_DOCKER_SOCK is not available"
|
||||
return 1
|
||||
fi
|
||||
export _USE_UNIX_SOCKET=1
|
||||
if ! _exists "curl"; then
|
||||
_err "Please install curl first."
|
||||
_err "We need curl to work."
|
||||
return 1
|
||||
fi
|
||||
if ! _check_curl_version; then
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
_getdeployconf DEPLOY_DOCKER_CONTAINER_KEY_FILE
|
||||
_debug2 DEPLOY_DOCKER_CONTAINER_KEY_FILE "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_KEY_FILE" ]; then
|
||||
_savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_FILE "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"
|
||||
fi
|
||||
|
||||
_getdeployconf DEPLOY_DOCKER_CONTAINER_KEY_MODE
|
||||
_debug2 DEPLOY_DOCKER_CONTAINER_KEY_MODE "$DEPLOY_DOCKER_CONTAINER_KEY_MODE"
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" ]; then
|
||||
_savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_MODE "$DEPLOY_DOCKER_CONTAINER_KEY_MODE"
|
||||
fi
|
||||
|
||||
_getdeployconf DEPLOY_DOCKER_CONTAINER_KEY_OWNER
|
||||
_debug2 DEPLOY_DOCKER_CONTAINER_KEY_OWNER "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER"
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" ]; then
|
||||
_savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_OWNER "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER"
|
||||
fi
|
||||
|
||||
_getdeployconf DEPLOY_DOCKER_CONTAINER_CERT_FILE
|
||||
_debug2 DEPLOY_DOCKER_CONTAINER_CERT_FILE "$DEPLOY_DOCKER_CONTAINER_CERT_FILE"
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" ]; then
|
||||
_savedeployconf DEPLOY_DOCKER_CONTAINER_CERT_FILE "$DEPLOY_DOCKER_CONTAINER_CERT_FILE"
|
||||
fi
|
||||
|
||||
_getdeployconf DEPLOY_DOCKER_CONTAINER_CA_FILE
|
||||
_debug2 DEPLOY_DOCKER_CONTAINER_CA_FILE "$DEPLOY_DOCKER_CONTAINER_CA_FILE"
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_CA_FILE" ]; then
|
||||
_savedeployconf DEPLOY_DOCKER_CONTAINER_CA_FILE "$DEPLOY_DOCKER_CONTAINER_CA_FILE"
|
||||
fi
|
||||
|
||||
_getdeployconf DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE
|
||||
_debug2 DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE"
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE" ]; then
|
||||
_savedeployconf DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE"
|
||||
fi
|
||||
|
||||
_getdeployconf DEPLOY_DOCKER_CONTAINER_PFX_FILE
|
||||
_debug2 DEPLOY_DOCKER_CONTAINER_PFX_FILE "$DEPLOY_DOCKER_CONTAINER_PFX_FILE"
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_PFX_FILE" ]; then
|
||||
_savedeployconf DEPLOY_DOCKER_CONTAINER_PFX_FILE "$DEPLOY_DOCKER_CONTAINER_PFX_FILE"
|
||||
fi
|
||||
|
||||
_getdeployconf DEPLOY_DOCKER_CONTAINER_RELOAD_CMD
|
||||
_debug2 DEPLOY_DOCKER_CONTAINER_RELOAD_CMD "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD"
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" ]; then
|
||||
_savedeployconf DEPLOY_DOCKER_CONTAINER_RELOAD_CMD "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" "base64"
|
||||
fi
|
||||
|
||||
_cid="$(_get_id "$DEPLOY_DOCKER_CONTAINER_LABEL")"
|
||||
_info "Container id: $_cid"
|
||||
if [ -z "$_cid" ]; then
|
||||
_err "can not find container id"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_KEY_FILE" ]; then
|
||||
if ! _docker_cp "$_cid" "$_ckey" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then
|
||||
return 1
|
||||
fi
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" ]; then
|
||||
_info "Setting key file owner to $DEPLOY_DOCKER_CONTAINER_KEY_OWNER"
|
||||
if ! _docker_exec "$_cid" chown "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then
|
||||
_err "Can not change owner of key file in container"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" ]; then
|
||||
_info "Setting key file mode to $DEPLOY_DOCKER_CONTAINER_KEY_MODE"
|
||||
if ! _docker_exec "$_cid" chmod "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then
|
||||
_err "Can not change mode of key file in container"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" ]; then
|
||||
if ! _docker_cp "$_cid" "$_ccert" "$DEPLOY_DOCKER_CONTAINER_CERT_FILE"; then
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_CA_FILE" ]; then
|
||||
if ! _docker_cp "$_cid" "$_cca" "$DEPLOY_DOCKER_CONTAINER_CA_FILE"; then
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE" ]; then
|
||||
if ! _docker_cp "$_cid" "$_cfullchain" "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE"; then
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_PFX_FILE" ]; then
|
||||
if ! _docker_cp "$_cid" "$_cpfx" "$DEPLOY_DOCKER_CONTAINER_PFX_FILE"; then
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" ]; then
|
||||
_info "Reloading: $DEPLOY_DOCKER_CONTAINER_RELOAD_CMD"
|
||||
if ! _docker_exec "$_cid" "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD"; then
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
#label
|
||||
_get_id() {
|
||||
_label="$1"
|
||||
if [ "$_USE_DOCKER_COMMAND" ]; then
|
||||
docker ps -f label="$_label" --format "{{.ID}}"
|
||||
elif [ "$_USE_REST" ]; then
|
||||
_err "Not implemented yet."
|
||||
return 1
|
||||
elif [ "$_USE_UNIX_SOCKET" ]; then
|
||||
_req="{\"label\":[\"$_label\"]}"
|
||||
_debug2 _req "$_req"
|
||||
_req="$(printf "%s" "$_req" | _url_encode)"
|
||||
_debug2 _req "$_req"
|
||||
listjson="$(_curl_unix_sock "${_DOCKER_SOCK:-$_DOCKER_HOST_DEFAULT}" GET "/containers/json?filters=$_req")"
|
||||
_debug2 "listjson" "$listjson"
|
||||
echo "$listjson" | tr '{,' '\n' | grep -i '"id":' | _head_n 1 | cut -d '"' -f 4
|
||||
else
|
||||
_err "Not implemented yet."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
#id cmd
|
||||
_docker_exec() {
|
||||
_eargs="$*"
|
||||
_debug2 "_docker_exec $_eargs"
|
||||
_dcid="$1"
|
||||
shift
|
||||
if [ "$_USE_DOCKER_COMMAND" ]; then
|
||||
docker exec -i "$_dcid" sh -c "$*"
|
||||
elif [ "$_USE_REST" ]; then
|
||||
_err "Not implemented yet."
|
||||
return 1
|
||||
elif [ "$_USE_UNIX_SOCKET" ]; then
|
||||
_cmd="$*"
|
||||
#_cmd="$(printf "%s" "$_cmd" | sed 's/ /","/g')"
|
||||
_debug2 _cmd "$_cmd"
|
||||
#create exec instance:
|
||||
cjson="$(_curl_unix_sock "$_DOCKER_SOCK" POST "/containers/$_dcid/exec" "{\"Cmd\": [\"sh\", \"-c\", \"$_cmd\"]}")"
|
||||
_debug2 cjson "$cjson"
|
||||
execid="$(echo "$cjson" | cut -d '"' -f 4)"
|
||||
_debug execid "$execid"
|
||||
#Detach:true is required for podman's docker-compatible API: with
|
||||
#Detach:false it streams the command output on the connection, so the
|
||||
#non-empty response was misread as an error (issue #4977). The real
|
||||
#result is checked via the exec inspect ExitCode below instead.
|
||||
ejson="$(_curl_unix_sock "$_DOCKER_SOCK" POST "/exec/$execid/start" "{\"Detach\": true,\"Tty\": false}")"
|
||||
_debug2 ejson "$ejson"
|
||||
_et=0
|
||||
ijson="$(_curl_unix_sock "$_DOCKER_SOCK" GET "/exec/$execid/json")"
|
||||
while _contains "$ijson" "\"Running\":true" && [ "$_et" -lt 10 ]; do
|
||||
sleep 1
|
||||
_et="$(_math "$_et" + 1)"
|
||||
ijson="$(_curl_unix_sock "$_DOCKER_SOCK" GET "/exec/$execid/json")"
|
||||
done
|
||||
_debug2 ijson "$ijson"
|
||||
if ! echo "$ijson" | _egrep_o "\"ExitCode\": *0[,}]" >/dev/null 2>&1; then
|
||||
_err "docker exec error: $ijson"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
_err "Not implemented yet."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
#id from to
|
||||
_docker_cp() {
|
||||
_dcid="$1"
|
||||
_from="$2"
|
||||
_to="$3"
|
||||
_info "Copying file from $_from to $_to"
|
||||
_dir="$(dirname "$_to")"
|
||||
_debug2 _dir "$_dir"
|
||||
if ! _docker_exec "$_dcid" mkdir -p "$_dir"; then
|
||||
_err "Can not create dir: $_dir"
|
||||
return 1
|
||||
fi
|
||||
if [ "$_USE_DOCKER_COMMAND" ]; then
|
||||
if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
|
||||
_docker_exec "$_dcid" tee "$_to" <"$_from"
|
||||
else
|
||||
_docker_exec "$_dcid" tee "$_to" <"$_from" >/dev/null
|
||||
fi
|
||||
if [ "$?" = "0" ]; then
|
||||
_info "Success"
|
||||
return 0
|
||||
else
|
||||
_info "Error"
|
||||
return 1
|
||||
fi
|
||||
elif [ "$_USE_REST" ]; then
|
||||
_err "Not implemented yet."
|
||||
return 1
|
||||
elif [ "$_USE_UNIX_SOCKET" ]; then
|
||||
_frompath="$_from"
|
||||
if _startswith "$_frompath" '/'; then
|
||||
_frompath="$(echo "$_from" | cut -b 2-)" #remove the first '/' char
|
||||
fi
|
||||
_debug2 "_frompath" "$_frompath"
|
||||
_toname="$(basename "$_to")"
|
||||
_debug2 "_toname" "$_toname"
|
||||
_debug2 "_from" "$_from"
|
||||
if ! tar --transform="s,$(printf "%s" "$_frompath" | tr '*' .),$_toname," -cz "$_from" 2>/dev/null | _curl_unix_sock "$_DOCKER_SOCK" PUT "/containers/$_dcid/archive?noOverwriteDirNonDir=1&path=$(printf "%s" "$_dir" | _url_encode)" '@-' "Content-Type: application/octet-stream"; then
|
||||
_err "copy error"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
else
|
||||
_err "Not implemented yet."
|
||||
return 1
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
#sock method endpoint data content-type
|
||||
_curl_unix_sock() {
|
||||
_socket="$1"
|
||||
_method="$2"
|
||||
_endpoint="$3"
|
||||
_data="$4"
|
||||
_ctype="$5"
|
||||
if [ -z "$_ctype" ]; then
|
||||
_ctype="Content-Type: application/json"
|
||||
fi
|
||||
_debug _data "$_data"
|
||||
_debug2 "url" "http://localhost$_endpoint"
|
||||
if [ "$_CURL_NO_HOST" ]; then
|
||||
_cux_url="http:$_endpoint"
|
||||
else
|
||||
_cux_url="http://localhost$_endpoint"
|
||||
fi
|
||||
|
||||
if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
|
||||
curl -vvv --silent --unix-socket "$_socket" -X "$_method" --data-binary "$_data" --header "$_ctype" "$_cux_url"
|
||||
else
|
||||
curl --silent --unix-socket "$_socket" -X "$_method" --data-binary "$_data" --header "$_ctype" "$_cux_url"
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
_check_curl_version() {
|
||||
_cversion="$(curl -V | grep '^curl ' | cut -d ' ' -f 2)"
|
||||
_debug2 "_cversion" "$_cversion"
|
||||
|
||||
_major="$(_getfield "$_cversion" 1 '.')"
|
||||
_debug2 "_major" "$_major"
|
||||
|
||||
_minor="$(_getfield "$_cversion" 2 '.')"
|
||||
_debug2 "_minor" "$_minor"
|
||||
|
||||
if [ "$_major" -ge "8" ]; then
|
||||
#ok
|
||||
return 0
|
||||
fi
|
||||
if [ "$_major" = "7" ]; then
|
||||
if [ "$_minor" -lt "40" ]; then
|
||||
_err "curl v$_cversion doesn't support unit socket"
|
||||
_err "Please upgrade to curl 7.40 or later."
|
||||
return 1
|
||||
fi
|
||||
if [ "$_minor" -lt "50" ]; then
|
||||
_debug "Use short host name"
|
||||
export _CURL_NO_HOST=1
|
||||
else
|
||||
export _CURL_NO_HOST=
|
||||
fi
|
||||
return 0
|
||||
else
|
||||
_err "curl v$_cversion doesn't support unit socket"
|
||||
_err "Please upgrade to curl 7.40 or later."
|
||||
return 1
|
||||
fi
|
||||
|
||||
}
|
||||
26
deploy/dovecot.sh
Normal file
26
deploy/dovecot.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#Here is a script to deploy cert to dovecot server.
|
||||
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
dovecot_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
_err "Not implemented yet"
|
||||
return 1
|
||||
|
||||
}
|
||||
86
deploy/edgio.sh
Normal file
86
deploy/edgio.sh
Normal file
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Here is a script to deploy cert to edgio using its API
|
||||
# https://docs.edg.io/guides/v7/develop/rest_api/authentication
|
||||
# https://docs.edg.io/rest_api/#tag/tls-certs/operation/postConfigV01TlsCerts
|
||||
|
||||
# This deployment required following variables
|
||||
# export EDGIO_CLIENT_ID="Your Edgio Client ID"
|
||||
# export EDGIO_CLIENT_SECRET="Your Edgio Client Secret"
|
||||
# export EDGIO_ENVIRONMENT_ID="Your Edgio Environment ID"
|
||||
|
||||
# If have more than one Environment ID
|
||||
# export EDGIO_ENVIRONMENT_ID="ENVIRONMENT_ID_1 ENVIRONMENT_ID_2"
|
||||
|
||||
# returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
edgio_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
if [ -z "$EDGIO_CLIENT_ID" ]; then
|
||||
_err "EDGIO_CLIENT_ID is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf EDGIO_CLIENT_ID "$EDGIO_CLIENT_ID"
|
||||
fi
|
||||
|
||||
if [ -z "$EDGIO_CLIENT_SECRET" ]; then
|
||||
_err "EDGIO_CLIENT_SECRET is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf EDGIO_CLIENT_SECRET "$EDGIO_CLIENT_SECRET"
|
||||
fi
|
||||
|
||||
if [ -z "$EDGIO_ENVIRONMENT_ID" ]; then
|
||||
_err "EDGIO_ENVIRONMENT_ID is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf EDGIO_ENVIRONMENT_ID "$EDGIO_ENVIRONMENT_ID"
|
||||
fi
|
||||
|
||||
_info "Getting access token"
|
||||
_data="client_id=$EDGIO_CLIENT_ID&client_secret=$EDGIO_CLIENT_SECRET&grant_type=client_credentials&scope=app.config"
|
||||
_debug Get_access_token_data "$_data"
|
||||
_response=$(_post "$_data" "https://id.edgio.app/connect/token" "" "POST" "application/x-www-form-urlencoded")
|
||||
_debug Get_access_token_response "$_response"
|
||||
_access_token=$(echo "$_response" | _json_decode | _egrep_o '"access_token":"[^"]*' | cut -d : -f 2 | tr -d '"')
|
||||
_debug _access_token "$_access_token"
|
||||
if [ -z "$_access_token" ]; then
|
||||
_err "Error in getting access token"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Uploading certificate"
|
||||
string_ccert=$(sed 's/$/\\n/' "$_ccert" | tr -d '\n')
|
||||
string_cca=$(sed 's/$/\\n/' "$_cca" | tr -d '\n')
|
||||
string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')
|
||||
|
||||
for ENVIRONMENT_ID in $EDGIO_ENVIRONMENT_ID; do
|
||||
_data="{\"environment_id\":\"$ENVIRONMENT_ID\",\"primary_cert\":\"$string_ccert\",\"intermediate_cert\":\"$string_cca\",\"private_key\":\"$string_key\"}"
|
||||
_debug Upload_certificate_data "$_data"
|
||||
_H1="Authorization: Bearer $_access_token"
|
||||
_response=$(_post "$_data" "https://edgioapis.com/config/v0.1/tls-certs" "" "POST" "application/json")
|
||||
if _contains "$_response" "message"; then
|
||||
_err "Error in deploying $_cdomain certificate to Edgio ENVIRONMENT_ID $ENVIRONMENT_ID."
|
||||
_err "$_response"
|
||||
return 1
|
||||
fi
|
||||
_debug Upload_certificate_response "$_response"
|
||||
_info "Domain $_cdomain certificate successfully deployed to Edgio ENVIRONMENT_ID $ENVIRONMENT_ID."
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
113
deploy/exim4.sh
Normal file
113
deploy/exim4.sh
Normal file
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#Here is a script to deploy cert to exim4 server.
|
||||
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
#DEPLOY_EXIM4_CONF="/etc/exim/exim.conf"
|
||||
#DEPLOY_EXIM4_RELOAD="service exim4 restart"
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
exim4_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
_ssl_path="/etc/acme.sh/exim4"
|
||||
if ! mkdir -p "$_ssl_path"; then
|
||||
_err "Can not create folder:$_ssl_path"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Copying key and cert"
|
||||
_real_key="$_ssl_path/exim4.key"
|
||||
if ! cat "$_ckey" >"$_real_key"; then
|
||||
_err "Error: write key file to: $_real_key"
|
||||
return 1
|
||||
fi
|
||||
_real_fullchain="$_ssl_path/exim4.pem"
|
||||
if ! cat "$_cfullchain" >"$_real_fullchain"; then
|
||||
_err "Error: write key file to: $_real_fullchain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
DEFAULT_EXIM4_RELOAD="service exim4 restart"
|
||||
_reload="${DEPLOY_EXIM4_RELOAD:-$DEFAULT_EXIM4_RELOAD}"
|
||||
|
||||
if [ -z "$IS_RENEW" ]; then
|
||||
DEFAULT_EXIM4_CONF="/etc/exim/exim.conf"
|
||||
if [ ! -f "$DEFAULT_EXIM4_CONF" ]; then
|
||||
DEFAULT_EXIM4_CONF="/etc/exim4/exim4.conf.template"
|
||||
fi
|
||||
_exim4_conf="${DEPLOY_EXIM4_CONF:-$DEFAULT_EXIM4_CONF}"
|
||||
_debug _exim4_conf "$_exim4_conf"
|
||||
if [ ! -f "$_exim4_conf" ]; then
|
||||
if [ -z "$DEPLOY_EXIM4_CONF" ]; then
|
||||
_err "exim4 conf is not found, please define DEPLOY_EXIM4_CONF"
|
||||
return 1
|
||||
else
|
||||
_err "It seems that the specified exim4 conf is not valid, please check."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
if [ ! -w "$_exim4_conf" ]; then
|
||||
_err "The file $_exim4_conf is not writable, please change the permission."
|
||||
return 1
|
||||
fi
|
||||
_backup_conf="$DOMAIN_BACKUP_PATH/exim4.conf.bak"
|
||||
_info "Backup $_exim4_conf to $_backup_conf"
|
||||
cp "$_exim4_conf" "$_backup_conf"
|
||||
|
||||
_info "Modify exim4 conf: $_exim4_conf"
|
||||
if _setopt "$_exim4_conf" "tls_certificate" "=" "$_real_fullchain" &&
|
||||
_setopt "$_exim4_conf" "tls_privatekey" "=" "$_real_key"; then
|
||||
_info "Set config success!"
|
||||
else
|
||||
_err "Config exim4 server error, please report bug to us."
|
||||
_info "Restoring exim4 conf"
|
||||
if cat "$_backup_conf" >"$_exim4_conf"; then
|
||||
_info "Restore conf success"
|
||||
eval "$_reload"
|
||||
else
|
||||
_err "Oops, error restore exim4 conf, please report bug to us."
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
_info "Run reload: $_reload"
|
||||
if eval "$_reload"; then
|
||||
_info "Reload success!"
|
||||
if [ "$DEPLOY_EXIM4_CONF" ]; then
|
||||
_savedomainconf DEPLOY_EXIM4_CONF "$DEPLOY_EXIM4_CONF"
|
||||
else
|
||||
_cleardomainconf DEPLOY_EXIM4_CONF
|
||||
fi
|
||||
if [ "$DEPLOY_EXIM4_RELOAD" ]; then
|
||||
_savedomainconf DEPLOY_EXIM4_RELOAD "$DEPLOY_EXIM4_RELOAD"
|
||||
else
|
||||
_cleardomainconf DEPLOY_EXIM4_RELOAD
|
||||
fi
|
||||
return 0
|
||||
else
|
||||
_err "Reload error, restoring"
|
||||
if cat "$_backup_conf" >"$_exim4_conf"; then
|
||||
_info "Restore conf success"
|
||||
eval "$_reload"
|
||||
else
|
||||
_err "Oops, error restore exim4 conf, please report bug to us."
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
|
||||
}
|
||||
175
deploy/fortigate.sh
Normal file
175
deploy/fortigate.sh
Normal file
@@ -0,0 +1,175 @@
|
||||
#!/usr/bin/env sh
|
||||
# Script to deploy a certificate to FortiGate via API and set it as the current web GUI certificate.
|
||||
#
|
||||
# FortiGate's native ACME integration does not support wildcard certificates or domain validation,
|
||||
# and is not supported if you have a custom management web port (eg. DNAT web traffic).
|
||||
#
|
||||
# REQUIRED:
|
||||
# export FGT_HOST="fortigate_hostname-or-ip"
|
||||
# export FGT_TOKEN="fortigate_api_token"
|
||||
#
|
||||
# OPTIONAL:
|
||||
# export FGT_PORT="10443" # Custom HTTPS port (defaults to 443 if not set)
|
||||
#
|
||||
# Run `acme.sh --deploy -d example.com --deploy-hook fortigate --insecure` to use this script.
|
||||
# `--insecure` is required on first run if not already using a valid SSL certificate on firewall.
|
||||
|
||||
# Function to parse a FortiGate API response
|
||||
_fortigate_parse_response() {
|
||||
_fortigate_response="$1"
|
||||
_fortigate_func="$2"
|
||||
_fortigate_status=$(echo "$_fortigate_response" | _egrep_o '"status":[ ]*"[^"]*"' | cut -d '"' -f 4)
|
||||
|
||||
if [ "$_fortigate_status" != "success" ]; then
|
||||
_err "[$_fortigate_func] Operation failed. Deploy with --insecure if current certificate is invalid. Try deploying with --debug to troubleshoot."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "[$_fortigate_func] Operation successful."
|
||||
return 0
|
||||
}
|
||||
|
||||
# Function to deploy a base64-encoded certificate to the firewall
|
||||
_fortigate_deployer() {
|
||||
_fortigate_cert_base64=$(_base64 <"$_fortigate_cfullchain" | tr -d '\n')
|
||||
_fortigate_key_base64=$(_base64 <"$_fortigate_ckey" | tr -d '\n')
|
||||
_fortigate_payload=$(
|
||||
cat <<EOF
|
||||
{
|
||||
"type": "regular",
|
||||
"scope": "global",
|
||||
"certname": "$_fortigate_cert_name",
|
||||
"key_file_content": "$_fortigate_key_base64",
|
||||
"file_content": "$_fortigate_cert_base64"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
_fortigate_url="https://${FGT_HOST}:${FGT_PORT}/api/v2/monitor/vpn-certificate/local/import"
|
||||
_debug "Uploading certificate via URL: $_fortigate_url"
|
||||
|
||||
_H1="Authorization: Bearer $FGT_TOKEN"
|
||||
_fortigate_response=$(_post "$_fortigate_payload" "$_fortigate_url" "" "POST" "application/json")
|
||||
_debug "FortiGate API Response: $_fortigate_response"
|
||||
|
||||
_fortigate_parse_response "$_fortigate_response" "Deploying certificate" || return 1
|
||||
}
|
||||
|
||||
# Function to upload a CA certificate to the firewall
|
||||
# FortiGate does not automatically extract the CA from the full chain.
|
||||
_fortigate_upload_ca_cert() {
|
||||
_fortigate_ca_base64=$(_base64 <"$_fortigate_cca" | tr -d '\n')
|
||||
_fortigate_payload=$(
|
||||
cat <<EOF
|
||||
{
|
||||
"import_method": "file",
|
||||
"scope": "global",
|
||||
"file_content": "$_fortigate_ca_base64"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
_fortigate_url="https://${FGT_HOST}:${FGT_PORT}/api/v2/monitor/vpn-certificate/ca/import"
|
||||
_debug "Uploading CA certificate via URL: $_fortigate_url"
|
||||
|
||||
_H1="Authorization: Bearer $FGT_TOKEN"
|
||||
_fortigate_response=$(_post "$_fortigate_payload" "$_fortigate_url" "" "POST" "application/json")
|
||||
_debug "FortiGate API CA Response: $_fortigate_response"
|
||||
|
||||
# FortiGate error -328 means that the CA certificate already exists.
|
||||
if echo "$_fortigate_response" | grep -q '"error":[ ]*-328'; then
|
||||
_debug "CA certificate already exists. Skipping CA upload."
|
||||
return 0
|
||||
fi
|
||||
|
||||
_fortigate_parse_response "$_fortigate_response" "Deploying CA certificate" || return 1
|
||||
}
|
||||
|
||||
# Function to activate the new certificate
|
||||
_fortigate_set_active_web_cert() {
|
||||
_fortigate_payload=$(
|
||||
cat <<EOF
|
||||
{
|
||||
"admin-server-cert": "$_fortigate_cert_name"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
_fortigate_url="https://${FGT_HOST}:${FGT_PORT}/api/v2/cmdb/system/global"
|
||||
_debug "Setting GUI certificate..."
|
||||
|
||||
_H1="Authorization: Bearer $FGT_TOKEN"
|
||||
_fortigate_response=$(_post "$_fortigate_payload" "$_fortigate_url" "" "PUT" "application/json")
|
||||
|
||||
_fortigate_parse_response "$_fortigate_response" "Assigning active certificate" || return 1
|
||||
}
|
||||
|
||||
# Function to clean up the previously deployed certificate
|
||||
_fortigate_cleanup_previous_certificate() {
|
||||
_getdeployconf FGT_LAST_CERT
|
||||
|
||||
if [ -n "$FGT_LAST_CERT" ] && [ "$FGT_LAST_CERT" != "$_fortigate_cert_name" ]; then
|
||||
_debug "Found previously deployed certificate: $FGT_LAST_CERT. Deleting it."
|
||||
|
||||
_fortigate_url="https://${FGT_HOST}:${FGT_PORT}/api/v2/cmdb/vpn.certificate/local/${FGT_LAST_CERT}"
|
||||
_H1="Authorization: Bearer $FGT_TOKEN"
|
||||
_fortigate_response=$(_post "" "$_fortigate_url" "" "DELETE" "application/json")
|
||||
_debug "Delete certificate API response: $_fortigate_response"
|
||||
|
||||
_fortigate_parse_response "$_fortigate_response" "Delete previous certificate" || return 1
|
||||
else
|
||||
_debug "No previous certificate found."
|
||||
fi
|
||||
}
|
||||
|
||||
# Main deploy-hook function
|
||||
fortigate_deploy() {
|
||||
# Include date and time to ensure unique names.
|
||||
_fortigate_cert_name="$(echo "$1" | sed 's/*/WILDCARD_/g')_$(date -u +"%Y-%m-%d_%H-%M-%S")"
|
||||
_fortigate_ckey="$2"
|
||||
_fortigate_cca="$4"
|
||||
_fortigate_cfullchain="$5"
|
||||
|
||||
if [ ! -f "$_fortigate_ckey" ] || [ ! -f "$_fortigate_cfullchain" ]; then
|
||||
_err "Valid key and/or certificate not found."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Save required environment variables if set; otherwise load saved values.
|
||||
for _fortigate_var in FGT_HOST FGT_TOKEN FGT_PORT; do
|
||||
if [ -n "$(eval echo "\$$_fortigate_var")" ]; then
|
||||
_debug "Detected ENV variable $_fortigate_var. Saving to file."
|
||||
_savedeployconf "$_fortigate_var" "$(eval echo "\$$_fortigate_var")" 1
|
||||
else
|
||||
_debug "Attempting to load variable $_fortigate_var from file."
|
||||
_getdeployconf "$_fortigate_var"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$FGT_HOST" ] || [ -z "$FGT_TOKEN" ]; then
|
||||
_err "FGT_HOST and FGT_TOKEN must be set."
|
||||
return 1
|
||||
fi
|
||||
|
||||
FGT_PORT="${FGT_PORT:-443}"
|
||||
_debug "Using FortiGate port: $FGT_PORT"
|
||||
|
||||
# Upload the new certificate.
|
||||
_fortigate_deployer || return 1
|
||||
|
||||
# Upload the CA certificate.
|
||||
if [ -n "$_fortigate_cca" ] && [ -f "$_fortigate_cca" ]; then
|
||||
_fortigate_upload_ca_cert || return 1
|
||||
else
|
||||
_debug "No CA certificate provided."
|
||||
fi
|
||||
|
||||
# Activate the new certificate.
|
||||
_fortigate_set_active_web_cert || return 1
|
||||
|
||||
# Delete the previously deployed certificate only after successful activation.
|
||||
_fortigate_cleanup_previous_certificate || return 1
|
||||
|
||||
# Save the new certificate name for cleanup during the next deployment.
|
||||
_savedeployconf "FGT_LAST_CERT" "$_fortigate_cert_name" 1
|
||||
}
|
||||
126
deploy/fritzbox.sh
Normal file
126
deploy/fritzbox.sh
Normal file
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#Here is a script to deploy cert to an AVM FRITZ!Box router.
|
||||
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
#DEPLOY_FRITZBOX_USERNAME="username"
|
||||
#DEPLOY_FRITZBOX_PASSWORD="password"
|
||||
#DEPLOY_FRITZBOX_URL="https://fritz.box"
|
||||
|
||||
# Kudos to wikrie at Github for his FRITZ!Box update script:
|
||||
# https://gist.github.com/wikrie/f1d5747a714e0a34d0582981f7cb4cfb
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
fritzbox_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
if ! _exists iconv; then
|
||||
if ! _exists uconv; then
|
||||
if ! _exists perl; then
|
||||
_err "iconv or uconv or perl not found"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Clear traces of incorrectly stored values
|
||||
_clearaccountconf DEPLOY_FRITZBOX_USERNAME
|
||||
_clearaccountconf DEPLOY_FRITZBOX_PASSWORD
|
||||
_clearaccountconf DEPLOY_FRITZBOX_URL
|
||||
|
||||
# Read config from saved values or env
|
||||
_getdeployconf DEPLOY_FRITZBOX_USERNAME
|
||||
_getdeployconf DEPLOY_FRITZBOX_PASSWORD
|
||||
_getdeployconf DEPLOY_FRITZBOX_URL
|
||||
|
||||
_debug DEPLOY_FRITZBOX_URL "$DEPLOY_FRITZBOX_URL"
|
||||
_debug DEPLOY_FRITZBOX_USERNAME "$DEPLOY_FRITZBOX_USERNAME"
|
||||
_secure_debug DEPLOY_FRITZBOX_PASSWORD "$DEPLOY_FRITZBOX_PASSWORD"
|
||||
|
||||
if [ -z "$DEPLOY_FRITZBOX_USERNAME" ]; then
|
||||
_err "FRITZ!Box username is not found, please define DEPLOY_FRITZBOX_USERNAME."
|
||||
return 1
|
||||
fi
|
||||
if [ -z "$DEPLOY_FRITZBOX_PASSWORD" ]; then
|
||||
_err "FRITZ!Box password is not found, please define DEPLOY_FRITZBOX_PASSWORD."
|
||||
return 1
|
||||
fi
|
||||
if [ -z "$DEPLOY_FRITZBOX_URL" ]; then
|
||||
_err "FRITZ!Box url is not found, please define DEPLOY_FRITZBOX_URL."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Save current values
|
||||
_savedeployconf DEPLOY_FRITZBOX_USERNAME "$DEPLOY_FRITZBOX_USERNAME"
|
||||
_savedeployconf DEPLOY_FRITZBOX_PASSWORD "$DEPLOY_FRITZBOX_PASSWORD"
|
||||
_savedeployconf DEPLOY_FRITZBOX_URL "$DEPLOY_FRITZBOX_URL"
|
||||
|
||||
# Do not check for a valid SSL certificate, because initially the cert is not valid, so it could not install the LE generated certificate
|
||||
export HTTPS_INSECURE=1
|
||||
|
||||
_info "Log in to the FRITZ!Box"
|
||||
_fritzbox_challenge="$(_get "${DEPLOY_FRITZBOX_URL}/login_sid.lua" | sed -e 's/^.*<Challenge>//' -e 's/<\/Challenge>.*$//')"
|
||||
if _exists iconv; then
|
||||
_fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${DEPLOY_FRITZBOX_PASSWORD}" | iconv -f ASCII -t UTF16LE | _digest md5 hex)"
|
||||
elif _exists uconv; then
|
||||
_fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${DEPLOY_FRITZBOX_PASSWORD}" | uconv -f ASCII -t UTF16LE | _digest md5 hex)"
|
||||
else
|
||||
_fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${DEPLOY_FRITZBOX_PASSWORD}" | perl -p -e 'use Encode qw/encode/; print encode("UTF-16LE","$_"); $_="";' | _digest md5 hex)"
|
||||
fi
|
||||
_fritzbox_sid="$(_get "${DEPLOY_FRITZBOX_URL}/login_sid.lua?sid=0000000000000000&username=${DEPLOY_FRITZBOX_USERNAME}&response=${_fritzbox_challenge}-${_fritzbox_hash}" | sed -e 's/^.*<SID>//' -e 's/<\/SID>.*$//')"
|
||||
|
||||
if [ -z "${_fritzbox_sid}" ] || [ "${_fritzbox_sid}" = "0000000000000000" ]; then
|
||||
_err "Logging in to the FRITZ!Box failed. Please check username, password and URL."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Generate form POST request"
|
||||
_post_request="$(_mktemp)"
|
||||
_post_boundary="---------------------------$(date +%Y%m%d%H%M%S)"
|
||||
# _CERTPASSWORD_ is unset because Let's Encrypt certificates don't have a password. But if they ever do, here's the place to use it!
|
||||
_CERTPASSWORD_=
|
||||
{
|
||||
printf -- "--"
|
||||
printf -- "%s\r\n" "${_post_boundary}"
|
||||
printf "Content-Disposition: form-data; name=\"sid\"\r\n\r\n%s\r\n" "${_fritzbox_sid}"
|
||||
printf -- "--"
|
||||
printf -- "%s\r\n" "${_post_boundary}"
|
||||
printf "Content-Disposition: form-data; name=\"BoxCertPassword\"\r\n\r\n%s\r\n" "${_CERTPASSWORD_}"
|
||||
printf -- "--"
|
||||
printf -- "%s\r\n" "${_post_boundary}"
|
||||
printf "Content-Disposition: form-data; name=\"BoxCertImportFile\"; filename=\"BoxCert.pem\"\r\n"
|
||||
printf "Content-Type: application/octet-stream\r\n\r\n"
|
||||
cat "${_ckey}" "${_cfullchain}"
|
||||
printf "\r\n"
|
||||
printf -- "--"
|
||||
printf -- "%s--" "${_post_boundary}"
|
||||
} >>"${_post_request}"
|
||||
|
||||
_info "Upload certificate to the FRITZ!Box"
|
||||
|
||||
export _H1="Content-type: multipart/form-data boundary=${_post_boundary}"
|
||||
_post "$(cat "${_post_request}")" "${DEPLOY_FRITZBOX_URL}/cgi-bin/firmwarecfg" | grep SSL
|
||||
|
||||
retval=$?
|
||||
if [ $retval = 0 ]; then
|
||||
_info "Upload successful"
|
||||
else
|
||||
_err "Upload failed"
|
||||
fi
|
||||
rm "${_post_request}"
|
||||
|
||||
return $retval
|
||||
}
|
||||
143
deploy/gcore_cdn.sh
Normal file
143
deploy/gcore_cdn.sh
Normal file
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Here is the script to deploy the cert to G-Core CDN service (https://gcore.com/) using the G-Core Labs API (https://apidocs.gcore.com/cdn).
|
||||
# Returns 0 when success.
|
||||
#
|
||||
# Written by temoffey <temofffey@gmail.com>
|
||||
# Public domain, 2019
|
||||
# Update by DreamOfIce <admin@dreamofice.cn> in 2023
|
||||
|
||||
#export DEPLOY_GCORE_CDN_USERNAME=myusername
|
||||
#export DEPLOY_GCORE_CDN_PASSWORD=mypassword
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
|
||||
gcore_cdn_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
_fullchain=$(tr '\r\n' '*#' <"$_cfullchain" | sed 's/*#/#/g;s/##/#/g;s/#/\\n/g')
|
||||
_key=$(tr '\r\n' '*#' <"$_ckey" | sed 's/*#/#/g;s/#/\\n/g')
|
||||
|
||||
_debug _fullchain "$_fullchain"
|
||||
_debug _key "$_key"
|
||||
|
||||
if [ -z "$DEPLOY_GCORE_CDN_USERNAME" ]; then
|
||||
if [ -z "$Le_Deploy_gcore_cdn_username" ]; then
|
||||
_err "Please define the target username: export DEPLOY_GCORE_CDN_USERNAME=username"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
Le_Deploy_gcore_cdn_username="$DEPLOY_GCORE_CDN_USERNAME"
|
||||
_savedomainconf Le_Deploy_gcore_cdn_username "$Le_Deploy_gcore_cdn_username"
|
||||
fi
|
||||
|
||||
if [ -z "$DEPLOY_GCORE_CDN_PASSWORD" ]; then
|
||||
if [ -z "$Le_Deploy_gcore_cdn_password" ]; then
|
||||
_err "Please define the target password: export DEPLOY_GCORE_CDN_PASSWORD=password"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
Le_Deploy_gcore_cdn_password="$DEPLOY_GCORE_CDN_PASSWORD"
|
||||
_savedomainconf Le_Deploy_gcore_cdn_password "$Le_Deploy_gcore_cdn_password"
|
||||
fi
|
||||
|
||||
_info "Get authorization token"
|
||||
_request="{\"username\":\"$Le_Deploy_gcore_cdn_username\",\"password\":\"$Le_Deploy_gcore_cdn_password\"}"
|
||||
_debug _request "$_request"
|
||||
export _H1="Content-Type:application/json"
|
||||
_response=$(_post "$_request" "https://api.gcore.com/iam/auth/jwt/login")
|
||||
_debug _response "$_response"
|
||||
_regex=".*\"access\":\"\([-._0-9A-Za-z]*\)\".*$"
|
||||
_debug _regex "$_regex"
|
||||
_token=$(echo "$_response" | sed -n "s/$_regex/\1/p")
|
||||
_debug _token "$_token"
|
||||
|
||||
if [ -z "$_token" ]; then
|
||||
_err "Error G-Core Labs API authorization"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Find CDN resource with cname $_cdomain"
|
||||
export _H2="Authorization:Bearer $_token"
|
||||
_response=$(_get "https://api.gcore.com/cdn/resources")
|
||||
_debug _response "$_response"
|
||||
_regex="\"primary_resource\":null},"
|
||||
_debug _regex "$_regex"
|
||||
_response=$(echo "$_response" | sed "s/$_regex/$_regex\n/g")
|
||||
_debug _response "$_response"
|
||||
_regex="^.*\"cname\":\"$_cdomain\".*$"
|
||||
_debug _regex "$_regex"
|
||||
_resource=$(echo "$_response" | _egrep_o "$_regex")
|
||||
_debug _resource "$_resource"
|
||||
_regex=".*\"id\":\([0-9]*\).*$"
|
||||
_debug _regex "$_regex"
|
||||
_resourceId=$(echo "$_resource" | sed -n "s/$_regex/\1/p")
|
||||
_debug _resourceId "$_resourceId"
|
||||
_regex=".*\"sslData\":\([0-9]*\).*$"
|
||||
_debug _regex "$_regex"
|
||||
_sslDataOld=$(echo "$_resource" | sed -n "s/$_regex/\1/p")
|
||||
_debug _sslDataOld "$_sslDataOld"
|
||||
_regex=".*\"originGroup\":\([0-9]*\).*$"
|
||||
_debug _regex "$_regex"
|
||||
_originGroup=$(echo "$_resource" | sed -n "s/$_regex/\1/p")
|
||||
_debug _originGroup "$_originGroup"
|
||||
|
||||
if [ -z "$_resourceId" ] || [ -z "$_originGroup" ]; then
|
||||
_err "Not found CDN resource with cname $_cdomain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Add new SSL certificate"
|
||||
_date=$(date "+%d.%m.%Y %H:%M:%S")
|
||||
_request="{\"name\":\"$_cdomain ($_date)\",\"sslCertificate\":\"$_fullchain\",\"sslPrivateKey\":\"$_key\"}"
|
||||
_debug _request "$_request"
|
||||
_response=$(_post "$_request" "https://api.gcore.com/cdn/sslData")
|
||||
_debug _response "$_response"
|
||||
_regex=".*\"id\":\([0-9]*\).*$"
|
||||
_debug _regex "$_regex"
|
||||
_sslDataAdd=$(echo "$_response" | sed -n "s/$_regex/\1/p")
|
||||
_debug _sslDataAdd "$_sslDataAdd"
|
||||
|
||||
if [ -z "$_sslDataAdd" ]; then
|
||||
_err "Error new SSL certificate add"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Update CDN resource"
|
||||
_request="{\"originGroup\":$_originGroup,\"sslData\":$_sslDataAdd}"
|
||||
_debug _request "$_request"
|
||||
_response=$(_post "$_request" "https://api.gcore.com/cdn/resources/$_resourceId" '' "PUT")
|
||||
_debug _response "$_response"
|
||||
_regex=".*\"sslData\":\([0-9]*\).*$"
|
||||
_debug _regex "$_regex"
|
||||
_sslDataNew=$(echo "$_response" | sed -n "s/$_regex/\1/p")
|
||||
_debug _sslDataNew "$_sslDataNew"
|
||||
|
||||
if [ "$_sslDataNew" != "$_sslDataAdd" ]; then
|
||||
_err "Error CDN resource update"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -z "$_sslDataOld" ] || [ "$_sslDataOld" = "null" ]; then
|
||||
_info "Not found old SSL certificate"
|
||||
else
|
||||
_info "Delete old SSL certificate"
|
||||
_response=$(_post '' "https://api.gcore.com/cdn/sslData/$_sslDataOld" '' "DELETE")
|
||||
_debug _response "$_response"
|
||||
fi
|
||||
|
||||
_info "Certificate successfully deployed"
|
||||
return 0
|
||||
}
|
||||
80
deploy/gitlab.sh
Normal file
80
deploy/gitlab.sh
Normal file
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Script to deploy certificate to a Gitlab hosted page
|
||||
|
||||
# The following variables exported from environment will be used.
|
||||
# If not set then values previously saved in domain.conf file are used.
|
||||
|
||||
# All the variables are required
|
||||
|
||||
# export GITLAB_TOKEN="xxxxxxx"
|
||||
# export GITLAB_PROJECT_ID=012345
|
||||
# export GITLAB_DOMAIN="mydomain.com"
|
||||
|
||||
gitlab_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
if [ -z "$GITLAB_TOKEN" ]; then
|
||||
if [ -z "$Le_Deploy_gitlab_token" ]; then
|
||||
_err "GITLAB_TOKEN not defined."
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
Le_Deploy_gitlab_token="$GITLAB_TOKEN"
|
||||
_savedomainconf Le_Deploy_gitlab_token "$Le_Deploy_gitlab_token"
|
||||
fi
|
||||
|
||||
if [ -z "$GITLAB_PROJECT_ID" ]; then
|
||||
if [ -z "$Le_Deploy_gitlab_project_id" ]; then
|
||||
_err "GITLAB_PROJECT_ID not defined."
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
Le_Deploy_gitlab_project_id="$GITLAB_PROJECT_ID"
|
||||
_savedomainconf Le_Deploy_gitlab_project_id "$Le_Deploy_gitlab_project_id"
|
||||
fi
|
||||
|
||||
if [ -z "$GITLAB_DOMAIN" ]; then
|
||||
if [ -z "$Le_Deploy_gitlab_domain" ]; then
|
||||
_err "GITLAB_DOMAIN not defined."
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
Le_Deploy_gitlab_domain="$GITLAB_DOMAIN"
|
||||
_savedomainconf Le_Deploy_gitlab_domain "$Le_Deploy_gitlab_domain"
|
||||
fi
|
||||
|
||||
string_fullchain=$(_url_encode <"$_cfullchain")
|
||||
string_key=$(_url_encode <"$_ckey")
|
||||
|
||||
body="certificate=$string_fullchain&key=$string_key"
|
||||
|
||||
export _H1="PRIVATE-TOKEN: $Le_Deploy_gitlab_token"
|
||||
|
||||
gitlab_url="https://gitlab.com/api/v4/projects/$Le_Deploy_gitlab_project_id/pages/domains/$Le_Deploy_gitlab_domain"
|
||||
|
||||
_response=$(_post "$body" "$gitlab_url" 0 PUT | _dbase64 "multiline")
|
||||
|
||||
error_response="error"
|
||||
|
||||
if test "${_response#*"$error_response"}" != "$_response"; then
|
||||
_err "Error in deploying certificate:"
|
||||
_err "$_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug response "$_response"
|
||||
_info "Certificate successfully deployed"
|
||||
|
||||
return 0
|
||||
}
|
||||
414
deploy/haproxy.sh
Normal file
414
deploy/haproxy.sh
Normal file
@@ -0,0 +1,414 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Script for acme.sh to deploy certificates to haproxy
|
||||
#
|
||||
# The following variables can be exported:
|
||||
#
|
||||
# export DEPLOY_HAPROXY_PEM_NAME="${domain}.pem"
|
||||
#
|
||||
# Defines the name of the PEM file.
|
||||
# Defaults to "<domain>.pem"
|
||||
#
|
||||
# export DEPLOY_HAPROXY_PEM_PATH="/etc/haproxy"
|
||||
#
|
||||
# Defines location of PEM file for HAProxy.
|
||||
# Defaults to /etc/haproxy
|
||||
#
|
||||
# export DEPLOY_HAPROXY_RELOAD="systemctl reload haproxy"
|
||||
#
|
||||
# OPTIONAL: Reload command used post deploy
|
||||
# This defaults to be a no-op (ie "true").
|
||||
# It is strongly recommended to set this something that makes sense
|
||||
# for your distro.
|
||||
#
|
||||
# export DEPLOY_HAPROXY_ISSUER="no"
|
||||
#
|
||||
# OPTIONAL: Places CA file as "${DEPLOY_HAPROXY_PEM}.issuer"
|
||||
# Note: Required for OCSP stapling to work
|
||||
#
|
||||
# export DEPLOY_HAPROXY_BUNDLE="no"
|
||||
#
|
||||
# OPTIONAL: Deploy this certificate as part of a multi-cert bundle
|
||||
# This adds a suffix to the certificate based on the certificate type
|
||||
# eg RSA certificates will have .rsa as a suffix to the file name
|
||||
# HAProxy will load all certificates and provide one or the other
|
||||
# depending on client capabilities
|
||||
# Note: This functionality requires HAProxy was compiled against
|
||||
# a version of OpenSSL that supports this.
|
||||
#
|
||||
# export DEPLOY_HAPROXY_HOT_UPDATE="yes"
|
||||
# export DEPLOY_HAPROXY_STATS_SOCKET="UNIX:/run/haproxy/admin.sock"
|
||||
#
|
||||
# OPTIONAL: Deploy the certificate over the HAProxy stats socket without
|
||||
# needing to reload HAProxy. Default is "no".
|
||||
#
|
||||
# Require the socat binary. DEPLOY_HAPROXY_STATS_SOCKET variable uses the socat
|
||||
# address format. The certificate can be deployed to a comma separated ',' list
|
||||
# of hosts ("TCP4:10.0.0.1:1999,TCP4:10.0.0.2:1999")
|
||||
#
|
||||
# export DEPLOY_HAPROXY_MASTER_CLI="UNIX:/run/haproxy-master.sock"
|
||||
#
|
||||
# OPTIONAL: To use the master CLI with DEPLOY_HAPROXY_HOT_UPDATE="yes" instead
|
||||
# of a stats socket, use this variable.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
haproxy_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
_cmdpfx=""
|
||||
|
||||
# Some defaults
|
||||
DEPLOY_HAPROXY_PEM_PATH_DEFAULT="/etc/haproxy"
|
||||
DEPLOY_HAPROXY_PEM_NAME_DEFAULT="${_cdomain}.pem"
|
||||
DEPLOY_HAPROXY_BUNDLE_DEFAULT="no"
|
||||
DEPLOY_HAPROXY_ISSUER_DEFAULT="no"
|
||||
DEPLOY_HAPROXY_RELOAD_DEFAULT="true"
|
||||
DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT="no"
|
||||
DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT="UNIX:/run/haproxy/admin.sock"
|
||||
|
||||
_debug _cdomain "${_cdomain}"
|
||||
_debug _ckey "${_ckey}"
|
||||
_debug _ccert "${_ccert}"
|
||||
_debug _cca "${_cca}"
|
||||
_debug _cfullchain "${_cfullchain}"
|
||||
|
||||
# PEM_PATH is optional. If not provided then assume "${DEPLOY_HAPROXY_PEM_PATH_DEFAULT}"
|
||||
_getdeployconf DEPLOY_HAPROXY_PEM_PATH
|
||||
_debug2 DEPLOY_HAPROXY_PEM_PATH "${DEPLOY_HAPROXY_PEM_PATH}"
|
||||
if [ -n "${DEPLOY_HAPROXY_PEM_PATH}" ]; then
|
||||
Le_Deploy_haproxy_pem_path="${DEPLOY_HAPROXY_PEM_PATH}"
|
||||
_savedomainconf Le_Deploy_haproxy_pem_path "${Le_Deploy_haproxy_pem_path}"
|
||||
elif [ -z "${Le_Deploy_haproxy_pem_path}" ]; then
|
||||
Le_Deploy_haproxy_pem_path="${DEPLOY_HAPROXY_PEM_PATH_DEFAULT}"
|
||||
fi
|
||||
|
||||
# Ensure PEM_PATH exists
|
||||
if [ -d "${Le_Deploy_haproxy_pem_path}" ]; then
|
||||
_debug "PEM_PATH ${Le_Deploy_haproxy_pem_path} exists"
|
||||
else
|
||||
_err "PEM_PATH ${Le_Deploy_haproxy_pem_path} does not exist"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# PEM_NAME is optional. If not provided then assume "${DEPLOY_HAPROXY_PEM_NAME_DEFAULT}"
|
||||
_getdeployconf DEPLOY_HAPROXY_PEM_NAME
|
||||
_debug2 DEPLOY_HAPROXY_PEM_NAME "${DEPLOY_HAPROXY_PEM_NAME}"
|
||||
if [ -n "${DEPLOY_HAPROXY_PEM_NAME}" ]; then
|
||||
Le_Deploy_haproxy_pem_name="${DEPLOY_HAPROXY_PEM_NAME}"
|
||||
_savedomainconf Le_Deploy_haproxy_pem_name "${Le_Deploy_haproxy_pem_name}"
|
||||
elif [ -z "${Le_Deploy_haproxy_pem_name}" ]; then
|
||||
Le_Deploy_haproxy_pem_name="${DEPLOY_HAPROXY_PEM_NAME_DEFAULT}"
|
||||
# We better not have '*' as the first character
|
||||
if [ "${Le_Deploy_haproxy_pem_name%%"${Le_Deploy_haproxy_pem_name#?}"}" = '*' ]; then
|
||||
# removes the first characters and add a _ instead
|
||||
Le_Deploy_haproxy_pem_name="_${Le_Deploy_haproxy_pem_name#?}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# BUNDLE is optional. If not provided then assume "${DEPLOY_HAPROXY_BUNDLE_DEFAULT}"
|
||||
_getdeployconf DEPLOY_HAPROXY_BUNDLE
|
||||
_debug2 DEPLOY_HAPROXY_BUNDLE "${DEPLOY_HAPROXY_BUNDLE}"
|
||||
if [ -n "${DEPLOY_HAPROXY_BUNDLE}" ]; then
|
||||
Le_Deploy_haproxy_bundle="${DEPLOY_HAPROXY_BUNDLE}"
|
||||
_savedomainconf Le_Deploy_haproxy_bundle "${Le_Deploy_haproxy_bundle}"
|
||||
elif [ -z "${Le_Deploy_haproxy_bundle}" ]; then
|
||||
Le_Deploy_haproxy_bundle="${DEPLOY_HAPROXY_BUNDLE_DEFAULT}"
|
||||
fi
|
||||
|
||||
# ISSUER is optional. If not provided then assume "${DEPLOY_HAPROXY_ISSUER_DEFAULT}"
|
||||
_getdeployconf DEPLOY_HAPROXY_ISSUER
|
||||
_debug2 DEPLOY_HAPROXY_ISSUER "${DEPLOY_HAPROXY_ISSUER}"
|
||||
if [ -n "${DEPLOY_HAPROXY_ISSUER}" ]; then
|
||||
Le_Deploy_haproxy_issuer="${DEPLOY_HAPROXY_ISSUER}"
|
||||
_savedomainconf Le_Deploy_haproxy_issuer "${Le_Deploy_haproxy_issuer}"
|
||||
elif [ -z "${Le_Deploy_haproxy_issuer}" ]; then
|
||||
Le_Deploy_haproxy_issuer="${DEPLOY_HAPROXY_ISSUER_DEFAULT}"
|
||||
fi
|
||||
|
||||
# RELOAD is optional. If not provided then assume "${DEPLOY_HAPROXY_RELOAD_DEFAULT}"
|
||||
_getdeployconf DEPLOY_HAPROXY_RELOAD
|
||||
_debug2 DEPLOY_HAPROXY_RELOAD "${DEPLOY_HAPROXY_RELOAD}"
|
||||
if [ -n "${DEPLOY_HAPROXY_RELOAD}" ]; then
|
||||
Le_Deploy_haproxy_reload="${DEPLOY_HAPROXY_RELOAD}"
|
||||
_savedomainconf Le_Deploy_haproxy_reload "${Le_Deploy_haproxy_reload}"
|
||||
elif [ -z "${Le_Deploy_haproxy_reload}" ]; then
|
||||
Le_Deploy_haproxy_reload="${DEPLOY_HAPROXY_RELOAD_DEFAULT}"
|
||||
fi
|
||||
|
||||
# HOT_UPDATE is optional. If not provided then assume "${DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT}"
|
||||
_getdeployconf DEPLOY_HAPROXY_HOT_UPDATE
|
||||
_debug2 DEPLOY_HAPROXY_HOT_UPDATE "${DEPLOY_HAPROXY_HOT_UPDATE}"
|
||||
if [ -n "${DEPLOY_HAPROXY_HOT_UPDATE}" ]; then
|
||||
Le_Deploy_haproxy_hot_update="${DEPLOY_HAPROXY_HOT_UPDATE}"
|
||||
_savedomainconf Le_Deploy_haproxy_hot_update "${Le_Deploy_haproxy_hot_update}"
|
||||
elif [ -z "${Le_Deploy_haproxy_hot_update}" ]; then
|
||||
Le_Deploy_haproxy_hot_update="${DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT}"
|
||||
fi
|
||||
|
||||
# STATS_SOCKET is optional. If not provided then assume "${DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT}"
|
||||
_getdeployconf DEPLOY_HAPROXY_STATS_SOCKET
|
||||
_debug2 DEPLOY_HAPROXY_STATS_SOCKET "${DEPLOY_HAPROXY_STATS_SOCKET}"
|
||||
if [ -n "${DEPLOY_HAPROXY_STATS_SOCKET}" ]; then
|
||||
Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_STATS_SOCKET}"
|
||||
_savedomainconf Le_Deploy_haproxy_stats_socket "${Le_Deploy_haproxy_stats_socket}"
|
||||
elif [ -z "${Le_Deploy_haproxy_stats_socket}" ]; then
|
||||
Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT}"
|
||||
fi
|
||||
|
||||
# MASTER_CLI is optional. No defaults are used. When the master CLI is used,
|
||||
# all commands are sent with a prefix.
|
||||
_getdeployconf DEPLOY_HAPROXY_MASTER_CLI
|
||||
_debug2 DEPLOY_HAPROXY_MASTER_CLI "${DEPLOY_HAPROXY_MASTER_CLI}"
|
||||
if [ -n "${DEPLOY_HAPROXY_MASTER_CLI}" ]; then
|
||||
Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_MASTER_CLI}"
|
||||
_savedomainconf Le_Deploy_haproxy_stats_socket "${Le_Deploy_haproxy_stats_socket}"
|
||||
_cmdpfx="@1 " # command prefix used for master CLI only.
|
||||
fi
|
||||
|
||||
# Set the suffix depending if we are creating a bundle or not
|
||||
if [ "${Le_Deploy_haproxy_bundle}" = "yes" ]; then
|
||||
_info "Bundle creation requested"
|
||||
# Initialise $Le_Keylength if its not already set
|
||||
if [ -z "${Le_Keylength}" ]; then
|
||||
Le_Keylength=""
|
||||
fi
|
||||
if _isEccKey "${Le_Keylength}"; then
|
||||
_info "ECC key type detected"
|
||||
_suffix=".ecdsa"
|
||||
else
|
||||
_info "RSA key type detected"
|
||||
_suffix=".rsa"
|
||||
fi
|
||||
else
|
||||
_suffix=""
|
||||
fi
|
||||
_debug _suffix "${_suffix}"
|
||||
|
||||
# Set variables for later
|
||||
_pem="${Le_Deploy_haproxy_pem_path}/${Le_Deploy_haproxy_pem_name}${_suffix}"
|
||||
_issuer="${_pem}.issuer"
|
||||
_ocsp="${_pem}.ocsp"
|
||||
_reload="${Le_Deploy_haproxy_reload}"
|
||||
|
||||
_info "Deploying PEM file"
|
||||
# Create a temporary PEM file
|
||||
_temppem="$(_mktemp)"
|
||||
_debug _temppem "${_temppem}"
|
||||
cat "${_ccert}" "${_cca}" "${_ckey}" | grep . >"${_temppem}"
|
||||
_ret="$?"
|
||||
|
||||
# Check that we could create the temporary file
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Error code ${_ret} returned during PEM file creation"
|
||||
[ -f "${_temppem}" ] && rm -f "${_temppem}"
|
||||
return ${_ret}
|
||||
fi
|
||||
|
||||
# Move PEM file into place
|
||||
_info "Moving new certificate into place"
|
||||
_debug _pem "${_pem}"
|
||||
cat "${_temppem}" >"${_pem}"
|
||||
_ret=$?
|
||||
|
||||
# Clean up temp file
|
||||
[ -f "${_temppem}" ] && rm -f "${_temppem}"
|
||||
|
||||
# Deal with any failure of moving PEM file into place
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Error code ${_ret} returned while moving new certificate into place"
|
||||
return ${_ret}
|
||||
fi
|
||||
|
||||
# Update .issuer file if requested
|
||||
if [ "${Le_Deploy_haproxy_issuer}" = "yes" ]; then
|
||||
_info "Updating .issuer file"
|
||||
_debug _issuer "${_issuer}"
|
||||
cat "${_cca}" >"${_issuer}"
|
||||
_ret="$?"
|
||||
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Error code ${_ret} returned while copying issuer/CA certificate into place"
|
||||
return ${_ret}
|
||||
fi
|
||||
else
|
||||
[ -f "${_issuer}" ] && _err "Issuer file update not requested but .issuer file exists"
|
||||
fi
|
||||
|
||||
# Update .ocsp file if certificate was requested with --ocsp/--ocsp-must-staple option
|
||||
if [ -z "${Le_OCSP_Staple}" ]; then
|
||||
Le_OCSP_Staple="0"
|
||||
fi
|
||||
if [ "${Le_OCSP_Staple}" = "1" ]; then
|
||||
_info "Updating OCSP stapling info"
|
||||
_debug _ocsp "${_ocsp}"
|
||||
_info "Extracting OCSP URL"
|
||||
_ocsp_url=$(${ACME_OPENSSL_BIN:-openssl} x509 -noout -ocsp_uri -in "${_pem}")
|
||||
_debug _ocsp_url "${_ocsp_url}"
|
||||
|
||||
# Only process OCSP if URL was present
|
||||
if [ "${_ocsp_url}" != "" ]; then
|
||||
# Extract the hostname from the OCSP URL
|
||||
_info "Extracting OCSP URL"
|
||||
_ocsp_host=$(echo "${_ocsp_url}" | cut -d/ -f3)
|
||||
_debug _ocsp_host "${_ocsp_host}"
|
||||
|
||||
# Only process the certificate if we have a .issuer file
|
||||
if [ -r "${_issuer}" ]; then
|
||||
# Check if issuer cert is also a root CA cert
|
||||
_subjectdn=$(${ACME_OPENSSL_BIN:-openssl} x509 -in "${_issuer}" -subject -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)
|
||||
_debug _subjectdn "${_subjectdn}"
|
||||
_issuerdn=$(${ACME_OPENSSL_BIN:-openssl} x509 -in "${_issuer}" -issuer -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)
|
||||
_debug _issuerdn "${_issuerdn}"
|
||||
_info "Requesting OCSP response"
|
||||
# If the issuer is a CA cert then our command line has "-CAfile" added
|
||||
if [ "${_subjectdn}" = "${_issuerdn}" ]; then
|
||||
_cafile_argument="-CAfile \"${_issuer}\""
|
||||
else
|
||||
_cafile_argument=""
|
||||
fi
|
||||
_debug _cafile_argument "${_cafile_argument}"
|
||||
# OpenSSL 1.1+ expects -header Host=value (one argument), while
|
||||
# LibreSSL keeps the old two-argument form -header Host value at any
|
||||
# version (3.x/4.x), so it must be detected by name, not by number.
|
||||
_openssl_name=$(${ACME_OPENSSL_BIN:-openssl} version | cut -d' ' -f1)
|
||||
_openssl_version=$(${ACME_OPENSSL_BIN:-openssl} version | cut -d' ' -f2)
|
||||
_debug _openssl_name "${_openssl_name}"
|
||||
_debug _openssl_version "${_openssl_version}"
|
||||
_openssl_major=$(echo "${_openssl_version}" | cut -d '.' -f1)
|
||||
_openssl_minor=$(echo "${_openssl_version}" | cut -d '.' -f2)
|
||||
if [ "${_openssl_name}" = "LibreSSL" ]; then
|
||||
_header_sep=" "
|
||||
elif [ "${_openssl_major}" -eq "1" ] && [ "${_openssl_minor}" -ge "1" ] || [ "${_openssl_major}" -ge "2" ]; then
|
||||
_header_sep="="
|
||||
else
|
||||
_header_sep=" "
|
||||
fi
|
||||
# Request the OCSP response from the issuer and store it
|
||||
_openssl_ocsp_cmd="${ACME_OPENSSL_BIN:-openssl} ocsp \
|
||||
-issuer \"${_issuer}\" \
|
||||
-cert \"${_pem}\" \
|
||||
-url \"${_ocsp_url}\" \
|
||||
-header Host${_header_sep}\"${_ocsp_host}\" \
|
||||
-respout \"${_ocsp}\" \
|
||||
-verify_other \"${_issuer}\" \
|
||||
${_cafile_argument} \
|
||||
| grep -q \"${_pem}: good\""
|
||||
_debug _openssl_ocsp_cmd "${_openssl_ocsp_cmd}"
|
||||
eval "${_openssl_ocsp_cmd}"
|
||||
_ret=$?
|
||||
else
|
||||
# Non fatal: No issuer file was present so no OCSP stapling file created
|
||||
_err "OCSP stapling in use but no .issuer file was present"
|
||||
fi
|
||||
else
|
||||
# Non fatal: No OCSP url was found int the certificate
|
||||
_err "OCSP update requested but no OCSP URL was found in certificate"
|
||||
fi
|
||||
|
||||
# Non fatal: Check return code of openssl command
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Updating OCSP stapling failed with return code ${_ret}"
|
||||
fi
|
||||
else
|
||||
# An OCSP file was already present but certificate did not have OCSP extension
|
||||
if [ -f "${_ocsp}" ]; then
|
||||
_err "OCSP was not requested but .ocsp file exists."
|
||||
# Could remove the file at this step, although HAProxy just ignores it in this case
|
||||
# rm -f "${_ocsp}" || _err "Problem removing stale .ocsp file"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${Le_Deploy_haproxy_hot_update}" = "yes" ]; then
|
||||
# set the socket name for messages
|
||||
if [ -n "${_cmdpfx}" ]; then
|
||||
_socketname="master CLI"
|
||||
else
|
||||
_socketname="stats socket"
|
||||
fi
|
||||
|
||||
# Update certificate over HAProxy stats socket or master CLI.
|
||||
if _exists socat; then
|
||||
IFS=','
|
||||
for _statssock in ${Le_Deploy_haproxy_stats_socket}; do
|
||||
# look for the certificate on the stats socket, to choose between updating or creating one
|
||||
_socat_cert_cmd="echo '${_cmdpfx}show ssl cert' | socat '${_statssock}' - | grep -q '^${_pem}$'"
|
||||
_debug _socat_cert_cmd "${_socat_cert_cmd}"
|
||||
eval "${_socat_cert_cmd}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_newcert="1"
|
||||
_info "Creating new certificate '${_pem}' over HAProxy ${_socketname}."
|
||||
# certificate wasn't found, it's a new one. We should check if the crt-list exists and creates/inserts the certificate.
|
||||
_socat_crtlist_show_cmd="echo '${_cmdpfx}show ssl crt-list' | socat '${_statssock}' - | grep -q '^${Le_Deploy_haproxy_pem_path}$'"
|
||||
_debug _socat_crtlist_show_cmd "${_socat_crtlist_show_cmd}"
|
||||
eval "${_socat_crtlist_show_cmd}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Couldn't find '${Le_Deploy_haproxy_pem_path}' in haproxy 'show ssl crt-list'"
|
||||
return "${_ret}"
|
||||
fi
|
||||
# create a new certificate
|
||||
_socat_new_cmd="echo '${_cmdpfx}new ssl cert ${_pem}' | socat '${_statssock}' - | grep -q 'New empty'"
|
||||
_debug _socat_new_cmd "${_socat_new_cmd}"
|
||||
eval "${_socat_new_cmd}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Couldn't create '${_pem}' in haproxy"
|
||||
return "${_ret}"
|
||||
fi
|
||||
else
|
||||
_info "Update existing certificate '${_pem}' over HAProxy ${_socketname}."
|
||||
fi
|
||||
# printf %b, not "echo -e": dash's echo has no -e and sends a literal "-e " to the socket.
|
||||
# "Transaction updated" is replied instead of "created" when an uncommitted transaction exists.
|
||||
_socat_cert_set_cmd="printf '%b\n' '${_cmdpfx}set ssl cert ${_pem} <<\n$(cat "${_pem}")\n' | socat '${_statssock}' - | grep -qE 'Transaction (created|updated)'"
|
||||
_secure_debug _socat_cert_set_cmd "${_socat_cert_set_cmd}"
|
||||
eval "${_socat_cert_set_cmd}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Can't update '${_pem}' in haproxy"
|
||||
return "${_ret}"
|
||||
fi
|
||||
_socat_cert_commit_cmd="echo '${_cmdpfx}commit ssl cert ${_pem}' | socat '${_statssock}' - | grep -q '^Success!$'"
|
||||
_debug _socat_cert_commit_cmd "${_socat_cert_commit_cmd}"
|
||||
eval "${_socat_cert_commit_cmd}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Can't commit '${_pem}' in haproxy"
|
||||
return ${_ret}
|
||||
fi
|
||||
if [ "${_newcert}" = "1" ]; then
|
||||
# if this is a new certificate, it needs to be inserted into the crt-list`
|
||||
_socat_cert_add_cmd="echo '${_cmdpfx}add ssl crt-list ${Le_Deploy_haproxy_pem_path} ${_pem}' | socat '${_statssock}' - | grep -q 'Success!'"
|
||||
_debug _socat_cert_add_cmd "${_socat_cert_add_cmd}"
|
||||
eval "${_socat_cert_add_cmd}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Can't update '${_pem}' in haproxy"
|
||||
return "${_ret}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
else
|
||||
_err "'socat' is not available, couldn't update over ${_socketname}"
|
||||
fi
|
||||
else
|
||||
# Reload HAProxy
|
||||
_debug _reload "${_reload}"
|
||||
eval "${_reload}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Error code ${_ret} during reload"
|
||||
return ${_ret}
|
||||
else
|
||||
_info "Reload successful"
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
114
deploy/ikuai.sh
Normal file
114
deploy/ikuai.sh
Normal file
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Here is a script to deploy cert to ikuai using curl
|
||||
#
|
||||
# it requires following environment variables:
|
||||
#
|
||||
# IKUAI_SCHEME="http" - http or https , defaults to "http"
|
||||
# IKUAI_HOSTNAME="localhost" - host , defaults to "192.168.9.1"
|
||||
# IKUAI_PORT="80" - port , defaults to "80"
|
||||
# IKUAI_USERNAME="admin" - username , defaults to "admin"
|
||||
# IKUAI_PASSWORD="yourPassword" - password
|
||||
# IKUAI_CERT_ID=1 - ikuai cert id , defaults to 1, and only 1 is supported for now !!!
|
||||
#
|
||||
#returns 0 means success, otherwise error.
|
||||
#
|
||||
######## Public functions #####################
|
||||
#
|
||||
#domain keyfile certfile cafile fullchain
|
||||
ikuai_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
# Get deploy conf
|
||||
_getdeployconf IKUAI_SCHEME
|
||||
_getdeployconf IKUAI_HOSTNAME
|
||||
_getdeployconf IKUAI_PORT
|
||||
_getdeployconf IKUAI_USERNAME
|
||||
_getdeployconf IKUAI_PASSWORD
|
||||
_getdeployconf IKUAI_CERT_ID
|
||||
|
||||
# Use default if not provided
|
||||
[ -n "$IKUAI_SCHEME" ] || IKUAI_SCHEME="http"
|
||||
[ -n "$IKUAI_HOSTNAME" ] || IKUAI_HOSTNAME="192.168.9.1"
|
||||
[ -n "$IKUAI_PORT" ] || IKUAI_PORT=80
|
||||
[ -n "$IKUAI_USERNAME" ] || IKUAI_USERNAME="admin"
|
||||
[ -n "$IKUAI_CERT_ID" ] || IKUAI_CERT_ID=1
|
||||
|
||||
if [ -z "$IKUAI_PASSWORD" ]; then
|
||||
_err "please define IKUAI_PASSWORD."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug2 IKUAI_SCHEME "$IKUAI_SCHEME"
|
||||
_debug2 IKUAI_HOSTNAME "$IKUAI_HOSTNAME"
|
||||
_debug2 IKUAI_PORT "$IKUAI_PORT"
|
||||
_debug2 IKUAI_USERNAME "$IKUAI_USERNAME"
|
||||
_secure_debug2 IKUAI_PASSWORD "$IKUAI_PASSWORD"
|
||||
|
||||
_info "Login to ikuai ..."
|
||||
_ikuai_url="$IKUAI_SCHEME://$IKUAI_HOSTNAME:$IKUAI_PORT"
|
||||
_pass_md5="$(printf "%s" "$IKUAI_PASSWORD" | _digest md5 hex | _lower_case)"
|
||||
_pass_salt="$(printf "salt_11%s" "$IKUAI_PASSWORD" | _base64)"
|
||||
_debug2 _ikuai_url "$_ikuai_url"
|
||||
|
||||
_login_req="{\"username\":\"$IKUAI_USERNAME\",\"passwd\":\"$_pass_md5\",\"pass\":\"$_pass_salt\",\"remember_password\":\"\"}"
|
||||
_response=$(_post "$_login_req" "$_ikuai_url/Action/login" "" "POST" "application/json")
|
||||
|
||||
_err_msg="$(printf "%s" "$_response" | _normalizeJson | _egrep_o '"ErrMsg":"[^"]*"' | cut -d'"' -f 4)"
|
||||
# check ErrMsg
|
||||
if [ "$_err_msg" != "Success" ]; then
|
||||
_err "Failed to login to ikuai: $_err_msg"
|
||||
return 1
|
||||
fi
|
||||
# check cookie
|
||||
_cookie="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2 | sed 's/;.*//')"
|
||||
if [ -z "$_cookie" ]; then
|
||||
_err "Fail to get the cookie."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Set cookie header
|
||||
_H1="Cookie: $_cookie; username=$IKUAI_USERNAME; login=1"
|
||||
|
||||
_info "Deploy the cert to ikuai ... "
|
||||
|
||||
# Should replace \n to @ ," " to #
|
||||
_cert_content_single_line="$(tr <"$_cfullchain" '\n' '@' | tr ' ' '#')"
|
||||
_key_content_single_line="$(tr <"$_ckey" '\n' '@' | tr ' ' '#')"
|
||||
|
||||
_debug2 _cert_content_single_line "$_cert_content_single_line"
|
||||
_secure_debug2 _key_content_single_line "$_key_content_single_line"
|
||||
|
||||
_key_manager_req="{\"func_name\":\"key_manager\",\"action\":\"save\",\"param\":{\"ca\":\"$_cert_content_single_line\",\"key\":\"$_key_content_single_line\",\"id\":$IKUAI_CERT_ID,\"enabled\":\"yes\",\"comment\":\"\"}}"
|
||||
_response=$(_post "$_key_manager_req" "$_ikuai_url/Action/call" "" "POST" "application/json")
|
||||
|
||||
_err_msg="$(printf "%s" "$_response" | _normalizeJson | _egrep_o '"ErrMsg":"[^"]*"' | cut -d'"' -f 4)"
|
||||
# check ErrMsg
|
||||
if [ "$_err_msg" != "Success" ]; then
|
||||
_err "Failed to deploy the cert to ikuai: $_err_msg"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Save the deploy config ... "
|
||||
# Save the config
|
||||
_savedeployconf IKUAI_SCHEME "$IKUAI_SCHEME"
|
||||
_savedeployconf IKUAI_HOSTNAME "$IKUAI_HOSTNAME"
|
||||
_savedeployconf IKUAI_PORT "$IKUAI_PORT"
|
||||
_savedeployconf IKUAI_USERNAME "$IKUAI_USERNAME"
|
||||
_savedeployconf IKUAI_PASSWORD "$IKUAI_PASSWORD"
|
||||
_savedeployconf IKUAI_CERT_ID "$IKUAI_CERT_ID"
|
||||
|
||||
_info "Successfully deployed certificate to ikuai. Enjoy! :>"
|
||||
|
||||
return 0
|
||||
}
|
||||
99
deploy/kemplm.sh
Executable file
99
deploy/kemplm.sh
Executable file
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#Here is a script to deploy cert to a Kemp Loadmaster.
|
||||
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
#DEPLOY_KEMP_TOKEN="token"
|
||||
#DEPLOY_KEMP_URL="https://kemplm.example.com"
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
kemplm_deploy() {
|
||||
_domain="$1"
|
||||
_key_file="$2"
|
||||
_cert_file="$3"
|
||||
_ca_file="$4"
|
||||
_fullchain_file="$5"
|
||||
|
||||
_debug _domain "$_domain"
|
||||
_debug _key_file "$_key_file"
|
||||
_debug _cert_file "$_cert_file"
|
||||
_debug _ca_file "$_ca_file"
|
||||
_debug _fullchain_file "$_fullchain_file"
|
||||
|
||||
if ! _exists jq; then
|
||||
_err "jq not found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Rename wildcard certs, kemp accepts only alphanumeric names so we delete '*.' from filename
|
||||
_kemp_domain=$(echo "${_domain}" | sed 's/\*\.//')
|
||||
_debug _kemp_domain "$_kemp_domain"
|
||||
|
||||
# Read config from saved values or env
|
||||
_getdeployconf DEPLOY_KEMP_TOKEN
|
||||
_getdeployconf DEPLOY_KEMP_URL
|
||||
|
||||
_debug DEPLOY_KEMP_URL "$DEPLOY_KEMP_URL"
|
||||
_secure_debug DEPLOY_KEMP_TOKEN "$DEPLOY_KEMP_TOKEN"
|
||||
|
||||
if [ -z "$DEPLOY_KEMP_TOKEN" ]; then
|
||||
_err "Kemp Loadmaster token is not found, please define DEPLOY_KEMP_TOKEN."
|
||||
return 1
|
||||
fi
|
||||
if [ -z "$DEPLOY_KEMP_URL" ]; then
|
||||
_err "Kemp Loadmaster URL is not found, please define DEPLOY_KEMP_URL."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Save current values
|
||||
_savedeployconf DEPLOY_KEMP_TOKEN "$DEPLOY_KEMP_TOKEN"
|
||||
_savedeployconf DEPLOY_KEMP_URL "$DEPLOY_KEMP_URL"
|
||||
|
||||
# Check if certificate is already installed
|
||||
_info "Check if certificate is already present"
|
||||
_list_request="{\"cmd\": \"listcert\", \"apikey\": \"${DEPLOY_KEMP_TOKEN}\"}"
|
||||
_debug3 _list_request "${_list_request}"
|
||||
_kemp_cert_count=$(HTTPS_INSECURE=1 _post "${_list_request}" "${DEPLOY_KEMP_URL}/accessv2" | jq -r '.cert[] | .name' | grep -c "^${_kemp_domain}$")
|
||||
_debug2 _kemp_cert_count "${_kemp_cert_count}"
|
||||
|
||||
_kemp_replace_cert=1
|
||||
if [ "${_kemp_cert_count}" -eq 0 ]; then
|
||||
_kemp_replace_cert=0
|
||||
_info "Certificate does not exist on Kemp Loadmaster"
|
||||
else
|
||||
_info "Certificate already exists on Kemp Loadmaster"
|
||||
fi
|
||||
_debug _kemp_replace_cert "${_kemp_replace_cert}"
|
||||
|
||||
# Upload new certificate to Kemp Loadmaster
|
||||
_kemp_upload_cert=$(_mktemp)
|
||||
cat "${_fullchain_file}" "${_key_file}" | base64 | tr -d '\n' >"${_kemp_upload_cert}"
|
||||
|
||||
_info "Uploading certificate to Kemp Loadmaster"
|
||||
_add_data=$(cat "${_kemp_upload_cert}")
|
||||
_add_request="{\"cmd\": \"addcert\", \"apikey\": \"${DEPLOY_KEMP_TOKEN}\", \"replace\": ${_kemp_replace_cert}, \"cert\": \"${_kemp_domain}\", \"data\": \"${_add_data}\"}"
|
||||
_debug3 _add_request "${_add_request}"
|
||||
_kemp_post_result=$(HTTPS_INSECURE=1 _post "${_add_request}" "${DEPLOY_KEMP_URL}/accessv2")
|
||||
_retval=$?
|
||||
_debug2 _kemp_post_result "${_kemp_post_result}"
|
||||
if [ "${_retval}" -eq 0 ]; then
|
||||
_kemp_post_status=$(echo "${_kemp_post_result}" | jq -r '.status')
|
||||
_kemp_post_message=$(echo "${_kemp_post_result}" | jq -r '.message')
|
||||
if [ "${_kemp_post_status}" = "ok" ]; then
|
||||
_info "Upload successful"
|
||||
else
|
||||
_err "Upload failed: ${_kemp_post_message}"
|
||||
_retval=1
|
||||
fi
|
||||
else
|
||||
_err "Upload failed"
|
||||
_retval=1
|
||||
fi
|
||||
|
||||
rm "${_kemp_upload_cert}"
|
||||
|
||||
return $_retval
|
||||
}
|
||||
25
deploy/keychain.sh
Normal file
25
deploy/keychain.sh
Normal file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
keychain_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
/usr/bin/security import "$_ckey" -k "/Library/Keychains/System.keychain"
|
||||
/usr/bin/security import "$_ccert" -k "/Library/Keychains/System.keychain"
|
||||
/usr/bin/security import "$_cca" -k "/Library/Keychains/System.keychain"
|
||||
/usr/bin/security import "$_cfullchain" -k "/Library/Keychains/System.keychain"
|
||||
|
||||
return 0
|
||||
}
|
||||
131
deploy/keyhelp.sh
Normal file
131
deploy/keyhelp.sh
Normal file
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Script to deploy certificate to KeyHelp
|
||||
# This deployment required following variables
|
||||
# export DEPLOY_KEYHELP_BASEURL="https://keyhelp.example.com"
|
||||
# export DEPLOY_KEYHELP_USERNAME="Your KeyHelp Username"
|
||||
# export DEPLOY_KEYHELP_PASSWORD="Your KeyHelp Password"
|
||||
# export DEPLOY_KEYHELP_DOMAIN_ID="Depoly certificate to this Domain ID"
|
||||
|
||||
# Open the 'Edit domain' page, and you will see id=xxx at the end of the URL. This is the Domain ID.
|
||||
# https://DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit&id=xxx
|
||||
|
||||
# If have more than one domain name
|
||||
# export DEPLOY_KEYHELP_DOMAIN_ID="111 222 333"
|
||||
|
||||
keyhelp_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
if [ -z "$DEPLOY_KEYHELP_BASEURL" ]; then
|
||||
_err "DEPLOY_KEYHELP_BASEURL is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf DEPLOY_KEYHELP_BASEURL "$DEPLOY_KEYHELP_BASEURL"
|
||||
fi
|
||||
|
||||
if [ -z "$DEPLOY_KEYHELP_USERNAME" ]; then
|
||||
_err "DEPLOY_KEYHELP_USERNAME is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf DEPLOY_KEYHELP_USERNAME "$DEPLOY_KEYHELP_USERNAME"
|
||||
fi
|
||||
|
||||
if [ -z "$DEPLOY_KEYHELP_PASSWORD" ]; then
|
||||
_err "DEPLOY_KEYHELP_PASSWORD is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf DEPLOY_KEYHELP_PASSWORD "$DEPLOY_KEYHELP_PASSWORD"
|
||||
fi
|
||||
|
||||
if [ -z "$DEPLOY_KEYHELP_DOMAIN_ID" ]; then
|
||||
_err "DEPLOY_KEYHELP_DOMAIN_ID is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf DEPLOY_KEYHELP_DOMAIN_ID "$DEPLOY_KEYHELP_DOMAIN_ID"
|
||||
fi
|
||||
|
||||
# Optional DEPLOY_KEYHELP_ENFORCE_HTTPS
|
||||
_getdeployconf DEPLOY_KEYHELP_ENFORCE_HTTPS
|
||||
# set default values for DEPLOY_KEYHELP_ENFORCE_HTTPS
|
||||
[ -n "${DEPLOY_KEYHELP_ENFORCE_HTTPS}" ] || DEPLOY_KEYHELP_ENFORCE_HTTPS="1"
|
||||
|
||||
_info "Logging in to keyhelp panel"
|
||||
username_encoded="$(printf "%s" "${DEPLOY_KEYHELP_USERNAME}" | _url_encode)"
|
||||
password_encoded="$(printf "%s" "${DEPLOY_KEYHELP_PASSWORD}" | _url_encode)"
|
||||
_H1="Content-Type: application/x-www-form-urlencoded"
|
||||
_response=$(_get "$DEPLOY_KEYHELP_BASEURL/index.php?submit=1&username=$username_encoded&password=$password_encoded" "TRUE")
|
||||
_cookie="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2)"
|
||||
|
||||
# If cookies is not empty then logon successful
|
||||
if [ -z "$_cookie" ]; then
|
||||
_err "Fail to get cookie."
|
||||
return 1
|
||||
fi
|
||||
_debug "cookie" "$_cookie"
|
||||
|
||||
_info "Uploading certificate"
|
||||
_date=$(date +"%Y%m%d")
|
||||
encoded_key="$(_url_encode <"$_ckey")"
|
||||
encoded_ccert="$(_url_encode <"$_ccert")"
|
||||
encoded_cca="$(_url_encode <"$_cca")"
|
||||
certificate_name="$_cdomain-$_date"
|
||||
|
||||
_request_body="submit=1&certificate_name=$certificate_name&add_type=upload&text_private_key=$encoded_key&text_certificate=$encoded_ccert&text_ca_certificate=$encoded_cca"
|
||||
_H1="Cookie: $_cookie"
|
||||
_response=$(_post "$_request_body" "$DEPLOY_KEYHELP_BASEURL/index.php?page=ssl_certificates&action=add" "" "POST")
|
||||
_message=$(echo "$_response" | sed -n '/<div class="message-body ">/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//')
|
||||
_info "_message" "$_message"
|
||||
if [ -z "$_message" ]; then
|
||||
_err "Fail to upload certificate."
|
||||
return 1
|
||||
fi
|
||||
|
||||
for DOMAIN_ID in $DEPLOY_KEYHELP_DOMAIN_ID; do
|
||||
_info "Apply certificate to domain id $DOMAIN_ID"
|
||||
_response=$(_get "$DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit&id=$DOMAIN_ID")
|
||||
cert_value=$(echo "$_response" | grep "$certificate_name" | sed -n 's/.*value="\([^"]*\).*/\1/p')
|
||||
target_type=$(echo "$_response" | grep 'target_type' | grep 'checked' | sed -n 's/.*value="\([^"]*\).*/\1/p')
|
||||
if [ "$target_type" = "directory" ]; then
|
||||
path=$(echo "$_response" | awk '/name="path"/{getline; print}' | sed -n 's/.*value="\([^"]*\).*/\1/p')
|
||||
fi
|
||||
echo "$_response" | grep "is_prefer_https" | grep "checked" >/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
is_prefer_https=1
|
||||
else
|
||||
is_prefer_https=0
|
||||
fi
|
||||
echo "$_response" | grep "hsts_enabled" | grep "checked" >/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
hsts_enabled=1
|
||||
else
|
||||
hsts_enabled=0
|
||||
fi
|
||||
_debug "cert_value" "$cert_value"
|
||||
if [ -z "$cert_value" ]; then
|
||||
_err "Fail to get certificate id."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_request_body="submit=1&id=$DOMAIN_ID&target_type=$target_type&path=$path&is_prefer_https=$is_prefer_https&hsts_enabled=$hsts_enabled&certificate_type=custom&certificate_id=$cert_value&enforce_https=$DEPLOY_KEYHELP_ENFORCE_HTTPS"
|
||||
_response=$(_post "$_request_body" "$DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit" "" "POST")
|
||||
_message=$(echo "$_response" | sed -n '/<div class="message-body ">/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//')
|
||||
_info "_message" "$_message"
|
||||
if [ -z "$_message" ]; then
|
||||
_err "Fail to apply certificate."
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
_info "Domain $_cdomain certificate successfully deployed to KeyHelp Domain ID $DEPLOY_KEYHELP_DOMAIN_ID."
|
||||
return 0
|
||||
}
|
||||
86
deploy/keyhelp_api.sh
Normal file
86
deploy/keyhelp_api.sh
Normal file
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
keyhelp_api_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
|
||||
# Read config from saved values or env
|
||||
_getdeployconf DEPLOY_KEYHELP_HOST
|
||||
_getdeployconf DEPLOY_KEYHELP_API_KEY
|
||||
|
||||
_debug DEPLOY_KEYHELP_HOST "$DEPLOY_KEYHELP_HOST"
|
||||
_secure_debug DEPLOY_KEYHELP_API_KEY "$DEPLOY_KEYHELP_API_KEY"
|
||||
|
||||
if [ -z "$DEPLOY_KEYHELP_HOST" ]; then
|
||||
_err "KeyHelp host not found, please define DEPLOY_KEYHELP_HOST."
|
||||
return 1
|
||||
fi
|
||||
if [ -z "$DEPLOY_KEYHELP_API_KEY" ]; then
|
||||
_err "KeyHelp api key not found, please define DEPLOY_KEYHELP_API_KEY."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Save current values
|
||||
_savedeployconf DEPLOY_KEYHELP_HOST "$DEPLOY_KEYHELP_HOST"
|
||||
_savedeployconf DEPLOY_KEYHELP_API_KEY "$DEPLOY_KEYHELP_API_KEY"
|
||||
|
||||
_request_key="$(tr '\n' ':' <"$_ckey" | sed 's/:/\\n/g')"
|
||||
_request_cert="$(tr '\n' ':' <"$_ccert" | sed 's/:/\\n/g')"
|
||||
_request_ca="$(tr '\n' ':' <"$_cca" | sed 's/:/\\n/g')"
|
||||
|
||||
_request_body="{
|
||||
\"name\": \"$_cdomain\",
|
||||
\"components\": {
|
||||
\"private_key\": \"$_request_key\",
|
||||
\"certificate\": \"$_request_cert\",
|
||||
\"ca_certificate\": \"$_request_ca\"
|
||||
}
|
||||
}"
|
||||
|
||||
_hosts="$(echo "$DEPLOY_KEYHELP_HOST" | tr "," " ")"
|
||||
_keys="$(echo "$DEPLOY_KEYHELP_API_KEY" | tr "," " ")"
|
||||
_i=1
|
||||
|
||||
for _host in $_hosts; do
|
||||
_key="$(_getfield "$_keys" "$_i" " ")"
|
||||
_i="$(_math "$_i" + 1)"
|
||||
|
||||
export _H1="X-API-Key: $_key"
|
||||
|
||||
_put_url="$_host/api/v2/certificates/name/$_cdomain"
|
||||
if _post "$_request_body" "$_put_url" "" "PUT" "application/json" >/dev/null; then
|
||||
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
|
||||
else
|
||||
_err "Cannot make PUT request to $_put_url"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$_code" = "404" ]; then
|
||||
_info "$_cdomain not found, creating new entry at $_host"
|
||||
|
||||
_post_url="$_host/api/v2/certificates"
|
||||
if _post "$_request_body" "$_post_url" "" "POST" "application/json" >/dev/null; then
|
||||
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
|
||||
else
|
||||
_err "Cannot make POST request to $_post_url"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if _startswith "$_code" "2"; then
|
||||
_info "$_cdomain set at $_host"
|
||||
else
|
||||
_err "HTTP status code is $_code"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
77
deploy/kong.sh
Executable file
77
deploy/kong.sh
Executable file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env sh
|
||||
# If certificate already exists it will update only cert and key, not touching other parameters
|
||||
# If certificate doesn't exist it will only upload cert and key, and not set other parameters
|
||||
# Note that we deploy full chain
|
||||
# Written by Geoffroi Genot <ggenot@voxbone.com>
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
kong_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
_info "Deploying certificate on Kong instance"
|
||||
if [ -z "$KONG_URL" ]; then
|
||||
_debug "KONG_URL Not set, using default http://localhost:8001"
|
||||
KONG_URL="http://localhost:8001"
|
||||
fi
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
#Get ssl_uuid linked to the domain
|
||||
ssl_uuid=$(_get "$KONG_URL/certificates/$_cdomain" | _normalizeJson | _egrep_o '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
|
||||
if [ -z "$ssl_uuid" ]; then
|
||||
_debug "Unable to get Kong ssl_uuid for domain $_cdomain"
|
||||
_debug "Make sure that KONG_URL is correctly configured"
|
||||
_debug "Make sure that a Kong certificate match the sni"
|
||||
_debug "Kong url: $KONG_URL"
|
||||
_info "No existing certificate, creating..."
|
||||
#return 1
|
||||
fi
|
||||
#Save kong url if it's succesful (First run case)
|
||||
_saveaccountconf KONG_URL "$KONG_URL"
|
||||
#Generate DEIM
|
||||
delim="-----MultipartDelimiter$(date "+%s%N")"
|
||||
nl="\015\012"
|
||||
#Set Header
|
||||
_H1="Content-Type: multipart/form-data; boundary=$delim"
|
||||
#Generate data for request (Multipart/form-data with mixed content)
|
||||
if [ -z "$ssl_uuid" ]; then
|
||||
#set sni to domain
|
||||
content="--$delim${nl}Content-Disposition: form-data; name=\"snis[]\"${nl}${nl}$_cdomain"
|
||||
fi
|
||||
#add key
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")"
|
||||
#Add cert
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"cert\"; filename=\"$(basename "$_cfullchain")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cfullchain")"
|
||||
#Close multipart
|
||||
content="$content${nl}--$delim--${nl}"
|
||||
#Convert CRLF
|
||||
content=$(printf %b "$content")
|
||||
#DEBUG
|
||||
_debug header "$_H1"
|
||||
_debug content "$content"
|
||||
#Check if sslcreated (if not => POST else => PATCH)
|
||||
|
||||
if [ -z "$ssl_uuid" ]; then
|
||||
#Post certificate to Kong
|
||||
response=$(_post "$content" "$KONG_URL/certificates" "" "POST")
|
||||
else
|
||||
#patch
|
||||
response=$(_post "$content" "$KONG_URL/certificates/$ssl_uuid" "" "PATCH")
|
||||
fi
|
||||
if ! [ "$(echo "$response" | _egrep_o "created_at")" = "created_at" ]; then
|
||||
_err "An error occurred with cert upload. Check response:"
|
||||
_err "$response"
|
||||
return 1
|
||||
fi
|
||||
_debug response "$response"
|
||||
_info "Certificate successfully deployed"
|
||||
}
|
||||
280
deploy/lighttpd.sh
Normal file
280
deploy/lighttpd.sh
Normal file
@@ -0,0 +1,280 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Script for acme.sh to deploy certificates to lighttpd
|
||||
#
|
||||
# The following variables can be exported:
|
||||
#
|
||||
# export DEPLOY_LIGHTTPD_PEM_NAME="${domain}.pem"
|
||||
#
|
||||
# Defines the name of the PEM file.
|
||||
# Defaults to "<domain>.pem"
|
||||
#
|
||||
# export DEPLOY_LIGHTTPD_PEM_PATH="/etc/lighttpd"
|
||||
#
|
||||
# Defines location of PEM file for Lighttpd.
|
||||
# Defaults to /etc/lighttpd
|
||||
#
|
||||
# export DEPLOY_LIGHTTPD_RELOAD="systemctl reload lighttpd"
|
||||
#
|
||||
# OPTIONAL: Reload command used post deploy
|
||||
# This defaults to be a no-op (ie "true").
|
||||
# It is strongly recommended to set this something that makes sense
|
||||
# for your distro.
|
||||
#
|
||||
# export DEPLOY_LIGHTTPD_ISSUER="yes"
|
||||
#
|
||||
# OPTIONAL: Places CA file as "${DEPLOY_LIGHTTPD_PEM}.issuer"
|
||||
# Note: Required for OCSP stapling to work
|
||||
#
|
||||
# export DEPLOY_LIGHTTPD_BUNDLE="no"
|
||||
#
|
||||
# OPTIONAL: Deploy this certificate as part of a multi-cert bundle
|
||||
# This adds a suffix to the certificate based on the certificate type
|
||||
# eg RSA certificates will have .rsa as a suffix to the file name
|
||||
# Lighttpd will load all certificates and provide one or the other
|
||||
# depending on client capabilities
|
||||
# Note: This functionality requires Lighttpd was compiled against
|
||||
# a version of OpenSSL that supports this.
|
||||
#
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
lighttpd_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
# Some defaults
|
||||
DEPLOY_LIGHTTPD_PEM_PATH_DEFAULT="/etc/lighttpd"
|
||||
DEPLOY_LIGHTTPD_PEM_NAME_DEFAULT="${_cdomain}.pem"
|
||||
DEPLOY_LIGHTTPD_BUNDLE_DEFAULT="no"
|
||||
DEPLOY_LIGHTTPD_ISSUER_DEFAULT="yes"
|
||||
DEPLOY_LIGHTTPD_RELOAD_DEFAULT="true"
|
||||
|
||||
_debug _cdomain "${_cdomain}"
|
||||
_debug _ckey "${_ckey}"
|
||||
_debug _ccert "${_ccert}"
|
||||
_debug _cca "${_cca}"
|
||||
_debug _cfullchain "${_cfullchain}"
|
||||
|
||||
# PEM_PATH is optional. If not provided then assume "${DEPLOY_LIGHTTPD_PEM_PATH_DEFAULT}"
|
||||
_getdeployconf DEPLOY_LIGHTTPD_PEM_PATH
|
||||
_debug2 DEPLOY_LIGHTTPD_PEM_PATH "${DEPLOY_LIGHTTPD_PEM_PATH}"
|
||||
if [ -n "${DEPLOY_LIGHTTPD_PEM_PATH}" ]; then
|
||||
Le_Deploy_lighttpd_pem_path="${DEPLOY_LIGHTTPD_PEM_PATH}"
|
||||
_savedomainconf Le_Deploy_lighttpd_pem_path "${Le_Deploy_lighttpd_pem_path}"
|
||||
elif [ -z "${Le_Deploy_lighttpd_pem_path}" ]; then
|
||||
Le_Deploy_lighttpd_pem_path="${DEPLOY_LIGHTTPD_PEM_PATH_DEFAULT}"
|
||||
fi
|
||||
|
||||
# Ensure PEM_PATH exists
|
||||
if [ -d "${Le_Deploy_lighttpd_pem_path}" ]; then
|
||||
_debug "PEM_PATH ${Le_Deploy_lighttpd_pem_path} exists"
|
||||
else
|
||||
_err "PEM_PATH ${Le_Deploy_lighttpd_pem_path} does not exist"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# PEM_NAME is optional. If not provided then assume "${DEPLOY_LIGHTTPD_PEM_NAME_DEFAULT}"
|
||||
_getdeployconf DEPLOY_LIGHTTPD_PEM_NAME
|
||||
_debug2 DEPLOY_LIGHTTPD_PEM_NAME "${DEPLOY_LIGHTTPD_PEM_NAME}"
|
||||
if [ -n "${DEPLOY_LIGHTTPD_PEM_NAME}" ]; then
|
||||
Le_Deploy_lighttpd_pem_name="${DEPLOY_LIGHTTPD_PEM_NAME}"
|
||||
_savedomainconf Le_Deploy_lighttpd_pem_name "${Le_Deploy_lighttpd_pem_name}"
|
||||
elif [ -z "${Le_Deploy_lighttpd_pem_name}" ]; then
|
||||
Le_Deploy_lighttpd_pem_name="${DEPLOY_LIGHTTPD_PEM_NAME_DEFAULT}"
|
||||
fi
|
||||
|
||||
# BUNDLE is optional. If not provided then assume "${DEPLOY_LIGHTTPD_BUNDLE_DEFAULT}"
|
||||
_getdeployconf DEPLOY_LIGHTTPD_BUNDLE
|
||||
_debug2 DEPLOY_LIGHTTPD_BUNDLE "${DEPLOY_LIGHTTPD_BUNDLE}"
|
||||
if [ -n "${DEPLOY_LIGHTTPD_BUNDLE}" ]; then
|
||||
Le_Deploy_lighttpd_bundle="${DEPLOY_LIGHTTPD_BUNDLE}"
|
||||
_savedomainconf Le_Deploy_lighttpd_bundle "${Le_Deploy_lighttpd_bundle}"
|
||||
elif [ -z "${Le_Deploy_lighttpd_bundle}" ]; then
|
||||
Le_Deploy_lighttpd_bundle="${DEPLOY_LIGHTTPD_BUNDLE_DEFAULT}"
|
||||
fi
|
||||
|
||||
# ISSUER is optional. If not provided then assume "${DEPLOY_LIGHTTPD_ISSUER_DEFAULT}"
|
||||
_getdeployconf DEPLOY_LIGHTTPD_ISSUER
|
||||
_debug2 DEPLOY_LIGHTTPD_ISSUER "${DEPLOY_LIGHTTPD_ISSUER}"
|
||||
if [ -n "${DEPLOY_LIGHTTPD_ISSUER}" ]; then
|
||||
Le_Deploy_lighttpd_issuer="${DEPLOY_LIGHTTPD_ISSUER}"
|
||||
_savedomainconf Le_Deploy_lighttpd_issuer "${Le_Deploy_lighttpd_issuer}"
|
||||
elif [ -z "${Le_Deploy_lighttpd_issuer}" ]; then
|
||||
Le_Deploy_lighttpd_issuer="${DEPLOY_LIGHTTPD_ISSUER_DEFAULT}"
|
||||
fi
|
||||
|
||||
# RELOAD is optional. If not provided then assume "${DEPLOY_LIGHTTPD_RELOAD_DEFAULT}"
|
||||
_getdeployconf DEPLOY_LIGHTTPD_RELOAD
|
||||
_debug2 DEPLOY_LIGHTTPD_RELOAD "${DEPLOY_LIGHTTPD_RELOAD}"
|
||||
if [ -n "${DEPLOY_LIGHTTPD_RELOAD}" ]; then
|
||||
Le_Deploy_lighttpd_reload="${DEPLOY_LIGHTTPD_RELOAD}"
|
||||
_savedomainconf Le_Deploy_lighttpd_reload "${Le_Deploy_lighttpd_reload}"
|
||||
elif [ -z "${Le_Deploy_lighttpd_reload}" ]; then
|
||||
Le_Deploy_lighttpd_reload="${DEPLOY_LIGHTTPD_RELOAD_DEFAULT}"
|
||||
fi
|
||||
|
||||
# Set the suffix depending if we are creating a bundle or not
|
||||
if [ "${Le_Deploy_lighttpd_bundle}" = "yes" ]; then
|
||||
_info "Bundle creation requested"
|
||||
# Initialise $Le_Keylength if its not already set
|
||||
if [ -z "${Le_Keylength}" ]; then
|
||||
Le_Keylength=""
|
||||
fi
|
||||
if _isEccKey "${Le_Keylength}"; then
|
||||
_info "ECC key type detected"
|
||||
_suffix=".ecdsa"
|
||||
else
|
||||
_info "RSA key type detected"
|
||||
_suffix=".rsa"
|
||||
fi
|
||||
else
|
||||
_suffix=""
|
||||
fi
|
||||
_debug _suffix "${_suffix}"
|
||||
|
||||
# Set variables for later
|
||||
_pem="${Le_Deploy_lighttpd_pem_path}/${Le_Deploy_lighttpd_pem_name}${_suffix}"
|
||||
_issuer="${_pem}.issuer"
|
||||
_ocsp="${_pem}.ocsp"
|
||||
_reload="${Le_Deploy_lighttpd_reload}"
|
||||
|
||||
_info "Deploying PEM file"
|
||||
# Create a temporary PEM file
|
||||
_temppem="$(_mktemp)"
|
||||
_debug _temppem "${_temppem}"
|
||||
cat "${_ckey}" "${_ccert}" "${_cca}" >"${_temppem}"
|
||||
_ret="$?"
|
||||
|
||||
# Check that we could create the temporary file
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Error code ${_ret} returned during PEM file creation"
|
||||
[ -f "${_temppem}" ] && rm -f "${_temppem}"
|
||||
return ${_ret}
|
||||
fi
|
||||
|
||||
# Move PEM file into place
|
||||
_info "Moving new certificate into place"
|
||||
_debug _pem "${_pem}"
|
||||
cat "${_temppem}" >"${_pem}"
|
||||
_ret=$?
|
||||
|
||||
# Clean up temp file
|
||||
[ -f "${_temppem}" ] && rm -f "${_temppem}"
|
||||
|
||||
# Deal with any failure of moving PEM file into place
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Error code ${_ret} returned while moving new certificate into place"
|
||||
return ${_ret}
|
||||
fi
|
||||
|
||||
# Update .issuer file if requested
|
||||
if [ "${Le_Deploy_lighttpd_issuer}" = "yes" ]; then
|
||||
_info "Updating .issuer file"
|
||||
_debug _issuer "${_issuer}"
|
||||
cat "${_cca}" >"${_issuer}"
|
||||
_ret="$?"
|
||||
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Error code ${_ret} returned while copying issuer/CA certificate into place"
|
||||
return ${_ret}
|
||||
fi
|
||||
else
|
||||
[ -f "${_issuer}" ] && _err "Issuer file update not requested but .issuer file exists"
|
||||
fi
|
||||
|
||||
# Update .ocsp file if certificate was requested with --ocsp/--ocsp-must-staple option
|
||||
if [ -z "${Le_OCSP_Staple}" ]; then
|
||||
Le_OCSP_Staple="0"
|
||||
fi
|
||||
if [ "${Le_OCSP_Staple}" = "1" ]; then
|
||||
_info "Updating OCSP stapling info"
|
||||
_debug _ocsp "${_ocsp}"
|
||||
_info "Extracting OCSP URL"
|
||||
_ocsp_url=$(${ACME_OPENSSL_BIN:-openssl} x509 -noout -ocsp_uri -in "${_pem}")
|
||||
_debug _ocsp_url "${_ocsp_url}"
|
||||
|
||||
# Only process OCSP if URL was present
|
||||
if [ "${_ocsp_url}" != "" ]; then
|
||||
# Extract the hostname from the OCSP URL
|
||||
_info "Extracting OCSP URL"
|
||||
_ocsp_host=$(echo "${_ocsp_url}" | cut -d/ -f3)
|
||||
_debug _ocsp_host "${_ocsp_host}"
|
||||
|
||||
# Only process the certificate if we have a .issuer file
|
||||
if [ -r "${_issuer}" ]; then
|
||||
# Check if issuer cert is also a root CA cert
|
||||
_subjectdn=$(${ACME_OPENSSL_BIN:-openssl} x509 -in "${_issuer}" -subject -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)
|
||||
_debug _subjectdn "${_subjectdn}"
|
||||
_issuerdn=$(${ACME_OPENSSL_BIN:-openssl} x509 -in "${_issuer}" -issuer -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)
|
||||
_debug _issuerdn "${_issuerdn}"
|
||||
_info "Requesting OCSP response"
|
||||
# If the issuer is a CA cert then our command line has "-CAfile" added
|
||||
if [ "${_subjectdn}" = "${_issuerdn}" ]; then
|
||||
_cafile_argument="-CAfile \"${_issuer}\""
|
||||
else
|
||||
_cafile_argument=""
|
||||
fi
|
||||
_debug _cafile_argument "${_cafile_argument}"
|
||||
# if OpenSSL/LibreSSL is v1.1 or above, the format for the -header option has changed
|
||||
_openssl_version=$(${ACME_OPENSSL_BIN:-openssl} version | cut -d' ' -f2)
|
||||
_debug _openssl_version "${_openssl_version}"
|
||||
_openssl_major=$(echo "${_openssl_version}" | cut -d '.' -f1)
|
||||
_openssl_minor=$(echo "${_openssl_version}" | cut -d '.' -f2)
|
||||
if [ "${_openssl_major}" -eq "1" ] && [ "${_openssl_minor}" -ge "1" ] || [ "${_openssl_major}" -ge "2" ]; then
|
||||
_header_sep="="
|
||||
else
|
||||
_header_sep=" "
|
||||
fi
|
||||
# Request the OCSP response from the issuer and store it
|
||||
_openssl_ocsp_cmd="${ACME_OPENSSL_BIN:-openssl} ocsp \
|
||||
-issuer \"${_issuer}\" \
|
||||
-cert \"${_pem}\" \
|
||||
-url \"${_ocsp_url}\" \
|
||||
-header Host${_header_sep}\"${_ocsp_host}\" \
|
||||
-respout \"${_ocsp}\" \
|
||||
-verify_other \"${_issuer}\" \
|
||||
${_cafile_argument} \
|
||||
| grep -q \"${_pem}: good\""
|
||||
_debug _openssl_ocsp_cmd "${_openssl_ocsp_cmd}"
|
||||
eval "${_openssl_ocsp_cmd}"
|
||||
_ret=$?
|
||||
else
|
||||
# Non fatal: No issuer file was present so no OCSP stapling file created
|
||||
_err "OCSP stapling in use but no .issuer file was present"
|
||||
fi
|
||||
else
|
||||
# Non fatal: No OCSP url was found int the certificate
|
||||
_err "OCSP update requested but no OCSP URL was found in certificate"
|
||||
fi
|
||||
|
||||
# Non fatal: Check return code of openssl command
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Updating OCSP stapling failed with return code ${_ret}"
|
||||
fi
|
||||
else
|
||||
# An OCSP file was already present but certificate did not have OCSP extension
|
||||
if [ -f "${_ocsp}" ]; then
|
||||
_err "OCSP was not requested but .ocsp file exists."
|
||||
# Could remove the file at this step, although Lighttpd just ignores it in this case
|
||||
# rm -f "${_ocsp}" || _err "Problem removing stale .ocsp file"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Reload Lighttpd
|
||||
_debug _reload "${_reload}"
|
||||
eval "${_reload}"
|
||||
_ret=$?
|
||||
if [ "${_ret}" != "0" ]; then
|
||||
_err "Error code ${_ret} during reload"
|
||||
return ${_ret}
|
||||
else
|
||||
_info "Reload successful"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
157
deploy/localcopy.sh
Normal file
157
deploy/localcopy.sh
Normal file
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Deploy-hook to very simply copy files to set directories and then
|
||||
# execute whatever reloadcmd the admin needs afterwards. This can be
|
||||
# useful for configurations where the "multideploy" hook (in development)
|
||||
# is used or when an admin wants ACME.SH to renew certs but needs to
|
||||
# manually configure deployment via an external script
|
||||
# (e.g. The deploy-freenas script for TrueNAS Core/Scale
|
||||
# https://github.com/danb35/deploy-freenas/ )
|
||||
#
|
||||
# If the same file is configured for the certificate key
|
||||
# and the certificate and/or full chain, a combined PEM file will
|
||||
# be output instead.
|
||||
#
|
||||
# Environment variables to be utilized are as follows:
|
||||
#
|
||||
# DEPLOY_LOCALCOPY_CERTKEY - /path/to/target/cert.key
|
||||
# DEPLOY_LOCALCOPY_CERTIFICATE - /path/to/target/cert.cer
|
||||
# DEPLOY_LOCALCOPY_FULLCHAIN - /path/to/target/fullchain.cer
|
||||
# DEPLOY_LOCALCOPY_CA - /path/to/target/ca.cer
|
||||
# DEPLOY_LOCALCOPY_PFX - /path/to/target/cert.pfx
|
||||
# DEPLOY_LOCALCOPY_RELOADCMD - "echo 'this is my cmd'"
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
localcopy_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
_cpfx="$6"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
_debug _cpfx "$_cpfx"
|
||||
|
||||
_getdeployconf DEPLOY_LOCALCOPY_CERTIFICATE
|
||||
_getdeployconf DEPLOY_LOCALCOPY_CERTKEY
|
||||
_getdeployconf DEPLOY_LOCALCOPY_FULLCHAIN
|
||||
_getdeployconf DEPLOY_LOCALCOPY_CA
|
||||
_getdeployconf DEPLOY_LOCALCOPY_RELOADCMD
|
||||
_getdeployconf DEPLOY_LOCALCOPY_PFX
|
||||
_combined_target=""
|
||||
_combined_srccert=""
|
||||
|
||||
# Create PEM file
|
||||
if [ "$DEPLOY_LOCALCOPY_CERTKEY" ] &&
|
||||
{ [ "$DEPLOY_LOCALCOPY_CERTKEY" = "$DEPLOY_LOCALCOPY_FULLCHAIN" ] ||
|
||||
[ "$DEPLOY_LOCALCOPY_CERTKEY" = "$DEPLOY_LOCALCOPY_CERTIFICATE" ]; }; then
|
||||
|
||||
_combined_target="$DEPLOY_LOCALCOPY_CERTKEY"
|
||||
_savedeployconf DEPLOY_LOCALCOPY_CERTKEY "$DEPLOY_LOCALCOPY_CERTKEY"
|
||||
if [ "$DEPLOY_LOCALCOPY_CERTKEY" = "$DEPLOY_LOCALCOPY_CERTIFICATE" ]; then
|
||||
_combined_srccert="$_ccert"
|
||||
_savedeployconf DEPLOY_LOCALCOPY_CERTIFICATE "$DEPLOY_LOCALCOPY_CERTIFICATE"
|
||||
DEPLOY_LOCALCOPY_CERTIFICATE=""
|
||||
fi
|
||||
if [ "$DEPLOY_LOCALCOPY_CERTKEY" = "$DEPLOY_LOCALCOPY_FULLCHAIN" ]; then
|
||||
_combined_srccert="$_cfullchain"
|
||||
_savedeployconf DEPLOY_LOCALCOPY_FULLCHAIN "$DEPLOY_LOCALCOPY_FULLCHAIN"
|
||||
DEPLOY_LOCALCOPY_FULLCHAIN=""
|
||||
fi
|
||||
DEPLOY_LOCALCOPY_CERTKEY=""
|
||||
_info "Creating combined PEM"
|
||||
_debug "Creating combined PEM at $_combined_target"
|
||||
if ! [ -f "$_combined_target" ]; then
|
||||
touch "$_combined_target" || return 1
|
||||
chmod 600 "$_combined_target"
|
||||
fi
|
||||
if ! cat "$_combined_srccert" "$_ckey" >"$_combined_target"; then
|
||||
_err "Failed to create PEM file"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_LOCALCOPY_CERTIFICATE" ]; then
|
||||
_info "Copying certificate"
|
||||
_debug "Copying $_ccert to $DEPLOY_LOCALCOPY_CERTIFICATE"
|
||||
if ! cat "$_ccert" >"$DEPLOY_LOCALCOPY_CERTIFICATE"; then
|
||||
_err "Failed to copy certificate, aborting."
|
||||
return 1
|
||||
fi
|
||||
_savedeployconf DEPLOY_LOCALCOPY_CERTIFICATE "$DEPLOY_LOCALCOPY_CERTIFICATE"
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_LOCALCOPY_CERTKEY" ]; then
|
||||
_info "Copying certificate key"
|
||||
_debug "Copying $_ckey to $DEPLOY_LOCALCOPY_CERTKEY"
|
||||
if ! [ -f "$DEPLOY_LOCALCOPY_CERTKEY" ]; then
|
||||
touch "$DEPLOY_LOCALCOPY_CERTKEY" || return 1
|
||||
chmod 600 "$DEPLOY_LOCALCOPY_CERTKEY"
|
||||
fi
|
||||
if ! cat "$_ckey" >"$DEPLOY_LOCALCOPY_CERTKEY"; then
|
||||
_err "Failed to copy certificate key, aborting."
|
||||
return 1
|
||||
fi
|
||||
_savedeployconf DEPLOY_LOCALCOPY_CERTKEY "$DEPLOY_LOCALCOPY_CERTKEY"
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_LOCALCOPY_FULLCHAIN" ]; then
|
||||
_info "Copying fullchain"
|
||||
_debug "Copying $_cfullchain to $DEPLOY_LOCALCOPY_FULLCHAIN"
|
||||
if ! cat "$_cfullchain" >"$DEPLOY_LOCALCOPY_FULLCHAIN"; then
|
||||
_err "Failed to copy fullchain, aborting."
|
||||
return 1
|
||||
fi
|
||||
_savedeployconf DEPLOY_LOCALCOPY_FULLCHAIN "$DEPLOY_LOCALCOPY_FULLCHAIN"
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_LOCALCOPY_CA" ]; then
|
||||
_info "Copying CA"
|
||||
_debug "Copying $_cca to $DEPLOY_LOCALCOPY_CA"
|
||||
if ! cat "$_cca" >"$DEPLOY_LOCALCOPY_CA"; then
|
||||
_err "Failed to copy CA, aborting."
|
||||
return 1
|
||||
fi
|
||||
_savedeployconf DEPLOY_LOCALCOPY_CA "$DEPLOY_LOCALCOPY_CA"
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_LOCALCOPY_PFX" ]; then
|
||||
_info "Copying PFX"
|
||||
_debug "Copying $_cpfx to $DEPLOY_LOCALCOPY_PFX"
|
||||
if ! [ -f "$DEPLOY_LOCALCOPY_PFX" ]; then
|
||||
touch "$DEPLOY_LOCALCOPY_PFX" || return 1
|
||||
chmod 600 "$DEPLOY_LOCALCOPY_PFX"
|
||||
fi
|
||||
if ! cat "$_cpfx" >"$DEPLOY_LOCALCOPY_PFX"; then
|
||||
_err "Failed to copy PFX, aborting."
|
||||
return 1
|
||||
fi
|
||||
_savedeployconf DEPLOY_LOCALCOPY_PFX "$DEPLOY_LOCALCOPY_PFX"
|
||||
fi
|
||||
|
||||
_reload=$DEPLOY_LOCALCOPY_RELOADCMD
|
||||
_debug "Running reloadcmd $_reload"
|
||||
|
||||
if [ -z "$_reload" ]; then
|
||||
_info "Reloadcmd not provided, skipping."
|
||||
else
|
||||
_info "Reloading"
|
||||
if eval "$_reload"; then
|
||||
_info "Reload successful."
|
||||
_savedeployconf DEPLOY_LOCALCOPY_RELOADCMD "$DEPLOY_LOCALCOPY_RELOADCMD" "base64"
|
||||
else
|
||||
_err "Reload failed."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
_info "$(__green "'localcopy' deploy success")"
|
||||
return 0
|
||||
}
|
||||
69
deploy/mailcow.sh
Normal file
69
deploy/mailcow.sh
Normal file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#Here is a script to deploy cert to mailcow.
|
||||
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
mailcow_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
_getdeployconf DEPLOY_MAILCOW_PATH
|
||||
_getdeployconf DEPLOY_MAILCOW_RELOAD
|
||||
|
||||
_debug DEPLOY_MAILCOW_PATH "$DEPLOY_MAILCOW_PATH"
|
||||
_debug DEPLOY_MAILCOW_RELOAD "$DEPLOY_MAILCOW_RELOAD"
|
||||
|
||||
if [ -z "$DEPLOY_MAILCOW_PATH" ]; then
|
||||
_err "Mailcow path is not found, please define DEPLOY_MAILCOW_PATH."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_savedeployconf DEPLOY_MAILCOW_PATH "$DEPLOY_MAILCOW_PATH"
|
||||
[ -n "$DEPLOY_MAILCOW_RELOAD" ] && _savedeployconf DEPLOY_MAILCOW_RELOAD "$DEPLOY_MAILCOW_RELOAD"
|
||||
|
||||
_ssl_path="$DEPLOY_MAILCOW_PATH"
|
||||
if [ -f "$DEPLOY_MAILCOW_PATH/generate_config.sh" ]; then
|
||||
_ssl_path="$DEPLOY_MAILCOW_PATH/data/assets/ssl/"
|
||||
fi
|
||||
|
||||
if [ ! -d "$_ssl_path" ]; then
|
||||
_err "Cannot find mailcow ssl path: $_ssl_path"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Copying key and cert"
|
||||
_real_key="$_ssl_path/key.pem"
|
||||
if ! cat "$_ckey" >"$_real_key"; then
|
||||
_err "Error: write key file to: $_real_key"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_real_fullchain="$_ssl_path/cert.pem"
|
||||
if ! cat "$_cfullchain" >"$_real_fullchain"; then
|
||||
_err "Error: write cert file to: $_real_fullchain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
DEFAULT_MAILCOW_RELOAD="docker restart \$(docker ps --quiet --filter name=nginx-mailcow --filter name=dovecot-mailcow --filter name=postfix-mailcow)"
|
||||
_reload="${DEPLOY_MAILCOW_RELOAD:-$DEFAULT_MAILCOW_RELOAD}"
|
||||
|
||||
_info "Run reload: $_reload"
|
||||
if eval "$_reload"; then
|
||||
_info "Reload success!"
|
||||
fi
|
||||
return 0
|
||||
|
||||
}
|
||||
287
deploy/multideploy.sh
Normal file
287
deploy/multideploy.sh
Normal file
@@ -0,0 +1,287 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
################################################################################
|
||||
# ACME.sh 3rd party deploy plugin for multiple (same) services
|
||||
################################################################################
|
||||
# Authors: tomo2403 (creator), https://github.com/tomo2403
|
||||
# Updated: 2025-03-01
|
||||
# Issues: https://github.com/acmesh-official/acme.sh/issues and mention @tomo2403
|
||||
################################################################################
|
||||
# Usage (shown values are the examples):
|
||||
# 1. Set optional environment variables
|
||||
# - export MULTIDEPLOY_FILENAME="multideploy.yaml" - "multideploy.yml" will be automatically used if not set"
|
||||
# A name without a leading '/' is looked up in the certificate directory
|
||||
# of the domain. An absolute path is used as is, so a single deploy file
|
||||
# can be shared by all domains, e.g.
|
||||
# - export MULTIDEPLOY_FILENAME="/etc/acme/multideploy.yml"
|
||||
#
|
||||
# 2. Run command:
|
||||
# acme.sh --deploy --deploy-hook multideploy -d example.com
|
||||
################################################################################
|
||||
# Dependencies:
|
||||
# - yq
|
||||
################################################################################
|
||||
# Return value:
|
||||
# 0 means success, otherwise error.
|
||||
################################################################################
|
||||
|
||||
MULTIDEPLOY_VERSION="1.0"
|
||||
|
||||
# Description: This function handles the deployment of certificates to multiple services.
|
||||
# It processes the provided certificate files and deploys them according to the
|
||||
# configuration specified in the multideploy file.
|
||||
#
|
||||
# Parameters:
|
||||
# _cdomain - The domain name for which the certificate is issued.
|
||||
# _ckey - The private key file for the certificate.
|
||||
# _ccert - The certificate file.
|
||||
# _cca - The CA (Certificate Authority) file.
|
||||
# _cfullchain - The full chain certificate file.
|
||||
# _cpfx - The PFX (Personal Information Exchange) file.
|
||||
multideploy_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
_cpfx="$6"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
_debug _cpfx "$_cpfx"
|
||||
|
||||
_getdeployconf MULTIDEPLOY_FILENAME
|
||||
if [ -z "$MULTIDEPLOY_FILENAME" ]; then
|
||||
MULTIDEPLOY_FILENAME="multideploy.yml"
|
||||
_info "MULTIDEPLOY_FILENAME is not set, so I will use 'multideploy.yml'."
|
||||
else
|
||||
_savedeployconf "MULTIDEPLOY_FILENAME" "$MULTIDEPLOY_FILENAME"
|
||||
_debug2 "MULTIDEPLOY_FILENAME" "$MULTIDEPLOY_FILENAME"
|
||||
fi
|
||||
|
||||
if ! file=$(_preprocess_deployfile "$MULTIDEPLOY_FILENAME"); then
|
||||
_err "Failed to preprocess deploy file."
|
||||
return 1
|
||||
fi
|
||||
_debug3 "File" "$file"
|
||||
|
||||
# Deploy to services
|
||||
_deploy_services "$file"
|
||||
_exitCode="$?"
|
||||
|
||||
return "$_exitCode"
|
||||
}
|
||||
|
||||
# Description:
|
||||
# This function preprocesses the deploy file by checking if 'yq' is installed,
|
||||
# verifying the existence of the deploy file, and ensuring only one deploy file is present.
|
||||
# Arguments:
|
||||
# $@ - Posible deploy file names. A name starting with '/' is treated as an
|
||||
# absolute path, any other name is relative to the domain directory.
|
||||
# Usage:
|
||||
# _preprocess_deployfile "<deploy_file1>" "<deploy_file2>?"
|
||||
_preprocess_deployfile() {
|
||||
# Check if yq is installed
|
||||
if ! command -v yq >/dev/null 2>&1; then
|
||||
_err "yq is not installed! Please install yq and try again."
|
||||
return 1
|
||||
fi
|
||||
_debug3 "yq is installed."
|
||||
|
||||
# Check if deploy file exists
|
||||
found_file=""
|
||||
for file in "$@"; do
|
||||
if _startswith "$file" "/"; then
|
||||
_multideploy_path="$file"
|
||||
else
|
||||
_multideploy_path="$DOMAIN_PATH/$file"
|
||||
fi
|
||||
_debug3 "Checking file" "$_multideploy_path"
|
||||
if [ -f "$_multideploy_path" ]; then
|
||||
_debug3 "File found"
|
||||
if [ -n "$found_file" ]; then
|
||||
_err "Multiple deploy files found. Please keep only one deploy file."
|
||||
return 1
|
||||
fi
|
||||
found_file="$_multideploy_path"
|
||||
else
|
||||
_debug3 "File not found"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$found_file" ]; then
|
||||
_err "Deploy file not found. Go to https://github.com/acmesh-official/acme.sh/wiki/deployhooks#36-deploying-to-multiple-services-with-the-same-hooks to see how to create one."
|
||||
return 1
|
||||
fi
|
||||
if ! _check_deployfile "$found_file"; then
|
||||
_err "Deploy file is not valid: $found_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$found_file"
|
||||
}
|
||||
|
||||
# Description:
|
||||
# This function checks the deploy file for version compatibility and the existence of the specified configuration and services.
|
||||
# Arguments:
|
||||
# $1 - The path to the deploy configuration file.
|
||||
# $2 - The name of the deploy configuration to use.
|
||||
# Usage:
|
||||
# _check_deployfile "<deploy_file_path>"
|
||||
_check_deployfile() {
|
||||
_deploy_file="$1"
|
||||
_debug2 "check: Deploy file" "$_deploy_file"
|
||||
|
||||
# Check version
|
||||
_deploy_file_version=$(yq -r '.version' "$_deploy_file")
|
||||
if [ "$MULTIDEPLOY_VERSION" != "$_deploy_file_version" ]; then
|
||||
_err "As of $PROJECT_NAME $VER, the deploy file needs version $MULTIDEPLOY_VERSION! Your current deploy file is of version $_deploy_file_version."
|
||||
return 1
|
||||
fi
|
||||
_debug2 "check: Deploy file version is compatible: $_deploy_file_version"
|
||||
|
||||
# Extract all services from config
|
||||
_services=$(yq -r '.services[].name' "$_deploy_file")
|
||||
|
||||
if [ -z "$_services" ]; then
|
||||
_err "Config does not have any services to deploy to."
|
||||
return 1
|
||||
fi
|
||||
_debug2 "check: Config has services."
|
||||
echo "$_services" | while read -r _service; do
|
||||
_debug3 " - $_service"
|
||||
done
|
||||
|
||||
# Check if extracted services exist in services list
|
||||
echo "$_services" | while read -r _service; do
|
||||
_debug2 "check: Checking service: $_service"
|
||||
# Check if service exists
|
||||
_service_config=$(yq -r ".services[] | select(.name == \"$_service\")" "$_deploy_file")
|
||||
if [ -z "$_service_config" ] || [ "$_service_config" = "null" ]; then
|
||||
_err "Service '$_service' not found."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_service_hook=$(echo "$_service_config" | yq -r ".hook" -)
|
||||
if [ -z "$_service_hook" ] || [ "$_service_hook" = "null" ]; then
|
||||
_err "Service '$_service' does not have a hook."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_service_environment=$(echo "$_service_config" | yq -r ".environment" -)
|
||||
if [ -z "$_service_environment" ] || [ "$_service_environment" = "null" ]; then
|
||||
_err "Service '$_service' does not have an environment."
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Description: This function takes a list of environment variables in YAML format,
|
||||
# parses them, and exports each key-value pair as environment variables.
|
||||
# Arguments:
|
||||
# $1 - A string containing the list of environment variables in YAML format.
|
||||
# Usage:
|
||||
# _export_envs "$env_list"
|
||||
_export_envs() {
|
||||
_env_list="$1"
|
||||
|
||||
_secure_debug3 "Exporting envs" "$_env_list"
|
||||
|
||||
echo "$_env_list" | yq -r 'to_entries | .[] | .key + "=" + .value' | while IFS='=' read -r _key _value; do
|
||||
# Using eval to expand nested variables in the configuration file
|
||||
_value=$(eval 'echo "'"$_value"'"')
|
||||
_savedeployconf "$_key" "$_value"
|
||||
_secure_debug3 "Saved $_key" "$_value"
|
||||
done
|
||||
}
|
||||
|
||||
# Description:
|
||||
# This function takes a YAML formatted string of environment variables, parses it,
|
||||
# and clears each environment variable. It logs the process of clearing each variable.
|
||||
#
|
||||
# Note: Environment variables for a hook may be optional and differ between
|
||||
# services using the same hook.
|
||||
# If one service sets optional environment variables and another does not, the
|
||||
# variables may persist and affect subsequent deployments.
|
||||
# Clearing these variables after each service ensures that only the
|
||||
# environment variables explicitly specified for each service in the deploy
|
||||
# file are used.
|
||||
# Arguments:
|
||||
# $1 - A YAML formatted string containing environment variable key-value pairs.
|
||||
# Usage:
|
||||
# _clear_envs "<yaml_string>"
|
||||
_clear_envs() {
|
||||
_env_list="$1"
|
||||
|
||||
_secure_debug3 "Clearing envs" "$_env_list"
|
||||
env_pairs=$(echo "$_env_list" | yq -r 'to_entries | .[] | .key + "=" + .value')
|
||||
|
||||
echo "$env_pairs" | while IFS='=' read -r _key _value; do
|
||||
_debug3 "Deleting key" "$_key"
|
||||
_cleardeployconf "$_key"
|
||||
unset -v "$_key"
|
||||
done
|
||||
}
|
||||
|
||||
# Description:
|
||||
# This function deploys services listed in the deploy configuration file.
|
||||
# Arguments:
|
||||
# $1 - The path to the deploy configuration file.
|
||||
# $2 - The list of services to deploy.
|
||||
# Usage:
|
||||
# _deploy_services "<deploy_file_path>" "<services_list>"
|
||||
_deploy_services() {
|
||||
_deploy_file="$1"
|
||||
_debug3 "Deploy file" "$_deploy_file"
|
||||
|
||||
_tempfile=$(mktemp)
|
||||
trap 'rm -f $_tempfile' EXIT
|
||||
|
||||
yq -r '.services[].name' "$_deploy_file" >"$_tempfile"
|
||||
_debug3 "Services" "$(cat "$_tempfile")"
|
||||
|
||||
_failedServices=""
|
||||
_failedCount=0
|
||||
while read -r _service <&3; do
|
||||
_debug2 "Service" "$_service"
|
||||
_hook=$(yq -r ".services[] | select(.name == \"$_service\").hook" "$_deploy_file")
|
||||
_envs=$(yq -r ".services[] | select(.name == \"$_service\").environment" "$_deploy_file")
|
||||
|
||||
_export_envs "$_envs"
|
||||
if ! _deploy_service "$_service" "$_hook"; then
|
||||
_failedServices="$_service, $_failedServices"
|
||||
_failedCount=$((_failedCount + 1))
|
||||
fi
|
||||
_clear_envs "$_envs"
|
||||
done 3<"$_tempfile"
|
||||
|
||||
_debug3 "Failed services" "$_failedServices"
|
||||
_debug2 "Failed count" "$_failedCount"
|
||||
if [ -n "$_failedServices" ]; then
|
||||
_info "$(__red "Deployment failed") for services: $_failedServices"
|
||||
else
|
||||
_debug "All services deployed successfully."
|
||||
fi
|
||||
|
||||
return "$_failedCount"
|
||||
}
|
||||
|
||||
# Description: Deploys a service using the specified hook.
|
||||
# Arguments:
|
||||
# $1 - The name of the service to deploy.
|
||||
# $2 - The hook to use for deployment.
|
||||
# Usage:
|
||||
# _deploy_service <service_name> <hook>
|
||||
_deploy_service() {
|
||||
_name="$1"
|
||||
_hook="$2"
|
||||
|
||||
_debug2 "SERVICE" "$_name"
|
||||
_debug2 "HOOK" "$_hook"
|
||||
|
||||
_info "$(__green "Deploying") to '$_name' using '$_hook'"
|
||||
_deploy "$_cdomain" "$_hook"
|
||||
}
|
||||
28
deploy/myapi.sh
Executable file
28
deploy/myapi.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#Here is a sample custom api script.
|
||||
#This file name is "myapi.sh"
|
||||
#So, here must be a method myapi_deploy()
|
||||
#Which will be called by acme.sh to deploy the cert
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
myapi_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
_err "Not implemented yet"
|
||||
return 1
|
||||
|
||||
}
|
||||
61
deploy/mydevil.sh
Executable file
61
deploy/mydevil.sh
Executable file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# MyDevil.net API (2019-02-03)
|
||||
#
|
||||
# MyDevil.net already supports automatic Let's Encrypt certificates,
|
||||
# except for wildcard domains.
|
||||
#
|
||||
# This script depends on `devil` command that MyDevil.net provides,
|
||||
# which means that it works only on server side.
|
||||
#
|
||||
# Author: Marcin Konicki <https://ahwayakchih.neoni.net>
|
||||
#
|
||||
######## Public functions #####################
|
||||
|
||||
# Usage: mydevil_deploy domain keyfile certfile cafile fullchain
|
||||
mydevil_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
ip=""
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
if ! _exists "devil"; then
|
||||
_err "Could not find 'devil' command."
|
||||
return 1
|
||||
fi
|
||||
|
||||
ip=$(mydevil_get_ip "$_cdomain")
|
||||
if [ -z "$ip" ]; then
|
||||
_err "Could not find IP for domain $_cdomain."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Delete old certificate first
|
||||
_info "Removing old certificate for $_cdomain at $ip"
|
||||
devil ssl www del "$ip" "$_cdomain"
|
||||
|
||||
# Add new certificate
|
||||
_info "Adding new certificate for $_cdomain at $ip"
|
||||
devil ssl www add "$ip" "$_cfullchain" "$_ckey" "$_cdomain" || return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
#################### Private functions below ##################################
|
||||
|
||||
# Usage: ip=$(mydevil_get_ip domain.com)
|
||||
# echo $ip
|
||||
mydevil_get_ip() {
|
||||
# tr squeezes runs of blanks into one tab so plain cut works everywhere;
|
||||
# cut -w is BSD-only and unknown to GNU coreutils
|
||||
devil dns list "$1" | tr -s ' \t' '\t' | cut -s -f 3,7 | grep "^A$(printf '\t')" | cut -s -f 2 || return 1
|
||||
return 0
|
||||
}
|
||||
26
deploy/mysqld.sh
Normal file
26
deploy/mysqld.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#Here is a script to deploy cert to mysqld server.
|
||||
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
mysqld_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
_err "deploy cert to mysqld server, Not implemented yet"
|
||||
return 1
|
||||
|
||||
}
|
||||
69
deploy/netlify.sh
Normal file
69
deploy/netlify.sh
Normal file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Script to deploy certificate to Netlify
|
||||
# https://docs.netlify.com/api/get-started/#authentication
|
||||
# https://open-api.netlify.com/#tag/sniCertificate
|
||||
|
||||
# This deployment required following variables
|
||||
# export Netlify_ACCESS_TOKEN="Your Netlify Access Token"
|
||||
# export Netlify_SITE_ID="Your Netlify Site ID"
|
||||
|
||||
# If have more than one SITE ID
|
||||
# export Netlify_SITE_ID="SITE_ID_1 SITE_ID_2"
|
||||
|
||||
# returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
netlify_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
if [ -z "$Netlify_ACCESS_TOKEN" ]; then
|
||||
_err "Netlify_ACCESS_TOKEN is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf Netlify_ACCESS_TOKEN "$Netlify_ACCESS_TOKEN"
|
||||
fi
|
||||
if [ -z "$Netlify_SITE_ID" ]; then
|
||||
_err "Netlify_SITE_ID is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf Netlify_SITE_ID "$Netlify_SITE_ID"
|
||||
fi
|
||||
|
||||
_info "Deploying certificate to Netlify..."
|
||||
|
||||
## upload certificate
|
||||
string_ccert=$(sed 's/$/\\n/' "$_ccert" | tr -d '\n')
|
||||
string_cca=$(sed 's/$/\\n/' "$_cca" | tr -d '\n')
|
||||
string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')
|
||||
|
||||
for SITE_ID in $Netlify_SITE_ID; do
|
||||
_request_body="{\"certificate\":\"$string_ccert\",\"key\":\"$string_key\",\"ca_certificates\":\"$string_cca\"}"
|
||||
_debug _request_body "$_request_body"
|
||||
_debug Netlify_ACCESS_TOKEN "$Netlify_ACCESS_TOKEN"
|
||||
export _H1="Authorization: Bearer $Netlify_ACCESS_TOKEN"
|
||||
_response=$(_post "$_request_body" "https://api.netlify.com/api/v1/sites/$SITE_ID/ssl" "" "POST" "application/json")
|
||||
|
||||
if _contains "$_response" "\"error\""; then
|
||||
_err "Error in deploying $_cdomain certificate to Netlify SITE_ID $SITE_ID."
|
||||
_err "$_response"
|
||||
return 1
|
||||
fi
|
||||
_debug response "$_response"
|
||||
_info "Domain $_cdomain certificate successfully deployed to Netlify SITE_ID $SITE_ID."
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
26
deploy/nginx.sh
Normal file
26
deploy/nginx.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#Here is a script to deploy cert to nginx server.
|
||||
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
nginx_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
_err "deploy cert to nginx server, Not implemented yet"
|
||||
return 1
|
||||
|
||||
}
|
||||
156
deploy/openmediavault.sh
Normal file
156
deploy/openmediavault.sh
Normal file
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# This deploy hook is tested on OpenMediaVault 5.x. It supports both local and remote deployment.
|
||||
# The way it works is that if a cert with the matching domain name is not found, it will firstly create a dummy cert to get its uuid, and then replace it with your cert.
|
||||
#
|
||||
# DEPLOY_OMV_WEBUI_ADMIN - This is OMV web gui admin account. Default value is admin. It's required as the user parameter (-u) for the omv-rpc command.
|
||||
# DEPLOY_OMV_HOST and DEPLOY_OMV_SSH_USER are optional. They are used for remote deployment through ssh (support public key authentication only). Per design, OMV web gui admin doesn't have ssh permission, so another account is needed for ssh.
|
||||
#
|
||||
# returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
openmediavault_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
_getdeployconf DEPLOY_OMV_WEBUI_ADMIN
|
||||
|
||||
if [ -z "$DEPLOY_OMV_WEBUI_ADMIN" ]; then
|
||||
DEPLOY_OMV_WEBUI_ADMIN="admin"
|
||||
fi
|
||||
|
||||
_savedeployconf DEPLOY_OMV_WEBUI_ADMIN "$DEPLOY_OMV_WEBUI_ADMIN"
|
||||
|
||||
_getdeployconf DEPLOY_OMV_HOST
|
||||
_getdeployconf DEPLOY_OMV_SSH_USER
|
||||
|
||||
if [ -n "$DEPLOY_OMV_HOST" ] && [ -n "$DEPLOY_OMV_SSH_USER" ]; then
|
||||
_info "[OMV deploy-hook] Deploy certificate remotely through ssh."
|
||||
_savedeployconf DEPLOY_OMV_HOST "$DEPLOY_OMV_HOST"
|
||||
_savedeployconf DEPLOY_OMV_SSH_USER "$DEPLOY_OMV_SSH_USER"
|
||||
else
|
||||
_info "[OMV deploy-hook] Deploy certificate locally."
|
||||
fi
|
||||
|
||||
if [ -n "$DEPLOY_OMV_HOST" ] && [ -n "$DEPLOY_OMV_SSH_USER" ]; then
|
||||
|
||||
_command="omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'CertificateMgmt' 'getList' '{\"start\": 0, \"limit\": -1}' | jq -r '.data[] | select(.name==\"/CN='$_cdomain'\") | .uuid'"
|
||||
# shellcheck disable=SC2029
|
||||
_uuid=$(ssh "$DEPLOY_OMV_SSH_USER@$DEPLOY_OMV_HOST" "$_command")
|
||||
_debug _command "$_command"
|
||||
|
||||
if [ -z "$_uuid" ]; then
|
||||
_info "[OMV deploy-hook] Domain $_cdomain has no certificate in openmediavault, creating it!"
|
||||
_command="omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'CertificateMgmt' 'create' '{\"cn\": \"test.example.com\", \"size\": 4096, \"days\": 3650, \"c\": \"\", \"st\": \"\", \"l\": \"\", \"o\": \"\", \"ou\": \"\", \"email\": \"\"}' | jq -r '.uuid'"
|
||||
# shellcheck disable=SC2029
|
||||
_uuid=$(ssh "$DEPLOY_OMV_SSH_USER@$DEPLOY_OMV_HOST" "$_command")
|
||||
_debug _command "$_command"
|
||||
|
||||
if [ -z "$_uuid" ]; then
|
||||
_err "[OMV deploy-hook] An error occured while creating the certificate"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
_info "[OMV deploy-hook] Domain $_cdomain has uuid: $_uuid"
|
||||
_fullchain=$(jq <"$_cfullchain" -aRs .)
|
||||
_key=$(jq <"$_ckey" -aRs .)
|
||||
|
||||
_debug _fullchain "$_fullchain"
|
||||
_debug _key "$_key"
|
||||
|
||||
_info "[OMV deploy-hook] Updating key and certificate in openmediavault"
|
||||
_command="omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'CertificateMgmt' 'set' '{\"uuid\":\"$_uuid\", \"certificate\":$_fullchain, \"privatekey\":$_key, \"comment\":\"acme.sh deployed $(date)\"}'"
|
||||
# shellcheck disable=SC2029
|
||||
_result=$(ssh "$DEPLOY_OMV_SSH_USER@$DEPLOY_OMV_HOST" "$_command")
|
||||
|
||||
_debug _command "$_command"
|
||||
_debug _result "$_result"
|
||||
|
||||
_command="omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'WebGui' 'setSettings' \$(omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'WebGui' 'getSettings' | jq -c '.sslcertificateref=\"$_uuid\"')"
|
||||
# shellcheck disable=SC2029
|
||||
_result=$(ssh "$DEPLOY_OMV_SSH_USER@$DEPLOY_OMV_HOST" "$_command")
|
||||
|
||||
_debug _command "$_command"
|
||||
_debug _result "$_result"
|
||||
|
||||
_info "[OMV deploy-hook] Asking openmediavault to apply changes... (this could take some time, hang in there)"
|
||||
_command="omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'Config' 'applyChanges' '{\"modules\":[], \"force\": false}'"
|
||||
# shellcheck disable=SC2029
|
||||
_result=$(ssh "$DEPLOY_OMV_SSH_USER@$DEPLOY_OMV_HOST" "$_command")
|
||||
|
||||
_debug _command "$_command"
|
||||
_debug _result "$_result"
|
||||
|
||||
_info "[OMV deploy-hook] Asking nginx to reload"
|
||||
_command="nginx -s reload"
|
||||
# shellcheck disable=SC2029
|
||||
_result=$(ssh "$DEPLOY_OMV_SSH_USER@$DEPLOY_OMV_HOST" "$_command")
|
||||
|
||||
_debug _command "$_command"
|
||||
_debug _result "$_result"
|
||||
|
||||
else
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
_uuid=$(omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'CertificateMgmt' 'getList' '{"start": 0, "limit": -1}' | jq -r '.data[] | select(.name=="/CN='$_cdomain'") | .uuid')
|
||||
if [ -z "$_uuid" ]; then
|
||||
_info "[OMV deploy-hook] Domain $_cdomain has no certificate in openmediavault, creating it!"
|
||||
# shellcheck disable=SC2086
|
||||
_uuid=$(omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'CertificateMgmt' 'create' '{"cn": "test.example.com", "size": 4096, "days": 3650, "c": "", "st": "", "l": "", "o": "", "ou": "", "email": ""}' | jq -r '.uuid')
|
||||
|
||||
if [ -z "$_uuid" ]; then
|
||||
_err "[OMB deploy-hook] An error occured while creating the certificate"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
_info "[OMV deploy-hook] Domain $_cdomain has uuid: $_uuid"
|
||||
_fullchain=$(jq <"$_cfullchain" -aRs .)
|
||||
_key=$(jq <"$_ckey" -aRs .)
|
||||
|
||||
_debug _fullchain "$_fullchain"
|
||||
_debug _key "$_key"
|
||||
|
||||
_info "[OMV deploy-hook] Updating key and certificate in openmediavault"
|
||||
_command="omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'CertificateMgmt' 'set' '{\"uuid\":\"$_uuid\", \"certificate\":$_fullchain, \"privatekey\":$_key, \"comment\":\"acme.sh deployed $(date)\"}'"
|
||||
_result=$(eval "$_command")
|
||||
|
||||
_debug _command "$_command"
|
||||
_debug _result "$_result"
|
||||
|
||||
_command="omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'WebGui' 'setSettings' \$(omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'WebGui' 'getSettings' | jq -c '.sslcertificateref=\"$_uuid\"')"
|
||||
_result=$(eval "$_command")
|
||||
|
||||
_debug _command "$_command"
|
||||
_debug _result "$_result"
|
||||
|
||||
_info "[OMV deploy-hook] Asking openmediavault to apply changes... (this could take some time, hang in there)"
|
||||
_command="omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'Config' 'applyChanges' '{\"modules\":[], \"force\": false}'"
|
||||
_result=$(eval "$_command")
|
||||
|
||||
_debug _command "$_command"
|
||||
_debug _result "$_result"
|
||||
|
||||
_info "[OMV deploy-hook] Asking nginx to reload"
|
||||
_command="nginx -s reload"
|
||||
_result=$(eval "$_command")
|
||||
|
||||
_debug _command "$_command"
|
||||
_debug _result "$_result"
|
||||
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
26
deploy/opensshd.sh
Normal file
26
deploy/opensshd.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#Here is a script to deploy cert to opensshd server.
|
||||
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
opensshd_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
_err "deploy cert to opensshd server, Not implemented yet"
|
||||
return 1
|
||||
|
||||
}
|
||||
262
deploy/openstack.sh
Normal file
262
deploy/openstack.sh
Normal file
@@ -0,0 +1,262 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# OpenStack Barbican deploy hook
|
||||
#
|
||||
# This requires you to have OpenStackClient and python-barbicanclient
|
||||
# installed.
|
||||
#
|
||||
# You will require Keystone V3 credentials loaded into your environment, which
|
||||
# could be either password or v3applicationcredential type.
|
||||
#
|
||||
# Author: Andy Botting <andy@andybotting.com>
|
||||
|
||||
openstack_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
if ! _exists openstack; then
|
||||
_err "OpenStack client not found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_openstack_credentials || return $?
|
||||
|
||||
_info "Generate import pkcs12"
|
||||
_import_pkcs12="$(_mktemp)"
|
||||
if ! _openstack_to_pkcs "$_import_pkcs12" "$_ckey" "$_ccert" "$_cca"; then
|
||||
_err "Error creating pkcs12 certificate"
|
||||
return 1
|
||||
fi
|
||||
_debug _import_pkcs12 "$_import_pkcs12"
|
||||
_base64_pkcs12=$(_base64 "multiline" <"$_import_pkcs12")
|
||||
|
||||
secretHrefs=$(_openstack_get_secrets)
|
||||
_debug secretHrefs "$secretHrefs"
|
||||
_openstack_store_secret || return $?
|
||||
|
||||
if [ -n "$secretHrefs" ]; then
|
||||
_info "Cleaning up existing secret"
|
||||
_openstack_delete_secrets || return $?
|
||||
fi
|
||||
|
||||
_info "Certificate successfully deployed"
|
||||
return 0
|
||||
}
|
||||
|
||||
_openstack_store_secret() {
|
||||
if ! openstack secret store --name "$_cdomain." -t 'application/octet-stream' -e base64 --payload "$_base64_pkcs12"; then
|
||||
_err "Failed to create OpenStack secret"
|
||||
return 1
|
||||
fi
|
||||
return
|
||||
}
|
||||
|
||||
_openstack_delete_secrets() {
|
||||
echo "$secretHrefs" | while read -r secretHref; do
|
||||
_info "Deleting old secret $secretHref"
|
||||
if ! openstack secret delete "$secretHref"; then
|
||||
_err "Failed to delete OpenStack secret"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
return
|
||||
}
|
||||
|
||||
_openstack_get_secrets() {
|
||||
if ! secretHrefs=$(openstack secret list -f value --name "$_cdomain." | cut -d' ' -f1); then
|
||||
_err "Failed to list secrets"
|
||||
return 1
|
||||
fi
|
||||
echo "$secretHrefs"
|
||||
}
|
||||
|
||||
_openstack_to_pkcs() {
|
||||
# The existing _toPkcs command can't allow an empty password, due to sh
|
||||
# -z test, so copied here and forcing the empty password.
|
||||
_cpfx="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
|
||||
${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca" -password "pass:"
|
||||
}
|
||||
|
||||
_openstack_credentials() {
|
||||
_debug "Check OpenStack credentials"
|
||||
|
||||
# If we have OS_AUTH_URL already set in the environment, then assume we want
|
||||
# to use those, otherwise use stored credentials
|
||||
if [ -n "$OS_AUTH_URL" ]; then
|
||||
_debug "OS_AUTH_URL env var found, using environment"
|
||||
else
|
||||
_debug "OS_AUTH_URL not found, loading stored credentials"
|
||||
OS_AUTH_URL="${OS_AUTH_URL:-$(_readaccountconf_mutable OS_AUTH_URL)}"
|
||||
OS_IDENTITY_API_VERSION="${OS_IDENTITY_API_VERSION:-$(_readaccountconf_mutable OS_IDENTITY_API_VERSION)}"
|
||||
OS_AUTH_TYPE="${OS_AUTH_TYPE:-$(_readaccountconf_mutable OS_AUTH_TYPE)}"
|
||||
OS_APPLICATION_CREDENTIAL_ID="${OS_APPLICATION_CREDENTIAL_ID:-$(_readaccountconf_mutable OS_APPLICATION_CREDENTIAL_ID)}"
|
||||
OS_APPLICATION_CREDENTIAL_SECRET="${OS_APPLICATION_CREDENTIAL_SECRET:-$(_readaccountconf_mutable OS_APPLICATION_CREDENTIAL_SECRET)}"
|
||||
OS_USERNAME="${OS_USERNAME:-$(_readaccountconf_mutable OS_USERNAME)}"
|
||||
OS_PASSWORD="${OS_PASSWORD:-$(_readaccountconf_mutable OS_PASSWORD)}"
|
||||
OS_PROJECT_NAME="${OS_PROJECT_NAME:-$(_readaccountconf_mutable OS_PROJECT_NAME)}"
|
||||
OS_PROJECT_ID="${OS_PROJECT_ID:-$(_readaccountconf_mutable OS_PROJECT_ID)}"
|
||||
OS_USER_DOMAIN_NAME="${OS_USER_DOMAIN_NAME:-$(_readaccountconf_mutable OS_USER_DOMAIN_NAME)}"
|
||||
OS_USER_DOMAIN_ID="${OS_USER_DOMAIN_ID:-$(_readaccountconf_mutable OS_USER_DOMAIN_ID)}"
|
||||
OS_PROJECT_DOMAIN_NAME="${OS_PROJECT_DOMAIN_NAME:-$(_readaccountconf_mutable OS_PROJECT_DOMAIN_NAME)}"
|
||||
OS_PROJECT_DOMAIN_ID="${OS_PROJECT_DOMAIN_ID:-$(_readaccountconf_mutable OS_PROJECT_DOMAIN_ID)}"
|
||||
fi
|
||||
|
||||
# Check each var and either save or clear it depending on whether its set.
|
||||
# The helps us clear out old vars in the case where a user may want
|
||||
# to switch between password and app creds
|
||||
_debug "OS_AUTH_URL" "$OS_AUTH_URL"
|
||||
if [ -n "$OS_AUTH_URL" ]; then
|
||||
export OS_AUTH_URL
|
||||
_saveaccountconf_mutable OS_AUTH_URL "$OS_AUTH_URL"
|
||||
else
|
||||
unset OS_AUTH_URL
|
||||
_clearaccountconf SAVED_OS_AUTH_URL
|
||||
fi
|
||||
|
||||
_debug "OS_IDENTITY_API_VERSION" "$OS_IDENTITY_API_VERSION"
|
||||
if [ -n "$OS_IDENTITY_API_VERSION" ]; then
|
||||
export OS_IDENTITY_API_VERSION
|
||||
_saveaccountconf_mutable OS_IDENTITY_API_VERSION "$OS_IDENTITY_API_VERSION"
|
||||
else
|
||||
unset OS_IDENTITY_API_VERSION
|
||||
_clearaccountconf SAVED_OS_IDENTITY_API_VERSION
|
||||
fi
|
||||
|
||||
_debug "OS_AUTH_TYPE" "$OS_AUTH_TYPE"
|
||||
if [ -n "$OS_AUTH_TYPE" ]; then
|
||||
export OS_AUTH_TYPE
|
||||
_saveaccountconf_mutable OS_AUTH_TYPE "$OS_AUTH_TYPE"
|
||||
else
|
||||
unset OS_AUTH_TYPE
|
||||
_clearaccountconf SAVED_OS_AUTH_TYPE
|
||||
fi
|
||||
|
||||
_debug "OS_APPLICATION_CREDENTIAL_ID" "$OS_APPLICATION_CREDENTIAL_ID"
|
||||
if [ -n "$OS_APPLICATION_CREDENTIAL_ID" ]; then
|
||||
export OS_APPLICATION_CREDENTIAL_ID
|
||||
_saveaccountconf_mutable OS_APPLICATION_CREDENTIAL_ID "$OS_APPLICATION_CREDENTIAL_ID"
|
||||
else
|
||||
unset OS_APPLICATION_CREDENTIAL_ID
|
||||
_clearaccountconf SAVED_OS_APPLICATION_CREDENTIAL_ID
|
||||
fi
|
||||
|
||||
_secure_debug "OS_APPLICATION_CREDENTIAL_SECRET" "$OS_APPLICATION_CREDENTIAL_SECRET"
|
||||
if [ -n "$OS_APPLICATION_CREDENTIAL_SECRET" ]; then
|
||||
export OS_APPLICATION_CREDENTIAL_SECRET
|
||||
_saveaccountconf_mutable OS_APPLICATION_CREDENTIAL_SECRET "$OS_APPLICATION_CREDENTIAL_SECRET"
|
||||
else
|
||||
unset OS_APPLICATION_CREDENTIAL_SECRET
|
||||
_clearaccountconf SAVED_OS_APPLICATION_CREDENTIAL_SECRET
|
||||
fi
|
||||
|
||||
_debug "OS_USERNAME" "$OS_USERNAME"
|
||||
if [ -n "$OS_USERNAME" ]; then
|
||||
export OS_USERNAME
|
||||
_saveaccountconf_mutable OS_USERNAME "$OS_USERNAME"
|
||||
else
|
||||
unset OS_USERNAME
|
||||
_clearaccountconf SAVED_OS_USERNAME
|
||||
fi
|
||||
|
||||
_secure_debug "OS_PASSWORD" "$OS_PASSWORD"
|
||||
if [ -n "$OS_PASSWORD" ]; then
|
||||
export OS_PASSWORD
|
||||
_saveaccountconf_mutable OS_PASSWORD "$OS_PASSWORD"
|
||||
else
|
||||
unset OS_PASSWORD
|
||||
_clearaccountconf SAVED_OS_PASSWORD
|
||||
fi
|
||||
|
||||
_debug "OS_PROJECT_NAME" "$OS_PROJECT_NAME"
|
||||
if [ -n "$OS_PROJECT_NAME" ]; then
|
||||
export OS_PROJECT_NAME
|
||||
_saveaccountconf_mutable OS_PROJECT_NAME "$OS_PROJECT_NAME"
|
||||
else
|
||||
unset OS_PROJECT_NAME
|
||||
_clearaccountconf SAVED_OS_PROJECT_NAME
|
||||
fi
|
||||
|
||||
_debug "OS_PROJECT_ID" "$OS_PROJECT_ID"
|
||||
if [ -n "$OS_PROJECT_ID" ]; then
|
||||
export OS_PROJECT_ID
|
||||
_saveaccountconf_mutable OS_PROJECT_ID "$OS_PROJECT_ID"
|
||||
else
|
||||
unset OS_PROJECT_ID
|
||||
_clearaccountconf SAVED_OS_PROJECT_ID
|
||||
fi
|
||||
|
||||
_debug "OS_USER_DOMAIN_NAME" "$OS_USER_DOMAIN_NAME"
|
||||
if [ -n "$OS_USER_DOMAIN_NAME" ]; then
|
||||
export OS_USER_DOMAIN_NAME
|
||||
_saveaccountconf_mutable OS_USER_DOMAIN_NAME "$OS_USER_DOMAIN_NAME"
|
||||
else
|
||||
unset OS_USER_DOMAIN_NAME
|
||||
_clearaccountconf SAVED_OS_USER_DOMAIN_NAME
|
||||
fi
|
||||
|
||||
_debug "OS_USER_DOMAIN_ID" "$OS_USER_DOMAIN_ID"
|
||||
if [ -n "$OS_USER_DOMAIN_ID" ]; then
|
||||
export OS_USER_DOMAIN_ID
|
||||
_saveaccountconf_mutable OS_USER_DOMAIN_ID "$OS_USER_DOMAIN_ID"
|
||||
else
|
||||
unset OS_USER_DOMAIN_ID
|
||||
_clearaccountconf SAVED_OS_USER_DOMAIN_ID
|
||||
fi
|
||||
|
||||
_debug "OS_PROJECT_DOMAIN_NAME" "$OS_PROJECT_DOMAIN_NAME"
|
||||
if [ -n "$OS_PROJECT_DOMAIN_NAME" ]; then
|
||||
export OS_PROJECT_DOMAIN_NAME
|
||||
_saveaccountconf_mutable OS_PROJECT_DOMAIN_NAME "$OS_PROJECT_DOMAIN_NAME"
|
||||
else
|
||||
unset OS_PROJECT_DOMAIN_NAME
|
||||
_clearaccountconf SAVED_OS_PROJECT_DOMAIN_NAME
|
||||
fi
|
||||
|
||||
_debug "OS_PROJECT_DOMAIN_ID" "$OS_PROJECT_DOMAIN_ID"
|
||||
if [ -n "$OS_PROJECT_DOMAIN_ID" ]; then
|
||||
export OS_PROJECT_DOMAIN_ID
|
||||
_saveaccountconf_mutable OS_PROJECT_DOMAIN_ID "$OS_PROJECT_DOMAIN_ID"
|
||||
else
|
||||
unset OS_PROJECT_DOMAIN_ID
|
||||
_clearaccountconf SAVED_OS_PROJECT_DOMAIN_ID
|
||||
fi
|
||||
|
||||
if [ "$OS_AUTH_TYPE" = "v3applicationcredential" ]; then
|
||||
# Application Credential auth
|
||||
if [ -z "$OS_APPLICATION_CREDENTIAL_ID" ] || [ -z "$OS_APPLICATION_CREDENTIAL_SECRET" ]; then
|
||||
_err "When using OpenStack application credentials, OS_APPLICATION_CREDENTIAL_ID"
|
||||
_err "and OS_APPLICATION_CREDENTIAL_SECRET must be set."
|
||||
_err "Please check your credentials and try again."
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
# Password auth
|
||||
if [ -z "$OS_USERNAME" ] || [ -z "$OS_PASSWORD" ]; then
|
||||
_err "OpenStack username or password not found."
|
||||
_err "Please check your credentials and try again."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -z "$OS_PROJECT_NAME" ] && [ -z "$OS_PROJECT_ID" ]; then
|
||||
_err "When using password authentication, OS_PROJECT_NAME or"
|
||||
_err "OS_PROJECT_ID must be set."
|
||||
_err "Please check your credentials and try again."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
329
deploy/panos.sh
Normal file
329
deploy/panos.sh
Normal file
@@ -0,0 +1,329 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Script to deploy certificates to Palo Alto Networks PANOS via API
|
||||
# Note PANOS API KEY and IP address needs to be set prior to running.
|
||||
# The following variables exported from environment will be used.
|
||||
# If not set then values previously saved in domain.conf file are used.
|
||||
#
|
||||
# Firewall admin with superuser and IP address is required.
|
||||
#
|
||||
# REQUIRED:
|
||||
# export PANOS_HOST=""
|
||||
# export PANOS_USER="" #User *MUST* have Commit and Import Permissions in XML API for Admin Role
|
||||
# export PANOS_PASS=""
|
||||
#
|
||||
# OPTIONAL
|
||||
# export PANOS_TEMPLATE="" # Template Name of panorama managed devices
|
||||
# export PANOS_TEMPLATE_STACK="" # set a Template Stack if certificate should also be pushed automatically
|
||||
# export PANOS_VSYS="Shared" # name of the vsys to import the certificate
|
||||
# export PANOS_CERTNAME="" # use a custom certificate name to work around Panorama's 31-character limit
|
||||
#
|
||||
# The script will automatically generate a new API key if
|
||||
# no key is found, or if a saved key has expired or is invalid.
|
||||
|
||||
_COMMIT_WAIT_INTERVAL=30 # query commit status every 30 seconds
|
||||
_COMMIT_WAIT_ITERATIONS=20 # query commit status 20 times (20*30 = 600 seconds = 10 minutes)
|
||||
|
||||
# This function is to parse the XML response from the firewall
|
||||
parse_response() {
|
||||
type=$2
|
||||
_debug "API Response: $1"
|
||||
if [ "$type" = 'keygen' ]; then
|
||||
status=$(echo "$1" | sed 's/^.*\(['\'']\)\([a-z]*\)'\''.*/\2/g')
|
||||
if [ "$status" = "success" ]; then
|
||||
panos_key=$(echo "$1" | sed 's/^.*\(<key>\)\(.*\)<\/key>.*/\2/g')
|
||||
_panos_key=$panos_key
|
||||
else
|
||||
message="PAN-OS Key could not be set."
|
||||
fi
|
||||
else
|
||||
if [ "$type" = 'commit' ]; then
|
||||
job_id=$(echo "$1" | sed 's/^.*\(<job>\)\(.*\)<\/job>.*/\2/g')
|
||||
_commit_job_id=$job_id
|
||||
elif [ "$type" = 'job_status' ]; then
|
||||
job_status=$(echo "$1" | tr -d '\n' | sed 's/^.*<result>\([^<]*\)<\/result>.*/\1/g')
|
||||
_commit_job_status=$job_status
|
||||
fi
|
||||
status=$(echo "$1" | tr -d '\n' | sed 's/^.*"\([a-z]*\)".*/\1/g')
|
||||
message=$(echo "$1" | tr -d '\n' | sed 's/.*\(<result>\|<msg>\|<line>\)\([^<]*\).*/\2/g')
|
||||
_debug "Firewall message: $message"
|
||||
if [ "$type" = 'keytest' ] && [ "$status" != "success" ]; then
|
||||
_debug "**** API Key has EXPIRED or is INVALID ****"
|
||||
unset _panos_key
|
||||
fi
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
#This function is used to deploy to the firewall
|
||||
deployer() {
|
||||
content=""
|
||||
type=$1 # Types are keytest, keygen, cert, key, commit, job_status, push
|
||||
panos_url="https://$_panos_host/api/"
|
||||
export _H1="Content-Type: application/x-www-form-urlencoded"
|
||||
|
||||
#Test API Key by performing a lookup
|
||||
if [ "$type" = 'keytest' ]; then
|
||||
_debug "**** Testing saved API Key ****"
|
||||
# Get Version Info to test key
|
||||
content="type=version&key=$_panos_key"
|
||||
## Exclude all scopes for the empty commit
|
||||
#_exclude_scope="<device-and-network>excluded</device-and-network><shared-object>excluded</shared-object>"
|
||||
#content="type=commit&action=partial&key=$_panos_key&cmd=<commit><partial>$_exclude_scope<admin><member>$_panos_user</member></admin></partial></commit>"
|
||||
fi
|
||||
|
||||
# Generate API Key
|
||||
if [ "$type" = 'keygen' ]; then
|
||||
_debug "**** Generating new API Key ****"
|
||||
content="type=keygen&user=$_panos_user&password=$_panos_pass"
|
||||
# content="$content${nl}--$delim${nl}Content-Disposition: form-data; type=\"keygen\"; user=\"$_panos_user\"; password=\"$_panos_pass\"${nl}Content-Type: application/octet-stream${nl}${nl}"
|
||||
fi
|
||||
|
||||
# Deploy Cert or Key
|
||||
if [ "$type" = 'cert' ] || [ "$type" = 'key' ]; then
|
||||
_debug "**** Deploying $type ****"
|
||||
#Generate DELIM
|
||||
delim="-----MultipartDelimiter$(date "+%s%N")"
|
||||
nl="\015\012"
|
||||
#Set Header
|
||||
export _H1="Content-Type: multipart/form-data; boundary=$delim"
|
||||
if [ "$type" = 'cert' ]; then
|
||||
panos_url="${panos_url}?type=import"
|
||||
content="--$delim${nl}Content-Disposition: form-data; name=\"category\"\r\n\r\ncertificate"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_panos_certname"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_cfullchain")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cfullchain")"
|
||||
if [ "$_panos_template" ]; then
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl\"\r\n\r\n$_panos_template"
|
||||
fi
|
||||
if [ "$_panos_vsys" ]; then
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl-vsys\"\r\n\r\n$_panos_vsys"
|
||||
fi
|
||||
fi
|
||||
if [ "$type" = 'key' ]; then
|
||||
panos_url="${panos_url}?type=import"
|
||||
content="--$delim${nl}Content-Disposition: form-data; name=\"category\"\r\n\r\nprivate-key"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_panos_certname"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"passphrase\"\r\n\r\n123456"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_panos_certname.key")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")"
|
||||
if [ "$_panos_template" ]; then
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl\"\r\n\r\n$_panos_template"
|
||||
fi
|
||||
if [ "$_panos_vsys" ]; then
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl-vsys\"\r\n\r\n$_panos_vsys"
|
||||
fi
|
||||
fi
|
||||
#Close multipart
|
||||
content="$content${nl}--$delim--${nl}${nl}"
|
||||
#Convert CRLF
|
||||
content=$(printf %b "$content")
|
||||
fi
|
||||
|
||||
# Commit changes
|
||||
if [ "$type" = 'commit' ]; then
|
||||
_debug "**** Committing changes ****"
|
||||
#Check for force commit - will commit ALL uncommited changes to the firewall. Use with caution!
|
||||
if [ "$FORCE" ]; then
|
||||
_debug "Force switch detected. Committing ALL changes to the firewall."
|
||||
cmd=$(printf "%s" "<commit><force><partial><admin><member>$_panos_user</member></admin></partial></force></commit>" | _url_encode)
|
||||
else
|
||||
cmd=$(printf "%s" "<commit><partial><admin><member>$_panos_user</member></admin></partial></commit>" | _url_encode)
|
||||
fi
|
||||
content="type=commit&action=partial&key=$_panos_key&cmd=$cmd"
|
||||
fi
|
||||
|
||||
# Query job status
|
||||
if [ "$type" = 'job_status' ]; then
|
||||
echo "**** Querying job $_commit_job_id status ****"
|
||||
cmd=$(printf "%s" "<show><jobs><id>$_commit_job_id</id></jobs></show>" | _url_encode)
|
||||
content="type=op&key=$_panos_key&cmd=$cmd"
|
||||
fi
|
||||
|
||||
# Push changes
|
||||
if [ "$type" = 'push' ]; then
|
||||
echo "**** Pushing changes ****"
|
||||
cmd=$(printf "%s" "<commit-all><template-stack><name>$_panos_template_stack</name><admin><member>$_panos_user</member></admin></template-stack></commit-all>" | _url_encode)
|
||||
content="type=commit&action=all&key=$_panos_key&cmd=$cmd"
|
||||
fi
|
||||
|
||||
response=$(_post "$content" "$panos_url" "" "POST")
|
||||
parse_response "$response" "$type"
|
||||
# Saving response to variables
|
||||
response_status=$status
|
||||
_debug response_status "$response_status"
|
||||
if [ "$response_status" = "success" ]; then
|
||||
_debug "Successfully deployed $type"
|
||||
return 0
|
||||
elif [ "$_commit_job_status" ]; then
|
||||
_debug "Commit Job Status = $_commit_job_status"
|
||||
else
|
||||
_err "Deploy of type $type failed. Try deploying with --debug to troubleshoot."
|
||||
_debug "$message"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# This is the main function that will call the other functions to deploy everything.
|
||||
panos_deploy() {
|
||||
_cdomain=$(echo "$1" | sed 's/*/WILDCARD_/g') #Wildcard Safe Filename
|
||||
_ckey="$2"
|
||||
_cfullchain="$5"
|
||||
|
||||
# VALID FILE CHECK
|
||||
if [ ! -f "$_ckey" ] || [ ! -f "$_cfullchain" ]; then
|
||||
_err "Unable to find a valid key and/or cert. If this is an ECDSA/ECC cert, use the --ecc flag when deploying."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# PANOS_HOST
|
||||
if [ "$PANOS_HOST" ]; then
|
||||
_debug "Detected ENV variable PANOS_HOST. Saving to file."
|
||||
_savedeployconf PANOS_HOST "$PANOS_HOST" 1
|
||||
else
|
||||
_debug "Attempting to load variable PANOS_HOST from file."
|
||||
_getdeployconf PANOS_HOST
|
||||
fi
|
||||
|
||||
# PANOS USER
|
||||
if [ "$PANOS_USER" ]; then
|
||||
_debug "Detected ENV variable PANOS_USER. Saving to file."
|
||||
_savedeployconf PANOS_USER "$PANOS_USER" 1
|
||||
else
|
||||
_debug "Attempting to load variable PANOS_USER from file."
|
||||
_getdeployconf PANOS_USER
|
||||
fi
|
||||
|
||||
# PANOS_PASS
|
||||
if [ "$PANOS_PASS" ]; then
|
||||
_debug "Detected ENV variable PANOS_PASS. Saving to file."
|
||||
_savedeployconf PANOS_PASS "$PANOS_PASS" 1
|
||||
else
|
||||
_debug "Attempting to load variable PANOS_PASS from file."
|
||||
_getdeployconf PANOS_PASS
|
||||
fi
|
||||
|
||||
# PANOS_KEY
|
||||
if [ "$PANOS_KEY" ]; then
|
||||
_debug "Detected ENV variable PANOS_KEY. Saving to file."
|
||||
_savedeployconf PANOS_KEY "$PANOS_KEY" 1
|
||||
else
|
||||
_debug "Attempting to load variable PANOS_KEY from file."
|
||||
_getdeployconf PANOS_KEY
|
||||
fi
|
||||
|
||||
# PANOS_TEMPLATE
|
||||
if [ "$PANOS_TEMPLATE" ]; then
|
||||
_debug "Detected ENV variable PANOS_TEMPLATE. Saving to file."
|
||||
_savedeployconf PANOS_TEMPLATE "$PANOS_TEMPLATE" 1
|
||||
else
|
||||
_debug "Attempting to load variable PANOS_TEMPLATE from file."
|
||||
_getdeployconf PANOS_TEMPLATE
|
||||
fi
|
||||
|
||||
# PANOS_TEMPLATE_STACK
|
||||
if [ "$PANOS_TEMPLATE_STACK" ]; then
|
||||
_debug "Detected ENV variable PANOS_TEMPLATE_STACK. Saving to file."
|
||||
_savedeployconf PANOS_TEMPLATE_STACK "$PANOS_TEMPLATE_STACK" 1
|
||||
else
|
||||
_debug "Attempting to load variable PANOS_TEMPLATE_STACK from file."
|
||||
_getdeployconf PANOS_TEMPLATE_STACK
|
||||
fi
|
||||
|
||||
# PANOS_TEMPLATE_STACK
|
||||
if [ "$PANOS_VSYS" ]; then
|
||||
_debug "Detected ENV variable PANOS_VSYS. Saving to file."
|
||||
_savedeployconf PANOS_VSYS "$PANOS_VSYS" 1
|
||||
else
|
||||
_debug "Attempting to load variable PANOS_VSYS from file."
|
||||
_getdeployconf PANOS_VSYS
|
||||
fi
|
||||
|
||||
# PANOS_CERTNAME
|
||||
if [ "$PANOS_CERTNAME" ]; then
|
||||
_debug "Detected ENV variable PANOS_CERTNAME. Saving to file."
|
||||
_savedeployconf PANOS_CERTNAME "$PANOS_CERTNAME" 1
|
||||
else
|
||||
_debug "Attempting to load variable PANOS_CERTNAME from file."
|
||||
_getdeployconf PANOS_CERTNAME
|
||||
fi
|
||||
|
||||
#Store variables
|
||||
_panos_host=$PANOS_HOST
|
||||
_panos_user=$PANOS_USER
|
||||
_panos_pass=$PANOS_PASS
|
||||
_panos_key=$PANOS_KEY
|
||||
_panos_template=$PANOS_TEMPLATE
|
||||
_panos_template_stack=$PANOS_TEMPLATE_STACK
|
||||
_panos_vsys=$PANOS_VSYS
|
||||
_panos_certname=$PANOS_CERTNAME
|
||||
|
||||
#Test API Key if found. If the key is invalid, the variable _panos_key will be unset.
|
||||
if [ "$_panos_host" ] && [ "$_panos_key" ]; then
|
||||
_debug "**** Testing API KEY ****"
|
||||
deployer keytest
|
||||
fi
|
||||
|
||||
# Check for valid variables
|
||||
if [ -z "$_panos_host" ]; then
|
||||
_err "No host found. If this is your first time deploying, please set PANOS_HOST in ENV variables. You can delete it after you have successfully deployed the certs."
|
||||
return 1
|
||||
else
|
||||
# Use certificate name based on the first domain on the certificate if no custom certificate name is set
|
||||
if [ -z "$_panos_certname" ]; then
|
||||
_panos_certname="$_cdomain"
|
||||
_savedeployconf PANOS_CERTNAME "$_panos_certname" 1
|
||||
fi
|
||||
|
||||
# Generate a new API key if no valid API key is found
|
||||
if [ -z "$_panos_key" ]; then
|
||||
if [ -z "$_panos_user" ]; then
|
||||
_err "No user found. If this is your first time deploying, please set PANOS_USER in ENV variables. You can delete it after you have successfully deployed the certs."
|
||||
return 1
|
||||
elif [ -z "$_panos_pass" ]; then
|
||||
_err "No password found. If this is your first time deploying, please set PANOS_PASS in ENV variables. You can delete it after you have successfully deployed the certs."
|
||||
return 1
|
||||
fi
|
||||
_debug "**** Generating new PANOS API KEY ****"
|
||||
deployer keygen
|
||||
_savedeployconf PANOS_KEY "$_panos_key" 1
|
||||
fi
|
||||
|
||||
# Confirm that a valid key was generated
|
||||
if [ -z "$_panos_key" ]; then
|
||||
_err "Unable to generate an API key. The user and pass may be invalid or not authorized to generate a new key. Please check the PANOS_USER and PANOS_PASS credentials and try again"
|
||||
return 1
|
||||
else
|
||||
# A commit of a failed import would leave a mismatched cert/key pair
|
||||
# on the firewall and can lock the admin out of the management
|
||||
# interface, see https://github.com/acmesh-official/acme.sh/issues/4716
|
||||
if ! deployer cert; then
|
||||
_err "Cert import failed. Aborting without committing."
|
||||
return 1
|
||||
fi
|
||||
if ! deployer key; then
|
||||
_err "Key import failed. Aborting without committing. Warning: the firewall now has an uncommitted mismatched cert/key pair in its candidate config."
|
||||
return 1
|
||||
fi
|
||||
if ! deployer commit; then
|
||||
return 1
|
||||
fi
|
||||
if [ "$_panos_template_stack" ]; then
|
||||
# try to get job status for 20 times in 30 sec interval
|
||||
i=0
|
||||
while [ "$i" -lt $_COMMIT_WAIT_ITERATIONS ]; do
|
||||
deployer job_status
|
||||
if [ "$_commit_job_status" = "OK" ]; then
|
||||
echo "Commit finished!"
|
||||
break
|
||||
fi
|
||||
sleep $_COMMIT_WAIT_INTERVAL
|
||||
i=$((i + 1))
|
||||
done
|
||||
deployer push
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
123
deploy/peplink.sh
Normal file
123
deploy/peplink.sh
Normal file
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Script to deploy cert to Peplink Routers
|
||||
#
|
||||
# The following environment variables must be set:
|
||||
#
|
||||
# PEPLINK_Hostname - Peplink hostname
|
||||
# PEPLINK_Username - Peplink username to login
|
||||
# PEPLINK_Password - Peplink password to login
|
||||
#
|
||||
# The following environmental variables may be set if you don't like their
|
||||
# default values:
|
||||
#
|
||||
# PEPLINK_Certtype - Certificate type to target for replacement
|
||||
# defaults to "webadmin", can be one of:
|
||||
# * "chub" (ContentHub)
|
||||
# * "openvpn" (OpenVPN CA)
|
||||
# * "portal" (Captive Portal SSL)
|
||||
# * "webadmin" (Web Admin SSL)
|
||||
# * "webproxy" (Proxy Root CA)
|
||||
# * "wwan_ca" (Wi-Fi WAN CA)
|
||||
# * "wwan_client" (Wi-Fi WAN Client)
|
||||
# PEPLINK_Scheme - defaults to "https"
|
||||
# PEPLINK_Port - defaults to "443"
|
||||
#
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
_peplink_get_cookie_data() {
|
||||
grep -i "\W$1=" | grep -i "^Set-Cookie:" | _tail_n 1 | _egrep_o "$1=[^;]*;" | tr -d ';'
|
||||
}
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
peplink_deploy() {
|
||||
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
_debug _ckey "$_ckey"
|
||||
|
||||
# Get Hostname, Username and Password, but don't save until we successfully authenticate
|
||||
_getdeployconf PEPLINK_Hostname
|
||||
_getdeployconf PEPLINK_Username
|
||||
_getdeployconf PEPLINK_Password
|
||||
if [ -z "${PEPLINK_Hostname:-}" ] || [ -z "${PEPLINK_Username:-}" ] || [ -z "${PEPLINK_Password:-}" ]; then
|
||||
_err "PEPLINK_Hostname & PEPLINK_Username & PEPLINK_Password must be set"
|
||||
return 1
|
||||
fi
|
||||
_debug2 PEPLINK_Hostname "$PEPLINK_Hostname"
|
||||
_debug2 PEPLINK_Username "$PEPLINK_Username"
|
||||
_secure_debug2 PEPLINK_Password "$PEPLINK_Password"
|
||||
|
||||
# Optional certificate type, scheme, and port for Peplink
|
||||
_getdeployconf PEPLINK_Certtype
|
||||
_getdeployconf PEPLINK_Scheme
|
||||
_getdeployconf PEPLINK_Port
|
||||
|
||||
# Don't save the certificate type until we verify it exists and is supported
|
||||
_savedeployconf PEPLINK_Scheme "$PEPLINK_Scheme"
|
||||
_savedeployconf PEPLINK_Port "$PEPLINK_Port"
|
||||
|
||||
# Default vaules for certificate type, scheme, and port
|
||||
[ -n "${PEPLINK_Certtype}" ] || PEPLINK_Certtype="webadmin"
|
||||
[ -n "${PEPLINK_Scheme}" ] || PEPLINK_Scheme="https"
|
||||
[ -n "${PEPLINK_Port}" ] || PEPLINK_Port="443"
|
||||
|
||||
_debug2 PEPLINK_Certtype "$PEPLINK_Certtype"
|
||||
_debug2 PEPLINK_Scheme "$PEPLINK_Scheme"
|
||||
_debug2 PEPLINK_Port "$PEPLINK_Port"
|
||||
|
||||
_base_url="$PEPLINK_Scheme://$PEPLINK_Hostname:$PEPLINK_Port"
|
||||
_debug _base_url "$_base_url"
|
||||
|
||||
# Login, get the auth token from the cookie
|
||||
_info "Logging into $PEPLINK_Hostname:$PEPLINK_Port"
|
||||
encoded_username="$(printf "%s" "$PEPLINK_Username" | _url_encode)"
|
||||
encoded_password="$(printf "%s" "$PEPLINK_Password" | _url_encode)"
|
||||
response=$(_post "func=login&username=$encoded_username&password=$encoded_password" "$_base_url/cgi-bin/MANGA/api.cgi")
|
||||
auth_token=$(_peplink_get_cookie_data "bauth" <"$HTTP_HEADER")
|
||||
_debug3 response "$response"
|
||||
_debug auth_token "$auth_token"
|
||||
|
||||
if [ -z "$auth_token" ]; then
|
||||
_err "Unable to authenticate to $PEPLINK_Hostname:$PEPLINK_Port using $PEPLINK_Scheme."
|
||||
_err "Check your username and password."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_H1="Cookie: $auth_token"
|
||||
export _H1
|
||||
_debug2 H1 "${_H1}"
|
||||
|
||||
# Now that we know the hostnameusername and password are good, save them
|
||||
_savedeployconf PEPLINK_Hostname "$PEPLINK_Hostname"
|
||||
_savedeployconf PEPLINK_Username "$PEPLINK_Username"
|
||||
_savedeployconf PEPLINK_Password "$PEPLINK_Password"
|
||||
|
||||
_info "Generate form POST request"
|
||||
|
||||
encoded_key="$(_url_encode <"$_ckey")"
|
||||
encoded_fullchain="$(_url_encode <"$_cfullchain")"
|
||||
body="cert_type=$PEPLINK_Certtype&cert_uid=§ion=CERT_modify&key_pem=$encoded_key&key_pem_passphrase=&key_pem_passphrase_confirm=&cert_pem=$encoded_fullchain"
|
||||
_debug3 body "$body"
|
||||
|
||||
_info "Upload $PEPLINK_Certtype certificate to the Peplink"
|
||||
|
||||
response=$(_post "$body" "$_base_url/cgi-bin/MANGA/admin.cgi")
|
||||
_debug3 response "$response"
|
||||
|
||||
if echo "$response" | grep 'Success' >/dev/null; then
|
||||
# We've verified this certificate type is valid, so save it
|
||||
_savedeployconf PEPLINK_Certtype "$PEPLINK_Certtype"
|
||||
_info "Certificate was updated"
|
||||
return 0
|
||||
else
|
||||
_err "Unable to update certificate, error code $response"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
139
deploy/proxmoxbs.sh
Normal file
139
deploy/proxmoxbs.sh
Normal file
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Deploy certificates to a proxmox backup server using the API.
|
||||
#
|
||||
# Environment variables that can be set are:
|
||||
# `DEPLOY_PROXMOXBS_SERVER`: The hostname of the proxmox backup server. Defaults to
|
||||
# _cdomain.
|
||||
# `DEPLOY_PROXMOXBS_SERVER_PORT`: The port number the management interface is on.
|
||||
# Defaults to 8007.
|
||||
# `DEPLOY_PROXMOXBS_USER`: The user we'll connect as. Defaults to root.
|
||||
# `DEPLOY_PROXMOXBS_USER_REALM`: The authentication realm the user authenticates
|
||||
# with. Defaults to pam.
|
||||
# `DEPLOY_PROXMOXBS_API_TOKEN_NAME`: The name of the API token created for the
|
||||
# user account. Defaults to acme.
|
||||
# `DEPLOY_PROXMOXBS_API_TOKEN_KEY`: The API token. Required.
|
||||
|
||||
proxmoxbs_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug2 _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
# "Sane" defaults.
|
||||
_getdeployconf DEPLOY_PROXMOXBS_SERVER
|
||||
if [ -z "$DEPLOY_PROXMOXBS_SERVER" ]; then
|
||||
_target_hostname="$_cdomain"
|
||||
else
|
||||
_target_hostname="$DEPLOY_PROXMOXBS_SERVER"
|
||||
_savedeployconf DEPLOY_PROXMOXBS_SERVER "$DEPLOY_PROXMOXBS_SERVER"
|
||||
fi
|
||||
_debug2 DEPLOY_PROXMOXBS_SERVER "$_target_hostname"
|
||||
|
||||
_getdeployconf DEPLOY_PROXMOXBS_SERVER_PORT
|
||||
if [ -z "$DEPLOY_PROXMOXBS_SERVER_PORT" ]; then
|
||||
_target_port="8007"
|
||||
else
|
||||
_target_port="$DEPLOY_PROXMOXBS_SERVER_PORT"
|
||||
_savedeployconf DEPLOY_PROXMOXBS_SERVER_PORT "$DEPLOY_PROXMOXBS_SERVER_PORT"
|
||||
fi
|
||||
_debug2 DEPLOY_PROXMOXBS_SERVER_PORT "$_target_port"
|
||||
|
||||
# Complete URL.
|
||||
_target_url="https://${_target_hostname}:${_target_port}/api2/json/nodes/localhost/certificates/custom"
|
||||
_debug TARGET_URL "$_target_url"
|
||||
|
||||
# More "sane" defaults.
|
||||
_getdeployconf DEPLOY_PROXMOXBS_USER
|
||||
if [ -z "$DEPLOY_PROXMOXBS_USER" ]; then
|
||||
_proxmoxbs_user="root"
|
||||
else
|
||||
_proxmoxbs_user="$DEPLOY_PROXMOXBS_USER"
|
||||
_savedeployconf DEPLOY_PROXMOXBS_USER "$DEPLOY_PROXMOXBS_USER"
|
||||
fi
|
||||
_debug2 DEPLOY_PROXMOXBS_USER "$_proxmoxbs_user"
|
||||
|
||||
_getdeployconf DEPLOY_PROXMOXBS_USER_REALM
|
||||
if [ -z "$DEPLOY_PROXMOXBS_USER_REALM" ]; then
|
||||
_proxmoxbs_user_realm="pam"
|
||||
else
|
||||
_proxmoxbs_user_realm="$DEPLOY_PROXMOXBS_USER_REALM"
|
||||
_savedeployconf DEPLOY_PROXMOXBS_USER_REALM "$DEPLOY_PROXMOXBS_USER_REALM"
|
||||
fi
|
||||
_debug2 DEPLOY_PROXMOXBS_USER_REALM "$_proxmoxbs_user_realm"
|
||||
|
||||
_getdeployconf DEPLOY_PROXMOXBS_API_TOKEN_NAME
|
||||
if [ -z "$DEPLOY_PROXMOXBS_API_TOKEN_NAME" ]; then
|
||||
_proxmoxbs_api_token_name="acme"
|
||||
else
|
||||
_proxmoxbs_api_token_name="$DEPLOY_PROXMOXBS_API_TOKEN_NAME"
|
||||
_savedeployconf DEPLOY_PROXMOXBS_API_TOKEN_NAME "$DEPLOY_PROXMOXBS_API_TOKEN_NAME"
|
||||
fi
|
||||
_debug2 DEPLOY_PROXMOXBS_API_TOKEN_NAME "$_proxmoxbs_api_token_name"
|
||||
|
||||
# This is required.
|
||||
_getdeployconf DEPLOY_PROXMOXBS_API_TOKEN_KEY
|
||||
if [ -z "$DEPLOY_PROXMOXBS_API_TOKEN_KEY" ]; then
|
||||
_err "API key not provided."
|
||||
return 1
|
||||
else
|
||||
_proxmoxbs_api_token_key="$DEPLOY_PROXMOXBS_API_TOKEN_KEY"
|
||||
_savedeployconf DEPLOY_PROXMOXBS_API_TOKEN_KEY "$DEPLOY_PROXMOXBS_API_TOKEN_KEY"
|
||||
fi
|
||||
_debug2 DEPLOY_PROXMOXBS_API_TOKEN_KEY "$_proxmoxbs_api_token_key"
|
||||
|
||||
# PBS API Token header value. Used in "Authorization: PBSAPIToken".
|
||||
_proxmoxbs_header_api_token="${_proxmoxbs_user}@${_proxmoxbs_user_realm}!${_proxmoxbs_api_token_name}:${_proxmoxbs_api_token_key}"
|
||||
_debug2 "Auth Header" "$_proxmoxbs_header_api_token"
|
||||
|
||||
# Ugly. I hate putting heredocs inside functions because heredocs don't
|
||||
# account for whitespace correctly but it _does_ work and is several times
|
||||
# cleaner than anything else I had here.
|
||||
#
|
||||
# This dumps the json payload to a variable that should be passable to the
|
||||
# _psot function.
|
||||
_json_payload=$(
|
||||
cat <<HEREDOC
|
||||
{
|
||||
"certificates": "$(tr '\n' ':' <"$_cfullchain" | sed 's/:/\\n/g')",
|
||||
"key": "$(tr '\n' ':' <"$_ckey" | sed 's/:/\\n/g')",
|
||||
"node":"localhost",
|
||||
"restart":true,
|
||||
"force":true
|
||||
}
|
||||
HEREDOC
|
||||
)
|
||||
_debug2 Payload "$_json_payload"
|
||||
|
||||
_info "Push certificates to server"
|
||||
export HTTPS_INSECURE=1
|
||||
export _H1="Authorization: PBSAPIToken=${_proxmoxbs_header_api_token}"
|
||||
response=$(_post "$_json_payload" "$_target_url" "" POST "application/json")
|
||||
_retval=$?
|
||||
# The API errors out with a non-2xx HTTP status and an empty body,
|
||||
# so the status line is checked too, not only the response body.
|
||||
_status_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
|
||||
_debug2 "HTTP status" "$_status_code"
|
||||
response="$(echo "$response" | _json_decode | _normalizeJson)"
|
||||
message=$(echo "$response" | _egrep_o '"message":"[^"]*' | cut -d : -f 2 | tr -d '"')
|
||||
case "$_status_code" in
|
||||
2[0-9][0-9])
|
||||
if [ "${_retval}" -eq 0 ] && [ -z "$message" ]; then
|
||||
_debug3 response "$response"
|
||||
_info "Certificate successfully deployed"
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
_err "Certificate deployment failed (HTTP status $_status_code). $message"
|
||||
_debug "Response" "$response"
|
||||
return 1
|
||||
|
||||
}
|
||||
151
deploy/proxmoxve.sh
Normal file
151
deploy/proxmoxve.sh
Normal file
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Deploy certificates to a proxmox virtual environment node using the API.
|
||||
#
|
||||
# Environment variables that can be set are:
|
||||
# `DEPLOY_PROXMOXVE_SERVER`: The hostname of the proxmox ve node. Defaults to
|
||||
# _cdomain.
|
||||
# `DEPLOY_PROXMOXVE_SERVER_PORT`: The port number the management interface is on.
|
||||
# Defaults to 8006.
|
||||
# `DEPLOY_PROXMOXVE_NODE_NAME`: The name of the node we'll be connecting to.
|
||||
# Defaults to the host portion of the server
|
||||
# domain name.
|
||||
# `DEPLOY_PROXMOXVE_USER`: The user we'll connect as. Defaults to root.
|
||||
# `DEPLOY_PROXMOXVE_USER_REALM`: The authentication realm the user authenticates
|
||||
# with. Defaults to pam.
|
||||
# `DEPLOY_PROXMOXVE_API_TOKEN_NAME`: The name of the API token created for the
|
||||
# user account. Defaults to acme.
|
||||
# `DEPLOY_PROXMOXVE_API_TOKEN_KEY`: The API token. Required.
|
||||
|
||||
proxmoxve_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug2 _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
# "Sane" defaults.
|
||||
_getdeployconf DEPLOY_PROXMOXVE_SERVER
|
||||
if [ -z "$DEPLOY_PROXMOXVE_SERVER" ]; then
|
||||
_target_hostname="$_cdomain"
|
||||
else
|
||||
_target_hostname="$DEPLOY_PROXMOXVE_SERVER"
|
||||
_savedeployconf DEPLOY_PROXMOXVE_SERVER "$DEPLOY_PROXMOXVE_SERVER"
|
||||
fi
|
||||
_debug2 DEPLOY_PROXMOXVE_SERVER "$_target_hostname"
|
||||
|
||||
_getdeployconf DEPLOY_PROXMOXVE_SERVER_PORT
|
||||
if [ -z "$DEPLOY_PROXMOXVE_SERVER_PORT" ]; then
|
||||
_target_port="8006"
|
||||
else
|
||||
_target_port="$DEPLOY_PROXMOXVE_SERVER_PORT"
|
||||
_savedeployconf DEPLOY_PROXMOXVE_SERVER_PORT "$DEPLOY_PROXMOXVE_SERVER_PORT"
|
||||
fi
|
||||
_debug2 DEPLOY_PROXMOXVE_SERVER_PORT "$_target_port"
|
||||
|
||||
_getdeployconf DEPLOY_PROXMOXVE_NODE_NAME
|
||||
if [ -z "$DEPLOY_PROXMOXVE_NODE_NAME" ]; then
|
||||
_node_name=$(echo "$_target_hostname" | cut -d. -f1)
|
||||
else
|
||||
_node_name="$DEPLOY_PROXMOXVE_NODE_NAME"
|
||||
_savedeployconf DEPLOY_PROXMOXVE_NODE_NAME "$DEPLOY_PROXMOXVE_NODE_NAME"
|
||||
fi
|
||||
_debug2 DEPLOY_PROXMOXVE_NODE_NAME "$_node_name"
|
||||
|
||||
# Complete URL.
|
||||
_target_url="https://${_target_hostname}:${_target_port}/api2/json/nodes/${_node_name}/certificates/custom"
|
||||
_debug TARGET_URL "$_target_url"
|
||||
|
||||
# More "sane" defaults.
|
||||
_getdeployconf DEPLOY_PROXMOXVE_USER
|
||||
if [ -z "$DEPLOY_PROXMOXVE_USER" ]; then
|
||||
_proxmoxve_user="root"
|
||||
else
|
||||
_proxmoxve_user="$DEPLOY_PROXMOXVE_USER"
|
||||
_savedeployconf DEPLOY_PROXMOXVE_USER "$DEPLOY_PROXMOXVE_USER"
|
||||
fi
|
||||
_debug2 DEPLOY_PROXMOXVE_USER "$_proxmoxve_user"
|
||||
|
||||
_getdeployconf DEPLOY_PROXMOXVE_USER_REALM
|
||||
if [ -z "$DEPLOY_PROXMOXVE_USER_REALM" ]; then
|
||||
_proxmoxve_user_realm="pam"
|
||||
else
|
||||
_proxmoxve_user_realm="$DEPLOY_PROXMOXVE_USER_REALM"
|
||||
_savedeployconf DEPLOY_PROXMOXVE_USER_REALM "$DEPLOY_PROXMOXVE_USER_REALM"
|
||||
fi
|
||||
_debug2 DEPLOY_PROXMOXVE_USER_REALM "$_proxmoxve_user_realm"
|
||||
|
||||
_getdeployconf DEPLOY_PROXMOXVE_API_TOKEN_NAME
|
||||
if [ -z "$DEPLOY_PROXMOXVE_API_TOKEN_NAME" ]; then
|
||||
_proxmoxve_api_token_name="acme"
|
||||
else
|
||||
_proxmoxve_api_token_name="$DEPLOY_PROXMOXVE_API_TOKEN_NAME"
|
||||
_savedeployconf DEPLOY_PROXMOXVE_API_TOKEN_NAME "$DEPLOY_PROXMOXVE_API_TOKEN_NAME"
|
||||
fi
|
||||
_debug2 DEPLOY_PROXMOXVE_API_TOKEN_NAME "$_proxmoxve_api_token_name"
|
||||
|
||||
# This is required.
|
||||
_getdeployconf DEPLOY_PROXMOXVE_API_TOKEN_KEY
|
||||
if [ -z "$DEPLOY_PROXMOXVE_API_TOKEN_KEY" ]; then
|
||||
_err "API key not provided."
|
||||
return 1
|
||||
else
|
||||
_proxmoxve_api_token_key="$DEPLOY_PROXMOXVE_API_TOKEN_KEY"
|
||||
_savedeployconf DEPLOY_PROXMOXVE_API_TOKEN_KEY "$DEPLOY_PROXMOXVE_API_TOKEN_KEY"
|
||||
fi
|
||||
_debug2 DEPLOY_PROXMOXVE_API_TOKEN_KEY "$_proxmoxve_api_token_key"
|
||||
|
||||
# PVE API Token header value. Used in "Authorization: PVEAPIToken".
|
||||
_proxmoxve_header_api_token="${_proxmoxve_user}@${_proxmoxve_user_realm}!${_proxmoxve_api_token_name}=${_proxmoxve_api_token_key}"
|
||||
_debug2 "Auth Header" "$_proxmoxve_header_api_token"
|
||||
|
||||
# Ugly. I hate putting heredocs inside functions because heredocs don't
|
||||
# account for whitespace correctly but it _does_ work and is several times
|
||||
# cleaner than anything else I had here.
|
||||
#
|
||||
# This dumps the json payload to a variable that should be passable to the
|
||||
# _psot function.
|
||||
_json_payload=$(
|
||||
cat <<HEREDOC
|
||||
{
|
||||
"certificates": "$(tr '\n' ':' <"$_cfullchain" | sed 's/:/\\n/g')",
|
||||
"key": "$(tr '\n' ':' <"$_ckey" | sed 's/:/\\n/g')",
|
||||
"node":"$_node_name",
|
||||
"restart":"1",
|
||||
"force":"1"
|
||||
}
|
||||
HEREDOC
|
||||
)
|
||||
_debug2 Payload "$_json_payload"
|
||||
|
||||
_info "Push certificates to server"
|
||||
export HTTPS_INSECURE=1
|
||||
export _H1="Authorization: PVEAPIToken=${_proxmoxve_header_api_token}"
|
||||
response=$(_post "$_json_payload" "$_target_url" "" POST "application/json")
|
||||
_retval=$?
|
||||
# The API errors out with a non-2xx HTTP status and an empty body,
|
||||
# so the status line is checked too, not only the response body.
|
||||
_status_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
|
||||
_debug2 "HTTP status" "$_status_code"
|
||||
response="$(echo "$response" | _json_decode | _normalizeJson)"
|
||||
message=$(echo "$response" | _egrep_o '"message":"[^"]*' | cut -d : -f 2 | tr -d '"')
|
||||
case "$_status_code" in
|
||||
2[0-9][0-9])
|
||||
if [ "${_retval}" -eq 0 ] && [ -z "$message" ]; then
|
||||
_debug3 response "$response"
|
||||
_info "Certificate successfully deployed"
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
_err "Certificate deployment failed (HTTP status $_status_code). $message"
|
||||
_debug "Response" "$response"
|
||||
return 1
|
||||
|
||||
}
|
||||
26
deploy/pureftpd.sh
Normal file
26
deploy/pureftpd.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#Here is a script to deploy cert to pureftpd server.
|
||||
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
pureftpd_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
_err "deploy cert to pureftpd server, Not implemented yet"
|
||||
return 1
|
||||
|
||||
}
|
||||
104
deploy/qiniu.sh
Normal file
104
deploy/qiniu.sh
Normal file
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Script to create certificate to qiniu.com
|
||||
#
|
||||
# This deployment required following variables
|
||||
# export QINIU_AK="QINIUACCESSKEY"
|
||||
# export QINIU_SK="QINIUSECRETKEY"
|
||||
# export QINIU_CDN_DOMAIN="cdn.example.com"
|
||||
# If you have more than one domain, just
|
||||
# export QINIU_CDN_DOMAIN="cdn1.example.com cdn2.example.com"
|
||||
# Optional: force HTTPS redirect (default: false)
|
||||
# export QINIU_FORCE_HTTPS="true"
|
||||
|
||||
QINIU_API_BASE="https://api.qiniu.com"
|
||||
|
||||
qiniu_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
if [ -z "$QINIU_AK" ]; then
|
||||
_err "QINIU_AK is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf QINIU_AK "$QINIU_AK"
|
||||
fi
|
||||
|
||||
if [ -z "$QINIU_SK" ]; then
|
||||
_err "QINIU_SK is not defined."
|
||||
return 1
|
||||
else
|
||||
_savedomainconf QINIU_SK "$QINIU_SK"
|
||||
fi
|
||||
|
||||
if [ "$QINIU_CDN_DOMAIN" ]; then
|
||||
_savedomainconf QINIU_CDN_DOMAIN "$QINIU_CDN_DOMAIN"
|
||||
else
|
||||
QINIU_CDN_DOMAIN="$_cdomain"
|
||||
fi
|
||||
|
||||
if [ -z "$QINIU_FORCE_HTTPS" ]; then
|
||||
QINIU_FORCE_HTTPS="false"
|
||||
else
|
||||
_savedomainconf QINIU_FORCE_HTTPS "$QINIU_FORCE_HTTPS"
|
||||
fi
|
||||
|
||||
## upload certificate
|
||||
string_fullchain=$(sed 's/$/\\n/' "$_cfullchain" | tr -d '\n')
|
||||
string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')
|
||||
|
||||
sslcert_path="/sslcert"
|
||||
sslcerl_body="{\"name\":\"$_cdomain\",\"common_name\":\"$QINIU_CDN_DOMAIN\",\"ca\":\"$string_fullchain\",\"pri\":\"$string_key\"}"
|
||||
sslcert_access_token="$(_make_access_token "$sslcert_path")"
|
||||
_debug sslcert_access_token "$sslcert_access_token"
|
||||
export _H1="Authorization: QBox $sslcert_access_token"
|
||||
sslcert_response=$(_post "$sslcerl_body" "$QINIU_API_BASE$sslcert_path" 0 "POST" "application/json" | _dbase64)
|
||||
|
||||
if ! _contains "$sslcert_response" "certID"; then
|
||||
_err "Error in creating certificate:"
|
||||
_err "$sslcert_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug sslcert_response "$sslcert_response"
|
||||
_info "Certificate successfully uploaded, updating domain $_cdomain"
|
||||
|
||||
## extract certId
|
||||
_certId="$(printf "%s" "$sslcert_response" | _normalizeJson | _egrep_o "certID\": *\"[^\"]*\"" | cut -d : -f 2)"
|
||||
_debug certId "$_certId"
|
||||
|
||||
## update domain ssl config
|
||||
update_body="{\"certid\":$_certId,\"forceHttps\":$QINIU_FORCE_HTTPS}"
|
||||
for domain in $QINIU_CDN_DOMAIN; do
|
||||
update_path="/domain/$domain/httpsconf"
|
||||
update_access_token="$(_make_access_token "$update_path")"
|
||||
_debug update_access_token "$update_access_token"
|
||||
export _H1="Authorization: QBox $update_access_token"
|
||||
update_response=$(_post "$update_body" "$QINIU_API_BASE$update_path" 0 "PUT" "application/json" | _dbase64)
|
||||
|
||||
if _contains "$update_response" "error"; then
|
||||
_err "Error in updating domain $domain httpsconf:"
|
||||
_err "$update_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug update_response "$update_response"
|
||||
_info "Domain $domain certificate has been deployed successfully"
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
_make_access_token() {
|
||||
_token="$(printf "%s\n" "$1" | _hmac "sha1" "$(printf "%s" "$QINIU_SK" | _hex_dump | tr -d " ")" | _base64 | tr -- '+/' '-_')"
|
||||
echo "$QINIU_AK:$_token"
|
||||
}
|
||||
205
deploy/routeros.sh
Normal file
205
deploy/routeros.sh
Normal file
@@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Here is a script to deploy cert to routeros router.
|
||||
# Deploy the cert to remote routeros
|
||||
#
|
||||
# ```sh
|
||||
# acme.sh --deploy -d ftp.example.com --deploy-hook routeros
|
||||
# ```
|
||||
#
|
||||
# Before you can deploy the certificate to router os, you need
|
||||
# to add the id_rsa.pub key to the routeros and assign a user
|
||||
# to that key.
|
||||
#
|
||||
# The user need to have access to ssh, ftp, read and write.
|
||||
#
|
||||
# There are no need to enable ftp service for the script to work,
|
||||
# as they are transmitted over SCP, however ftp is needed to store
|
||||
# the files on the router.
|
||||
#
|
||||
# Then you need to set the environment variables for the
|
||||
# deploy script to work.
|
||||
#
|
||||
# ```sh
|
||||
# export ROUTER_OS_USERNAME=certuser
|
||||
# export ROUTER_OS_HOST=router.example.com
|
||||
# export ROUTER_OS_PORT=22
|
||||
#
|
||||
# acme.sh --deploy -d ftp.example.com --deploy-hook routeros
|
||||
# ```
|
||||
#
|
||||
# The deploy script will remove previously deployed certificates,
|
||||
# and it does this with an assumption on how RouterOS names imported
|
||||
# certificates, adding a "cer_0" suffix at the end. This is true for
|
||||
# versions 6.32 -> 6.41.3, but it is not guaranteed that it will be
|
||||
# true for future versions when upgrading.
|
||||
#
|
||||
# If the router have other certificates with the same name as the one
|
||||
# beeing deployed, then this script will remove those certificates.
|
||||
#
|
||||
# At the end of the script, the services that use those certificates
|
||||
# could be updated. Currently only the www-ssl service is beeing
|
||||
# updated, but more services could be added.
|
||||
#
|
||||
# For instance:
|
||||
# ```sh
|
||||
# export ROUTER_OS_ADDITIONAL_SERVICES="/ip service set api-ssl certificate=$_cdomain.cer_0"
|
||||
# ```
|
||||
#
|
||||
# One optional thing to do as well is to create a script that updates
|
||||
# all the required services and run that script in a single command.
|
||||
#
|
||||
# To adopt parameters to `scp` and/or `ssh` set the optional
|
||||
# `ROUTER_OS_SSH_CMD` and `ROUTER_OS_SCP_CMD` variables accordingly,
|
||||
# see ssh(1) and scp(1) for parameters to those commands.
|
||||
#
|
||||
# Example:
|
||||
# ```ssh
|
||||
# export ROUTER_OS_SSH_CMD="ssh -i /acme.sh/.ssh/router.example.com -o UserKnownHostsFile=/acme.sh/.ssh/known_hosts"
|
||||
# export ROUTER_OS_SCP_CMD="scp -i /acme.sh/.ssh/router.example.com -o UserKnownHostsFile=/acme.sh/.ssh/known_hosts"
|
||||
# ````
|
||||
#
|
||||
# returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
routeros_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
_err_code=0
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
_getdeployconf ROUTER_OS_HOST
|
||||
|
||||
if [ -z "$ROUTER_OS_HOST" ]; then
|
||||
_debug "Using _cdomain as ROUTER_OS_HOST, please set if not correct."
|
||||
ROUTER_OS_HOST="$_cdomain"
|
||||
fi
|
||||
|
||||
_getdeployconf ROUTER_OS_USERNAME
|
||||
|
||||
if [ -z "$ROUTER_OS_USERNAME" ]; then
|
||||
_err "Need to set the env variable ROUTER_OS_USERNAME"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_getdeployconf ROUTER_OS_PORT
|
||||
|
||||
if [ -z "$ROUTER_OS_PORT" ]; then
|
||||
_debug "Using default port 22 as ROUTER_OS_PORT, please set if not correct."
|
||||
ROUTER_OS_PORT=22
|
||||
fi
|
||||
|
||||
_getdeployconf ROUTER_OS_SSH_CMD
|
||||
|
||||
if [ -z "$ROUTER_OS_SSH_CMD" ]; then
|
||||
_debug "Use default ssh setup."
|
||||
ROUTER_OS_SSH_CMD="ssh -p $ROUTER_OS_PORT"
|
||||
fi
|
||||
|
||||
_getdeployconf ROUTER_OS_SCP_CMD
|
||||
|
||||
if [ -z "$ROUTER_OS_SCP_CMD" ]; then
|
||||
_debug "USe default scp setup."
|
||||
ROUTER_OS_SCP_CMD="scp -P $ROUTER_OS_PORT"
|
||||
fi
|
||||
|
||||
_getdeployconf ROUTER_OS_ADDITIONAL_SERVICES
|
||||
|
||||
if [ -z "$ROUTER_OS_ADDITIONAL_SERVICES" ]; then
|
||||
_debug "Not enabling additional services"
|
||||
ROUTER_OS_ADDITIONAL_SERVICES=""
|
||||
fi
|
||||
|
||||
_savedeployconf ROUTER_OS_HOST "$ROUTER_OS_HOST"
|
||||
_savedeployconf ROUTER_OS_USERNAME "$ROUTER_OS_USERNAME"
|
||||
_savedeployconf ROUTER_OS_PORT "$ROUTER_OS_PORT"
|
||||
_savedeployconf ROUTER_OS_SSH_CMD "$ROUTER_OS_SSH_CMD"
|
||||
_savedeployconf ROUTER_OS_SCP_CMD "$ROUTER_OS_SCP_CMD"
|
||||
_savedeployconf ROUTER_OS_ADDITIONAL_SERVICES "$ROUTER_OS_ADDITIONAL_SERVICES" "base64"
|
||||
|
||||
# push key to routeros
|
||||
if ! _scp_certificate "$_ckey" "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST:$_cdomain.key"; then
|
||||
return $_err_code
|
||||
fi
|
||||
|
||||
# push certificate chain to routeros
|
||||
if ! _scp_certificate "$_cfullchain" "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST:$_cdomain.cer"; then
|
||||
return $_err_code
|
||||
fi
|
||||
|
||||
DEPLOY_SCRIPT_CMD=":do {/system script remove \"LECertDeploy-$_cdomain\" } on-error={ }; \
|
||||
/system script add name=\"LECertDeploy-$_cdomain\" owner=$ROUTER_OS_USERNAME \
|
||||
comment=\"generated by routeros deploy script in acme.sh\" \
|
||||
source=\"/certificate remove [ find name=$_cdomain.cer_0 ];\
|
||||
\n/certificate remove [ find name=$_cdomain.cer_1 ];\
|
||||
\n/certificate remove [ find name=$_cdomain.cer_2 ];\
|
||||
\n/certificate remove [ find name=$_cdomain.cer_3 ];\
|
||||
\ndelay 1;\
|
||||
\n/certificate import file-name=\\\"$_cdomain.cer\\\" passphrase=\\\"\\\";\
|
||||
\n/certificate import file-name=\\\"$_cdomain.key\\\" passphrase=\\\"\\\";\
|
||||
\ndelay 1;\
|
||||
\n:do {/file remove $_cdomain.cer; } on-error={ }\
|
||||
\n:do {/file remove $_cdomain.key; } on-error={ }\
|
||||
\ndelay 2;\
|
||||
\n/ip service set www-ssl certificate=$_cdomain.cer_0;\
|
||||
\n$ROUTER_OS_ADDITIONAL_SERVICES;\
|
||||
\n\"
|
||||
"
|
||||
|
||||
if ! _ssh_remote_cmd "$DEPLOY_SCRIPT_CMD"; then
|
||||
return $_err_code
|
||||
fi
|
||||
|
||||
if ! _ssh_remote_cmd "/system script run \"LECertDeploy-$_cdomain\""; then
|
||||
return $_err_code
|
||||
fi
|
||||
|
||||
if ! _ssh_remote_cmd "/system script remove \"LECertDeploy-$_cdomain\""; then
|
||||
return $_err_code
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# inspired by deploy/ssh.sh
|
||||
_ssh_remote_cmd() {
|
||||
_cmd="$1"
|
||||
_secure_debug "Remote commands to execute: $_cmd"
|
||||
_info "Submitting sequence of commands to routeros"
|
||||
# quotations in bash cmd below intended. Squash travis spellcheck error
|
||||
# shellcheck disable=SC2029
|
||||
$ROUTER_OS_SSH_CMD "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST" "$_cmd"
|
||||
_err_code="$?"
|
||||
|
||||
if [ "$_err_code" != "0" ]; then
|
||||
_err "Error code $_err_code returned from routeros"
|
||||
fi
|
||||
|
||||
return $_err_code
|
||||
}
|
||||
|
||||
_scp_certificate() {
|
||||
_src="$1"
|
||||
_dst="$2"
|
||||
_secure_debug "scp '$_src' to '$_dst'"
|
||||
_info "Push key '$_src' to routeros"
|
||||
|
||||
$ROUTER_OS_SCP_CMD "$_src" "$_dst"
|
||||
_err_code="$?"
|
||||
|
||||
if [ "$_err_code" != "0" ]; then
|
||||
_err "Error code $_err_code returned from scp"
|
||||
fi
|
||||
|
||||
return $_err_code
|
||||
}
|
||||
200
deploy/ruckus.sh
Executable file
200
deploy/ruckus.sh
Executable file
@@ -0,0 +1,200 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Here is a script to deploy cert to Ruckus ZoneDirector / Unleashed.
|
||||
#
|
||||
# Public domain, 2024, Tony Rielly <https://github.com/ms264556>
|
||||
#
|
||||
# ```sh
|
||||
# acme.sh --deploy -d ruckus.example.com --deploy-hook ruckus
|
||||
# ```
|
||||
#
|
||||
# Then you need to set the environment variables for the
|
||||
# deploy script to work.
|
||||
#
|
||||
# ```sh
|
||||
# export RUCKUS_HOST=myruckus.example.com
|
||||
# export RUCKUS_USER=myruckususername
|
||||
# export RUCKUS_PASS=myruckuspassword
|
||||
#
|
||||
# acme.sh --deploy -d myruckus.example.com --deploy-hook ruckus
|
||||
# ```
|
||||
#
|
||||
# returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
ruckus_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
_err_code=0
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
_getdeployconf RUCKUS_HOST
|
||||
_getdeployconf RUCKUS_USER
|
||||
_getdeployconf RUCKUS_PASS
|
||||
|
||||
if [ -z "$RUCKUS_HOST" ]; then
|
||||
_debug "Using _cdomain as RUCKUS_HOST, please set if not correct."
|
||||
RUCKUS_HOST="$_cdomain"
|
||||
fi
|
||||
|
||||
if [ -z "$RUCKUS_USER" ]; then
|
||||
_err "Need to set the env variable RUCKUS_USER"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -z "$RUCKUS_PASS" ]; then
|
||||
_err "Need to set the env variable RUCKUS_PASS"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_savedeployconf RUCKUS_HOST "$RUCKUS_HOST"
|
||||
_savedeployconf RUCKUS_USER "$RUCKUS_USER"
|
||||
_savedeployconf RUCKUS_PASS "$RUCKUS_PASS"
|
||||
|
||||
_debug RUCKUS_HOST "$RUCKUS_HOST"
|
||||
_debug RUCKUS_USER "$RUCKUS_USER"
|
||||
_secure_debug RUCKUS_PASS "$RUCKUS_PASS"
|
||||
|
||||
export ACME_HTTP_NO_REDIRECTS=1
|
||||
|
||||
_info "Discovering the login URL"
|
||||
_get "https://$RUCKUS_HOST" >/dev/null
|
||||
_login_url="$(_response_header 'Location')"
|
||||
if [ -n "$_login_url" ]; then
|
||||
_login_path=$(echo "$_login_url" | sed 's|https\?://[^/]\+||')
|
||||
if [ -z "$_login_path" ]; then
|
||||
# redirect was to a different host
|
||||
_err "Connection failed: redirected to a different host. Configure Unleashed with a Preferred Master or Management Interface."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "${_login_url}" ]; then
|
||||
_err "Connection failed: couldn't find login page."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_base_url=$(dirname "$_login_url")
|
||||
_login_page=$(basename "$_login_url")
|
||||
|
||||
if [ "$_login_page" = "index.html" ]; then
|
||||
_err "Connection temporarily unavailable: Unleashed Rebuilding."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$_login_page" = "wizard.jsp" ]; then
|
||||
_err "Connection failed: Setup Wizard not complete."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Login"
|
||||
_username_encoded="$(printf "%s" "$RUCKUS_USER" | _url_encode)"
|
||||
_password_encoded="$(printf "%s" "$RUCKUS_PASS" | _url_encode)"
|
||||
_login_query="$(printf "%s" "username=${_username_encoded}&password=${_password_encoded}&ok=Log+In")"
|
||||
_post "$_login_query" "$_login_url" >/dev/null
|
||||
|
||||
_login_code="$(_response_code)"
|
||||
if [ "$_login_code" = "200" ]; then
|
||||
_err "Login failed: incorrect credentials."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Collect Session Cookie"
|
||||
_H1="Cookie: $(_response_cookie)"
|
||||
export _H1
|
||||
_info "Collect CSRF Token"
|
||||
_H2="X-CSRF-Token: $(_response_header 'HTTP_X_CSRF_TOKEN')"
|
||||
export _H2
|
||||
|
||||
if _isRSA "$_ckey" >/dev/null 2>&1; then
|
||||
_debug "Using RSA certificate."
|
||||
else
|
||||
_info "Verifying ECC certificate support."
|
||||
|
||||
_ul_version="$(_get_unleashed_version)"
|
||||
if [ -z "$_ul_version" ]; then
|
||||
_err "Your controller doesn't support ECC certificates. Please deploy an RSA certificate."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_ul_version_major="$(echo "$_ul_version" | cut -d . -f 1)"
|
||||
_ul_version_minor="$(echo "$_ul_version" | cut -d . -f 2)"
|
||||
if [ "$_ul_version_major" -lt "200" ]; then
|
||||
_err "ZoneDirector doesn't support ECC certificates. Please deploy an RSA certificate."
|
||||
return 1
|
||||
elif [ "$_ul_version_minor" -lt "13" ]; then
|
||||
_err "Unleashed $_ul_version_major.$_ul_version_minor doesn't support ECC certificates. Please deploy an RSA certificate or upgrade to Unleashed 200.13+."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "ECC certificates OK for Unleashed $_ul_version_major.$_ul_version_minor."
|
||||
fi
|
||||
|
||||
_info "Uploading certificate"
|
||||
_post_upload "uploadcert" "$_cfullchain"
|
||||
|
||||
_info "Uploading private key"
|
||||
_post_upload "uploadprivatekey" "$_ckey"
|
||||
|
||||
_info "Replacing certificate"
|
||||
_replace_cert_ajax='<ajax-request action="docmd" comp="system" updater="rid.0.5" xcmd="replace-cert" checkAbility="6" timeout="-1"><xcmd cmd="replace-cert" cn="'$RUCKUS_HOST'"/></ajax-request>'
|
||||
_post "$_replace_cert_ajax" "$_base_url/_cmdstat.jsp" >/dev/null
|
||||
|
||||
_info "Rebooting"
|
||||
_cert_reboot_ajax='<ajax-request action="docmd" comp="worker" updater="rid.0.5" xcmd="cert-reboot" checkAbility="6"><xcmd cmd="cert-reboot" action="undefined"/></ajax-request>'
|
||||
_post "$_cert_reboot_ajax" "$_base_url/_cmdstat.jsp" >/dev/null
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
_response_code() {
|
||||
_egrep_o <"$HTTP_HEADER" "^HTTP[^ ]* .*$" | cut -d " " -f 2-100 | tr -d "\f\n" | _egrep_o "^[0-9]*"
|
||||
}
|
||||
|
||||
_response_header() {
|
||||
grep <"$HTTP_HEADER" -i "^$1:" | cut -d ':' -f 2- | tr -d "\r\n\t "
|
||||
}
|
||||
|
||||
_response_cookie() {
|
||||
_response_header 'Set-Cookie' | sed 's/;.*//'
|
||||
}
|
||||
|
||||
_get_unleashed_version() {
|
||||
_post '<ajax-request action="getstat" comp="system"><sysinfo/></ajax-request>' "$_base_url/_cmdstat.jsp" | _egrep_o "version-num=\"[^\"]*\"" | cut -d '"' -f 2
|
||||
}
|
||||
|
||||
_post_upload() {
|
||||
_post_action="$1"
|
||||
_post_file="$2"
|
||||
|
||||
_post_boundary="----FormBoundary$(date "+%s%N")"
|
||||
|
||||
_post_data="$({
|
||||
printf -- "--%s\r\n" "$_post_boundary"
|
||||
printf -- "Content-Disposition: form-data; name=\"u\"; filename=\"%s\"\r\n" "$_post_action"
|
||||
printf -- "Content-Type: application/octet-stream\r\n\r\n"
|
||||
printf -- "%s\r\n" "$(cat "$_post_file")"
|
||||
|
||||
printf -- "--%s\r\n" "$_post_boundary"
|
||||
printf -- "Content-Disposition: form-data; name=\"action\"\r\n\r\n"
|
||||
printf -- "%s\r\n" "$_post_action"
|
||||
|
||||
printf -- "--%s\r\n" "$_post_boundary"
|
||||
printf -- "Content-Disposition: form-data; name=\"callback\"\r\n\r\n"
|
||||
printf -- "%s\r\n" "uploader_$_post_action"
|
||||
|
||||
printf -- "--%s--\r\n\r\n" "$_post_boundary"
|
||||
})"
|
||||
|
||||
_post "$_post_data" "$_base_url/_upload.jsp?request_type=xhr" "" "" "multipart/form-data; boundary=$_post_boundary" >/dev/null
|
||||
}
|
||||
280
deploy/shelly.sh
Normal file
280
deploy/shelly.sh
Normal file
@@ -0,0 +1,280 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Here is a script to deploy cert to a Shelly Gen3+ device.
|
||||
# Deploy the HTTPS server certificate to a Shelly device on the local network.
|
||||
#
|
||||
# ```sh
|
||||
# export SHELLY_HOST=192.168.1.100
|
||||
# export SHELLY_PASSWORD=mysecret # only if auth is enabled on the device
|
||||
# acme.sh --deploy -d shelly.example.com --deploy-hook shelly
|
||||
# ```
|
||||
#
|
||||
# Environment variables:
|
||||
# SHELLY_HOST (required) IP or hostname of the Shelly device
|
||||
# SHELLY_PASSWORD (optional) Admin password for digest authentication.
|
||||
# Omit if auth is disabled on the device.
|
||||
# SHELLY_USER (optional) Username for auth. Default: admin
|
||||
# SHELLY_REBOOT (optional) Set to "0" to skip auto-reboot.
|
||||
# Default: 1 (reboot after upload)
|
||||
#
|
||||
# Requirements:
|
||||
# - Shelly Gen3+ device (Gen4 recommended)
|
||||
# - Firmware 2.0.0+ for HTTPS server certificate support
|
||||
# - curl or wget
|
||||
# - openssl (for SHA-256 digest and random cnonce)
|
||||
#
|
||||
# The device must be reachable via HTTP on the local network.
|
||||
# The hook uploads the fullchain.pem and private key,
|
||||
# then reboots the device to apply the new certificate.
|
||||
#
|
||||
# Authentication uses standard RFC 7616 HTTP Digest (SHA-256) since
|
||||
# firmware 2.0.0. The JSON-RPC auth object is not used for HTTP transport.
|
||||
#
|
||||
# returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
shelly_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
_getdeployconf SHELLY_HOST
|
||||
_getdeployconf SHELLY_PASSWORD
|
||||
_getdeployconf SHELLY_USER
|
||||
_getdeployconf SHELLY_REBOOT
|
||||
|
||||
_debug SHELLY_HOST "$SHELLY_HOST"
|
||||
_debug SHELLY_USER "$SHELLY_USER"
|
||||
_secure_debug SHELLY_PASSWORD "$SHELLY_PASSWORD"
|
||||
_debug SHELLY_REBOOT "$SHELLY_REBOOT"
|
||||
|
||||
if [ -z "$SHELLY_HOST" ]; then
|
||||
_err "SHELLY_HOST is required. Please set the IP or hostname of your Shelly device."
|
||||
return 1
|
||||
fi
|
||||
|
||||
SHELLY_USER="${SHELLY_USER:-admin}"
|
||||
SHELLY_REBOOT="${SHELLY_REBOOT:-1}"
|
||||
|
||||
_savedeployconf SHELLY_HOST "$SHELLY_HOST"
|
||||
_savedeployconf SHELLY_PASSWORD "$SHELLY_PASSWORD"
|
||||
_savedeployconf SHELLY_USER "$SHELLY_USER"
|
||||
_savedeployconf SHELLY_REBOOT "$SHELLY_REBOOT"
|
||||
|
||||
# --- Auth handshake (only if password is set) ---
|
||||
_shelly_auth_header=""
|
||||
if [ -n "$SHELLY_PASSWORD" ]; then
|
||||
_info "Authenticating to Shelly device at $SHELLY_HOST"
|
||||
if ! _shelly_handshake; then
|
||||
_err "Authentication handshake failed. Check SHELLY_PASSWORD and device accessibility."
|
||||
return 1
|
||||
fi
|
||||
_info "Authentication successful"
|
||||
fi
|
||||
|
||||
# --- Upload certificate ---
|
||||
_info "Uploading certificate to Shelly device at $SHELLY_HOST"
|
||||
if ! _shelly_upload_cert; then
|
||||
_err "Certificate upload failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# --- Upload key ---
|
||||
_info "Uploading private key to Shelly device"
|
||||
if ! _shelly_upload_key; then
|
||||
_err "Private key upload failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Certificate and key uploaded successfully"
|
||||
|
||||
# --- Reboot ---
|
||||
if [ "$SHELLY_REBOOT" != "0" ]; then
|
||||
_info "Rebooting Shelly device to apply certificate"
|
||||
# Reboot may close the connection before sending a response
|
||||
_shelly_rpc "Shelly.Reboot" '{}' || _debug "Reboot may have closed connection (expected)"
|
||||
_info "Reboot command sent. Device will restart shortly."
|
||||
else
|
||||
_info "Skipping reboot (SHELLY_REBOOT=0). Certificate will apply on next restart."
|
||||
fi
|
||||
|
||||
# Clear auth header so it does not leak to other hooks
|
||||
export _H1=""
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# --- Helper functions ---
|
||||
|
||||
# Perform RFC 7616 HTTP Digest auth handshake.
|
||||
# Sets _shelly_auth_header on success (the Authorization header value).
|
||||
_shelly_handshake() {
|
||||
_inithttp
|
||||
|
||||
_debug "Probing device for auth challenge"
|
||||
|
||||
# Use a protected method (Shelly.GetStatus) to trigger 401.
|
||||
# Shelly.GetDeviceInfo is excluded from auth and would miss the challenge.
|
||||
_post '{"id":1,"method":"Shelly.GetStatus"}' \
|
||||
"http://${SHELLY_HOST}/rpc" "" "" "application/json"
|
||||
|
||||
# Detect auth from HTTP status line rather than response body
|
||||
if ! _shelly_has_auth_challenge "$HTTP_HEADER"; then
|
||||
# No auth challenge — device accepted the request without credentials
|
||||
_debug "Device responded without auth challenge. Proceeding without auth."
|
||||
return 0
|
||||
fi
|
||||
|
||||
_shelly_realm="$(grep -i '^WWW-Authenticate:' "$HTTP_HEADER" | sed 's/.*realm="//;s/".*//')"
|
||||
_shelly_nonce="$(grep -i '^WWW-Authenticate:' "$HTTP_HEADER" | sed 's/.*nonce="//;s/".*//')"
|
||||
_shelly_qop="$(grep -i '^WWW-Authenticate:' "$HTTP_HEADER" | sed 's/.*qop="//;s/".*//')"
|
||||
|
||||
if [ -z "$_shelly_nonce" ]; then
|
||||
_err "Failed to extract nonce from WWW-Authenticate header. Is SHELLY_PASSWORD correct?"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_shelly_qop="${_shelly_qop:-auth}"
|
||||
|
||||
_debug "Shelly realm: $_shelly_realm"
|
||||
_debug "Shelly qop: $_shelly_qop"
|
||||
_secure_debug "Shelly nonce" "$_shelly_nonce"
|
||||
|
||||
# ha1 = SHA256(username:realm:password)
|
||||
_shelly_ha1="$(printf '%s' "${SHELLY_USER}:${_shelly_realm}:${SHELLY_PASSWORD}" | _digest sha256 hex)"
|
||||
_secure_debug "Shelly ha1" "$_shelly_ha1"
|
||||
|
||||
# Generate client nonce (openssl is required for _digest, so always available)
|
||||
_shelly_cnonce="$(${ACME_OPENSSL_BIN:-openssl} rand -hex 8 2>/dev/null)"
|
||||
_debug "Shelly cnonce: $_shelly_cnonce"
|
||||
|
||||
# Build the digest Authorization header value (stored for reuse)
|
||||
_shelly_nc=1
|
||||
_shelly_build_auth_header
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Check whether the HTTP response headers contain a digest auth challenge.
|
||||
# Returns 0 (true) if a 401 with WWW-Authenticate is present.
|
||||
_shelly_has_auth_challenge() {
|
||||
_shelly_headers_file="$1"
|
||||
_shelly_status="$(grep -i '^HTTP/' "$_shelly_headers_file" | _tail_n 1 | awk '{print $2}')"
|
||||
[ "$_shelly_status" = "401" ] && grep -qi '^WWW-Authenticate:' "$_shelly_headers_file"
|
||||
}
|
||||
|
||||
# Build or rebuild the RFC 7616 Authorization header.
|
||||
# Uses: _shelly_ha1, _shelly_nonce, _shelly_cnonce, _shelly_qop, _shelly_realm, _shelly_nc
|
||||
# Sets: _shelly_auth_header
|
||||
_shelly_build_auth_header() {
|
||||
_shelly_nc_hex="$(printf '%08x' "$_shelly_nc")"
|
||||
|
||||
# ha2 = SHA256(POST:/rpc)
|
||||
_shelly_ha2="$(printf '%s' "POST:/rpc" | _digest sha256 hex)"
|
||||
|
||||
# response = SHA256(ha1:nonce:nc:cnonce:qop:ha2)
|
||||
_shelly_digest_response="$(printf '%s' "${_shelly_ha1}:${_shelly_nonce}:${_shelly_nc_hex}:${_shelly_cnonce}:${_shelly_qop}:${_shelly_ha2}" | _digest sha256 hex)"
|
||||
|
||||
# Build the Authorization header value (without the "Authorization: " prefix)
|
||||
_shelly_auth_header="Digest username=\"${SHELLY_USER}\", realm=\"${_shelly_realm}\", nonce=\"${_shelly_nonce}\", uri=\"/rpc\", qop=${_shelly_qop}, nc=${_shelly_nc_hex}, cnonce=\"${_shelly_cnonce}\", response=\"${_shelly_digest_response}\", algorithm=SHA-256"
|
||||
|
||||
_secure_debug "Authorization header" "$_shelly_auth_header"
|
||||
}
|
||||
|
||||
# Make a Shelly JSON-RPC call.
|
||||
# Usage: _shelly_rpc <method> <params_json>
|
||||
# Returns 0 on success, 1 on error.
|
||||
_shelly_rpc() {
|
||||
_shelly_method="$1"
|
||||
_shelly_params="$2"
|
||||
|
||||
_shelly_body='{"id":1,"method":"'"$_shelly_method"'","params":'"$_shelly_params"'}'
|
||||
|
||||
_debug "RPC method: $_shelly_method"
|
||||
_debug2 "RPC body: $_shelly_body"
|
||||
|
||||
# shellcheck disable=SC2090
|
||||
if [ -n "$_shelly_auth_header" ]; then
|
||||
export _H1="Authorization: $_shelly_auth_header"
|
||||
else
|
||||
export _H1=""
|
||||
fi
|
||||
|
||||
_post "$_shelly_body" "http://${SHELLY_HOST}/rpc" "" "" "application/json"
|
||||
_shelly_ret=$?
|
||||
|
||||
if [ "$_shelly_ret" != "0" ]; then
|
||||
_err "HTTP request failed for $_shelly_method (curl/wget error $_shelly_ret)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Empty response means something went wrong (auth required but not provided, etc.)
|
||||
if [ -z "$response" ]; then
|
||||
_err "Empty response from Shelly device. If authentication is enabled on the device, set SHELLY_PASSWORD."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Validate response looks like a Shelly JSON-RPC response.
|
||||
# Catches non-JSON responses such as HTTP 429 "Too Many Requests" which
|
||||
# would otherwise pass the empty and "error" checks below.
|
||||
if ! _startswith "$response" '{' || ! _contains "$response" '"id"'; then
|
||||
_err "Invalid response from Shelly device: $response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check for JSON-RPC error in response
|
||||
if _contains "$response" '"error"'; then
|
||||
_err "RPC error from Shelly: $response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "RPC response: $response"
|
||||
|
||||
# Increment nonce counter and rebuild auth header for next request
|
||||
if [ -n "$_shelly_auth_header" ]; then
|
||||
_shelly_nc=$((_shelly_nc + 1))
|
||||
_shelly_build_auth_header
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Upload the certificate to the device.
|
||||
# Note: We do NOT clear the existing certificate first, because the Shelly
|
||||
# auto-removes all three files (cert, key, CA) when any one is cleared.
|
||||
# Uploading overwrites in place — no clearing needed.
|
||||
_shelly_upload_cert() {
|
||||
_shelly_cert_data="$(_json_encode <"$_cfullchain")"
|
||||
|
||||
_debug "Uploading certificate"
|
||||
if ! _shelly_rpc "Shelly.PutHTTPServerCert" '{"data":"'"$_shelly_cert_data"'"}'; then
|
||||
_err "Failed to upload certificate to device"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Upload the private key to the device.
|
||||
# Note: Do not clear first — see _shelly_upload_cert for rationale.
|
||||
_shelly_upload_key() {
|
||||
_shelly_key_data="$(_json_encode <"$_ckey")"
|
||||
|
||||
_debug "Uploading key"
|
||||
if ! _shelly_rpc "Shelly.PutHTTPServerKey" '{"data":"'"$_shelly_key_data"'"}'; then
|
||||
_err "Failed to upload key to device"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
493
deploy/ssh.sh
Normal file
493
deploy/ssh.sh
Normal file
@@ -0,0 +1,493 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Script to deploy certificates to remote server by SSH
|
||||
# Note that SSH must be able to login to remote host without a password...
|
||||
# SSH Keys must have been exchanged with the remote host. Validate and
|
||||
# test that you can login to USER@SERVER from the host running acme.sh before
|
||||
# using this script.
|
||||
#
|
||||
# The following variables exported from environment will be used.
|
||||
# If not set then values previously saved in domain.conf file are used.
|
||||
#
|
||||
# Only a username is required. All others are optional.
|
||||
#
|
||||
# The following examples are for QNAP NAS running QTS 4.2
|
||||
# export DEPLOY_SSH_CMD="" # defaults to "ssh -T"
|
||||
# export DEPLOY_SSH_USER="admin" # required
|
||||
# export DEPLOY_SSH_SERVER="host1 host2:8022 192.168.0.1:9022" # defaults to domain name, support multiple servers with optional port
|
||||
# export DEPLOY_SSH_KEYFILE="/etc/stunnel/stunnel.pem"
|
||||
# export DEPLOY_SSH_CERTFILE="/etc/stunnel/stunnel.pem"
|
||||
# export DEPLOY_SSH_CAFILE="/etc/stunnel/uca.pem"
|
||||
# export DEPLOY_SSH_FULLCHAIN=""
|
||||
# export DEPLOY_SSH_REMOTE_CMD="/etc/init.d/stunnel.sh restart"
|
||||
# export DEPLOY_SSH_BACKUP="" # yes or no, default to yes or previously saved value
|
||||
# export DEPLOY_SSH_BACKUP_PATH=".acme_ssh_deploy" # path on remote system. Defaults to .acme_ssh_deploy
|
||||
# export DEPLOY_SSH_MULTI_CALL="" # yes or no, default to no or previously saved value
|
||||
# export DEPLOY_SSH_USE_SCP="" yes or no, default to no
|
||||
# export DEPLOY_SSH_SCP_CMD="" defaults to "scp -q"
|
||||
# export DEPLOY_SSH_REMOTE_SHELL="" # defaults to sh -c
|
||||
# export DEPLOY_SSH_REMOTE_CMD_QUOTE="" # yes or no, defaults to yes
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
ssh_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
_deploy_ssh_servers=""
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
# USER is required to login by SSH to remote host.
|
||||
_migratedeployconf Le_Deploy_ssh_user DEPLOY_SSH_USER
|
||||
_getdeployconf DEPLOY_SSH_USER
|
||||
_debug2 DEPLOY_SSH_USER "$DEPLOY_SSH_USER"
|
||||
if [ -z "$DEPLOY_SSH_USER" ]; then
|
||||
_err "DEPLOY_SSH_USER not defined."
|
||||
return 1
|
||||
fi
|
||||
_savedeployconf DEPLOY_SSH_USER "$DEPLOY_SSH_USER"
|
||||
|
||||
# SERVER is optional. If not provided then use _cdomain
|
||||
_migratedeployconf Le_Deploy_ssh_server DEPLOY_SSH_SERVER
|
||||
_getdeployconf DEPLOY_SSH_SERVER
|
||||
_debug2 DEPLOY_SSH_SERVER "$DEPLOY_SSH_SERVER"
|
||||
if [ -z "$DEPLOY_SSH_SERVER" ]; then
|
||||
DEPLOY_SSH_SERVER="$_cdomain"
|
||||
fi
|
||||
_savedeployconf DEPLOY_SSH_SERVER "$DEPLOY_SSH_SERVER"
|
||||
|
||||
# CMD is optional. If not provided then use ssh
|
||||
_migratedeployconf Le_Deploy_ssh_cmd DEPLOY_SSH_CMD
|
||||
_getdeployconf DEPLOY_SSH_CMD
|
||||
_debug2 DEPLOY_SSH_CMD "$DEPLOY_SSH_CMD"
|
||||
if [ -z "$DEPLOY_SSH_CMD" ]; then
|
||||
DEPLOY_SSH_CMD="ssh -T"
|
||||
fi
|
||||
_savedeployconf DEPLOY_SSH_CMD "$DEPLOY_SSH_CMD"
|
||||
|
||||
# REMOTE_SHELL is optional. If not provided then use sh
|
||||
_migratedeployconf Le_Deploy_ssh_remote_shell DEPLOY_SSH_REMOTE_SHELL
|
||||
_getdeployconf DEPLOY_SSH_REMOTE_SHELL
|
||||
_debug2 DEPLOY_SSH_REMOTE_SHELL "$DEPLOY_SSH_REMOTE_SHELL"
|
||||
if [ -z "$DEPLOY_SSH_REMOTE_SHELL" ]; then
|
||||
DEPLOY_SSH_REMOTE_SHELL="sh -c"
|
||||
fi
|
||||
_savedeployconf DEPLOY_SSH_REMOTE_SHELL "$DEPLOY_SSH_REMOTE_SHELL"
|
||||
|
||||
# REMOTE_CMD_QUOTE is optional. If not provided then yes
|
||||
_migratedeployconf Le_Deploy_ssh_remote_cmd_quote DEPLOY_SSH_REMOTE_CMD_QUOTE
|
||||
_getdeployconf DEPLOY_SSH_REMOTE_CMD_QUOTE
|
||||
_debug2 DEPLOY_SSH_REMOTE_CMD_QUOTE "$DEPLOY_SSH_REMOTE_CMD_QUOTE"
|
||||
if [ -z "$DEPLOY_SSH_REMOTE_CMD_QUOTE" ]; then
|
||||
DEPLOY_SSH_REMOTE_CMD_QUOTE="yes"
|
||||
fi
|
||||
_savedeployconf DEPLOY_SSH_REMOTE_CMD_QUOTE "$DEPLOY_SSH_REMOTE_CMD_QUOTE"
|
||||
|
||||
# BACKUP is optional. If not provided then default to previously saved value or yes.
|
||||
_migratedeployconf Le_Deploy_ssh_backup DEPLOY_SSH_BACKUP
|
||||
_getdeployconf DEPLOY_SSH_BACKUP
|
||||
_debug2 DEPLOY_SSH_BACKUP "$DEPLOY_SSH_BACKUP"
|
||||
if [ -z "$DEPLOY_SSH_BACKUP" ]; then
|
||||
DEPLOY_SSH_BACKUP="yes"
|
||||
fi
|
||||
_savedeployconf DEPLOY_SSH_BACKUP "$DEPLOY_SSH_BACKUP"
|
||||
|
||||
# BACKUP_PATH is optional. If not provided then default to previously saved value or .acme_ssh_deploy
|
||||
_migratedeployconf Le_Deploy_ssh_backup_path DEPLOY_SSH_BACKUP_PATH
|
||||
_getdeployconf DEPLOY_SSH_BACKUP_PATH
|
||||
_debug2 DEPLOY_SSH_BACKUP_PATH "$DEPLOY_SSH_BACKUP_PATH"
|
||||
if [ -z "$DEPLOY_SSH_BACKUP_PATH" ]; then
|
||||
DEPLOY_SSH_BACKUP_PATH=".acme_ssh_deploy"
|
||||
fi
|
||||
_savedeployconf DEPLOY_SSH_BACKUP_PATH "$DEPLOY_SSH_BACKUP_PATH"
|
||||
|
||||
# MULTI_CALL is optional. If not provided then default to previously saved
|
||||
# value (which may be undefined... equivalent to "no").
|
||||
_migratedeployconf Le_Deploy_ssh_multi_call DEPLOY_SSH_MULTI_CALL
|
||||
_getdeployconf DEPLOY_SSH_MULTI_CALL
|
||||
_debug2 DEPLOY_SSH_MULTI_CALL "$DEPLOY_SSH_MULTI_CALL"
|
||||
if [ -z "$DEPLOY_SSH_MULTI_CALL" ]; then
|
||||
DEPLOY_SSH_MULTI_CALL="no"
|
||||
fi
|
||||
_savedeployconf DEPLOY_SSH_MULTI_CALL "$DEPLOY_SSH_MULTI_CALL"
|
||||
|
||||
# KEYFILE is optional.
|
||||
# If provided then private key will be copied to provided filename.
|
||||
_migratedeployconf Le_Deploy_ssh_keyfile DEPLOY_SSH_KEYFILE
|
||||
_getdeployconf DEPLOY_SSH_KEYFILE
|
||||
_debug2 DEPLOY_SSH_KEYFILE "$DEPLOY_SSH_KEYFILE"
|
||||
if [ -n "$DEPLOY_SSH_KEYFILE" ]; then
|
||||
_savedeployconf DEPLOY_SSH_KEYFILE "$DEPLOY_SSH_KEYFILE"
|
||||
fi
|
||||
|
||||
# CERTFILE is optional.
|
||||
# If provided then certificate will be copied or appended to provided filename.
|
||||
_migratedeployconf Le_Deploy_ssh_certfile DEPLOY_SSH_CERTFILE
|
||||
_getdeployconf DEPLOY_SSH_CERTFILE
|
||||
_debug2 DEPLOY_SSH_CERTFILE "$DEPLOY_SSH_CERTFILE"
|
||||
if [ -n "$DEPLOY_SSH_CERTFILE" ]; then
|
||||
_savedeployconf DEPLOY_SSH_CERTFILE "$DEPLOY_SSH_CERTFILE"
|
||||
fi
|
||||
|
||||
# CAFILE is optional.
|
||||
# If provided then CA intermediate certificate will be copied or appended to provided filename.
|
||||
_migratedeployconf Le_Deploy_ssh_cafile DEPLOY_SSH_CAFILE
|
||||
_getdeployconf DEPLOY_SSH_CAFILE
|
||||
_debug2 DEPLOY_SSH_CAFILE "$DEPLOY_SSH_CAFILE"
|
||||
if [ -n "$DEPLOY_SSH_CAFILE" ]; then
|
||||
_savedeployconf DEPLOY_SSH_CAFILE "$DEPLOY_SSH_CAFILE"
|
||||
fi
|
||||
|
||||
# FULLCHAIN is optional.
|
||||
# If provided then fullchain certificate will be copied or appended to provided filename.
|
||||
_migratedeployconf Le_Deploy_ssh_fullchain DEPLOY_SSH_FULLCHAIN
|
||||
_getdeployconf DEPLOY_SSH_FULLCHAIN
|
||||
_debug2 DEPLOY_SSH_FULLCHAIN "$DEPLOY_SSH_FULLCHAIN"
|
||||
if [ -n "$DEPLOY_SSH_FULLCHAIN" ]; then
|
||||
_savedeployconf DEPLOY_SSH_FULLCHAIN "$DEPLOY_SSH_FULLCHAIN"
|
||||
fi
|
||||
|
||||
# REMOTE_CMD is optional.
|
||||
# If provided then this command will be executed on remote host.
|
||||
_migratedeployconf Le_Deploy_ssh_remote_cmd DEPLOY_SSH_REMOTE_CMD
|
||||
_getdeployconf DEPLOY_SSH_REMOTE_CMD
|
||||
_debug2 DEPLOY_SSH_REMOTE_CMD "$DEPLOY_SSH_REMOTE_CMD"
|
||||
if [ -n "$DEPLOY_SSH_REMOTE_CMD" ]; then
|
||||
_savedeployconf DEPLOY_SSH_REMOTE_CMD "$DEPLOY_SSH_REMOTE_CMD"
|
||||
fi
|
||||
|
||||
# USE_SCP is optional. If not provided then default to previously saved
|
||||
# value (which may be undefined... equivalent to "no").
|
||||
_getdeployconf DEPLOY_SSH_USE_SCP
|
||||
_debug2 DEPLOY_SSH_USE_SCP "$DEPLOY_SSH_USE_SCP"
|
||||
if [ -z "$DEPLOY_SSH_USE_SCP" ]; then
|
||||
DEPLOY_SSH_USE_SCP="no"
|
||||
fi
|
||||
_savedeployconf DEPLOY_SSH_USE_SCP "$DEPLOY_SSH_USE_SCP"
|
||||
|
||||
# SCP_CMD is optional. If not provided then use scp
|
||||
_getdeployconf DEPLOY_SSH_SCP_CMD
|
||||
_debug2 DEPLOY_SSH_SCP_CMD "$DEPLOY_SSH_SCP_CMD"
|
||||
if [ -z "$DEPLOY_SSH_SCP_CMD" ]; then
|
||||
DEPLOY_SSH_SCP_CMD="scp -q"
|
||||
fi
|
||||
_savedeployconf DEPLOY_SSH_SCP_CMD "$DEPLOY_SSH_SCP_CMD"
|
||||
|
||||
if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then
|
||||
DEPLOY_SSH_MULTI_CALL="yes"
|
||||
_info "Using scp as alternate method for copying files. Multicall Mode is implicit"
|
||||
elif [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
|
||||
_info "Using MULTI_CALL mode... Required commands sent in multiple calls to remote host"
|
||||
else
|
||||
_info "Required commands batched and sent in single call to remote host"
|
||||
fi
|
||||
|
||||
_returnCode=0
|
||||
_deploy_ssh_servers="$DEPLOY_SSH_SERVER"
|
||||
for DEPLOY_SSH_SERVER in $_deploy_ssh_servers; do
|
||||
if ! _ssh_deploy; then
|
||||
# in case of an error, remember it, but keep going for the remaining servers
|
||||
_returnCode=1
|
||||
fi
|
||||
done
|
||||
|
||||
return $_returnCode
|
||||
}
|
||||
|
||||
_ssh_deploy() {
|
||||
_err_code=0
|
||||
_cmdstr=""
|
||||
_backupprefix=""
|
||||
_backupdir=""
|
||||
_local_cert_file=""
|
||||
_local_ca_file=""
|
||||
_local_full_file=""
|
||||
|
||||
case $DEPLOY_SSH_SERVER in
|
||||
*:*)
|
||||
_host=${DEPLOY_SSH_SERVER%:*}
|
||||
_port=${DEPLOY_SSH_SERVER##*:}
|
||||
;;
|
||||
*)
|
||||
_host=$DEPLOY_SSH_SERVER
|
||||
_port=
|
||||
;;
|
||||
esac
|
||||
|
||||
_info "Deploy certificates to remote server $DEPLOY_SSH_USER@$_host:$_port"
|
||||
|
||||
if [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
|
||||
_backupprefix="$DEPLOY_SSH_BACKUP_PATH/$_cdomain-backup"
|
||||
_backupdir="$_backupprefix-$(_utc_date | tr ' ' '-')"
|
||||
# run cleanup on the backup directory, erase all older
|
||||
# than 180 days (15552000 seconds).
|
||||
_cmdstr="{ now=\"\$(date -u +%s)\"; for fn in $_backupprefix*; \
|
||||
do if [ -d \"\$fn\" ] && [ \"\$(expr \$now - \$(date -ur \$fn +%s) )\" -ge \"15552000\" ]; \
|
||||
then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; done; }; $_cmdstr"
|
||||
# Alternate version of above... _cmdstr="find $_backupprefix* -type d -mtime +180 2>/dev/null | xargs rm -rf; $_cmdstr"
|
||||
# Create our backup directory for overwritten cert files.
|
||||
_cmdstr="mkdir -p $_backupdir; $_cmdstr"
|
||||
_info "Backup of old certificate files will be placed in remote directory $_backupdir"
|
||||
_info "Backup directories erased after 180 days."
|
||||
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
_cmdstr=""
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$DEPLOY_SSH_KEYFILE" ]; then
|
||||
if [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
|
||||
# backup file we are about to overwrite.
|
||||
_cmdstr="$_cmdstr cp $DEPLOY_SSH_KEYFILE $_backupdir >/dev/null;"
|
||||
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
_cmdstr=""
|
||||
fi
|
||||
fi
|
||||
|
||||
# copy new key into file.
|
||||
if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then
|
||||
# scp the file
|
||||
if ! _scp_remote_cmd "$_ckey" "$DEPLOY_SSH_KEYFILE"; then
|
||||
return $_err_code
|
||||
fi
|
||||
else
|
||||
# If file doesn't exist, create it and change its permissions.
|
||||
_cmdstr="$_cmdstr test ! -f $DEPLOY_SSH_KEYFILE && touch $DEPLOY_SSH_KEYFILE && chmod 600 $DEPLOY_SSH_KEYFILE;"
|
||||
# ssh echo to the file
|
||||
_cmdstr="$_cmdstr echo \"$(cat "$_ckey")\" > $DEPLOY_SSH_KEYFILE;"
|
||||
_info "will copy private key to remote file $DEPLOY_SSH_KEYFILE"
|
||||
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
_cmdstr=""
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$DEPLOY_SSH_CERTFILE" ]; then
|
||||
_pipe=">"
|
||||
if [ "$DEPLOY_SSH_CERTFILE" = "$DEPLOY_SSH_KEYFILE" ]; then
|
||||
# if filename is same as previous file then append.
|
||||
_pipe=">>"
|
||||
elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
|
||||
# backup file we are about to overwrite.
|
||||
_cmdstr="$_cmdstr cp $DEPLOY_SSH_CERTFILE $_backupdir >/dev/null;"
|
||||
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
_cmdstr=""
|
||||
fi
|
||||
fi
|
||||
|
||||
# copy new certificate into file.
|
||||
if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then
|
||||
# scp the file
|
||||
_local_cert_file=$(_mktemp)
|
||||
if [ "$DEPLOY_SSH_CERTFILE" = "$DEPLOY_SSH_KEYFILE" ]; then
|
||||
cat "$_ckey" >>"$_local_cert_file"
|
||||
fi
|
||||
cat "$_ccert" >>"$_local_cert_file"
|
||||
if ! _scp_remote_cmd "$_local_cert_file" "$DEPLOY_SSH_CERTFILE"; then
|
||||
return $_err_code
|
||||
fi
|
||||
else
|
||||
# ssh echo to the file
|
||||
_cmdstr="$_cmdstr echo \"$(cat "$_ccert")\" $_pipe $DEPLOY_SSH_CERTFILE;"
|
||||
_info "will copy certificate to remote file $DEPLOY_SSH_CERTFILE"
|
||||
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
_cmdstr=""
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$DEPLOY_SSH_CAFILE" ]; then
|
||||
_pipe=">"
|
||||
if [ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_KEYFILE" ] ||
|
||||
[ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_CERTFILE" ]; then
|
||||
# if filename is same as previous file then append.
|
||||
_pipe=">>"
|
||||
elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
|
||||
# backup file we are about to overwrite.
|
||||
_cmdstr="$_cmdstr cp $DEPLOY_SSH_CAFILE $_backupdir >/dev/null;"
|
||||
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
_cmdstr=""
|
||||
fi
|
||||
fi
|
||||
|
||||
# copy new certificate into file.
|
||||
if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then
|
||||
# scp the file
|
||||
_local_ca_file=$(_mktemp)
|
||||
if [ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_KEYFILE" ]; then
|
||||
cat "$_ckey" >>"$_local_ca_file"
|
||||
fi
|
||||
if [ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_CERTFILE" ]; then
|
||||
cat "$_ccert" >>"$_local_ca_file"
|
||||
fi
|
||||
cat "$_cca" >>"$_local_ca_file"
|
||||
if ! _scp_remote_cmd "$_local_ca_file" "$DEPLOY_SSH_CAFILE"; then
|
||||
return $_err_code
|
||||
fi
|
||||
else
|
||||
# ssh echo to the file
|
||||
_cmdstr="$_cmdstr echo \"$(cat "$_cca")\" $_pipe $DEPLOY_SSH_CAFILE;"
|
||||
_info "will copy CA file to remote file $DEPLOY_SSH_CAFILE"
|
||||
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
_cmdstr=""
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$DEPLOY_SSH_FULLCHAIN" ]; then
|
||||
_pipe=">"
|
||||
if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_KEYFILE" ] ||
|
||||
[ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CERTFILE" ] ||
|
||||
[ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CAFILE" ]; then
|
||||
# if filename is same as previous file then append.
|
||||
_pipe=">>"
|
||||
elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
|
||||
# backup file we are about to overwrite.
|
||||
_cmdstr="$_cmdstr cp $DEPLOY_SSH_FULLCHAIN $_backupdir >/dev/null;"
|
||||
if [ "$DEPLOY_SSH_FULLCHAIN" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
_cmdstr=""
|
||||
fi
|
||||
fi
|
||||
|
||||
# copy new certificate into file.
|
||||
if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then
|
||||
# scp the file
|
||||
_local_full_file=$(_mktemp)
|
||||
if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_KEYFILE" ]; then
|
||||
cat "$_ckey" >>"$_local_full_file"
|
||||
fi
|
||||
if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CERTFILE" ]; then
|
||||
cat "$_ccert" >>"$_local_full_file"
|
||||
fi
|
||||
if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CAFILE" ]; then
|
||||
cat "$_cca" >>"$_local_full_file"
|
||||
fi
|
||||
cat "$_cfullchain" >>"$_local_full_file"
|
||||
if ! _scp_remote_cmd "$_local_full_file" "$DEPLOY_SSH_FULLCHAIN"; then
|
||||
return $_err_code
|
||||
fi
|
||||
else
|
||||
# ssh echo to the file
|
||||
_cmdstr="$_cmdstr echo \"$(cat "$_cfullchain")\" $_pipe $DEPLOY_SSH_FULLCHAIN;"
|
||||
_info "will copy fullchain to remote file $DEPLOY_SSH_FULLCHAIN"
|
||||
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
_cmdstr=""
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# cleanup local files if any
|
||||
if [ -f "$_local_cert_file" ]; then
|
||||
rm -f "$_local_cert_file"
|
||||
fi
|
||||
if [ -f "$_local_ca_file" ]; then
|
||||
rm -f "$_local_ca_file"
|
||||
fi
|
||||
if [ -f "$_local_full_file" ]; then
|
||||
rm -f "$_local_full_file"
|
||||
fi
|
||||
|
||||
if [ -n "$DEPLOY_SSH_REMOTE_CMD" ]; then
|
||||
_cmdstr="$_cmdstr $DEPLOY_SSH_REMOTE_CMD;"
|
||||
_info "Will execute remote command $DEPLOY_SSH_REMOTE_CMD"
|
||||
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
_cmdstr=""
|
||||
fi
|
||||
fi
|
||||
|
||||
# if commands not all sent in multiple calls then all commands sent in a single SSH call now...
|
||||
if [ -n "$_cmdstr" ]; then
|
||||
if ! _ssh_remote_cmd "$_cmdstr"; then
|
||||
return $_err_code
|
||||
fi
|
||||
fi
|
||||
# cleanup in case all is ok
|
||||
return 0
|
||||
}
|
||||
|
||||
#cmd
|
||||
_ssh_remote_cmd() {
|
||||
_cmd="$1"
|
||||
|
||||
_ssh_cmd="$DEPLOY_SSH_CMD"
|
||||
if [ -n "$_port" ]; then
|
||||
_ssh_cmd="$_ssh_cmd -p $_port"
|
||||
fi
|
||||
|
||||
_secure_debug "Remote commands to execute: $_cmd"
|
||||
_info "Submitting sequence of commands to remote server by $_ssh_cmd"
|
||||
|
||||
if [ "$DEPLOY_SSH_REMOTE_CMD_QUOTE" = "yes" ]; then
|
||||
# quotations in bash cmd below intended. Squash travis spellcheck error
|
||||
# shellcheck disable=SC2029
|
||||
$_ssh_cmd "$DEPLOY_SSH_USER@$_host" "$DEPLOY_SSH_REMOTE_SHELL" "'$_cmd'"
|
||||
else
|
||||
$_ssh_cmd "$DEPLOY_SSH_USER@$_host" "$DEPLOY_SSH_REMOTE_SHELL" "$_cmd"
|
||||
fi
|
||||
_err_code="$?"
|
||||
|
||||
if [ "$_err_code" != "0" ]; then
|
||||
_err "Error code $_err_code returned from ssh"
|
||||
fi
|
||||
|
||||
return $_err_code
|
||||
}
|
||||
|
||||
# cmd scp
|
||||
_scp_remote_cmd() {
|
||||
_src=$1
|
||||
_dest=$2
|
||||
|
||||
_scp_cmd="$DEPLOY_SSH_SCP_CMD"
|
||||
if [ -n "$_port" ]; then
|
||||
_scp_cmd="$_scp_cmd -P $_port"
|
||||
fi
|
||||
|
||||
_secure_debug "Remote copy source $_src to destination $_dest"
|
||||
_info "Submitting secure copy by $_scp_cmd"
|
||||
|
||||
$_scp_cmd "$_src" "$DEPLOY_SSH_USER"@"$_host":"$_dest"
|
||||
_err_code="$?"
|
||||
|
||||
if [ "$_err_code" != "0" ]; then
|
||||
_err "Error code $_err_code returned from scp"
|
||||
fi
|
||||
|
||||
return $_err_code
|
||||
}
|
||||
98
deploy/strongswan.sh
Normal file
98
deploy/strongswan.sh
Normal file
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#Here is a sample custom api script.
|
||||
#This file name is "myapi.sh"
|
||||
#So, here must be a method myapi_deploy()
|
||||
#Which will be called by acme.sh to deploy the cert
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
strongswan_deploy() {
|
||||
_cdomain="${1}"
|
||||
_ckey="${2}"
|
||||
_ccert="${3}"
|
||||
_cca="${4}"
|
||||
_cfullchain="${5}"
|
||||
_info "Using strongswan"
|
||||
if _exists ipsec; then
|
||||
_ipsec=ipsec
|
||||
elif _exists strongswan; then
|
||||
_ipsec=strongswan
|
||||
fi
|
||||
if _exists swanctl; then
|
||||
_swanctl=swanctl
|
||||
fi
|
||||
# For legacy stroke mode
|
||||
if [ -n "${_ipsec}" ]; then
|
||||
_info "${_ipsec} command detected"
|
||||
_confdir=$(${_ipsec} --confdir)
|
||||
if [ -z "${_confdir}" ]; then
|
||||
_err "no strongswan --confdir is detected"
|
||||
return 1
|
||||
fi
|
||||
_info _confdir "${_confdir}"
|
||||
__deploy_cert "stroke" "${_confdir}" "$@"
|
||||
${_ipsec} reload
|
||||
fi
|
||||
# For modern vici mode
|
||||
if [ -n "${_swanctl}" ]; then
|
||||
_info "${_swanctl} command detected"
|
||||
for _dir in /usr/local/etc/swanctl /etc/swanctl /etc/strongswan/swanctl; do
|
||||
if [ -d ${_dir} ]; then
|
||||
_confdir=${_dir}
|
||||
_info _confdir "${_confdir}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -z "${_confdir}" ]; then
|
||||
_err "no swanctl config dir is found"
|
||||
return 1
|
||||
fi
|
||||
__deploy_cert "vici" "${_confdir}" "$@"
|
||||
${_swanctl} --load-creds
|
||||
fi
|
||||
if [ -z "${_swanctl}" ] && [ -z "${_ipsec}" ]; then
|
||||
_err "no strongswan or ipsec command is detected"
|
||||
_err "no swanctl is detected"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
#################### Private functions below ##################################
|
||||
|
||||
__deploy_cert() {
|
||||
_swan_mode="${1}"
|
||||
_confdir="${2}"
|
||||
_cdomain="${3}"
|
||||
_ckey="${4}"
|
||||
_ccert="${5}"
|
||||
_cca="${6}"
|
||||
_cfullchain="${7}"
|
||||
_debug _cdomain "${_cdomain}"
|
||||
_debug _ckey "${_ckey}"
|
||||
_debug _ccert "${_ccert}"
|
||||
_debug _cca "${_cca}"
|
||||
_debug _cfullchain "${_cfullchain}"
|
||||
_debug _swan_mode "${_swan_mode}"
|
||||
_debug _confdir "${_confdir}"
|
||||
if [ "${_swan_mode}" = "vici" ]; then
|
||||
_dir_private="private"
|
||||
_dir_cert="x509"
|
||||
_dir_ca="x509ca"
|
||||
elif [ "${_swan_mode}" = "stroke" ]; then
|
||||
_dir_private="ipsec.d/private"
|
||||
_dir_cert="ipsec.d/certs"
|
||||
_dir_ca="ipsec.d/cacerts"
|
||||
else
|
||||
_err "unknown StrongSwan mode ${_swan_mode}"
|
||||
return 1
|
||||
fi
|
||||
cat "${_ckey}" >"${_confdir}/${_dir_private}/$(basename "${_ckey}")"
|
||||
cat "${_ccert}" >"${_confdir}/${_dir_cert}/$(basename "${_ccert}")"
|
||||
cat "${_cca}" >"${_confdir}/${_dir_ca}/$(basename "${_cca}")"
|
||||
if [ "${_swan_mode}" = "stroke" ]; then
|
||||
cat "${_cfullchain}" >"${_confdir}/${_dir_ca}/$(basename "${_cfullchain}")"
|
||||
fi
|
||||
}
|
||||
444
deploy/synology_dsm.sh
Normal file
444
deploy/synology_dsm.sh
Normal file
@@ -0,0 +1,444 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
################################################################################
|
||||
# ACME.sh 3rd party deploy plugin for Synology DSM
|
||||
################################################################################
|
||||
# Authors: Brian Hartvigsen (creator), https://github.com/tresni
|
||||
# Martin Arndt (contributor), https://troublezone.net/
|
||||
# Updated: 2023-07-03
|
||||
# Issues: https://github.com/acmesh-official/acme.sh/issues/2727
|
||||
################################################################################
|
||||
# Usage (shown values are the examples):
|
||||
# 1. Set required environment variables:
|
||||
# - use automatically created temp admin user to authenticate
|
||||
# export SYNO_USE_TEMP_ADMIN=1
|
||||
# - or provide your own admin user credential to authenticate
|
||||
# 1. export SYNO_USERNAME="adminUser"
|
||||
# 2. export SYNO_PASSWORD="adminPassword"
|
||||
# 2. Set optional environment variables
|
||||
# - common optional variables
|
||||
# - export SYNO_SCHEME="http" - defaults to "http"
|
||||
# - export SYNO_HOSTNAME="localhost" - defaults to "localhost"
|
||||
# - export SYNO_PORT="5000" - defaults to "5000"
|
||||
# - export SYNO_CREATE=1 - to allow creating the cert if it doesn't exist
|
||||
# - export SYNO_CERTIFICATE="" - to replace a specific cert by its
|
||||
# description
|
||||
# - temp admin optional variables
|
||||
# - export SYNO_LOCAL_HOSTNAME=1 - if set to 1, force to treat hostname is
|
||||
# targeting current local machine (since
|
||||
# this method only locally supported)
|
||||
# - exsiting admin 2FA-OTP optional variables
|
||||
# - export SYNO_OTP_CODE="XXXXXX" - if set, script won't require to
|
||||
# interactive input the OTP code
|
||||
# - export SYNO_DEVICE_NAME="CertRenewal" - if set, script won't require to
|
||||
# interactive input the device name
|
||||
# - export SYNO_DEVICE_ID="" - (deprecated, auth with OTP code instead)
|
||||
# required for omitting 2FA-OTP
|
||||
# 3. Run command:
|
||||
# acme.sh --deploy --deploy-hook synology_dsm -d example.com
|
||||
################################################################################
|
||||
# Dependencies:
|
||||
# - curl
|
||||
# - synouser & synogroup & synosetkeyvalue (Required for SYNO_USE_TEMP_ADMIN=1)
|
||||
################################################################################
|
||||
# Return value:
|
||||
# 0 means success, otherwise error.
|
||||
################################################################################
|
||||
|
||||
########## Public functions ####################################################
|
||||
#domain keyfile certfile cafile fullchain
|
||||
synology_dsm_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
|
||||
# Get username and password, but don't save until we authenticated successfully
|
||||
_migratedeployconf SYNO_Username SYNO_USERNAME
|
||||
_migratedeployconf SYNO_Password SYNO_PASSWORD
|
||||
_migratedeployconf SYNO_Device_ID SYNO_DEVICE_ID
|
||||
_migratedeployconf SYNO_Device_Name SYNO_DEVICE_NAME
|
||||
_getdeployconf SYNO_USERNAME
|
||||
_getdeployconf SYNO_PASSWORD
|
||||
_getdeployconf SYNO_DEVICE_ID
|
||||
_getdeployconf SYNO_DEVICE_NAME
|
||||
|
||||
# Prepare to use temp admin if SYNO_USE_TEMP_ADMIN is set
|
||||
_getdeployconf SYNO_USE_TEMP_ADMIN
|
||||
_check2cleardeployconfexp SYNO_USE_TEMP_ADMIN
|
||||
_debug2 SYNO_USE_TEMP_ADMIN "$SYNO_USE_TEMP_ADMIN"
|
||||
|
||||
if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then
|
||||
if ! _exists synouser || ! _exists synogroup || ! _exists synosetkeyvalue; then
|
||||
_err "Missing required tools to create temp admin user, please set SYNO_USERNAME and SYNO_PASSWORD instead."
|
||||
_err "Notice: temp admin user authorization method only supports local deployment on DSM."
|
||||
return 1
|
||||
fi
|
||||
if synouser --help 2>&1 | grep -q 'Permission denied'; then
|
||||
_err "For creating temp admin user, the deploy script must be run as root."
|
||||
return 1
|
||||
fi
|
||||
|
||||
[ -n "$SYNO_USERNAME" ] || _savedeployconf SYNO_USERNAME ""
|
||||
[ -n "$SYNO_PASSWORD" ] || _savedeployconf SYNO_PASSWORD ""
|
||||
|
||||
_debug "Setting temp admin user credential..."
|
||||
SYNO_USERNAME=sc-acmesh-tmp
|
||||
SYNO_PASSWORD=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16)
|
||||
# Set 2FA-OTP settings to empty consider they won't be needed.
|
||||
SYNO_DEVICE_ID=
|
||||
SYNO_DEVICE_NAME=
|
||||
SYNO_OTP_CODE=
|
||||
else
|
||||
_debug2 SYNO_USERNAME "$SYNO_USERNAME"
|
||||
_secure_debug2 SYNO_PASSWORD "$SYNO_PASSWORD"
|
||||
_debug2 SYNO_DEVICE_NAME "$SYNO_DEVICE_NAME"
|
||||
_secure_debug2 SYNO_DEVICE_ID "$SYNO_DEVICE_ID"
|
||||
fi
|
||||
|
||||
if [ -z "$SYNO_USERNAME" ] || [ -z "$SYNO_PASSWORD" ]; then
|
||||
_err "You must set either SYNO_USE_TEMP_ADMIN, or set both SYNO_USERNAME and SYNO_PASSWORD."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Optional scheme, hostname and port for Synology DSM
|
||||
_migratedeployconf SYNO_Scheme SYNO_SCHEME
|
||||
_migratedeployconf SYNO_Hostname SYNO_HOSTNAME
|
||||
_migratedeployconf SYNO_Port SYNO_PORT
|
||||
_getdeployconf SYNO_SCHEME
|
||||
_getdeployconf SYNO_HOSTNAME
|
||||
_getdeployconf SYNO_PORT
|
||||
|
||||
# Default values for scheme, hostname and port
|
||||
# Defaulting to localhost and http, because it's localhost…
|
||||
[ -n "$SYNO_SCHEME" ] || SYNO_SCHEME=http
|
||||
[ -n "$SYNO_HOSTNAME" ] || SYNO_HOSTNAME=localhost
|
||||
[ -n "$SYNO_PORT" ] || SYNO_PORT=5000
|
||||
_savedeployconf SYNO_SCHEME "$SYNO_SCHEME"
|
||||
_savedeployconf SYNO_HOSTNAME "$SYNO_HOSTNAME"
|
||||
_savedeployconf SYNO_PORT "$SYNO_PORT"
|
||||
_debug2 SYNO_SCHEME "$SYNO_SCHEME"
|
||||
_debug2 SYNO_HOSTNAME "$SYNO_HOSTNAME"
|
||||
_debug2 SYNO_PORT "$SYNO_PORT"
|
||||
|
||||
# Get the certificate description, but don't save it until we verify it's real
|
||||
_migratedeployconf SYNO_Certificate SYNO_CERTIFICATE "base64"
|
||||
_getdeployconf SYNO_CERTIFICATE
|
||||
_check2cleardeployconfexp SYNO_CERTIFICATE
|
||||
_debug SYNO_CERTIFICATE "${SYNO_CERTIFICATE:-}"
|
||||
|
||||
# shellcheck disable=SC1003 # We are not trying to escape a single quote
|
||||
if printf "%s" "$SYNO_CERTIFICATE" | grep '\\'; then
|
||||
_err "Do not use a backslash (\) in your certificate description"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "Getting API version..."
|
||||
_base_url="$SYNO_SCHEME://$SYNO_HOSTNAME:$SYNO_PORT"
|
||||
_debug _base_url "$_base_url"
|
||||
response=$(_get "$_base_url/webapi/query.cgi?api=SYNO.API.Info&version=1&method=query&query=SYNO.API.Auth")
|
||||
api_path=$(echo "$response" | grep "SYNO.API.Auth" | sed -n 's/.*"path" *: *"\([^"]*\)".*/\1/p')
|
||||
api_version=$(echo "$response" | grep "SYNO.API.Auth" | sed -n 's/.*"maxVersion" *: *\([0-9]*\).*/\1/p')
|
||||
_debug3 response "$response"
|
||||
_debug3 api_path "$api_path"
|
||||
_debug3 api_version "$api_version"
|
||||
|
||||
# Login, get the session ID and SynoToken from JSON
|
||||
_info "Logging into $SYNO_HOSTNAME:$SYNO_PORT..."
|
||||
encoded_username="$(printf "%s" "$SYNO_USERNAME" | _url_encode)"
|
||||
encoded_password="$(printf "%s" "$SYNO_PASSWORD" | _url_encode)"
|
||||
|
||||
# ## START ## - DEPRECATED, for backward compatibility
|
||||
_getdeployconf SYNO_TOTP_SECRET
|
||||
|
||||
if [ -n "$SYNO_TOTP_SECRET" ]; then
|
||||
_info "WARNING: Usage of SYNO_TOTP_SECRET is deprecated!"
|
||||
_info " See synology_dsm.sh script or ACME.sh Wiki page for details:"
|
||||
_info " https://github.com/acmesh-official/acme.sh/wiki/Synology-NAS-Guide"
|
||||
if ! _exists oathtool; then
|
||||
_err "oathtool could not be found, install oathtool to use SYNO_TOTP_SECRET"
|
||||
return 1
|
||||
fi
|
||||
DEPRECATED_otp_code="$(oathtool --base32 --totp "$SYNO_TOTP_SECRET" 2>/dev/null)"
|
||||
|
||||
if [ -z "$SYNO_DEVICE_ID" ]; then
|
||||
_getdeployconf SYNO_DID
|
||||
[ -n "$SYNO_DID" ] || SYNO_DEVICE_ID="$SYNO_DID"
|
||||
fi
|
||||
if [ -n "$SYNO_DEVICE_ID" ]; then
|
||||
_H1="Cookie: did=$SYNO_DEVICE_ID"
|
||||
export _H1
|
||||
_debug3 H1 "${_H1}"
|
||||
fi
|
||||
|
||||
response=$(_post "method=login&account=$encoded_username&passwd=$encoded_password&api=SYNO.API.Auth&version=$api_version&enable_syno_token=yes&otp_code=$DEPRECATED_otp_code&device_name=certrenewal&device_id=$SYNO_DEVICE_ID" "$_base_url/webapi/$api_path?enable_syno_token=yes")
|
||||
_debug3 response "$response"
|
||||
# ## END ## - DEPRECATED, for backward compatibility
|
||||
# If SYNO_DEVICE_ID or SYNO_OTP_CODE is set, we treat current account enabled 2FA-OTP.
|
||||
# Notice that if SYNO_USE_TEMP_ADMIN=1, both variables will be unset
|
||||
else
|
||||
if [ -n "$SYNO_DEVICE_ID" ] || [ -n "$SYNO_OTP_CODE" ]; then
|
||||
response='{"error":{"code":403}}'
|
||||
# Assume the current account disabled 2FA-OTP, try to log in right away.
|
||||
else
|
||||
if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then
|
||||
_getdeployconf SYNO_LOCAL_HOSTNAME
|
||||
_debug SYNO_LOCAL_HOSTNAME "${SYNO_LOCAL_HOSTNAME:-}"
|
||||
if [ "$SYNO_HOSTNAME" != "localhost" ] && [ "$SYNO_HOSTNAME" != "127.0.0.1" ]; then
|
||||
if [ "$SYNO_LOCAL_HOSTNAME" != "1" ]; then
|
||||
_err "SYNO_USE_TEMP_ADMIN=1 only support local deployment, though if you are sure that the hostname $SYNO_HOSTNAME is targeting to your **current local machine**, execute 'export SYNO_LOCAL_HOSTNAME=1' then rerun."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
_debug "Creating temp admin user in Synology DSM..."
|
||||
if synogroup --help | grep -q '\-\-memberadd '; then
|
||||
_temp_admin_create "$SYNO_USERNAME" "$SYNO_PASSWORD"
|
||||
synogroup --memberadd administrators "$SYNO_USERNAME" >/dev/null
|
||||
elif synogroup --help | grep -q '\-\-member '; then
|
||||
# For supporting DSM 6.x which only has `--member` parameter.
|
||||
cur_admins=$(synogroup --get administrators | awk -F '[][]' '/Group Members/,0{if(NF>1)printf "%s ", $2}')
|
||||
if [ -n "$cur_admins" ]; then
|
||||
_temp_admin_create "$SYNO_USERNAME" "$SYNO_PASSWORD"
|
||||
_secure_debug3 admin_users "$cur_admins$SYNO_USERNAME"
|
||||
# shellcheck disable=SC2086
|
||||
synogroup --member administrators $cur_admins $SYNO_USERNAME >/dev/null
|
||||
else
|
||||
_err "The tool synogroup may be broken, please set SYNO_USERNAME and SYNO_PASSWORD instead."
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
_err "Unsupported synogroup tool detected, please set SYNO_USERNAME and SYNO_PASSWORD instead."
|
||||
return 1
|
||||
fi
|
||||
# havig a workaround to temporary disable enforce 2FA-OTP, will restore
|
||||
# it soon (after a single request), though if any accident occurs like
|
||||
# unexpected interruption, this setting can be easily reverted manually.
|
||||
otp_enforce_option=$(synogetkeyvalue /etc/synoinfo.conf otp_enforce_option)
|
||||
if [ -n "$otp_enforce_option" ] && [ "${otp_enforce_option:-"none"}" != "none" ]; then
|
||||
synosetkeyvalue /etc/synoinfo.conf otp_enforce_option none
|
||||
_info "Enforcing 2FA-OTP has been disabled to complete temp admin authentication."
|
||||
_info "Notice: it will be restored soon, if not, you can restore it manually via Control Panel."
|
||||
_info "previous_otp_enforce_option" "$otp_enforce_option"
|
||||
else
|
||||
otp_enforce_option=""
|
||||
fi
|
||||
fi
|
||||
response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes")
|
||||
if [ -n "$SYNO_USE_TEMP_ADMIN" ] && [ -n "$otp_enforce_option" ]; then
|
||||
synosetkeyvalue /etc/synoinfo.conf otp_enforce_option "$otp_enforce_option"
|
||||
_info "Restored previous enforce 2FA-OTP option."
|
||||
fi
|
||||
_debug3 response "$response"
|
||||
fi
|
||||
fi
|
||||
|
||||
error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -Eo '[0-9]+')
|
||||
_debug2 error_code "$error_code"
|
||||
# Account has 2FA-OTP enabled, since error 403 reported.
|
||||
# https://global.download.synology.com/download/Document/Software/DeveloperGuide/Os/DSM/All/enu/DSM_Login_Web_API_Guide_enu.pdf
|
||||
if [ "$error_code" = "403" ]; then
|
||||
if [ -z "$SYNO_DEVICE_NAME" ]; then
|
||||
printf "Enter device name or leave empty for default (CertRenewal): "
|
||||
read -r SYNO_DEVICE_NAME
|
||||
[ -n "$SYNO_DEVICE_NAME" ] || SYNO_DEVICE_NAME="CertRenewal"
|
||||
fi
|
||||
|
||||
if [ -n "$SYNO_DEVICE_ID" ]; then
|
||||
# Omit OTP code with SYNO_DEVICE_ID.
|
||||
response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes&device_name=$SYNO_DEVICE_NAME&device_id=$SYNO_DEVICE_ID")
|
||||
_secure_debug3 response "$response"
|
||||
else
|
||||
# Require the OTP code if still unset.
|
||||
if [ -z "$SYNO_OTP_CODE" ]; then
|
||||
printf "Enter OTP code for user '%s': " "$SYNO_USERNAME"
|
||||
read -r SYNO_OTP_CODE
|
||||
fi
|
||||
_secure_debug SYNO_OTP_CODE "${SYNO_OTP_CODE:-}"
|
||||
|
||||
if [ -z "$SYNO_OTP_CODE" ]; then
|
||||
response='{"error":{"code":404}}'
|
||||
else
|
||||
response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes&enable_device_token=yes&device_name=$SYNO_DEVICE_NAME&otp_code=$SYNO_OTP_CODE")
|
||||
_secure_debug3 response "$response"
|
||||
|
||||
id_property='device_id'
|
||||
[ "${api_version}" -gt '6' ] || id_property='did'
|
||||
SYNO_DEVICE_ID=$(echo "$response" | grep "$id_property" | sed -n 's/.*"'$id_property'" *: *"\([^"]*\).*/\1/p')
|
||||
_secure_debug2 SYNO_DEVICE_ID "$SYNO_DEVICE_ID"
|
||||
fi
|
||||
fi
|
||||
error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -Eo '[0-9]+')
|
||||
_debug2 error_code "$error_code"
|
||||
fi
|
||||
|
||||
if [ -n "$error_code" ]; then
|
||||
if [ "$error_code" = "403" ] && [ -n "$SYNO_DEVICE_ID" ]; then
|
||||
_cleardeployconf SYNO_DEVICE_ID
|
||||
_err "Failed to authenticate with SYNO_DEVICE_ID (may be expired or invalid), please try again in a new terminal window."
|
||||
elif [ "$error_code" = "404" ]; then
|
||||
_err "Failed to authenticate with provided 2FA-OTP code, please try again in a new terminal window."
|
||||
elif [ "$error_code" = "406" ]; then
|
||||
if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then
|
||||
_err "Failed with unexcepted error, please report this by providing full log with '--debug 3'."
|
||||
else
|
||||
_err "Enforce auth with 2FA-OTP enabled, please configure the user to enable 2FA-OTP to continue."
|
||||
fi
|
||||
elif [ "$error_code" = "400" ]; then
|
||||
_err "Failed to authenticate, no such account or incorrect password."
|
||||
elif [ "$error_code" = "401" ]; then
|
||||
_err "Failed to authenticate with a non-existent account."
|
||||
elif [ "$error_code" = "408" ] || [ "$error_code" = "409" ] || [ "$error_code" = "410" ]; then
|
||||
_err "Failed to authenticate, the account password has expired or must be changed."
|
||||
else
|
||||
_err "Failed to authenticate with error: $error_code."
|
||||
fi
|
||||
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
|
||||
return 1
|
||||
fi
|
||||
|
||||
sid=$(echo "$response" | grep "sid" | sed -n 's/.*"sid" *: *"\([^"]*\).*/\1/p')
|
||||
token=$(echo "$response" | grep "synotoken" | sed -n 's/.*"synotoken" *: *"\([^"]*\).*/\1/p')
|
||||
_debug "Session ID" "$sid"
|
||||
_debug SynoToken "$token"
|
||||
if [ -z "$sid" ] || [ -z "$token" ]; then
|
||||
# Still can't get necessary info even got no errors, may Synology have API updated?
|
||||
_err "Unable to authenticate to $_base_url, you may report this by providing full log with '--debug 3'."
|
||||
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_H1="X-SYNO-TOKEN: $token"
|
||||
export _H1
|
||||
_debug2 H1 "${_H1}"
|
||||
|
||||
# Now that we know the username and password are good, save them if not in temp admin mode.
|
||||
if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then
|
||||
_cleardeployconf SYNO_USERNAME
|
||||
_cleardeployconf SYNO_PASSWORD
|
||||
_cleardeployconf SYNO_DEVICE_ID
|
||||
_cleardeployconf SYNO_DEVICE_NAME
|
||||
_savedeployconf SYNO_USE_TEMP_ADMIN "$SYNO_USE_TEMP_ADMIN"
|
||||
_savedeployconf SYNO_LOCAL_HOSTNAME "$SYNO_LOCAL_HOSTNAME"
|
||||
else
|
||||
_savedeployconf SYNO_USERNAME "$SYNO_USERNAME" "base64"
|
||||
_savedeployconf SYNO_PASSWORD "$SYNO_PASSWORD" "base64"
|
||||
_savedeployconf SYNO_DEVICE_ID "$SYNO_DEVICE_ID"
|
||||
_savedeployconf SYNO_DEVICE_NAME "$SYNO_DEVICE_NAME"
|
||||
fi
|
||||
|
||||
_info "Getting certificates in Synology DSM..."
|
||||
response=$(_post "api=SYNO.Core.Certificate.CRT&method=list&version=1&_sid=$sid" "$_base_url/webapi/entry.cgi")
|
||||
_debug3 response "$response"
|
||||
escaped_certificate="$(printf "%s" "$SYNO_CERTIFICATE" | sed 's/\([].*^$[]\)/\\\1/g;s/"/\\\\"/g')"
|
||||
_debug escaped_certificate "$escaped_certificate"
|
||||
id=$(echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\"id\":\"\([^\"]*\).*/\1/p")
|
||||
_debug2 id "$id"
|
||||
|
||||
error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -Eo '[0-9]+')
|
||||
_debug2 error_code "$error_code"
|
||||
if [ -n "$error_code" ]; then
|
||||
if [ "$error_code" -eq 105 ]; then
|
||||
_err "Current user is not administrator and does not have sufficient permission for deploying."
|
||||
else
|
||||
_err "Failed to fetch certificate info: $error_code, please try again or contact Synology to learn more."
|
||||
fi
|
||||
_logout
|
||||
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_migratedeployconf SYNO_Create SYNO_CREATE
|
||||
_getdeployconf SYNO_CREATE
|
||||
_debug2 SYNO_CREATE "$SYNO_CREATE"
|
||||
|
||||
if [ -z "$id" ] && [ -z "$SYNO_CREATE" ]; then
|
||||
_err "Unable to find certificate: $SYNO_CERTIFICATE and \$SYNO_CREATE is not set."
|
||||
_logout
|
||||
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# We've verified this certificate description is a thing, so save it
|
||||
_savedeployconf SYNO_CERTIFICATE "$SYNO_CERTIFICATE" "base64"
|
||||
|
||||
_info "Generating form POST request..."
|
||||
nl="\0015\0012"
|
||||
delim="--------------------------$(_utc_date | tr -d -- '-: ')"
|
||||
content="--$delim${nl}Content-Disposition: form-data; name=\"key\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")\0012"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"cert\"; filename=\"$(basename "$_ccert")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ccert")\0012"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"inter_cert\"; filename=\"$(basename "$_cca")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cca")\0012"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"id\"${nl}${nl}$id"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"desc\"${nl}${nl}${SYNO_CERTIFICATE}"
|
||||
if echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\([^{]*\).*/\1/p" | grep -- 'is_default":true' >/dev/null; then
|
||||
_debug2 default "This is the default certificate"
|
||||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"as_default\"${nl}${nl}true"
|
||||
else
|
||||
_debug2 default "This is NOT the default certificate"
|
||||
fi
|
||||
content="$content${nl}--$delim--${nl}"
|
||||
content="$(printf "%b_" "$content")"
|
||||
content="${content%_}" # protect trailing \n
|
||||
|
||||
_info "Upload certificate to the Synology DSM."
|
||||
response=$(_post "$content" "$_base_url/webapi/entry.cgi?api=SYNO.Core.Certificate&method=import&version=1&SynoToken=$token&_sid=$sid" "" "POST" "multipart/form-data; boundary=${delim}")
|
||||
_debug3 response "$response"
|
||||
|
||||
if ! echo "$response" | grep '"error":' >/dev/null; then
|
||||
if echo "$response" | grep '"restart_httpd":true' >/dev/null; then
|
||||
_info "Restart HTTP services succeeded."
|
||||
else
|
||||
_info "Restart HTTP services not necessary."
|
||||
fi
|
||||
_logout
|
||||
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
|
||||
return 0
|
||||
else
|
||||
_err "Unable to update certificate, got error response: $response."
|
||||
_logout
|
||||
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
#################### Private functions below ##################################
|
||||
_logout() {
|
||||
# Logout CERT user only to not occupy a permanent session, e.g. in DSM's "Connected Users" widget (based on previous variables)
|
||||
# Must be called before _temp_admin_cleanup: once the temp admin is deleted, its session can no longer be logged out.
|
||||
# Note: this overwrites $response, so print any error message that needs it before calling.
|
||||
response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=logout&_sid=$sid")
|
||||
_debug3 response "$response"
|
||||
}
|
||||
|
||||
_temp_admin_create() {
|
||||
_username="$1"
|
||||
_password="$2"
|
||||
synouser --del "$_username" >/dev/null 2>/dev/null
|
||||
synouser --add "$_username" "$_password" "" 0 "" 0 >/dev/null
|
||||
}
|
||||
|
||||
_temp_admin_cleanup() {
|
||||
_flag=$1
|
||||
_username=$2
|
||||
|
||||
if [ -n "${_flag}" ]; then
|
||||
_debug "Cleanuping temp admin info..."
|
||||
synouser --del "$_username" >/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# key
|
||||
_check2cleardeployconfexp() {
|
||||
_key="$1"
|
||||
_clear_key="CLEAR_$_key"
|
||||
# Clear saved settings if explicitly requested
|
||||
if [ -n "$(eval echo \$"$_clear_key")" ]; then
|
||||
_debug2 "$_key: value cleared from config, exported value will be ignored."
|
||||
_cleardeployconf "$_key"
|
||||
eval "$_key"=
|
||||
export "$_key"=
|
||||
eval SAVED_"$_key"=
|
||||
export SAVED_"$_key"=
|
||||
fi
|
||||
}
|
||||
272
deploy/truenas.sh
Normal file
272
deploy/truenas.sh
Normal file
@@ -0,0 +1,272 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Here is a scipt to deploy the cert to your TrueNAS using the REST API.
|
||||
# https://www.truenas.com/docs/hub/additional-topics/api/rest_api.html
|
||||
#
|
||||
# Written by Frank Plass github@f-plass.de
|
||||
# https://github.com/danb35/deploy-freenas/blob/master/deploy_freenas.py
|
||||
# Thanks to danb35 for your template!
|
||||
#
|
||||
# Following environment variables must be set:
|
||||
#
|
||||
# export DEPLOY_TRUENAS_APIKEY="<API_KEY_GENERATED_IN_THE_WEB_UI>"
|
||||
#
|
||||
# The following environmental variables may be set if you don't like their
|
||||
# default values:
|
||||
#
|
||||
# DEPLOY_TRUENAS_HOSTNAME - defaults to localhost
|
||||
# DEPLOY_TRUENAS_SCHEME - defaults to http, set alternatively to https
|
||||
#
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
truenas_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
_getdeployconf DEPLOY_TRUENAS_APIKEY
|
||||
|
||||
if [ -z "$DEPLOY_TRUENAS_APIKEY" ]; then
|
||||
_err "TrueNAS API key not found, please set the DEPLOY_TRUENAS_APIKEY environment variable."
|
||||
return 1
|
||||
fi
|
||||
_secure_debug2 DEPLOY_TRUENAS_APIKEY "$DEPLOY_TRUENAS_APIKEY"
|
||||
|
||||
# Optional hostname, scheme for TrueNAS
|
||||
_getdeployconf DEPLOY_TRUENAS_HOSTNAME
|
||||
_getdeployconf DEPLOY_TRUENAS_SCHEME
|
||||
|
||||
# default values for hostname and scheme
|
||||
[ -n "${DEPLOY_TRUENAS_HOSTNAME}" ] || DEPLOY_TRUENAS_HOSTNAME="localhost"
|
||||
[ -n "${DEPLOY_TRUENAS_SCHEME}" ] || DEPLOY_TRUENAS_SCHEME="http"
|
||||
|
||||
_debug2 DEPLOY_TRUENAS_HOSTNAME "$DEPLOY_TRUENAS_HOSTNAME"
|
||||
_debug2 DEPLOY_TRUENAS_SCHEME "$DEPLOY_TRUENAS_SCHEME"
|
||||
|
||||
_api_url="$DEPLOY_TRUENAS_SCHEME://$DEPLOY_TRUENAS_HOSTNAME/api/v2.0"
|
||||
_debug _api_url "$_api_url"
|
||||
|
||||
_H1="Authorization: Bearer $DEPLOY_TRUENAS_APIKEY"
|
||||
_secure_debug3 _H1 "$_H1"
|
||||
|
||||
_info "Testing Connection TrueNAS"
|
||||
_response=$(_get "$_api_url/system/state")
|
||||
_info "TrueNAS system state: $_response."
|
||||
|
||||
_info "Getting TrueNAS version"
|
||||
_response=$(_get "$_api_url/system/version")
|
||||
|
||||
if echo "$_response" | grep -q "SCALE"; then
|
||||
_truenas_os=$(echo "$_response" | cut -d '-' -f 2)
|
||||
_truenas_version=$(echo "$_response" | cut -d '-' -f 3 | tr -d '"' | cut -d '.' -f 1,2)
|
||||
else
|
||||
_truenas_os="unknown"
|
||||
_truenas_version="unknown"
|
||||
fi
|
||||
|
||||
_info "Detected TrueNAS system os: $_truenas_os"
|
||||
_info "Detected TrueNAS system version: $_truenas_version"
|
||||
|
||||
if [ -z "$_response" ]; then
|
||||
_err "Unable to authenticate to $_api_url."
|
||||
_err 'Check your connection settings are correct, e.g.'
|
||||
_err 'DEPLOY_TRUENAS_HOSTNAME="192.168.x.y" or DEPLOY_TRUENAS_HOSTNAME="truenas.example.com".'
|
||||
_err 'DEPLOY_TRUENAS_SCHEME="https" or DEPLOY_TRUENAS_SCHEME="http".'
|
||||
_err "Verify your TrueNAS API key is valid and set correctly, e.g. DEPLOY_TRUENAS_APIKEY=xxxx...."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_savedeployconf DEPLOY_TRUENAS_APIKEY "$DEPLOY_TRUENAS_APIKEY"
|
||||
_savedeployconf DEPLOY_TRUENAS_HOSTNAME "$DEPLOY_TRUENAS_HOSTNAME"
|
||||
_savedeployconf DEPLOY_TRUENAS_SCHEME "$DEPLOY_TRUENAS_SCHEME"
|
||||
|
||||
_info "Getting current active certificate from TrueNAS"
|
||||
_response=$(_get "$_api_url/system/general")
|
||||
_active_cert_id=$(echo "$_response" | grep -B2 '"name":' | grep 'id' | tr -d -- '"id: ,')
|
||||
_active_cert_name=$(echo "$_response" | grep '"name":' | sed -n 's/.*: "\(.\{1,\}\)",$/\1/p')
|
||||
_param_httpsredirect=$(echo "$_response" | grep '"ui_httpsredirect":' | sed -n 's/.*": \(.\{1,\}\),$/\1/p')
|
||||
_debug Active_UI_Certificate_ID "$_active_cert_id"
|
||||
_debug Active_UI_Certificate_Name "$_active_cert_name"
|
||||
_debug Active_UI_http_redirect "$_param_httpsredirect"
|
||||
|
||||
if [ "$DEPLOY_TRUENAS_SCHEME" = "http" ] && [ "$_param_httpsredirect" = "true" ]; then
|
||||
_info "HTTP->HTTPS redirection is enabled"
|
||||
_info "Setting DEPLOY_TRUENAS_SCHEME to 'https'"
|
||||
DEPLOY_TRUENAS_SCHEME="https"
|
||||
_api_url="$DEPLOY_TRUENAS_SCHEME://$DEPLOY_TRUENAS_HOSTNAME/api/v2.0"
|
||||
_savedeployconf DEPLOY_TRUENAS_SCHEME "$DEPLOY_TRUENAS_SCHEME"
|
||||
fi
|
||||
|
||||
_info "Uploading new certificate to TrueNAS"
|
||||
_certname="Letsencrypt_$(_utc_date | tr ' ' '_' | tr -d -- ':')"
|
||||
_debug3 _certname "$_certname"
|
||||
|
||||
_certData="{\"create_type\": \"CERTIFICATE_CREATE_IMPORTED\", \"name\": \"${_certname}\", \"certificate\": \"$(_json_encode <"$_cfullchain")\", \"privatekey\": \"$(_json_encode <"$_ckey")\"}"
|
||||
_add_cert_result="$(_post "$_certData" "$_api_url/certificate" "" "POST" "application/json")"
|
||||
|
||||
_debug3 _add_cert_result "$_add_cert_result"
|
||||
|
||||
_info "Fetching list of installed certificates"
|
||||
_cert_list=$(_get "$_api_url/system/general/ui_certificate_choices")
|
||||
_cert_id=$(echo "$_cert_list" | grep "$_certname" | sed -n 's/.*"\([0-9]\{1,\}\)".*$/\1/p')
|
||||
|
||||
_debug3 _cert_id "$_cert_id"
|
||||
|
||||
_info "Current activate certificate ID: $_cert_id"
|
||||
_activateData="{\"ui_certificate\": \"${_cert_id}\"}"
|
||||
_activate_result="$(_post "$_activateData" "$_api_url/system/general" "" "PUT" "application/json")"
|
||||
|
||||
_debug3 _activate_result "$_activate_result"
|
||||
|
||||
_truenas_version_23_10="23.10"
|
||||
_truenas_version_24_10="24.10"
|
||||
|
||||
_check_version=$(printf "%s\n%s" "$_truenas_version_23_10" "$_truenas_version" | sort -V | head -n 1)
|
||||
if [ "$_truenas_os" != "SCALE" ] || [ "$_check_version" != "$_truenas_version_23_10" ]; then
|
||||
_info "Checking if WebDAV certificate is the same as the TrueNAS web UI"
|
||||
_webdav_list=$(_get "$_api_url/webdav")
|
||||
_webdav_cert_id=$(echo "$_webdav_list" | grep '"certssl":' | tr -d -- '"certsl: ,')
|
||||
|
||||
if [ "$_webdav_cert_id" = "$_active_cert_id" ]; then
|
||||
_info "Updating the WebDAV certificate"
|
||||
_debug _webdav_cert_id "$_webdav_cert_id"
|
||||
_webdav_data="{\"certssl\": \"${_cert_id}\"}"
|
||||
_activate_webdav_cert="$(_post "$_webdav_data" "$_api_url/webdav" "" "PUT" "application/json")"
|
||||
_webdav_new_cert_id=$(echo "$_activate_webdav_cert" | _json_decode | grep '"certssl":' | sed -n 's/.*: \([0-9]\{1,\}\),\{0,1\}$/\1/p')
|
||||
if [ "$_webdav_new_cert_id" -eq "$_cert_id" ]; then
|
||||
_info "WebDAV certificate updated successfully"
|
||||
else
|
||||
_err "Unable to set WebDAV certificate"
|
||||
_debug3 _activate_webdav_cert "$_activate_webdav_cert"
|
||||
_debug3 _webdav_new_cert_id "$_webdav_new_cert_id"
|
||||
return 1
|
||||
fi
|
||||
_debug3 _webdav_new_cert_id "$_webdav_new_cert_id"
|
||||
else
|
||||
_info "WebDAV certificate is not configured or is not the same as TrueNAS web UI"
|
||||
fi
|
||||
|
||||
_info "Checking if S3 certificate is the same as the TrueNAS web UI"
|
||||
_s3_list=$(_get "$_api_url/s3")
|
||||
_s3_cert_id=$(echo "$_s3_list" | grep '"certificate":' | tr -d -- '"certifa:_ ,')
|
||||
|
||||
if [ "$_s3_cert_id" = "$_active_cert_id" ]; then
|
||||
_info "Updating the S3 certificate"
|
||||
_debug _s3_cert_id "$_s3_cert_id"
|
||||
_s3_data="{\"certificate\": \"${_cert_id}\"}"
|
||||
_activate_s3_cert="$(_post "$_s3_data" "$_api_url/s3" "" "PUT" "application/json")"
|
||||
_s3_new_cert_id=$(echo "$_activate_s3_cert" | _json_decode | grep '"certificate":' | sed -n 's/.*: \([0-9]\{1,\}\),\{0,1\}$/\1/p')
|
||||
if [ "$_s3_new_cert_id" -eq "$_cert_id" ]; then
|
||||
_info "S3 certificate updated successfully"
|
||||
else
|
||||
_err "Unable to set S3 certificate"
|
||||
_debug3 _activate_s3_cert "$_activate_s3_cert"
|
||||
_debug3 _s3_new_cert_id "$_s3_new_cert_id"
|
||||
return 1
|
||||
fi
|
||||
_debug3 _activate_s3_cert "$_activate_s3_cert"
|
||||
else
|
||||
_info "S3 certificate is not configured or is not the same as TrueNAS web UI"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$_truenas_os" = "SCALE" ]; then
|
||||
_check_version=$(printf "%s\n%s" "$_truenas_version_24_10" "$_truenas_version" | sort -V | head -n 1)
|
||||
if [ "$_check_version" != "$_truenas_version_24_10" ]; then
|
||||
_info "Checking if any chart release Apps is using the same certificate as TrueNAS web UI. Tool 'jq' is required"
|
||||
if _exists jq; then
|
||||
_info "Query all chart release"
|
||||
_release_list=$(_get "$_api_url/chart/release")
|
||||
_related_name_list=$(printf "%s" "$_release_list" | jq -r "[.[] | {name,certId: .config.ingress?.main.tls[]?.scaleCert} | select(.certId==$_active_cert_id) | .name ] | unique")
|
||||
_release_length=$(printf "%s" "$_related_name_list" | jq -r "length")
|
||||
_info "Found $_release_length related chart release in list: $_related_name_list"
|
||||
for i in $(seq 0 $((_release_length - 1))); do
|
||||
_release_name=$(echo "$_related_name_list" | jq -r ".[$i]")
|
||||
_info "Updating certificate from $_active_cert_id to $_cert_id for chart release: $_release_name"
|
||||
#Read the chart release configuration
|
||||
_chart_config=$(printf "%s" "$_release_list" | jq -r ".[] | select(.name==\"$_release_name\")")
|
||||
#Replace the old certificate id with the new one in path .config.ingress.main.tls[].scaleCert. Then update .config.ingress
|
||||
_updated_chart_config=$(printf "%s" "$_chart_config" | jq "(.config.ingress?.main.tls[]? | select(.scaleCert==$_active_cert_id) | .scaleCert ) |= $_cert_id | .config.ingress ")
|
||||
_update_chart_result="$(_post "{\"values\" : { \"ingress\" : $_updated_chart_config } }" "$_api_url/chart/release/id/$_release_name" "" "PUT" "application/json")"
|
||||
_debug3 _update_chart_result "$_update_chart_result"
|
||||
done
|
||||
else
|
||||
_info "Tool 'jq' does not exists, skip chart release checking"
|
||||
fi
|
||||
else
|
||||
_info "Checking if any app is using the same certificate as TrueNAS web UI. Tool 'jq' is required"
|
||||
if _exists jq; then
|
||||
_info "Query all apps"
|
||||
_app_list=$(_get "$_api_url/app")
|
||||
_app_id_list=$(printf "%s" "$_app_list" | jq -r '.[].name')
|
||||
_app_length=$(echo "$_app_id_list" | wc -l)
|
||||
_info "Found $_app_length apps"
|
||||
_info "Checking for each app if an update is needed"
|
||||
for i in $(seq 1 "$_app_length"); do
|
||||
_app_id=$(echo "$_app_id_list" | sed -n "${i}p")
|
||||
_app_config="$(_post "\"$_app_id\"" "$_api_url/app/config" "" "POST" "application/json")"
|
||||
# Check if the app use the same certificate TrueNAS web UI
|
||||
_app_active_cert_config=$(echo "$_app_config" | tr -d '\000-\037' | _json_decode | jq -r ".ix_certificates[\"$_active_cert_id\"]")
|
||||
if [ "$_app_active_cert_config" != "null" ]; then
|
||||
_info "Updating certificate from $_active_cert_id to $_cert_id for app: $_app_id"
|
||||
#Replace the old certificate id with the new one in path
|
||||
_update_app_result="$(_post "{\"values\" : { \"network\": { \"certificate_id\": $_cert_id } } }" "$_api_url/app/id/$_app_id" "" "PUT" "application/json")"
|
||||
_debug3 _update_app_result "$_update_app_result"
|
||||
fi
|
||||
done
|
||||
else
|
||||
_info "Tool 'jq' does not exists, skip app checking"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
_info "Checking if FTP certificate is the same as the TrueNAS web UI"
|
||||
_ftp_list=$(_get "$_api_url/ftp")
|
||||
_ftp_cert_id=$(echo "$_ftp_list" | grep '"ssltls_certificate":' | tr -d -- '"certislfa:_ ,')
|
||||
|
||||
if [ "$_ftp_cert_id" = "$_active_cert_id" ]; then
|
||||
_info "Updating the FTP certificate"
|
||||
_debug _ftp_cert_id "$_ftp_cert_id"
|
||||
_ftp_data="{\"ssltls_certificate\": \"${_cert_id}\"}"
|
||||
_activate_ftp_cert="$(_post "$_ftp_data" "$_api_url/ftp" "" "PUT" "application/json")"
|
||||
_ftp_new_cert_id=$(echo "$_activate_ftp_cert" | _json_decode | grep '"ssltls_certificate":' | sed -n 's/.*: \([0-9]\{1,\}\),\{0,1\}$/\1/p')
|
||||
if [ "$_ftp_new_cert_id" -eq "$_cert_id" ]; then
|
||||
_info "FTP certificate updated successfully"
|
||||
else
|
||||
_err "Unable to set FTP certificate"
|
||||
_debug3 _activate_ftp_cert "$_activate_ftp_cert"
|
||||
_debug3 _ftp_new_cert_id "$_ftp_new_cert_id"
|
||||
return 1
|
||||
fi
|
||||
_debug3 _activate_ftp_cert "$_activate_ftp_cert"
|
||||
else
|
||||
_info "FTP certificate is not configured or is not the same as TrueNAS web UI"
|
||||
fi
|
||||
|
||||
_info "Deleting old certificate"
|
||||
_delete_result="$(_post "" "$_api_url/certificate/id/$_active_cert_id" "" "DELETE" "application/json")"
|
||||
|
||||
_debug3 _delete_result "$_delete_result"
|
||||
|
||||
_info "Reloading TrueNAS web UI"
|
||||
_restart_UI=$(_get "$_api_url/system/general/ui_restart")
|
||||
_debug2 _restart_UI "$_restart_UI"
|
||||
|
||||
if [ -n "$_add_cert_result" ] && [ -n "$_activate_result" ]; then
|
||||
return 0
|
||||
else
|
||||
_err "Certificate update was not succesful, please try again with --debug"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
363
deploy/truenas_ws.sh
Normal file
363
deploy/truenas_ws.sh
Normal file
@@ -0,0 +1,363 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# TrueNAS deploy script for SCALE/CORE using websocket
|
||||
# It is recommend to use a wildcard certificate
|
||||
#
|
||||
# Websocket Documentation: https://www.truenas.com/docs/api/scale_websocket_api.html
|
||||
#
|
||||
# Tested with TrueNAS Scale - Electric Eel 24.10
|
||||
# Changes certificate in the following services:
|
||||
# - Web UI
|
||||
# - FTP
|
||||
# - iX Apps
|
||||
#
|
||||
# The following environment variables must be set:
|
||||
# ------------------------------------------------
|
||||
#
|
||||
# # API KEY
|
||||
# # Use the folowing URL to create a new API token: <TRUENAS_HOSTNAME OR IP>/ui/apikeys
|
||||
# export DEPLOY_TRUENAS_APIKEY="<API_KEY_GENERATED_IN_THE_WEB_UI>"
|
||||
# Optional:
|
||||
# export DEPLOY_TRUENAS_HOSTNAME="<TRUENAS_HOSTNAME_OR_IP>"
|
||||
# export DEPLOY_TRUENAS_PROTOCOL="wss" # ws or wss
|
||||
# export DEPLOY_TRUENAS_PORT="443" # optional, e.g. 80, 443, 8443
|
||||
|
||||
#
|
||||
|
||||
### Private functions
|
||||
|
||||
# Call websocket method
|
||||
# Usage:
|
||||
# _ws_response=$(_ws_call "math.dummycalc" "'{"x": 4, "y": 5}'")
|
||||
# _info "$_ws_response"
|
||||
#
|
||||
# Output:
|
||||
# {"z": 9}
|
||||
#
|
||||
# Arguments:
|
||||
# $@ - midclt arguments for call
|
||||
#
|
||||
# Returns:
|
||||
# JSON/JOBID
|
||||
_ws_call() {
|
||||
_debug "_ws_call arg1" "$1"
|
||||
_debug "_ws_call arg2" "$2"
|
||||
_debug "_ws_call arg3" "$3"
|
||||
if [ $# -eq 3 ]; then
|
||||
_ws_response=$(midclt --uri "$_ws_uri" -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2" "$3")
|
||||
fi
|
||||
if [ $# -eq 2 ]; then
|
||||
_ws_response=$(midclt --uri "$_ws_uri" -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2")
|
||||
fi
|
||||
if [ $# -eq 1 ]; then
|
||||
_ws_response=$(midclt --uri "$_ws_uri" -K "$DEPLOY_TRUENAS_APIKEY" call "$1")
|
||||
fi
|
||||
_debug "_ws_response" "$_ws_response"
|
||||
printf "%s" "$_ws_response"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Upload certificate with webclient api
|
||||
_ws_upload_cert() {
|
||||
|
||||
/usr/bin/env python - <<EOF
|
||||
import sys
|
||||
|
||||
from truenas_api_client import Client
|
||||
with Client(uri="$_ws_uri") as c:
|
||||
|
||||
### Login with API key
|
||||
print("I:Trying to upload new certificate...")
|
||||
ret = c.call("auth.login_with_api_key", "${DEPLOY_TRUENAS_APIKEY}")
|
||||
if ret:
|
||||
### upload certificate
|
||||
with open('$1', 'r') as file:
|
||||
fullchain = file.read()
|
||||
with open('$2', 'r') as file:
|
||||
privatekey = file.read()
|
||||
ret = c.call("certificate.create", {"name": "$3", "create_type": "CERTIFICATE_CREATE_IMPORTED", "certificate": fullchain, "privatekey": privatekey}, job=True)
|
||||
print("R:" + str(ret["id"]))
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("R:0")
|
||||
print("E:_ws_upload_cert error!")
|
||||
sys.exit(7)
|
||||
EOF
|
||||
|
||||
return $?
|
||||
|
||||
}
|
||||
|
||||
# Check argument is a number
|
||||
# Usage:
|
||||
#
|
||||
# Output:
|
||||
# n/a
|
||||
#
|
||||
# Arguments:
|
||||
# $1 - Anything
|
||||
#
|
||||
# Returns:
|
||||
# 0: true
|
||||
# 1: false
|
||||
_ws_check_jobid() {
|
||||
case "$1" in
|
||||
[0-9]*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
# Wait for job to finish and return result as JSON
|
||||
# Usage:
|
||||
# _ws_result=$(_ws_get_job_result "$_ws_jobid")
|
||||
# _new_certid=$(printf "%s" "$_ws_result" | jq -r '."id"')
|
||||
#
|
||||
# Output:
|
||||
# JSON result of the job
|
||||
#
|
||||
# Arguments:
|
||||
# $1 - JobID
|
||||
#
|
||||
# Returns:
|
||||
# n/a
|
||||
_ws_get_job_result() {
|
||||
while true; do
|
||||
_sleep 2
|
||||
_ws_response=$(_ws_call "core.get_jobs" "[[\"id\", \"=\", $1]]")
|
||||
if [ "$(printf "%s" "$_ws_response" | jq -r '.[]."state"')" != "RUNNING" ]; then
|
||||
_ws_result="$(printf "%s" "$_ws_response" | jq '.[]."result"')"
|
||||
_debug "_ws_result" "$_ws_result"
|
||||
printf "%s" "$_ws_result"
|
||||
_ws_error="$(printf "%s" "$_ws_response" | jq '.[]."error"')"
|
||||
if [ "$_ws_error" != "null" ]; then
|
||||
_err "Job $1 failed:"
|
||||
_err "$_ws_error"
|
||||
return 7
|
||||
fi
|
||||
break
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
########################
|
||||
### Public functions ###
|
||||
########################
|
||||
|
||||
# truenas_ws_deploy
|
||||
#
|
||||
# Deploy new certificate to TrueNAS services
|
||||
#
|
||||
# Arguments
|
||||
# 1: Domain
|
||||
# 2: Key-File
|
||||
# 3: Certificate-File
|
||||
# 4: CA-File
|
||||
# 5: FullChain-File
|
||||
# Returns:
|
||||
# 0: Success
|
||||
# 1: Missing API Key
|
||||
# 2: TrueNAS not ready
|
||||
# 3: Not a JobID
|
||||
# 4: FTP cert error
|
||||
# 5: WebUI cert error
|
||||
# 6: Job error
|
||||
# 7: WS call error
|
||||
#
|
||||
truenas_ws_deploy() {
|
||||
_domain="$1"
|
||||
_file_key="$2"
|
||||
_file_cert="$3"
|
||||
_file_ca="$4"
|
||||
_file_fullchain="$5"
|
||||
_debug _domain "$_domain"
|
||||
_debug _file_key "$_file_key"
|
||||
_debug _file_cert "$_file_cert"
|
||||
_debug _file_ca "$_file_ca"
|
||||
_debug _file_fullchain "$_file_fullchain"
|
||||
|
||||
########## Environment check
|
||||
|
||||
_info "Checking environment variables..."
|
||||
_getdeployconf DEPLOY_TRUENAS_APIKEY
|
||||
_getdeployconf DEPLOY_TRUENAS_HOSTNAME
|
||||
_getdeployconf DEPLOY_TRUENAS_PROTOCOL
|
||||
_getdeployconf DEPLOY_TRUENAS_PORT
|
||||
|
||||
# Check API Key
|
||||
if [ -z "$DEPLOY_TRUENAS_APIKEY" ]; then
|
||||
_err "TrueNAS API key not found, please set the DEPLOY_TRUENAS_APIKEY environment variable."
|
||||
return 1
|
||||
fi
|
||||
# Check Hostname, default to localhost if not set
|
||||
if [ -z "$DEPLOY_TRUENAS_HOSTNAME" ]; then
|
||||
_info "TrueNAS hostname not set. Using 'localhost'."
|
||||
DEPLOY_TRUENAS_HOSTNAME="localhost"
|
||||
fi
|
||||
# Check protocol, default to ws if not set
|
||||
if [ -z "$DEPLOY_TRUENAS_PROTOCOL" ]; then
|
||||
_info "TrueNAS protocol not set. Using 'ws'."
|
||||
DEPLOY_TRUENAS_PROTOCOL="ws"
|
||||
fi
|
||||
|
||||
# Check port, optional
|
||||
if [ -n "$DEPLOY_TRUENAS_PORT" ]; then
|
||||
case "$DEPLOY_TRUENAS_PORT" in
|
||||
'' | *[!0-9]*)
|
||||
_err "Invalid TrueNAS port '$DEPLOY_TRUENAS_PORT'. DEPLOY_TRUENAS_PORT must be numeric."
|
||||
return 8
|
||||
;;
|
||||
esac
|
||||
|
||||
_ws_uri="$DEPLOY_TRUENAS_PROTOCOL://$DEPLOY_TRUENAS_HOSTNAME:$DEPLOY_TRUENAS_PORT/websocket"
|
||||
else
|
||||
_ws_uri="$DEPLOY_TRUENAS_PROTOCOL://$DEPLOY_TRUENAS_HOSTNAME/websocket"
|
||||
fi
|
||||
|
||||
_debug2 DEPLOY_TRUENAS_HOSTNAME "$DEPLOY_TRUENAS_HOSTNAME"
|
||||
_debug2 DEPLOY_TRUENAS_PROTOCOL "$DEPLOY_TRUENAS_PROTOCOL"
|
||||
_debug _ws_uri "$_ws_uri"
|
||||
_secure_debug2 DEPLOY_TRUENAS_APIKEY "$DEPLOY_TRUENAS_APIKEY"
|
||||
_info "Environment variables: OK"
|
||||
|
||||
########## Health check
|
||||
|
||||
_info "Checking TrueNAS health..."
|
||||
_ws_response=$(_ws_call "system.ready" | tr '[:lower:]' '[:upper:]')
|
||||
_ws_ret=$?
|
||||
if [ $_ws_ret -gt 0 ]; then
|
||||
_err "Error calling system.ready:"
|
||||
_err "$_ws_response"
|
||||
return $_ws_ret
|
||||
fi
|
||||
|
||||
if [ "$_ws_response" != "TRUE" ]; then
|
||||
_err "TrueNAS is not ready."
|
||||
_err "Please check environment variables DEPLOY_TRUENAS_APIKEY, DEPLOY_TRUENAS_HOSTNAME, DEPLOY_TRUENAS_PROTOCOL and DEPLOY_TRUENAS_PORT."
|
||||
_err "Verify API key."
|
||||
return 2
|
||||
fi
|
||||
_savedeployconf DEPLOY_TRUENAS_APIKEY "$DEPLOY_TRUENAS_APIKEY"
|
||||
_savedeployconf DEPLOY_TRUENAS_HOSTNAME "$DEPLOY_TRUENAS_HOSTNAME"
|
||||
_savedeployconf DEPLOY_TRUENAS_PROTOCOL "$DEPLOY_TRUENAS_PROTOCOL"
|
||||
_savedeployconf DEPLOY_TRUENAS_PORT "$DEPLOY_TRUENAS_PORT"
|
||||
_info "TrueNAS health: OK"
|
||||
|
||||
########## System info
|
||||
|
||||
_info "Gather system info..."
|
||||
_ws_response=$(_ws_call "system.info")
|
||||
_truenas_version=$(printf "%s" "$_ws_response" | jq -r '."version"')
|
||||
_info "TrueNAS version: $_truenas_version"
|
||||
|
||||
########## Gather current certificate
|
||||
|
||||
_info "Gather current WebUI certificate..."
|
||||
_ws_response="$(_ws_call "system.general.config")"
|
||||
_ui_certificate_id=$(printf "%s" "$_ws_response" | jq -r '."ui_certificate"."id"')
|
||||
_ui_certificate_name=$(printf "%s" "$_ws_response" | jq -r '."ui_certificate"."name"')
|
||||
_info "Current WebUI certificate ID: $_ui_certificate_id"
|
||||
_info "Current WebUI certificate name: $_ui_certificate_name"
|
||||
|
||||
########## Upload new certificate
|
||||
|
||||
_info "Upload new certificate..."
|
||||
_certname="acme_$(_utc_date | tr -d '\-\:' | tr ' ' '_')"
|
||||
_info "New WebUI certificate name: $_certname"
|
||||
_debug _certname "$_certname"
|
||||
_ws_out=$(_ws_upload_cert "$_file_fullchain" "$_file_key" "$_certname")
|
||||
|
||||
echo "$_ws_out" | while IFS= read -r LINE; do
|
||||
case "$LINE" in
|
||||
I:*)
|
||||
_info "${LINE#I:}"
|
||||
;;
|
||||
D:*)
|
||||
_debug "${LINE#D:}"
|
||||
;;
|
||||
E*)
|
||||
_err "${LINE#E:}"
|
||||
;;
|
||||
*) ;;
|
||||
|
||||
esac
|
||||
done
|
||||
|
||||
_new_certid=$(echo "$_ws_out" | grep 'R:' | cut -d ':' -f 2)
|
||||
|
||||
_info "New certificate ID: $_new_certid"
|
||||
|
||||
########## FTP
|
||||
|
||||
_info "Replace FTP certificate..."
|
||||
_ws_response=$(_ws_call "ftp.update" "{\"ssltls_certificate\": $_new_certid}")
|
||||
_ftp_certid=$(printf "%s" "$_ws_response" | jq -r '."ssltls_certificate"')
|
||||
if [ "$_ftp_certid" != "$_new_certid" ]; then
|
||||
_err "Cannot set FTP certificate."
|
||||
_debug "_ws_response" "$_ws_response"
|
||||
return 4
|
||||
fi
|
||||
|
||||
########## ix Apps (SCALE only)
|
||||
|
||||
_info "Replace app certificates..."
|
||||
_ws_response=$(_ws_call "app.query")
|
||||
for _app_name in $(printf "%s" "$_ws_response" | jq -r '.[]."name"'); do
|
||||
_info "Checking app $_app_name..."
|
||||
_ws_response=$(_ws_call "app.config" "$_app_name")
|
||||
if [ "$(printf "%s" "$_ws_response" | jq -r '."network" | has("certificate_id")')" = "true" ]; then
|
||||
_info "App has certificate option, setup new certificate..."
|
||||
_info "App will be redeployed after updating the certificate."
|
||||
_ws_jobid=$(_ws_call "app.update" "$_app_name" "{\"values\": {\"network\": {\"certificate_id\": $_new_certid}}}")
|
||||
_debug "_ws_jobid" "$_ws_jobid"
|
||||
if ! _ws_check_jobid "$_ws_jobid"; then
|
||||
_err "No JobID returned from websocket method."
|
||||
return 3
|
||||
fi
|
||||
_ws_result=$(_ws_get_job_result "$_ws_jobid")
|
||||
_ws_ret=$?
|
||||
if [ $_ws_ret -gt 0 ]; then
|
||||
return $_ws_ret
|
||||
fi
|
||||
_debug "_ws_result" "$_ws_result"
|
||||
_info "App certificate replaced."
|
||||
else
|
||||
_info "App has no certificate option, skipping..."
|
||||
fi
|
||||
done
|
||||
|
||||
########## WebUI
|
||||
|
||||
_info "Replace WebUI certificate..."
|
||||
_ws_response=$(_ws_call "system.general.update" "{\"ui_certificate\": $_new_certid}")
|
||||
_changed_certid=$(printf "%s" "$_ws_response" | jq -r '."ui_certificate"."id"')
|
||||
if [ "$_changed_certid" != "$_new_certid" ]; then
|
||||
_err "WebUI certificate change error.."
|
||||
return 5
|
||||
else
|
||||
_info "WebUI certificate replaced."
|
||||
fi
|
||||
_info "Restarting WebUI..."
|
||||
_ws_response=$(_ws_call "system.general.ui_restart")
|
||||
_info "Waiting for UI restart..."
|
||||
_sleep 15
|
||||
|
||||
########## Certificates
|
||||
|
||||
_info "Deleting old certificate..."
|
||||
_ws_jobid=$(_ws_call "certificate.delete" "$_ui_certificate_id")
|
||||
if ! _ws_check_jobid "$_ws_jobid"; then
|
||||
_err "No JobID returned from websocket method."
|
||||
return 3
|
||||
fi
|
||||
_ws_result=$(_ws_get_job_result "$_ws_jobid")
|
||||
_ws_ret=$?
|
||||
if [ $_ws_ret -gt 0 ]; then
|
||||
return $_ws_ret
|
||||
fi
|
||||
|
||||
_info "Have a nice day...bye!"
|
||||
|
||||
}
|
||||
307
deploy/unifi.sh
Normal file
307
deploy/unifi.sh
Normal file
@@ -0,0 +1,307 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Here is a script to deploy cert on a Unifi Controller or Cloud Key device.
|
||||
# It supports:
|
||||
# - self-hosted Unifi Controller
|
||||
# - Unifi Cloud Key (Gen1/2/2+)
|
||||
# - Unifi Cloud Key running UnifiOS (v2.0.0+, Gen2/2+ only)
|
||||
# - Unifi Dream Machine
|
||||
# This has not been tested on other "all-in-one" devices such as
|
||||
# UDM Pro or Unifi Express.
|
||||
#
|
||||
# OS Version v2.0.0+
|
||||
# Network Application version 7.0.0+
|
||||
# OS version ~3.1 removed java and keytool from the UnifiOS.
|
||||
# Using PKCS12 format keystore appears to work fine.
|
||||
#
|
||||
# Please report bugs to https://github.com/acmesh-official/acme.sh/issues/3359
|
||||
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
# The deploy-hook automatically detects standard Unifi installations
|
||||
# for each of the supported environments. Most users should not need
|
||||
# to set any of these variables, but if you are running a self-hosted
|
||||
# Controller with custom locations, set these as necessary before running
|
||||
# the deploy hook. (Defaults shown below.)
|
||||
#
|
||||
# Settings for Unifi Controller:
|
||||
# Location of Java keystore or unifi.keystore.jks file:
|
||||
#DEPLOY_UNIFI_KEYSTORE="/usr/lib/unifi/data/keystore"
|
||||
# Keystore password (built into Unifi Controller, not a user-set password):
|
||||
#DEPLOY_UNIFI_KEYPASS="aircontrolenterprise"
|
||||
# Command to restart Unifi Controller:
|
||||
# DEPLOY_UNIFI_RELOAD="systemctl restart unifi"
|
||||
# System Properties file location for controller
|
||||
#DEPLOY_UNIFI_SYSTEM_PROPERTIES="/usr/lib/unifi/data/system.properties"
|
||||
#
|
||||
# Settings for Unifi Cloud Key Gen1 (nginx admin pages):
|
||||
# Directory where cloudkey.crt and cloudkey.key live:
|
||||
#DEPLOY_UNIFI_CLOUDKEY_CERTDIR="/etc/ssl/private"
|
||||
# Command to restart maintenance pages and Controller
|
||||
# (same setting as above, default is updated when running on Cloud Key Gen1):
|
||||
#DEPLOY_UNIFI_RELOAD="service nginx restart && service unifi restart"
|
||||
#
|
||||
# Settings for UnifiOS (Cloud Key Gen2):
|
||||
# Directory where unifi-core.crt and unifi-core.key live:
|
||||
#DEPLOY_UNIFI_CORE_CONFIG="/data/unifi-core/config/"
|
||||
# Command to restart unifi-core:
|
||||
# DEPLOY_UNIFI_OS_RELOAD="systemctl restart unifi-core"
|
||||
#
|
||||
# At least one of DEPLOY_UNIFI_KEYSTORE, DEPLOY_UNIFI_CLOUDKEY_CERTDIR,
|
||||
# or DEPLOY_UNIFI_CORE_CONFIG must exist to receive the deployed certs.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
unifi_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
_getdeployconf DEPLOY_UNIFI_KEYSTORE
|
||||
_getdeployconf DEPLOY_UNIFI_KEYPASS
|
||||
_getdeployconf DEPLOY_UNIFI_CLOUDKEY_CERTDIR
|
||||
_getdeployconf DEPLOY_UNIFI_CORE_CONFIG
|
||||
_getdeployconf DEPLOY_UNIFI_RELOAD
|
||||
_getdeployconf DEPLOY_UNIFI_SYSTEM_PROPERTIES
|
||||
_getdeployconf DEPLOY_UNIFI_OS_RELOAD
|
||||
|
||||
_debug2 DEPLOY_UNIFI_KEYSTORE "$DEPLOY_UNIFI_KEYSTORE"
|
||||
_debug2 DEPLOY_UNIFI_KEYPASS "$DEPLOY_UNIFI_KEYPASS"
|
||||
_debug2 DEPLOY_UNIFI_CLOUDKEY_CERTDIR "$DEPLOY_UNIFI_CLOUDKEY_CERTDIR"
|
||||
_debug2 DEPLOY_UNIFI_CORE_CONFIG "$DEPLOY_UNIFI_CORE_CONFIG"
|
||||
_debug2 DEPLOY_UNIFI_RELOAD "$DEPLOY_UNIFI_RELOAD"
|
||||
_debug2 DEPLOY_UNIFI_OS_RELOAD "$DEPLOY_UNIFI_OS_RELOAD"
|
||||
_debug2 DEPLOY_UNIFI_SYSTEM_PROPERTIES "$DEPLOY_UNIFI_SYSTEM_PROPERTIES"
|
||||
|
||||
# Space-separated list of environments detected and installed:
|
||||
_services_updated=""
|
||||
|
||||
# Default reload commands accumulated as we auto-detect environments:
|
||||
_reload_cmd=""
|
||||
|
||||
# Unifi Controller environment (self hosted or any Cloud Key) --
|
||||
# auto-detect by file /usr/lib/unifi/data/keystore
|
||||
_unifi_keystore="${DEPLOY_UNIFI_KEYSTORE:-/usr/lib/unifi/data/keystore}"
|
||||
if [ -f "$_unifi_keystore" ]; then
|
||||
_debug _unifi_keystore "$_unifi_keystore"
|
||||
if ! _exists keytool; then
|
||||
_do_keytool=0
|
||||
_info "Installing certificate for Unifi Controller (PKCS12 keystore)."
|
||||
else
|
||||
_do_keytool=1
|
||||
_info "Installing certificate for Unifi Controller (Java keystore)"
|
||||
fi
|
||||
if [ ! -w "$_unifi_keystore" ]; then
|
||||
_err "The file $_unifi_keystore is not writable, please change the permission."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_unifi_keypass="${DEPLOY_UNIFI_KEYPASS:-aircontrolenterprise}"
|
||||
|
||||
_debug "Generate import pkcs12"
|
||||
_import_pkcs12="$(_mktemp)"
|
||||
_debug "_toPkcs $_import_pkcs12 $_ckey $_ccert $_cca $_unifi_keypass unifi root"
|
||||
_toPkcs "$_import_pkcs12" "$_ckey" "$_ccert" "$_cca" "$_unifi_keypass" unifi root
|
||||
# shellcheck disable=SC2181
|
||||
if [ "$?" != "0" ]; then
|
||||
_err "Error generating pkcs12. Please re-run with --debug and report a bug."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Save the existing keystore in case something goes wrong.
|
||||
mv -f "${_unifi_keystore}" "${_unifi_keystore}"_original
|
||||
_info "Previous keystore saved to ${_unifi_keystore}_original."
|
||||
|
||||
if [ "$_do_keytool" -eq 1 ]; then
|
||||
_debug "Import into keystore: $_unifi_keystore"
|
||||
if keytool -importkeystore \
|
||||
-deststorepass "$_unifi_keypass" -destkeypass "$_unifi_keypass" -destkeystore "$_unifi_keystore" \
|
||||
-srckeystore "$_import_pkcs12" -srcstoretype PKCS12 -srcstorepass "$_unifi_keypass" \
|
||||
-alias unifi -noprompt; then
|
||||
_debug "Import keystore success!"
|
||||
else
|
||||
_err "Error importing into Unifi Java keystore."
|
||||
_err "Please re-run with --debug and report a bug."
|
||||
_info "Restoring original keystore."
|
||||
mv -f "${_unifi_keystore}"_original "${_unifi_keystore}"
|
||||
rm "$_import_pkcs12"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
_debug "Copying new keystore to $_unifi_keystore"
|
||||
cp -f "$_import_pkcs12" "$_unifi_keystore"
|
||||
fi
|
||||
|
||||
# correct file ownership according to the directory, the keystore is placed in
|
||||
_unifi_keystore_dir=$(dirname "${_unifi_keystore}")
|
||||
# shellcheck disable=SC2012
|
||||
_unifi_keystore_dir_owner=$(ls -ld "${_unifi_keystore_dir}" | awk '{print $3}')
|
||||
# shellcheck disable=SC2012
|
||||
_unifi_keystore_owner=$(ls -l "${_unifi_keystore}" | awk '{print $3}')
|
||||
if ! [ "${_unifi_keystore_owner}" = "${_unifi_keystore_dir_owner}" ]; then
|
||||
_debug "Changing keystore owner to ${_unifi_keystore_dir_owner}"
|
||||
chown "$_unifi_keystore_dir_owner" "${_unifi_keystore}" >/dev/null 2>&1 # fail quietly if we're not running as root
|
||||
fi
|
||||
|
||||
# Update unifi service for certificate cipher compatibility
|
||||
_unifi_system_properties="${DEPLOY_UNIFI_SYSTEM_PROPERTIES:-/usr/lib/unifi/data/system.properties}"
|
||||
if ${ACME_OPENSSL_BIN:-openssl} pkcs12 \
|
||||
-in "$_import_pkcs12" \
|
||||
-password pass:aircontrolenterprise \
|
||||
-nokeys | ${ACME_OPENSSL_BIN:-openssl} x509 -text \
|
||||
-noout | grep -i "signature" | grep -iq ecdsa >/dev/null 2>&1; then
|
||||
if [ -f "$(dirname "${DEPLOY_UNIFI_KEYSTORE}")/system.properties" ]; then
|
||||
_unifi_system_properties="$(dirname "${DEPLOY_UNIFI_KEYSTORE}")/system.properties"
|
||||
else
|
||||
_unifi_system_properties="/usr/lib/unifi/data/system.properties"
|
||||
fi
|
||||
if [ -f "${_unifi_system_properties}" ]; then
|
||||
cp -f "${_unifi_system_properties}" "${_unifi_system_properties}"_original
|
||||
_info "Updating system configuration for cipher compatibility."
|
||||
_info "Saved original system config to ${_unifi_system_properties}_original"
|
||||
sed -i '/unifi\.https\.ciphers/d' "${_unifi_system_properties}"
|
||||
echo "unifi.https.ciphers=ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES128-GCM-SHA256" >>"${_unifi_system_properties}"
|
||||
sed -i '/unifi\.https\.sslEnabledProtocols/d' "${_unifi_system_properties}"
|
||||
echo "unifi.https.sslEnabledProtocols=TLSv1.3,TLSv1.2" >>"${_unifi_system_properties}"
|
||||
_info "System configuration updated."
|
||||
fi
|
||||
fi
|
||||
|
||||
rm "$_import_pkcs12"
|
||||
|
||||
# Restarting unifi-core will bring up unifi, doing it out of order results in
|
||||
# a certificate error, and breaks wifiman.
|
||||
# Restart if we aren't doing Unifi OS (e.g. unifi-core service), otherwise stop for later restart.
|
||||
_unifi_reload="${DEPLOY_UNIFI_RELOAD:-systemctl restart unifi}"
|
||||
if [ ! -f "${DEPLOY_UNIFI_CORE_CONFIG:-/data/unifi-core/config}/unifi-core.key" ]; then
|
||||
_reload_cmd="${_reload_cmd:+$_reload_cmd && }$_unifi_reload"
|
||||
else
|
||||
_info "Stopping Unifi Controller for later restart."
|
||||
_unifi_stop=$(echo "${_unifi_reload}" | sed -e 's/restart/stop/')
|
||||
$_unifi_stop
|
||||
_reload_cmd="${_reload_cmd:+$_reload_cmd && }$_unifi_reload"
|
||||
_info "Unifi Controller stopped."
|
||||
fi
|
||||
_services_updated="${_services_updated} unifi"
|
||||
_info "Install Unifi Controller certificate success!"
|
||||
elif [ "$DEPLOY_UNIFI_KEYSTORE" ]; then
|
||||
_err "The specified DEPLOY_UNIFI_KEYSTORE='$DEPLOY_UNIFI_KEYSTORE' is not valid, please check."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Cloud Key environment (non-UnifiOS -- nginx serves admin pages) --
|
||||
# auto-detect by file /etc/ssl/private/cloudkey.key:
|
||||
_cloudkey_certdir="${DEPLOY_UNIFI_CLOUDKEY_CERTDIR:-/etc/ssl/private}"
|
||||
if [ -f "${_cloudkey_certdir}/cloudkey.key" ]; then
|
||||
_info "Installing certificate for Cloud Key Gen1 (nginx admin pages)"
|
||||
_debug _cloudkey_certdir "$_cloudkey_certdir"
|
||||
if [ ! -w "$_cloudkey_certdir" ]; then
|
||||
_err "The directory $_cloudkey_certdir is not writable; please check permissions."
|
||||
return 1
|
||||
fi
|
||||
# Cloud Key expects to load the keystore from /etc/ssl/private/unifi.keystore.jks.
|
||||
# It appears that unifi won't start if this is a symlink, so we'll copy it instead.
|
||||
|
||||
# if ! cmp -s "$_unifi_keystore" "${_cloudkey_certdir}/unifi.keystore.jks"; then
|
||||
# _err "Unsupported Cloud Key configuration: keystore not found at '${_cloudkey_certdir}/unifi.keystore.jks'"
|
||||
# return 1
|
||||
# fi
|
||||
|
||||
_info "Updating ${_cloudkey_certdir}/unifi.keystore.jks"
|
||||
if [ -e "${_cloudkey_certdir}/unifi.keystore.jks" ]; then
|
||||
if [ -L "${_cloudkey_certdir}/unifi.keystore.jks" ]; then
|
||||
rm -f "${_cloudkey_certdir}/unifi.keystore.jks"
|
||||
else
|
||||
mv "${_cloudkey_certdir}/unifi.keystore.jks" "${_cloudkey_certdir}/unifi.keystore.jks_original"
|
||||
fi
|
||||
fi
|
||||
|
||||
cp "${_unifi_keystore}" "${_cloudkey_certdir}/unifi.keystore.jks"
|
||||
|
||||
cat "$_cfullchain" >"${_cloudkey_certdir}/cloudkey.crt"
|
||||
cat "$_ckey" >"${_cloudkey_certdir}/cloudkey.key"
|
||||
(cd "$_cloudkey_certdir" && tar -cf cert.tar cloudkey.crt cloudkey.key unifi.keystore.jks)
|
||||
|
||||
if systemctl -q is-active nginx; then
|
||||
_reload_cmd="${_reload_cmd:+$_reload_cmd && }service nginx restart"
|
||||
fi
|
||||
_info "Install Cloud Key Gen1 certificate success!"
|
||||
_services_updated="${_services_updated} nginx"
|
||||
elif [ "$DEPLOY_UNIFI_CLOUDKEY_CERTDIR" ]; then
|
||||
_err "The specified DEPLOY_UNIFI_CLOUDKEY_CERTDIR='$DEPLOY_UNIFI_CLOUDKEY_CERTDIR' is not valid, please check."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# UnifiOS environment -- auto-detect by /data/unifi-core/config/unifi-core.key:
|
||||
_unifi_core_config="${DEPLOY_UNIFI_CORE_CONFIG:-/data/unifi-core/config}"
|
||||
if [ -f "${_unifi_core_config}/unifi-core.key" ]; then
|
||||
_info "Installing certificate for UnifiOS"
|
||||
_debug _unifi_core_config "$_unifi_core_config"
|
||||
if [ ! -w "$_unifi_core_config" ]; then
|
||||
_err "The directory $_unifi_core_config is not writable; please check permissions."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Save the existing certs in case something goes wrong.
|
||||
cp -f "${_unifi_core_config}"/unifi-core.crt "${_unifi_core_config}"/unifi-core_original.crt
|
||||
cp -f "${_unifi_core_config}"/unifi-core.key "${_unifi_core_config}"/unifi-core_original.key
|
||||
_info "Previous certificate and key saved to ${_unifi_core_config}/unifi-core_original.crt.key."
|
||||
|
||||
cat "$_cfullchain" >"${_unifi_core_config}/unifi-core.crt"
|
||||
cat "$_ckey" >"${_unifi_core_config}/unifi-core.key"
|
||||
|
||||
_unifi_os_reload="${DEPLOY_UNIFI_OS_RELOAD:-systemctl restart unifi-core}"
|
||||
_reload_cmd="${_reload_cmd:+$_reload_cmd && }$_unifi_os_reload"
|
||||
|
||||
_info "Install UnifiOS certificate success!"
|
||||
_services_updated="${_services_updated} unifi-core"
|
||||
elif [ "$DEPLOY_UNIFI_CORE_CONFIG" ]; then
|
||||
_err "The specified DEPLOY_UNIFI_CORE_CONFIG='$DEPLOY_UNIFI_CORE_CONFIG' is not valid, please check."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -z "$_services_updated" ]; then
|
||||
# None of the Unifi environments were auto-detected, so no deployment has occurred
|
||||
# (and none of DEPLOY_UNIFI_{KEYSTORE,CLOUDKEY_CERTDIR,CORE_CONFIG} were set).
|
||||
_err "Unable to detect Unifi environment in standard location."
|
||||
_err "(This deploy hook must be run on the Unifi device, not a remote machine.)"
|
||||
_err "For non-standard Unifi installations, set DEPLOY_UNIFI_KEYSTORE,"
|
||||
_err "DEPLOY_UNIFI_CLOUDKEY_CERTDIR, and/or DEPLOY_UNIFI_CORE_CONFIG as appropriate."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_reload_cmd="${DEPLOY_UNIFI_RELOAD:-$_reload_cmd}"
|
||||
if [ -z "$_reload_cmd" ]; then
|
||||
_err "Certificates were installed for services:${_services_updated},"
|
||||
_err "but none appear to be active. Please set DEPLOY_UNIFI_RELOAD"
|
||||
_err "to a command that will restart the necessary services."
|
||||
return 1
|
||||
fi
|
||||
_info "Reload services (this may take some time): $_reload_cmd"
|
||||
if eval "$_reload_cmd"; then
|
||||
_info "Reload success!"
|
||||
else
|
||||
_err "Reload error"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Successful, so save all (non-default) config:
|
||||
_savedeployconf DEPLOY_UNIFI_KEYSTORE "$DEPLOY_UNIFI_KEYSTORE"
|
||||
_savedeployconf DEPLOY_UNIFI_KEYPASS "$DEPLOY_UNIFI_KEYPASS"
|
||||
_savedeployconf DEPLOY_UNIFI_CLOUDKEY_CERTDIR "$DEPLOY_UNIFI_CLOUDKEY_CERTDIR"
|
||||
_savedeployconf DEPLOY_UNIFI_CORE_CONFIG "$DEPLOY_UNIFI_CORE_CONFIG"
|
||||
_savedeployconf DEPLOY_UNIFI_RELOAD "$DEPLOY_UNIFI_RELOAD"
|
||||
_savedeployconf DEPLOY_UNIFI_OS_RELOAD "$DEPLOY_UNIFI_OS_RELOAD"
|
||||
_savedeployconf DEPLOY_UNIFI_SYSTEM_PROPERTIES "$DEPLOY_UNIFI_SYSTEM_PROPERTIES"
|
||||
|
||||
return 0
|
||||
}
|
||||
307
deploy/unifios.sh
Normal file
307
deploy/unifios.sh
Normal file
@@ -0,0 +1,307 @@
|
||||
#!/usr/bin/env sh
|
||||
# Deploy hook for UniFi OS Server (self-hosted).
|
||||
#
|
||||
# Supports:
|
||||
# - UniFi OS Server on macOS
|
||||
# - UniFi OS Server on Linux
|
||||
# - UniFi OS Server on Windows should also work (runs under WSL2), but
|
||||
# has not been tested.
|
||||
#
|
||||
# Tested on: Ubuntu 26.04 (remote) and macOS 26.6 (local).
|
||||
#
|
||||
# This is a different product from the Cloud Key / UDM hardware and
|
||||
# self-hosted Unifi Controller covered by the `unifi` deploy hook above
|
||||
# (that hook already covers Cloud Key running UnifiOS v2.0.0+/Gen2/2+) --
|
||||
# this hook targets the separately-installed, self-hosted "UniFi OS Server"
|
||||
# application instead, which stores certificates in its own Postgres
|
||||
# database via a REST API rather than a Java keystore, so the `unifi`
|
||||
# hook's approach does not apply here.
|
||||
#
|
||||
# UniFi OS Server exposes a REST API on its management port (default
|
||||
# 11443) that its own web UI uses for certificate management:
|
||||
# POST /api/auth/login - session login (cookie + JWT)
|
||||
# GET /api/userCertificates - list uploaded certificates
|
||||
# POST /api/userCertificates - upload a new certificate
|
||||
# DELETE /api/userCertificates/{id} - remove a certificate
|
||||
# PUT /api/userCertificates/{id}/status - activate/deactivate a certificate
|
||||
#
|
||||
# This was reverse-engineered from the browser's Network tab while using the
|
||||
# real GUI upload/activate/delete flow -- it is undocumented but is the same
|
||||
# code path the UI uses, so it's far more robust than editing settings.yaml,
|
||||
# http/local-certs.conf, or the underlying Postgres user_certificates table
|
||||
# directly (all of which are also touched by this API, but only as a result
|
||||
# of the app's own internal logic, which handles cert parsing, active-cert
|
||||
# bookkeeping, and nginx config regeneration correctly on its own).
|
||||
#
|
||||
# Auth: POST /api/auth/login returns a `TOKEN` cookie containing a JWT whose
|
||||
# payload has a `csrfToken` claim. That value must be echoed back as the
|
||||
# `x-csrf-token` header on every subsequent state-changing request (a classic
|
||||
# double-submit CSRF pattern). No other cookies were found to be necessary.
|
||||
#
|
||||
# Uses core acme.sh helpers throughout (_post/_get, _json_encode,
|
||||
# _durl_replace_base64, _dbase64, _egrep_o) rather than raw curl -k or
|
||||
# python3, so the wget fallback, --debug tracing, and CA_BUNDLE are all
|
||||
# honored the same as every other hook. The management API's cert is
|
||||
# self-signed (it's a management-only port, not meant for public exposure),
|
||||
# so this hook sets HTTPS_INSECURE=1 itself, scoped to its own subshell (see
|
||||
# acme.sh's per-hook sourcing in _deploy) -- it does not weaken TLS
|
||||
# verification for the rest of the acme.sh run, e.g. the connection to the
|
||||
# ACME CA.
|
||||
#
|
||||
# Design: This hook does not save a certificate ID between renewals. Each
|
||||
# upload gets a name unique to that run: the domain name plus a timestamp.
|
||||
# This name never collides with an entry from a previous deploy. This is
|
||||
# true even if that entry is still active. The hook uploads and activates
|
||||
# the new certificate before it removes any old entries. If a failure
|
||||
# occurs during this process, the server still has a valid, active
|
||||
# certificate. The hook removes old entries only after activation is
|
||||
# complete. It removes only entries whose name starts with the domain name,
|
||||
# because this is the hook's own naming convention. As a result, this step
|
||||
# can only affect entries that this hook created for this domain. It can
|
||||
# never affect a certificate that a user uploaded manually, and it can
|
||||
# never affect a self-signed certificate.
|
||||
#
|
||||
# Settings:
|
||||
# DEPLOY_UNIFIOS_HOST - base URL of the management API
|
||||
# (default: "https://localhost:11443")
|
||||
# DEPLOY_UNIFIOS_USERNAME - UniFi OS Server admin username (required)
|
||||
# DEPLOY_UNIFIOS_PASSWORD - UniFi OS Server admin password (required)
|
||||
#
|
||||
# Example:
|
||||
# export DEPLOY_UNIFIOS_USERNAME="acmeuser"
|
||||
# export DEPLOY_UNIFIOS_PASSWORD="xxxxx"
|
||||
# acme.sh --deploy -d example.com --deploy-hook unifios
|
||||
#
|
||||
# Please report bugs to https://github.com/acmesh-official/acme.sh/issues/7182
|
||||
|
||||
_uos_response_code() {
|
||||
# tr strips the trailing newline along with form feeds; re-terminate
|
||||
# before the second _egrep_o, whose sed fallback (used wherever egrep -o
|
||||
# is unavailable) drops an unterminated final line on some platforms.
|
||||
_uos_code="$(_egrep_o <"$HTTP_HEADER" "^HTTP[^ ]* .*$" | cut -d " " -f 2-100 | tr -d "\f\n")"
|
||||
printf '%s\n' "$_uos_code" | _egrep_o "^[0-9][0-9]*"
|
||||
}
|
||||
|
||||
_uos_response_cookie() {
|
||||
# $1 = cookie name
|
||||
grep <"$HTTP_HEADER" -i "^Set-Cookie: *$1=" | _tail_n 1 | _egrep_o "$1=[^;]*" | _head_n 1
|
||||
}
|
||||
|
||||
unifios_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
# Scoped to this hook's own subshell -- does not affect the rest of the
|
||||
# acme.sh run (e.g. the connection to the ACME CA).
|
||||
export HTTPS_INSECURE=1
|
||||
|
||||
_getdeployconf DEPLOY_UNIFIOS_HOST
|
||||
DEPLOY_UNIFIOS_HOST="${DEPLOY_UNIFIOS_HOST:-https://localhost:11443}"
|
||||
_savedeployconf DEPLOY_UNIFIOS_HOST "$DEPLOY_UNIFIOS_HOST"
|
||||
_debug DEPLOY_UNIFIOS_HOST "$DEPLOY_UNIFIOS_HOST"
|
||||
|
||||
_getdeployconf DEPLOY_UNIFIOS_USERNAME
|
||||
_getdeployconf DEPLOY_UNIFIOS_PASSWORD
|
||||
|
||||
if [ -z "$DEPLOY_UNIFIOS_USERNAME" ] || [ -z "$DEPLOY_UNIFIOS_PASSWORD" ]; then
|
||||
_err "DEPLOY_UNIFIOS_USERNAME and DEPLOY_UNIFIOS_PASSWORD must be set."
|
||||
return 1
|
||||
fi
|
||||
_debug DEPLOY_UNIFIOS_USERNAME "$DEPLOY_UNIFIOS_USERNAME"
|
||||
_secure_debug DEPLOY_UNIFIOS_PASSWORD "$DEPLOY_UNIFIOS_PASSWORD"
|
||||
|
||||
_info "Logging in to UniFi OS Server API at $DEPLOY_UNIFIOS_HOST..."
|
||||
|
||||
# _json_encode always appends a trailing "\n" escape, even to input with
|
||||
# no trailing newline (it normalizes via `echo`, unconditionally adding
|
||||
# one). That's harmless for the key/cert file content below, which
|
||||
# legitimately ends in a real newline anyway, but wrong for these plain
|
||||
# strings -- strip the spurious escape it leaves behind.
|
||||
_uos_user_json="$(printf '%s' "$DEPLOY_UNIFIOS_USERNAME" | _json_encode)"
|
||||
_uos_user_json="${_uos_user_json%\\n}"
|
||||
_uos_pass_json="$(printf '%s' "$DEPLOY_UNIFIOS_PASSWORD" | _json_encode)"
|
||||
_uos_pass_json="${_uos_pass_json%\\n}"
|
||||
_login_body="{\"username\":\"$_uos_user_json\",\"password\":\"$_uos_pass_json\",\"token\":\"\",\"rememberMe\":false}"
|
||||
|
||||
_login_json="$(_post "$_login_body" "$DEPLOY_UNIFIOS_HOST/api/auth/login" "" "POST" "application/json")"
|
||||
_login_code="$(_uos_response_code)"
|
||||
|
||||
if [ "$_login_code" != "200" ]; then
|
||||
_err "Login failed (HTTP $_login_code)."
|
||||
_err "Response: $_login_json"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Credentials are proven correct now -- save them, rather than only at the
|
||||
# very end, so a later step failing doesn't discard a working login.
|
||||
# base64-encoded: _save_conf wraps values in single quotes with no
|
||||
# escaping, so a literal "'" in the password would otherwise corrupt the
|
||||
# domain conf (see deploy/synology_dsm.sh for the same pattern).
|
||||
_savedeployconf DEPLOY_UNIFIOS_USERNAME "$DEPLOY_UNIFIOS_USERNAME" "base64"
|
||||
_savedeployconf DEPLOY_UNIFIOS_PASSWORD "$DEPLOY_UNIFIOS_PASSWORD" "base64"
|
||||
|
||||
_uos_token="$(_uos_response_cookie TOKEN)"
|
||||
if [ -z "$_uos_token" ]; then
|
||||
_err "Login succeeded but no TOKEN cookie was returned."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_H1="Cookie: $_uos_token"
|
||||
export _H1
|
||||
|
||||
_uos_jwt_payload="$(echo "$_uos_token" | cut -d '=' -f 2- | cut -d '.' -f 2)"
|
||||
_uos_csrf="$(_durl_replace_base64 "$_uos_jwt_payload" | _dbase64 | _egrep_o '"csrfToken":"[^"]*"' | cut -d '"' -f 4)"
|
||||
if [ -z "$_uos_csrf" ]; then
|
||||
_err "Could not extract csrfToken from session token."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_H2="x-csrf-token: $_uos_csrf"
|
||||
export _H2
|
||||
|
||||
_info "Uploading new certificate..."
|
||||
# "name" is a purely cosmetic label -- the server never validates it
|
||||
# against the certificate's actual CN/SAN, and accepts arbitrary text
|
||||
# including spaces (confirmed: a cert for example.com served correctly
|
||||
# after being uploaded under the unrelated name "totally unrelated label").
|
||||
# The only constraint that matters here is uniqueness: the server rejects
|
||||
# a second entry with a name it already has, so a bare domain name would
|
||||
# collide with the previous deploy's entry on every renewal after the
|
||||
# first. A full human-readable timestamp would make that obvious in the
|
||||
# UI, but the certificate list's name column is fixed-width and doesn't
|
||||
# wrap (confirmed against the real UI: a long name overlaps the Expires
|
||||
# column and makes both unreadable), so keep the suffix short instead --
|
||||
# Unix epoch seconds are still unique enough for this purpose.
|
||||
_uos_name="$_cdomain $(_time)"
|
||||
_uos_key_json="$(_json_encode <"$_ckey")"
|
||||
_uos_cert_json="$(_json_encode <"$_cfullchain")"
|
||||
_create_body="{\"name\":\"$_uos_name\",\"key\":\"$_uos_key_json\",\"cert\":\"$_uos_cert_json\"}"
|
||||
|
||||
_create_json="$(_post "$_create_body" "$DEPLOY_UNIFIOS_HOST/api/userCertificates" "" "POST" "application/json")"
|
||||
_create_code="$(_uos_response_code)"
|
||||
|
||||
if [ "$_create_code" = "201" ]; then
|
||||
_new_id="$(echo "$_create_json" | _egrep_o '"id":"[^"]*"' | _head_n 1 | cut -d '"' -f 4)"
|
||||
if [ -z "$_new_id" ]; then
|
||||
_err "Could not determine new certificate ID from upload response."
|
||||
return 1
|
||||
fi
|
||||
elif [ "$_create_code" = "400" ] && echo "$_create_json" | grep -q "USER_CERTIFICATE_DUPLICATE"; then
|
||||
# HTTP 400 alone just means "bad request" -- it's the USER_CERTIFICATE_DUPLICATE
|
||||
# code in the response body, checked above, that actually confirms this.
|
||||
# The name above is unique to this run, so a duplicate here can only be
|
||||
# the server's other uniqueness constraint: this exact certificate (by
|
||||
# fingerprint) already exists as some other entry -- most likely a retry
|
||||
# after a prior run already uploaded it (a real renewal always produces a
|
||||
# new fingerprint, so this shouldn't happen in normal cron use). The
|
||||
# response body doesn't include the existing entry's id, so look it up
|
||||
# by fingerprint instead.
|
||||
# The API's own fingerprint field is SHA-1 (20 bytes), not SHA-256 --
|
||||
# confirmed against a real response, e.g.
|
||||
# "fingerprint":"FC:02:50:9C:3B:3F:B7:79:9D:CA:4D:7C:AC:92:E7:D5:EA:F1:3A:29"
|
||||
# (20 colon-separated groups). _fingerprint (core helper) strips the
|
||||
# colons that field has, so re-insert them rather than stripping the
|
||||
# JSON's own colons, which would also remove the ones separating every
|
||||
# key from its value.
|
||||
_uos_fingerprint="$(_fingerprint "$_cfullchain" sha1)"
|
||||
if [ -z "$_uos_fingerprint" ]; then
|
||||
_err "Could not compute the certificate's fingerprint."
|
||||
return 1
|
||||
fi
|
||||
_uos_fingerprint="$(echo "$_uos_fingerprint" | sed 's/\(..\)/\1:/g; s/:$//')"
|
||||
|
||||
_list_json="$(_get "$DEPLOY_UNIFIOS_HOST/api/userCertificates")"
|
||||
_list_code="$(_uos_response_code)"
|
||||
if [ "$_list_code" != "200" ]; then
|
||||
_err "Failed to list existing certificates (HTTP $_list_code)."
|
||||
_err "Response: $_list_json"
|
||||
return 1
|
||||
fi
|
||||
# _normalizeJson collapses the response to one predictable line (no stray
|
||||
# whitespace around colons, no embedded CR/LF the server might emit) but
|
||||
# also strips the trailing newline entirely -- re-terminate before the
|
||||
# split below, since some sed implementations drop an unterminated final
|
||||
# line rather than processing it.
|
||||
_list_json="$(echo "$_list_json" | _normalizeJson)"
|
||||
# A literal embedded newline (not the two-character "\n", which GNU sed
|
||||
# treats as a newline in the replacement but POSIX doesn't define and BSD
|
||||
# sed emits literally) splits it one JSON object per line so grep can
|
||||
# match a single certificate entry at a time.
|
||||
_list_json="$(
|
||||
printf '%s\n' "$_list_json" | sed 's/},{/},\
|
||||
{/g'
|
||||
)"
|
||||
_new_id="$(echo "$_list_json" | grep -F "\"fingerprint\":\"$_uos_fingerprint\"" | _egrep_o '"id":"[^"]*"' | _head_n 1 | cut -d '"' -f 4)"
|
||||
if [ -z "$_new_id" ]; then
|
||||
_err "Certificate upload rejected as a duplicate (server reported USER_CERTIFICATE_DUPLICATE), but no existing entry matching this fingerprint was found."
|
||||
_err "Response: $_create_json"
|
||||
return 1
|
||||
fi
|
||||
# Reusing the existing entry rather than deleting it and re-uploading
|
||||
# under today's name+timestamp: the served content is identical either
|
||||
# way, so replacing it would only cost an extra delete+create round trip
|
||||
# for no functional benefit. The tradeoff is cosmetic -- this entry keeps
|
||||
# whatever name it was given whenever it was originally uploaded, so it
|
||||
# won't reflect today's date in the UI.
|
||||
_info "Certificate already present as entry $_new_id; reusing it."
|
||||
else
|
||||
_err "Certificate upload failed (HTTP $_create_code)."
|
||||
_err "Response: $_create_json"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Activating certificate $_new_id..."
|
||||
_activate_json="$(_post '{"active":true}' "$DEPLOY_UNIFIOS_HOST/api/userCertificates/$_new_id/status" "" "PUT" "application/json")"
|
||||
_activate_code="$(_uos_response_code)"
|
||||
|
||||
if [ "$_activate_code" != "200" ]; then
|
||||
_err "Failed to activate new certificate (HTTP $_activate_code)."
|
||||
_err "Response: $_activate_json"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# UniFi OS Server activation is exclusive server-wide. Tests against the
|
||||
# real API confirm this: activation of one entry deactivates whichever
|
||||
# other entry was active before, no matter its name or domain. As a
|
||||
# result, the server serves the certificate that this hook just activated.
|
||||
# This certificate is already live. If the removal of old entries below
|
||||
# fails, the hook logs the failure. The deploy does not fail because of
|
||||
# this.
|
||||
_info "Checking for old certificate entries to remove..."
|
||||
_list_json="$(_get "$DEPLOY_UNIFIOS_HOST/api/userCertificates")"
|
||||
_list_code="$(_uos_response_code)"
|
||||
if [ "$_list_code" != "200" ]; then
|
||||
_err "Failed to list certificates for cleanup (HTTP $_list_code) -- leaving old entries in place."
|
||||
else
|
||||
_list_json="$(echo "$_list_json" | _normalizeJson)"
|
||||
_list_json="$(
|
||||
printf '%s\n' "$_list_json" | sed 's/},{/},\
|
||||
{/g'
|
||||
)"
|
||||
# The pattern below matches the domain name followed by a space. If the
|
||||
# space is missing, the pattern can also match a different domain that
|
||||
# starts with the same text as this domain.
|
||||
_old_ids="$(echo "$_list_json" | grep -F "\"name\":\"$_cdomain " | _egrep_o '"id":"[^"]*"' | cut -d '"' -f 4 | grep -v "^$_new_id$")"
|
||||
for _old_id in $_old_ids; do
|
||||
_info "Removing old certificate entry $_old_id..."
|
||||
_del_json="$(_post "" "$DEPLOY_UNIFIOS_HOST/api/userCertificates/$_old_id" "" "DELETE")"
|
||||
_del_code="$(_uos_response_code)"
|
||||
if [ "$_del_code" != "204" ] && [ "$_del_code" != "200" ]; then
|
||||
_err "Failed to delete old certificate $_old_id (HTTP $_del_code) -- leaving it in place."
|
||||
_err "Response: $_del_json"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
_info "UniFi OS Server certificate deployed and activated successfully."
|
||||
return 0
|
||||
}
|
||||
201
deploy/vault.sh
Normal file
201
deploy/vault.sh
Normal file
@@ -0,0 +1,201 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Here is a script to deploy cert to hashicorp vault using curl
|
||||
# (https://www.vaultproject.io/)
|
||||
#
|
||||
# it requires following environment variables:
|
||||
#
|
||||
# VAULT_PREFIX - this contains the prefix path in vault
|
||||
# VAULT_ADDR - vault requires this to find your vault server
|
||||
# VAULT_SAVE_TOKEN - set to anything if you want to save the token
|
||||
# VAULT_RENEW_TOKEN - set to anything if you want to renew the token to default TTL before deploying
|
||||
# VAULT_KV_V2 - set to anything if you are using v2 of the kv engine
|
||||
#
|
||||
# additionally, you need to ensure that VAULT_TOKEN is avialable
|
||||
# to access the vault server
|
||||
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
vault_deploy() {
|
||||
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
# validate required env vars
|
||||
_getdeployconf VAULT_PREFIX
|
||||
if [ -z "$VAULT_PREFIX" ]; then
|
||||
_err "VAULT_PREFIX needs to be defined (contains prefix path in vault)"
|
||||
return 1
|
||||
fi
|
||||
_savedeployconf VAULT_PREFIX "$VAULT_PREFIX"
|
||||
|
||||
_getdeployconf VAULT_ADDR
|
||||
if [ -z "$VAULT_ADDR" ]; then
|
||||
_err "VAULT_ADDR needs to be defined (contains vault connection address)"
|
||||
return 1
|
||||
fi
|
||||
_savedeployconf VAULT_ADDR "$VAULT_ADDR"
|
||||
|
||||
_getdeployconf VAULT_SAVE_TOKEN
|
||||
_savedeployconf VAULT_SAVE_TOKEN "$VAULT_SAVE_TOKEN"
|
||||
|
||||
_getdeployconf VAULT_RENEW_TOKEN
|
||||
_savedeployconf VAULT_RENEW_TOKEN "$VAULT_RENEW_TOKEN"
|
||||
|
||||
_getdeployconf VAULT_KV_V2
|
||||
_savedeployconf VAULT_KV_V2 "$VAULT_KV_V2"
|
||||
|
||||
_getdeployconf VAULT_TOKEN
|
||||
if [ -z "$VAULT_TOKEN" ]; then
|
||||
_err "VAULT_TOKEN needs to be defined"
|
||||
return 1
|
||||
fi
|
||||
if [ -n "$VAULT_SAVE_TOKEN" ]; then
|
||||
_savedeployconf VAULT_TOKEN "$VAULT_TOKEN"
|
||||
fi
|
||||
|
||||
_migratedeployconf FABIO VAULT_FABIO_MODE
|
||||
|
||||
# JSON does not allow multiline strings.
|
||||
# So replacing new-lines with "\n" here
|
||||
_ckey=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\n/\\n/g' <"$2")
|
||||
_ccert=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\n/\\n/g' <"$3")
|
||||
_cca=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\n/\\n/g' <"$4")
|
||||
_cfullchain=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\n/\\n/g' <"$5")
|
||||
|
||||
export _H1="X-Vault-Token: $VAULT_TOKEN"
|
||||
|
||||
if [ -n "$VAULT_RENEW_TOKEN" ]; then
|
||||
URL="$VAULT_ADDR/v1/auth/token/renew-self"
|
||||
_info "Renew the Vault token to default TTL"
|
||||
_response=$(_post "" "$URL")
|
||||
if [ "$?" != "0" ]; then
|
||||
_err "Failed to renew the Vault token"
|
||||
return 1
|
||||
fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Failed to renew the Vault token: $_response"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
URL="$VAULT_ADDR/v1/$VAULT_PREFIX/$_cdomain"
|
||||
|
||||
if [ -n "$VAULT_FABIO_MODE" ]; then
|
||||
_info "Writing certificate and key to $URL in Fabio mode"
|
||||
if [ -n "$VAULT_KV_V2" ]; then
|
||||
_response=$(_post "{ \"data\": {\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"} }" "$URL")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error: $_response"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
_response=$(_post "{\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"}" "$URL")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error: $_response"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if [ -n "$VAULT_KV_V2" ]; then
|
||||
_info "Writing certificate to $URL/cert.pem"
|
||||
_response=$(_post "{\"data\": {\"value\": \"$_ccert\"}}" "$URL/cert.pem")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error writing cert.pem: $_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Writing key to $URL/cert.key"
|
||||
_response=$(_post "{\"data\": {\"value\": \"$_ckey\"}}" "$URL/cert.key")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error writing cert.key: $_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Writing CA certificate to $URL/ca.pem"
|
||||
_response=$(_post "{\"data\": {\"value\": \"$_cca\"}}" "$URL/ca.pem")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error writing ca.pem: $_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Writing full-chain certificate to $URL/fullchain.pem"
|
||||
_response=$(_post "{\"data\": {\"value\": \"$_cfullchain\"}}" "$URL/fullchain.pem")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error writing fullchain.pem: $_response"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
_info "Writing certificate to $URL/cert.pem"
|
||||
_response=$(_post "{\"value\": \"$_ccert\"}" "$URL/cert.pem")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error writing cert.pem: $_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Writing key to $URL/cert.key"
|
||||
_response=$(_post "{\"value\": \"$_ckey\"}" "$URL/cert.key")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error writing cert.key: $_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Writing CA certificate to $URL/ca.pem"
|
||||
_response=$(_post "{\"value\": \"$_cca\"}" "$URL/ca.pem")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error writing ca.pem: $_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Writing full-chain certificate to $URL/fullchain.pem"
|
||||
_response=$(_post "{\"value\": \"$_cfullchain\"}" "$URL/fullchain.pem")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error writing fullchain.pem: $_response"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# To make it compatible with the wrong ca path `chain.pem` which was used in former versions
|
||||
if _contains "$(_get "$URL/chain.pem")" "-----BEGIN CERTIFICATE-----"; then
|
||||
_err "The CA certificate has moved from chain.pem to ca.pem, if you don't depend on chain.pem anymore, you can delete it to avoid this warning"
|
||||
_info "Updating CA certificate to $URL/chain.pem for backward compatibility"
|
||||
if [ -n "$VAULT_KV_V2" ]; then
|
||||
_response=$(_post "{\"data\": {\"value\": \"$_cca\"}}" "$URL/chain.pem")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error writing chain.pem: $_response"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
_response=$(_post "{\"value\": \"$_cca\"}" "$URL/chain.pem")
|
||||
if [ "$?" != "0" ]; then return 1; fi
|
||||
if echo "$_response" | grep -q '"errors":\['; then
|
||||
_err "Vault error writing chain.pem: $_response"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
104
deploy/vault_cli.sh
Normal file
104
deploy/vault_cli.sh
Normal file
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Here is a script to deploy cert to hashicorp vault
|
||||
# (https://www.vaultproject.io/)
|
||||
#
|
||||
# it requires the vault binary to be available in PATH, and the following
|
||||
# environment variables:
|
||||
#
|
||||
# VAULT_PREFIX - this contains the prefix path in vault
|
||||
# VAULT_ADDR - vault requires this to find your vault server
|
||||
# VAULT_SAVE_TOKEN - set to anything if you want to save the token
|
||||
# VAULT_RENEW_TOKEN - set to anything if you want to renew the token to default TTL before deploying
|
||||
#
|
||||
# additionally, you need to ensure that VAULT_TOKEN is avialable or
|
||||
# `vault auth` has applied the appropriate authorization for the vault binary
|
||||
# to access the vault server
|
||||
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
vault_cli_deploy() {
|
||||
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
# validate required env vars
|
||||
_getdeployconf VAULT_PREFIX
|
||||
if [ -z "$VAULT_PREFIX" ]; then
|
||||
_err "VAULT_PREFIX needs to be defined (contains prefix path in vault)"
|
||||
return 1
|
||||
fi
|
||||
_savedeployconf VAULT_PREFIX "$VAULT_PREFIX"
|
||||
|
||||
_getdeployconf VAULT_ADDR
|
||||
if [ -z "$VAULT_ADDR" ]; then
|
||||
_err "VAULT_ADDR needs to be defined (contains vault connection address)"
|
||||
return 1
|
||||
fi
|
||||
_savedeployconf VAULT_ADDR "$VAULT_ADDR"
|
||||
|
||||
_getdeployconf VAULT_SAVE_TOKEN
|
||||
_savedeployconf VAULT_SAVE_TOKEN "$VAULT_SAVE_TOKEN"
|
||||
|
||||
_getdeployconf VAULT_RENEW_TOKEN
|
||||
_savedeployconf VAULT_RENEW_TOKEN "$VAULT_RENEW_TOKEN"
|
||||
|
||||
_getdeployconf VAULT_TOKEN
|
||||
if [ -z "$VAULT_TOKEN" ]; then
|
||||
_err "VAULT_TOKEN needs to be defined"
|
||||
return 1
|
||||
fi
|
||||
if [ -n "$VAULT_SAVE_TOKEN" ]; then
|
||||
_savedeployconf VAULT_TOKEN "$VAULT_TOKEN"
|
||||
fi
|
||||
|
||||
_migratedeployconf FABIO VAULT_FABIO_MODE
|
||||
|
||||
VAULT_CMD=$(command -v vault)
|
||||
if [ ! $? ]; then
|
||||
_err "cannot find vault binary!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -n "$VAULT_RENEW_TOKEN" ]; then
|
||||
_info "Renew the Vault token to default TTL"
|
||||
if ! $VAULT_CMD token renew; then
|
||||
_err "Failed to renew the Vault token"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$VAULT_FABIO_MODE" ]; then
|
||||
_info "Writing certificate and key to ${VAULT_PREFIX}/${_cdomain} in Fabio mode"
|
||||
$VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}" cert=@"$_cfullchain" key=@"$_ckey" || return 1
|
||||
else
|
||||
_info "Writing certificate to ${VAULT_PREFIX}/${_cdomain}/cert.pem"
|
||||
$VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/cert.pem" value=@"$_ccert" || return 1
|
||||
_info "Writing key to ${VAULT_PREFIX}/${_cdomain}/cert.key"
|
||||
$VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/cert.key" value=@"$_ckey" || return 1
|
||||
_info "Writing CA certificate to ${VAULT_PREFIX}/${_cdomain}/ca.pem"
|
||||
$VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/ca.pem" value=@"$_cca" || return 1
|
||||
_info "Writing full-chain certificate to ${VAULT_PREFIX}/${_cdomain}/fullchain.pem"
|
||||
$VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/fullchain.pem" value=@"$_cfullchain" || return 1
|
||||
|
||||
# To make it compatible with the wrong ca path `chain.pem` which was used in former versions
|
||||
if $VAULT_CMD kv get "${VAULT_PREFIX}/${_cdomain}/chain.pem" >/dev/null; then
|
||||
_err "The CA certificate has moved from chain.pem to ca.pem, if you don't depend on chain.pem anymore, you can delete it to avoid this warning"
|
||||
_info "Updating CA certificate to ${VAULT_PREFIX}/${_cdomain}/chain.pem for backward compatibility"
|
||||
$VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/chain.pem" value=@"$_cca" || return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
}
|
||||
110
deploy/vsftpd.sh
Normal file
110
deploy/vsftpd.sh
Normal file
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#Here is a script to deploy cert to vsftpd server.
|
||||
|
||||
#returns 0 means success, otherwise error.
|
||||
|
||||
#DEPLOY_VSFTPD_CONF="/etc/vsftpd.conf"
|
||||
#DEPLOY_VSFTPD_RELOAD="service vsftpd restart"
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
vsftpd_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
_ssl_path="/etc/acme.sh/vsftpd"
|
||||
if ! mkdir -p "$_ssl_path"; then
|
||||
_err "Can not create folder:$_ssl_path"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Copying key and cert"
|
||||
_real_key="$_ssl_path/vsftpd.key"
|
||||
if ! cat "$_ckey" >"$_real_key"; then
|
||||
_err "Error: write key file to: $_real_key"
|
||||
return 1
|
||||
fi
|
||||
_real_fullchain="$_ssl_path/vsftpd.chain.pem"
|
||||
if ! cat "$_cfullchain" >"$_real_fullchain"; then
|
||||
_err "Error: write key file to: $_real_fullchain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
DEFAULT_VSFTPD_RELOAD="service vsftpd restart"
|
||||
_reload="${DEPLOY_VSFTPD_RELOAD:-$DEFAULT_VSFTPD_RELOAD}"
|
||||
|
||||
if [ -z "$IS_RENEW" ]; then
|
||||
DEFAULT_VSFTPD_CONF="/etc/vsftpd.conf"
|
||||
_vsftpd_conf="${DEPLOY_VSFTPD_CONF:-$DEFAULT_VSFTPD_CONF}"
|
||||
if [ ! -f "$_vsftpd_conf" ]; then
|
||||
if [ -z "$DEPLOY_VSFTPD_CONF" ]; then
|
||||
_err "vsftpd conf is not found, please define DEPLOY_VSFTPD_CONF"
|
||||
return 1
|
||||
else
|
||||
_err "It seems that the specified vsftpd conf is not valid, please check."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
if [ ! -w "$_vsftpd_conf" ]; then
|
||||
_err "The file $_vsftpd_conf is not writable, please change the permission."
|
||||
return 1
|
||||
fi
|
||||
_backup_conf="$DOMAIN_BACKUP_PATH/vsftpd.conf.bak"
|
||||
_info "Backup $_vsftpd_conf to $_backup_conf"
|
||||
cp "$_vsftpd_conf" "$_backup_conf"
|
||||
|
||||
_info "Modify vsftpd conf: $_vsftpd_conf"
|
||||
if _setopt "$_vsftpd_conf" "rsa_cert_file" "=" "$_real_fullchain" &&
|
||||
_setopt "$_vsftpd_conf" "rsa_private_key_file" "=" "$_real_key" &&
|
||||
_setopt "$_vsftpd_conf" "ssl_enable" "=" "YES"; then
|
||||
_info "Set config success!"
|
||||
else
|
||||
_err "Config vsftpd server error, please report bug to us."
|
||||
_info "Restoring vsftpd conf"
|
||||
if cat "$_backup_conf" >"$_vsftpd_conf"; then
|
||||
_info "Restore conf success"
|
||||
eval "$_reload"
|
||||
else
|
||||
_err "Oops, error restore vsftpd conf, please report bug to us."
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
_info "Run reload: $_reload"
|
||||
if eval "$_reload"; then
|
||||
_info "Reload success!"
|
||||
if [ "$DEPLOY_VSFTPD_CONF" ]; then
|
||||
_savedomainconf DEPLOY_VSFTPD_CONF "$DEPLOY_VSFTPD_CONF"
|
||||
else
|
||||
_cleardomainconf DEPLOY_VSFTPD_CONF
|
||||
fi
|
||||
if [ "$DEPLOY_VSFTPD_RELOAD" ]; then
|
||||
_savedomainconf DEPLOY_VSFTPD_RELOAD "$DEPLOY_VSFTPD_RELOAD"
|
||||
else
|
||||
_cleardomainconf DEPLOY_VSFTPD_RELOAD
|
||||
fi
|
||||
return 0
|
||||
else
|
||||
_err "Reload error, restoring"
|
||||
if cat "$_backup_conf" >"$_vsftpd_conf"; then
|
||||
_info "Restore conf success"
|
||||
eval "$_reload"
|
||||
else
|
||||
_err "Oops, error restore vsftpd conf, please report bug to us."
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
|
||||
}
|
||||
158
deploy/windows_rdp.sh
Normal file
158
deploy/windows_rdp.sh
Normal file
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# install a certificate on a Windows host over OpenSSH and bind it to the Remote
|
||||
# Desktop listener (RDP-Tcp).
|
||||
#
|
||||
# One ssh invocation does the whole job:
|
||||
# * the PFX is built locally, base64'd, and embedded as a string literal
|
||||
# inside a generated PowerShell script;
|
||||
# * the script is piped to `powershell.exe -Command -` over ssh. No scp,
|
||||
# no temp files on the Windows host.
|
||||
#
|
||||
# First run:
|
||||
# export DEPLOY_WIN_RDP_HOST=winserver.example.com
|
||||
# acme.sh --deploy -d winserver.example.com --deploy-hook windows_rdp
|
||||
#
|
||||
# Available variables:
|
||||
# DEPLOY_WIN_RDP_HOST required SSH host
|
||||
# DEPLOY_WIN_RDP_USER optional SSH user, must be a local administrator (can also by set via ssh_config)
|
||||
# DEPLOY_WIN_RDP_PORT optional SSH port, default 22
|
||||
# DEPLOY_WIN_RDP_SSH_OPTS optional extra ssh options, e.g.
|
||||
# "-i /root/.ssh/win_id_ed25519 -o StrictHostKeyChecking=yes"
|
||||
# DEPLOY_WIN_RDP_LISTENER optional RDP listener name, default RDP-Tcp
|
||||
# DEPLOY_WIN_RDP_RESTART optional "1" to restart TermService after install.
|
||||
# Active RDP sessions will drop!
|
||||
|
||||
windows_rdp_deploy() {
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
if ! _exists "ssh"; then
|
||||
_err "ssh is required but was not found in PATH."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# ---- configuration ------------------------------------------------------
|
||||
_getdeployconf DEPLOY_WIN_RDP_HOST
|
||||
_getdeployconf DEPLOY_WIN_RDP_USER
|
||||
_getdeployconf DEPLOY_WIN_RDP_PORT
|
||||
_getdeployconf DEPLOY_WIN_RDP_SSH_OPTS
|
||||
_getdeployconf DEPLOY_WIN_RDP_LISTENER
|
||||
_getdeployconf DEPLOY_WIN_RDP_RESTART
|
||||
|
||||
if [ -z "$DEPLOY_WIN_RDP_HOST" ]; then
|
||||
_err "DEPLOY_WIN_RDP_HOST must be set."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_savedeployconf DEPLOY_WIN_RDP_HOST "$DEPLOY_WIN_RDP_HOST"
|
||||
[ -n "$DEPLOY_WIN_RDP_USER" ] && _savedeployconf DEPLOY_WIN_RDP_USER "$DEPLOY_WIN_RDP_USER"
|
||||
[ -n "$DEPLOY_WIN_RDP_PORT" ] && _savedeployconf DEPLOY_WIN_RDP_PORT "$DEPLOY_WIN_RDP_PORT"
|
||||
[ -n "$DEPLOY_WIN_RDP_SSH_OPTS" ] && _savedeployconf DEPLOY_WIN_RDP_SSH_OPTS "$DEPLOY_WIN_RDP_SSH_OPTS"
|
||||
[ -n "$DEPLOY_WIN_RDP_LISTENER" ] && _savedeployconf DEPLOY_WIN_RDP_LISTENER "$DEPLOY_WIN_RDP_LISTENER"
|
||||
[ -n "$DEPLOY_WIN_RDP_RESTART" ] && _savedeployconf DEPLOY_WIN_RDP_RESTART "$DEPLOY_WIN_RDP_RESTART"
|
||||
|
||||
_port="${DEPLOY_WIN_RDP_PORT:-22}"
|
||||
_listener="${DEPLOY_WIN_RDP_LISTENER:-RDP-Tcp}"
|
||||
if [ -n "$DEPLOY_WIN_RDP_USER" ]; then
|
||||
_target="$DEPLOY_WIN_RDP_USER@$DEPLOY_WIN_RDP_HOST"
|
||||
else
|
||||
_target="$DEPLOY_WIN_RDP_HOST"
|
||||
fi
|
||||
_pfx_pass="acme"
|
||||
|
||||
# ---- build thumbprint + PFX locally ------------------------------------
|
||||
_thumb="$(_fingerprint "$_ccert" 'sha1')"
|
||||
if [ -z "$_thumb" ]; then
|
||||
_err "Failed to compute certificate thumbprint."
|
||||
return 1
|
||||
fi
|
||||
_debug "Thumbprint: $_thumb"
|
||||
|
||||
_debug "Building PFX at $_pfx_file"
|
||||
_pfx_file="$(_mktemp)"
|
||||
if ! _toPkcs "$_pfx_file" "$_ckey" "$_ccert" "$_cca" "$_pfx_pass"; then
|
||||
_err "Failed to build PFX archive."
|
||||
rm -f "$_pfx_file"
|
||||
return 1
|
||||
fi
|
||||
_pfx_b64=$(_base64 "multiline" <"$_pfx_file")
|
||||
rm -f "$_pfx_file"
|
||||
|
||||
# ---- build installer script --------------------------------------------
|
||||
if [ "$DEPLOY_WIN_RDP_RESTART" = "1" ]; then
|
||||
_restart_ps='Restart-Service -Name TermService -Force'
|
||||
else
|
||||
_restart_ps='# New RdP connections will pick up the new cert automatically.'
|
||||
fi
|
||||
|
||||
# Escape every literal `$` with `\$` so the shell does not expand it.
|
||||
# Values substituted from shell: $_pfx_b64, $_pfx_pass, $_thumb, $_listener.
|
||||
_ps1=$(
|
||||
cat <<PSEOF
|
||||
|
||||
\$ErrorActionPreference = 'Stop'
|
||||
|
||||
\$pfxBytes = [Convert]::FromBase64String('${_pfx_b64}')
|
||||
|
||||
# Note: It is quite important to use a X509Certificate2Collection here in any case, since we otherwise
|
||||
# could run into quite a lot of trouble when importing the certificate including its entire chain
|
||||
# and its private key. Windows might behave arbitrarily and not consistently import the certificate
|
||||
# at all - unless "Exportable" is included in the storage flags. However, then the certificate seems
|
||||
# unaccessible to TermService for some weird reasons despite all permissions being set (at least on my
|
||||
# Win 11 lab machine). This might be some security setting that prevents TermService from working with
|
||||
# exportable keys? I don't know - importing the entire collection including chain or not always fixes
|
||||
# the issues.
|
||||
#
|
||||
# Note2: If you should have kicked yourself out for some reason, then deleting the certificate will make
|
||||
# TermService restore the original, self-signed certificate after at least after the second login attempt.
|
||||
# Deleting the certificate can be easily accomplished via the Powershell, since SSH access will still be
|
||||
# present in any case - the following command should get you out of trouble:
|
||||
# \$cert = Get-ChildItem -Path 'Cert:\LocalMachine\My\\${_thumb}' | Select-Object -First 1 | Remove-Item
|
||||
|
||||
\$flags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]'MachineKeySet,PersistKeySet'
|
||||
\$certs = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection
|
||||
\$certs.Import(\$pfxBytes, '${_pfx_pass}', \$flags)
|
||||
|
||||
\$store = [System.Security.Cryptography.X509Certificates.X509Store]::new('My', 'LocalMachine')
|
||||
\$store.Open('ReadWrite')
|
||||
\$store.AddRange(\$certs)
|
||||
\$store.Close()
|
||||
Write-Host "Installed certs into LocalMachine\\My"
|
||||
|
||||
\$ts = Get-CimInstance -Namespace root/cimv2/terminalservices -ClassName Win32_TSGeneralSetting -Filter "TerminalName='${_listener}'"
|
||||
if (-not \$ts) { throw "Listener '${_listener}' not found." }
|
||||
Set-CimInstance -InputObject \$ts -Property @{SSLCertificateSHA1Hash="${_thumb}"}
|
||||
Write-Host "Listener ${_listener} now uses ${_thumb}"
|
||||
|
||||
${_restart_ps}
|
||||
PSEOF
|
||||
)
|
||||
_debug "Powershell script:${_ps1}"
|
||||
|
||||
# ---- run over a single ssh connection ----------------------------------
|
||||
_ssh_opts="-o BatchMode=yes -p $_port"
|
||||
if [ -n "$DEPLOY_WIN_RDP_SSH_OPTS" ]; then
|
||||
_ssh_opts="$_ssh_opts $DEPLOY_WIN_RDP_SSH_OPTS"
|
||||
fi
|
||||
|
||||
_info "Deploying to $DEPLOY_WIN_RDP_HOST ..."
|
||||
# shellcheck disable=SC2086
|
||||
if ! printf '%s\n' "$_ps1" | ssh $_ssh_opts "$_target" \
|
||||
'powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command -'; then
|
||||
_err "Remote install failed. Re-run acme.sh with --debug to see the PowerShell output."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Certificate for $_cdomain deployed and bound to $_listener on $DEPLOY_WIN_RDP_HOST."
|
||||
return 0
|
||||
}
|
||||
500
deploy/zyxel_gs1900.sh
Normal file
500
deploy/zyxel_gs1900.sh
Normal file
@@ -0,0 +1,500 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Deploy certificates to Zyxel GS1900 series switches
|
||||
#
|
||||
# This script uses the https web administration interface in order
|
||||
# to upload updated certificates to Zyxel GS1900 series switches.
|
||||
# Only a few models have been tested but untested switches from the
|
||||
# same model line may work as well. If you test and confirm a switch
|
||||
# as working please submit a pull request updating this compatibility
|
||||
# list!
|
||||
#
|
||||
# Known Issues:
|
||||
# 1. This is a consumer grade switch and is a bit underpowered
|
||||
# the longer the RSA key size the slower your switch web UI
|
||||
# will be. RSA 2048 will work, RSA 4096 will work but you may
|
||||
# experience performance problems.
|
||||
# 2. You must use RSA certificates. The switch will reject EC-256
|
||||
# and EC-384 certificates in firmware 2.80
|
||||
# See: https://community.zyxel.com/en/discussion/21506/bug-cannot-import-ssl-cert-on-gs1900-8-and-gs1900-24e-firmware-v2-80/
|
||||
#
|
||||
# Current GS1900 Switch Compatibility:
|
||||
# GS1900-8 - Working as of firmware V2.80
|
||||
# GS1900-8HP - Untested
|
||||
# GS1900-10HP - Untested
|
||||
# GS1900-16 - Untested
|
||||
# GS1900-24 - Untested
|
||||
# GS1900-24E - Working as of firmware V2.80
|
||||
# GS1900-24EP - Untested
|
||||
# GS1900-24HP - Untested
|
||||
# GS1900-48 - Untested
|
||||
# GS1900-48HP - Untested
|
||||
#
|
||||
# Prerequisite Setup Steps:
|
||||
# 1. Install at least firmware V2.80 on your switch
|
||||
# 2. Enable HTTPS web management on your switch
|
||||
#
|
||||
# Usage:
|
||||
# 1. Ensure the switch has firmware V2.80 or later.
|
||||
# 2. Ensure the switch has HTTPS management enabled.
|
||||
# 3. Set the appropriate environment variables for your environment.
|
||||
#
|
||||
# DEPLOY_ZYXEL_SWITCH - The switch hostname. (Default: _cdomain)
|
||||
# DEPLOY_ZYXEL_SWITCH_USER - The webadmin user. (Default: admin)
|
||||
# DEPLOY_ZYXEL_SWITCH_PASSWORD - The webadmin password for the switch.
|
||||
# DEPLOY_ZYXEL_SWITCH_REBOOT - If "1" reboot after update. (Default: "0")
|
||||
#
|
||||
# 4. Run the deployment plugin:
|
||||
# acme.sh --deploy --deploy-hook zyxel_gs1900 -d example.com
|
||||
#
|
||||
# returns 0 means success, otherwise error.
|
||||
|
||||
#domain keyfile certfile cafile fullchain
|
||||
zyxel_gs1900_deploy() {
|
||||
_zyxel_gs1900_minimum_firmware_version="v2.80"
|
||||
|
||||
_cdomain="$1"
|
||||
_ckey="$2"
|
||||
_ccert="$3"
|
||||
_cca="$4"
|
||||
_cfullchain="$5"
|
||||
|
||||
_debug _cdomain "$_cdomain"
|
||||
_debug2 _ckey "$_ckey"
|
||||
_debug _ccert "$_ccert"
|
||||
_debug _cca "$_cca"
|
||||
_debug _cfullchain "$_cfullchain"
|
||||
|
||||
_getdeployconf DEPLOY_ZYXEL_SWITCH
|
||||
_getdeployconf DEPLOY_ZYXEL_SWITCH_USER
|
||||
_getdeployconf DEPLOY_ZYXEL_SWITCH_PASSWORD
|
||||
_getdeployconf DEPLOY_ZYXEL_SWITCH_REBOOT
|
||||
|
||||
if [ -z "$DEPLOY_ZYXEL_SWITCH" ]; then
|
||||
DEPLOY_ZYXEL_SWITCH="$_cdomain"
|
||||
fi
|
||||
|
||||
if [ -z "$DEPLOY_ZYXEL_SWITCH_USER" ]; then
|
||||
DEPLOY_ZYXEL_SWITCH_USER="admin"
|
||||
fi
|
||||
|
||||
if [ -z "$DEPLOY_ZYXEL_SWITCH_PASSWORD" ]; then
|
||||
DEPLOY_ZYXEL_SWITCH_PASSWORD="1234"
|
||||
fi
|
||||
|
||||
if [ -z "$DEPLOY_ZYXEL_SWITCH_REBOOT" ]; then
|
||||
DEPLOY_ZYXEL_SWITCH_REBOOT="0"
|
||||
fi
|
||||
|
||||
_savedeployconf DEPLOY_ZYXEL_SWITCH "$DEPLOY_ZYXEL_SWITCH"
|
||||
_savedeployconf DEPLOY_ZYXEL_SWITCH_USER "$DEPLOY_ZYXEL_SWITCH_USER"
|
||||
_savedeployconf DEPLOY_ZYXEL_SWITCH_PASSWORD "$DEPLOY_ZYXEL_SWITCH_PASSWORD"
|
||||
_savedeployconf DEPLOY_ZYXEL_SWITCH_REBOOT "$DEPLOY_ZYXEL_SWITCH_REBOOT"
|
||||
|
||||
_debug DEPLOY_ZYXEL_SWITCH "$DEPLOY_ZYXEL_SWITCH"
|
||||
_debug DEPLOY_ZYXEL_SWITCH_USER "$DEPLOY_ZYXEL_SWITCH_USER"
|
||||
_secure_debug DEPLOY_ZYXEL_SWITCH_PASSWORD "$DEPLOY_ZYXEL_SWITCH_PASSWORD"
|
||||
_debug DEPLOY_ZYXEL_SWITCH_REBOOT "$DEPLOY_ZYXEL_SWITCH_REBOOT"
|
||||
|
||||
_zyxel_switch_base_uri="https://${DEPLOY_ZYXEL_SWITCH}"
|
||||
|
||||
_info "Beginning to deploy to a Zyxel GS1900 series switch at ${_zyxel_switch_base_uri}."
|
||||
_zyxel_gs1900_deployment_precheck || return $?
|
||||
|
||||
_zyxel_gs1900_should_update
|
||||
if [ "$?" != "0" ]; then
|
||||
_info "The switch already has our certificate installed. No update required."
|
||||
return 0
|
||||
else
|
||||
_info "The switch does not yet have our certificate installed."
|
||||
fi
|
||||
|
||||
_info "Logging into the switch web interface."
|
||||
_zyxel_gs1900_login || return $?
|
||||
|
||||
_info "Validating the switch is compatible with this deployment process."
|
||||
_zyxel_gs1900_validate_device_compatibility || return $?
|
||||
|
||||
_info "Uploading the certificate."
|
||||
_zyxel_gs1900_upload_certificate || return $?
|
||||
|
||||
if [ "$DEPLOY_ZYXEL_SWITCH_REBOOT" = "1" ]; then
|
||||
_info "Rebooting the switch."
|
||||
_zyxel_gs1900_trigger_reboot || return $?
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
_zyxel_gs1900_deployment_precheck() {
|
||||
# Initialize the keylength if it isn't already
|
||||
if [ -z "$Le_Keylength" ]; then
|
||||
Le_Keylength=""
|
||||
fi
|
||||
|
||||
if _isEccKey "$Le_Keylength"; then
|
||||
_info "Warning: Zyxel GS1900 switches are not currently known to work with ECC keys!"
|
||||
_info "You can continue, but your switch may reject your key."
|
||||
elif [ -n "$Le_Keylength" ] && [ "$Le_Keylength" -gt "2048" ]; then
|
||||
_info "Warning: Your RSA key length is greater than 2048!"
|
||||
_info "You can continue, but you may experience performance issues in the web administration interface."
|
||||
fi
|
||||
|
||||
# Check the server for some common failure modes prior to authentication and certificate upload in order to avoid
|
||||
# sending a certificate when we may not want to.
|
||||
test_login_response=$(_post "username=test&password=test&login=true;" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=0.html" '' "POST" "application/x-www-form-urlencoded" 2>&1)
|
||||
test_login_page_exitcode="$?"
|
||||
_debug3 "Test Login Response: ${test_login_response}"
|
||||
if [ "$test_login_page_exitcode" -ne "0" ]; then
|
||||
if { [ "${ACME_USE_WGET:-0}" = "0" ] && [ "$test_login_page_exitcode" = "60" ]; } || { [ "${ACME_USE_WGET:-0}" = "1" ] && [ "$test_login_page_exitcode" = "5" ]; }; then
|
||||
_err "The SSL certificate at $_zyxel_switch_base_uri could not be validated."
|
||||
_err "Please double check your hostname, port, and that you are actually connecting to your switch."
|
||||
_err "If the problem persists then please ensure that the certificate is not self-signed, has not"
|
||||
_err "expired, and matches the switch hostname. If you expect validation to fail then you can disable"
|
||||
_err "certificate validation by running with --insecure."
|
||||
return 1
|
||||
elif [ "${ACME_USE_WGET:-0}" = "0" ] && [ "$test_login_page_exitcode" = "56" ]; then
|
||||
_debug3 "Intentionally ignore curl exit code 56 in our precheck"
|
||||
else
|
||||
_err "Failed to submit the initial login attempt to $_zyxel_switch_base_uri."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
_zyxel_gs1900_login() {
|
||||
# Login to the switch and set the appropriate auth cookie in _H1
|
||||
username_encoded=$(printf "%s" "$DEPLOY_ZYXEL_SWITCH_USER" | _url_encode)
|
||||
password_encoded=$(_zyxel_gs1900_password_obfuscate "$DEPLOY_ZYXEL_SWITCH_PASSWORD" | _url_encode)
|
||||
|
||||
login_response=$(_post "username=${username_encoded}&password=${password_encoded}&login=true;" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=0.html" '' "POST" "application/x-www-form-urlencoded" | tr -d '\n')
|
||||
auth_response=$(_post "authId=${login_response}&login_chk=true" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=0.html" '' "POST" "application/x-www-form-urlencoded" | tr -d '\n')
|
||||
if [ "$auth_response" != "OK" ]; then
|
||||
_err "Login failed due to invalid credentials."
|
||||
_err "Please double check the configured username and password and try again."
|
||||
return 1
|
||||
fi
|
||||
|
||||
sessionid=$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'HTTPS_XSSID=[^;]*;' | tr -d ';')
|
||||
_secure_debug2 "sessionid" "$sessionid"
|
||||
|
||||
export _H1="Cookie: $sessionid"
|
||||
_secure_debug2 "_H1" "$_H1"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
_zyxel_gs1900_validate_device_compatibility() {
|
||||
# Check the switches model and firmware version and throw errors
|
||||
# if this script isn't compatible.
|
||||
device_info_html=$(_get "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=12" | tr -d '\n')
|
||||
|
||||
model_name=$(_zyxel_gs1900_get_model "$device_info_html")
|
||||
_debug2 "model_name" "$model_name"
|
||||
if [ -z "$model_name" ]; then
|
||||
_err "Could not find the switch model name."
|
||||
_err "Please re-run with --debug and report a bug."
|
||||
return $?
|
||||
fi
|
||||
|
||||
if ! expr "$model_name" : "GS1900-" >/dev/null; then
|
||||
_err "Switch is an unsupported model: $model_name"
|
||||
return 1
|
||||
fi
|
||||
|
||||
firmware_version=$(_zyxel_gs1900_get_firmware_version "$device_info_html")
|
||||
_debug2 "firmware_version" "$firmware_version"
|
||||
if [ -z "$firmware_version" ]; then
|
||||
_err "Could not find the switch firmware version."
|
||||
_err "Please re-run with --debug and report a bug."
|
||||
return $?
|
||||
fi
|
||||
|
||||
_debug2 "_zyxel_gs1900_minimum_firmware_version" "$_zyxel_gs1900_minimum_firmware_version"
|
||||
minimum_major_version=$(_zyxel_gs1900_parse_major_version "$_zyxel_gs1900_minimum_firmware_version")
|
||||
_debug2 "minimum_major_version" "$minimum_major_version"
|
||||
minimum_minor_version=$(_zyxel_gs1900_parse_minor_version "$_zyxel_gs1900_minimum_firmware_version")
|
||||
_debug2 "minimum_minor_version" "$minimum_minor_version"
|
||||
|
||||
_debug2 "firmware_version" "$firmware_version"
|
||||
firmware_major_version=$(_zyxel_gs1900_parse_major_version "$firmware_version")
|
||||
_debug2 "firmware_major_version" "$firmware_major_version"
|
||||
firmware_minor_version=$(_zyxel_gs1900_parse_minor_version "$firmware_version")
|
||||
_debug2 "firmware_minor_version" "$firmware_minor_version"
|
||||
|
||||
_ret=0
|
||||
if [ "$firmware_major_version" -lt "$minimum_major_version" ]; then
|
||||
_ret=1
|
||||
elif [ "$firmware_major_version" -eq "$minimum_major_version" ] && [ "$firmware_minor_version" -lt "$minimum_minor_version" ]; then
|
||||
_ret=1
|
||||
fi
|
||||
|
||||
if [ "$_ret" != "0" ]; then
|
||||
_err "Unsupported firmware version $firmware_version. Please upgrade to at least version $_zyxel_gs1900_minimum_firmware_version."
|
||||
fi
|
||||
|
||||
return $?
|
||||
}
|
||||
|
||||
_zyxel_gs1900_should_update() {
|
||||
# Get the remote certificate serial number
|
||||
_remote_cert=$(${ACME_OPENSSL_BIN:-openssl} s_client -showcerts -connect "${DEPLOY_ZYXEL_SWITCH}:443" 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p')
|
||||
_debug3 "_remote_cert" "$_remote_cert"
|
||||
|
||||
_remote_cert_serial=$(printf "%s" "${_remote_cert}" | ${ACME_OPENSSL_BIN:-openssl} x509 -noout -serial)
|
||||
_debug2 "_remote_cert_serial" "$_remote_cert_serial"
|
||||
|
||||
# Get our certificate serial number
|
||||
_our_cert_serial=$(${ACME_OPENSSL_BIN:-openssl} x509 -noout -serial <"${_ccert}")
|
||||
_debug2 "_our_cert_serial" "$_our_cert_serial"
|
||||
|
||||
[ "${_remote_cert_serial}" != "${_our_cert_serial}" ]
|
||||
}
|
||||
|
||||
_zyxel_gs1900_upload_certificate() {
|
||||
# Generate a PKCS12 certificate with a temporary password since the web interface
|
||||
# requires a password be present. Then upload that certificate.
|
||||
temp_cert_password=$(head /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 64)
|
||||
_secure_debug2 "temp_cert_password" "$temp_cert_password"
|
||||
|
||||
temp_pkcs12="$(_mktemp)"
|
||||
_debug2 "temp_pkcs12" "$temp_pkcs12"
|
||||
_toPkcs "$temp_pkcs12" "$_ckey" "$_ccert" "$_cca" "$temp_cert_password"
|
||||
if [ "$?" != "0" ]; then
|
||||
_err "Failed to generate a pkcs12 certificate."
|
||||
_err "Please re-run with --debug and report a bug."
|
||||
|
||||
# ensure the temporary certificate file is cleaned up
|
||||
[ -f "${temp_pkcs12}" ] && rm -f "${temp_pkcs12}"
|
||||
|
||||
return $?
|
||||
fi
|
||||
|
||||
# Load the upload page
|
||||
upload_page_html=$(_get "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=5914" | tr -d '\n')
|
||||
|
||||
# Get the first instance of XSSID from the upload page
|
||||
form_xss_value=$(printf "%s" "$upload_page_html" | _egrep_o 'name="XSSID"\s*value="[^"]+"' | sed 's/^.*="\([^"]\{1,\}\)"$/\1/g' | head -n 1)
|
||||
_secure_debug2 "form_xss_value" "$form_xss_value"
|
||||
|
||||
_info "Generating the certificate upload request"
|
||||
upload_post_request="$(_mktemp)"
|
||||
upload_post_boundary="---------------------------$(date +%Y%m%d%H%M%S)"
|
||||
|
||||
{
|
||||
printf -- "--%s\r\n" "${upload_post_boundary}"
|
||||
printf "Content-Disposition: form-data; name=\"XSSID\"\r\n\r\n%s\r\n" "${form_xss_value}"
|
||||
printf -- "--%s\r\n" "${upload_post_boundary}"
|
||||
printf "Content-Disposition: form-data; name=\"http_file\"; filename=\"temp_pkcs12.pfx\"\r\n"
|
||||
printf "Content-Type: application/pkcs12\r\n\r\n"
|
||||
cat "${temp_pkcs12}"
|
||||
printf "\r\n"
|
||||
printf -- "--%s\r\n" "${upload_post_boundary}"
|
||||
printf "Content-Disposition: form-data; name=\"pwd\"\r\n\r\n%s\r\n" "${temp_cert_password}"
|
||||
printf -- "--%s\r\n" "${upload_post_boundary}"
|
||||
printf "Content-Disposition: form-data; name=\"cmd\"\r\n\r\n%s\r\n" "31"
|
||||
printf -- "--%s\r\n" "${upload_post_boundary}"
|
||||
printf "Content-Disposition: form-data; name=\"sysSubmit\"\r\n\r\n%s\r\n" "Import"
|
||||
printf -- "--%s--\r\n" "${upload_post_boundary}"
|
||||
} >"${upload_post_request}"
|
||||
|
||||
_info "Upload certificate to the switch"
|
||||
|
||||
# Unfortunately we cannot rely upon the switch response across switch models
|
||||
# to return a consistent body return - so we cannot inspect the result of this
|
||||
# upload to determine success.
|
||||
upload_response=$(_zyxel_upload_pkcs12 "${upload_post_request}" "${upload_post_boundary}" 2>&1)
|
||||
_debug3 "Upload response: ${upload_response}"
|
||||
rm "${upload_post_request}"
|
||||
|
||||
# Pause for a few seconds to give the switch a chance to process the certificate
|
||||
# For some reason I've found this to be necessary on my GS1900-24E
|
||||
_debug2 "Waiting 4 seconds for the switch to process the newly uploaded certificate."
|
||||
sleep "4"
|
||||
|
||||
# Check to see whether or not our update was successful
|
||||
_ret=0
|
||||
_zyxel_gs1900_should_update
|
||||
if [ "$?" != "0" ]; then
|
||||
_info "The certificate was updated successfully"
|
||||
else
|
||||
_ret=1
|
||||
_err "The certificate upload does not appear to have worked."
|
||||
_err "The remote certificate does not match the certificate we tried to upload."
|
||||
_err "Please re-run with --debug 2 and review for unexpected errors. If none can be found please submit a bug."
|
||||
fi
|
||||
|
||||
# ensure the temporary files are cleaned up
|
||||
[ -f "${temp_pkcs12}" ] && rm -f "${temp_pkcs12}"
|
||||
|
||||
return $_ret
|
||||
}
|
||||
|
||||
# make the certificate upload request using either
|
||||
# --data binary with @ for file access in CURL
|
||||
# or using --post-file for wget to ensure we upload
|
||||
# the pkcs12 without getting tripped up on null bytes
|
||||
#
|
||||
# Usage _zyxel_upload_pkcs12 [body file name] [post boundary marker]
|
||||
_zyxel_upload_pkcs12() {
|
||||
bodyfilename="$1"
|
||||
multipartformmarker="$2"
|
||||
_post_url="${_zyxel_switch_base_uri}/cgi-bin/httpuploadcert.cgi"
|
||||
httpmethod="POST"
|
||||
_postContentType="multipart/form-data; boundary=${multipartformmarker}"
|
||||
|
||||
if [ -z "$httpmethod" ]; then
|
||||
httpmethod="POST"
|
||||
fi
|
||||
_debug $httpmethod
|
||||
_debug "_post_url" "$_post_url"
|
||||
_debug2 "bodyfilename" "$bodyfilename"
|
||||
_debug2 "_postContentType" "$_postContentType"
|
||||
|
||||
_inithttp
|
||||
|
||||
if [ "$_ACME_CURL" ] && [ "${ACME_USE_WGET:-0}" = "0" ]; then
|
||||
_CURL="$_ACME_CURL"
|
||||
if [ "$HTTPS_INSECURE" ]; then
|
||||
_CURL="$_CURL --insecure "
|
||||
fi
|
||||
if [ "$httpmethod" = "HEAD" ]; then
|
||||
_CURL="$_CURL -I "
|
||||
fi
|
||||
_debug "_CURL" "$_CURL"
|
||||
|
||||
response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data-binary "@${bodyfilename}" "$_post_url")"
|
||||
|
||||
_ret="$?"
|
||||
if [ "$_ret" != "0" ]; then
|
||||
_err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $_ret"
|
||||
if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
|
||||
_err "Here is the curl dump log:"
|
||||
_err "$(cat "$_CURL_DUMP")"
|
||||
fi
|
||||
fi
|
||||
elif [ "$_ACME_WGET" ]; then
|
||||
_WGET="$_ACME_WGET"
|
||||
if [ "$HTTPS_INSECURE" ]; then
|
||||
_WGET="$_WGET --no-check-certificate "
|
||||
fi
|
||||
_debug "_WGET" "$_WGET"
|
||||
|
||||
response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-file="${bodyfilename}" "$_post_url" 2>"$HTTP_HEADER")"
|
||||
|
||||
_ret="$?"
|
||||
if [ "$_ret" = "8" ]; then
|
||||
_ret=0
|
||||
_debug "wget returned 8 as the server returned a 'Bad Request' response. Let's process the response later."
|
||||
fi
|
||||
if [ "$_ret" != "0" ]; then
|
||||
_err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $_ret"
|
||||
fi
|
||||
if _contains "$_WGET" " -d "; then
|
||||
# Demultiplex wget debug output
|
||||
cat "$HTTP_HEADER" >&2
|
||||
_sed_i '/^[^ ][^ ]/d; /^ *$/d' "$HTTP_HEADER"
|
||||
fi
|
||||
# remove leading whitespaces from header to match curl format
|
||||
_sed_i 's/^ //g' "$HTTP_HEADER"
|
||||
else
|
||||
_ret="$?"
|
||||
_err "Neither curl nor wget have been found, cannot make $httpmethod request."
|
||||
fi
|
||||
_debug "_ret" "$_ret"
|
||||
printf "%s" "$response"
|
||||
return $_ret
|
||||
}
|
||||
|
||||
_zyxel_gs1900_trigger_reboot() {
|
||||
# Trigger a reboot via the management reboot page in the web ui
|
||||
reboot_page_html=$(_get "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=5888" | tr -d '\n')
|
||||
reboot_xss_value=$(printf "%s" "$reboot_page_html" | _egrep_o 'name="XSSID"\s*value="[^"]+"' | sed 's/^.*="\([^"]\{1,\}\)"$/\1/g')
|
||||
_secure_debug2 "reboot_xss_value" "$reboot_xss_value"
|
||||
|
||||
reboot_response_html=$(_post "XSSID=${reboot_xss_value}&cmd=5889&sysSubmit=Reboot" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi" '' "POST" "application/x-www-form-urlencoded")
|
||||
reboot_message=$(printf "%s" "$reboot_response_html" | tr -d '\t\r\n\v\f' | _egrep_o "Rebooting now...")
|
||||
|
||||
if [ -z "$reboot_message" ]; then
|
||||
_err "Failed to trigger switch reboot!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# password
|
||||
_zyxel_gs1900_password_obfuscate() {
|
||||
# Return the password obfuscated via the same method used by the
|
||||
# switch's web UI login process
|
||||
echo "$1" | awk '{
|
||||
encoded = "";
|
||||
password = $1;
|
||||
allowed = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
len = length($1);
|
||||
pwi = length($1);
|
||||
|
||||
for (i=1; i <= (321 - pwi); i++)
|
||||
{
|
||||
if (0 == i % 5 && pwi > 0)
|
||||
{
|
||||
encoded = (encoded)(substr(password, pwi--, 1));
|
||||
}
|
||||
else if (i == 123)
|
||||
{
|
||||
if (len < 10)
|
||||
{
|
||||
encoded = (encoded)(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
encoded = (encoded)(int(len / 10));
|
||||
}
|
||||
}
|
||||
else if (i == 289)
|
||||
{
|
||||
encoded = (encoded)(len % 10)
|
||||
}
|
||||
else
|
||||
{
|
||||
encoded = (encoded)(substr(allowed, int(rand() * length(allowed)), 1))
|
||||
}
|
||||
}
|
||||
printf("%s", encoded);
|
||||
}'
|
||||
}
|
||||
|
||||
# html label
|
||||
_zyxel_html_table_lookup() {
|
||||
# Look up a value in the html representing the status page of the switch
|
||||
# when provided with the html of the page and the label (i.e. "Model Name:")
|
||||
html="$1"
|
||||
label=$(printf "%s" "$2" | tr -d ' ')
|
||||
lookup_result=$(printf "%s" "$html" | tr -d "\t\r\n\v\f" | sed 's/<tr>/\n<tr>/g' | sed 's/<td[^>]*>/<td>/g' | tr -d ' ' | grep -i "$label" | sed "s/<tr><td>$label<\/td><td>\([^<]\{1,\}\)<\/td><\/tr>/\1/i")
|
||||
printf "%s" "$lookup_result"
|
||||
return 0
|
||||
}
|
||||
|
||||
# html
|
||||
_zyxel_gs1900_get_model() {
|
||||
html="$1"
|
||||
model_name=$(_zyxel_html_table_lookup "$html" "Model Name:")
|
||||
printf "%s" "$model_name"
|
||||
}
|
||||
|
||||
# html
|
||||
_zyxel_gs1900_get_firmware_version() {
|
||||
html="$1"
|
||||
firmware_version=$(_zyxel_html_table_lookup "$html" "Firmware Version:" | _egrep_o "V[^.]+.[^(]+")
|
||||
printf "%s" "$firmware_version"
|
||||
}
|
||||
|
||||
# version_number
|
||||
_zyxel_gs1900_parse_major_version() {
|
||||
printf "%s" "$1" | sed 's/^V\([0-9]\{1,\}\).\{1,\}$/\1/gi'
|
||||
}
|
||||
|
||||
# version_number
|
||||
_zyxel_gs1900_parse_minor_version() {
|
||||
printf "%s" "$1" | sed 's/^.\{1,\}\.\([0-9]\{1,\}\)$/\1/gi'
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user