Compare commits
2180 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cdea411136 | |||
| fdb000ee7c | |||
| 4a0be1c29d | |||
| 892d22e225 | |||
| 4958a48b92 | |||
| 28bbea85f0 | |||
| 73b3a29cfc | |||
| 77b8024453 | |||
| 1175e1164b | |||
| 5546af518e | |||
| 99fd3083f2 | |||
| c0df8be832 | |||
| 73cab6a6ee | |||
| 7a71e45799 | |||
| fdb1d1e6dd | |||
| 6dd4d6adeb | |||
| d368f9666a | |||
| ee50458333 | |||
| 9792c616b5 | |||
| 205a7d134e | |||
| 2cbacb4338 | |||
| cdb812ef63 | |||
| ea010f8c9b | |||
| 362bbc5f32 | |||
| 2e19dda72e | |||
| 021155278e | |||
| 3db87218ee | |||
| 91d5c90eb0 | |||
| f8b71a0e61 | |||
| 3e4b7f30ac | |||
| c637985575 | |||
| 2960e2459b | |||
| 81d6289a86 | |||
| dc1507a5ea | |||
| 3d960c3869 | |||
| 5d60e6191f | |||
| 4b49f8a5a6 | |||
| 94459fe922 | |||
| e834e31510 | |||
| 4b57a0d729 | |||
| acd440106b | |||
| 5096df5cc0 | |||
| 3c2d450aa8 | |||
| b083b3cc41 | |||
| 6624769032 | |||
| 55f75c6051 | |||
| 42d9c3ef14 | |||
| c7a9363422 | |||
| 969b6e3288 | |||
| 03ce030754 | |||
| f2c1e362a0 | |||
| 6426aa57a2 | |||
| 7ceb0f6306 | |||
| b26a1944c6 | |||
| 235aec3e42 | |||
| 346fb730a3 | |||
| c87bc22a5f | |||
| 7198e24945 | |||
| 1a08bd340e | |||
| af9047bf3c | |||
| 9566fc4e03 | |||
| 41254d10b7 | |||
| ed97f41884 | |||
| 02b83ce0ad | |||
| f1d2a1033a | |||
| ba1fe54ef8 | |||
| deac92faf8 | |||
| 1c36a79162 | |||
| b6f7042adc | |||
| f721cefb4a | |||
| fc2c947afe | |||
| d0272095cc | |||
| 4a09cf289d | |||
| 89c23fef35 | |||
| 5e59651d45 | |||
| 5e72f75395 | |||
| 0a77fe0169 | |||
| 961abb0f80 | |||
| 4efe12d2d3 | |||
| 67b05e2d75 | |||
| 8edb6f8727 | |||
| b30f02a1fb | |||
| 7e2333a63a | |||
| 0be66cccbc | |||
| ed26ed196d | |||
| a204f270dd | |||
| 7585d7bbd0 | |||
| 2981f086c8 | |||
| 784ef8a6a4 | |||
| 01c91ba294 | |||
| 05629acfa9 | |||
| 12a3afe15b | |||
| 3bb68e3111 | |||
| 5ba33be30f | |||
| 1e7b057946 | |||
| fb5b00d73f | |||
| 09ccfa2624 | |||
| 5c80c99b94 | |||
| adcf570b15 | |||
| 5ccd6e64bc | |||
| ff7e7858c0 | |||
| 37bcd8ce08 | |||
| 678b70cee8 | |||
| c0ca8da4ed | |||
| a07dcb1cf5 | |||
| cb4a86d1d5 | |||
| 0499347588 | |||
| f4bb459b5e | |||
| 83a5a21f95 | |||
| 85c633fddf | |||
| cb08e061d2 | |||
| 787b52bef7 | |||
| 9bf7ca400f | |||
| 9f75e30af3 | |||
| 3f0243ba9a | |||
| 1a9b367c9d | |||
| 73996f055b | |||
| 57ea2f317a | |||
| c66c05b442 | |||
| 6450d2bd56 | |||
| b5368d120d | |||
| f9a310b6c3 | |||
| 1bdcfe646f | |||
| a88d14a3bd | |||
| bdb3d09c09 | |||
| 667e7b185b | |||
| 8483ee0d41 | |||
| c3baaf3ac7 | |||
| c63745d1ba | |||
| 59b9ffadd0 | |||
| d131ea3790 | |||
| b849d34be5 | |||
| 58fc9a551c | |||
| 5801f34b3a | |||
| 17cf16ca92 | |||
| 7015b1b232 | |||
| 3b72ca09c6 | |||
| a815d0245b | |||
| 229f22d5a9 | |||
| 22f5cfcfd8 | |||
| 90ba55c043 | |||
| 9f878a353c | |||
| af7297d671 | |||
| 2f172b56e9 | |||
| 9076c8b20e | |||
| 639756dfcd | |||
| 7aa0c7e491 | |||
| 45dedf5bc7 | |||
| 4681ec9008 | |||
| b91826c6e6 | |||
| 686856d0ae | |||
| 9b09d2578d | |||
| f8f51adf88 | |||
| f8ce639717 | |||
| 1c6dc169ac | |||
| 3e5366c74e | |||
| b49ddbfef9 | |||
| b92fd73f53 | |||
| 41b8f51a6a | |||
| aad9045de5 | |||
| fdd5848df4 | |||
| 118c15d046 | |||
| bae4f8e320 | |||
| e0189a566e | |||
| 1cd8d73cdb | |||
| d6e9e5987b | |||
| 8c5aa37745 | |||
| a18a871ac3 | |||
| 90cbff9cf9 | |||
| bae5a04dcc | |||
| 7146570392 | |||
| ae88f85d8e | |||
| a362860137 | |||
| c966896522 | |||
| 5f88da1985 | |||
| 043b80a298 | |||
| ed0da28896 | |||
| 61a0d69d58 | |||
| 3833a9216e | |||
| e59566b5e2 | |||
| e4be0ce464 | |||
| 022dbf0cab | |||
| 1e6b559b89 | |||
| 74bae2005d | |||
| 1731a35d94 | |||
| 9f7d766cb3 | |||
| 6c0d0b00c9 | |||
| a82a38421d | |||
| c4b01da384 | |||
| 3e1473dba5 | |||
| d383706554 | |||
| e0eb0e21f6 | |||
| 7266af1749 | |||
| f93bc09438 | |||
| fe3bb7c1b4 | |||
| 923676c7d5 | |||
| 4755216505 | |||
| 37d03c10f9 | |||
| 490b724808 | |||
| d8f132919d | |||
| b8a64a6b5b | |||
| 25ad1e6f86 | |||
| 6b6f1604e9 | |||
| 63be1c1cbd | |||
| b75c625ddc | |||
| 7083e7aff7 | |||
| 9d2937dd4b | |||
| a7e281e278 | |||
| 72b6597817 | |||
| 91a1b97550 | |||
| 9951ab678f | |||
| 930aa355e8 | |||
| e0143fa540 | |||
| 7c1d92ff4b | |||
| 0a0f1e90e1 | |||
| 80092823db | |||
| 146098d9ce | |||
| 519743dbdb | |||
| 7ab661ecd7 | |||
| bb46cb08f7 | |||
| 028932c04a | |||
| 73e6480853 | |||
| aa176b081a | |||
| 267243e71b | |||
| 33fbef8380 | |||
| 45a128a050 | |||
| 2ddc668954 | |||
| 898bc9b9f2 | |||
| d8e5928523 | |||
| 36808a953e | |||
| 39d3f79026 | |||
| 6463e1ca22 | |||
| c985a13544 | |||
| ad76c5177c | |||
| 64b3184b28 | |||
| 2f1ad7201f | |||
| cd23ee2055 | |||
| e00830bebc | |||
| 00e6d580c2 | |||
| 9c7b419e8f | |||
| 95edc0d303 | |||
| 5991b1e37c | |||
| 1aa50cf53a | |||
| eab66e2d19 | |||
| 5b504f094f | |||
| 1460cb9ac1 | |||
| 53782cbf49 | |||
| 0ea22dddf0 | |||
| ec466dc818 | |||
| 181064901d | |||
| d1988dc982 | |||
| 1f1d687317 | |||
| edc7bfc230 | |||
| 7b6b3aa293 | |||
| 2f7514a2e7 | |||
| 575415b93a | |||
| c28dfa8aca | |||
| 91141922ee | |||
| cc5154e04e | |||
| 77db5ecd12 | |||
| 7ac789c9c7 | |||
| 24dff05f64 | |||
| 64a350364d | |||
| 11b7cfe5cb | |||
| 71cfcad2a1 | |||
| ab4373b26e | |||
| d23ddc96ac | |||
| 147708e779 | |||
| dc969dd7ed | |||
| ef7d1d9327 | |||
| 2e6e9ed925 | |||
| 296dcab4c7 | |||
| f9e1c46c45 | |||
| 94fd5bd7ec | |||
| eb6ca96e85 | |||
| a2bbc7e272 | |||
| f075a991f0 | |||
| edeb817c39 | |||
| 23b4658672 | |||
| 5f95ee987f | |||
| cc73f156a7 | |||
| ee72d10718 | |||
| 831871d37f | |||
| 6072550ec1 | |||
| 112a565bf7 | |||
| 59e5c76286 | |||
| 21620ac6bd | |||
| d05129ec67 | |||
| 0998de4ae6 | |||
| 2bdf1832da | |||
| a846c4b66e | |||
| ee535895a3 | |||
| 1e549dfd43 | |||
| 6ee718a252 | |||
| 557e98c33f | |||
| 7a9eec88e8 | |||
| a7a4f66633 | |||
| a88d0a6ae1 | |||
| db87bc770e | |||
| 7b6b71cd4b | |||
| df98463325 | |||
| f7492db8bd | |||
| 70b46d4a8f | |||
| 411486e1e7 | |||
| 6f81305232 | |||
| 79bc22d8ce | |||
| 1c634a702a | |||
| 909a9e4050 | |||
| b5cc794061 | |||
| 73b8e85976 | |||
| 282b5d6893 | |||
| c6628e7311 | |||
| 6b109d172f | |||
| 6b29972399 | |||
| 0fcd3c09fd | |||
| af503442b8 | |||
| c875971b71 | |||
| d1a65922d7 | |||
| 6ef34f95d5 | |||
| 8b79022179 | |||
| 21aec77e5c | |||
| 74c5259af8 | |||
| a3e7d4414d | |||
| 986d32eb81 | |||
| de0ae14544 | |||
| 6b52276fb6 | |||
| a19ea7489c | |||
| 14229c2f00 | |||
| 6eb20a1f2e | |||
| 8debac2edf | |||
| a68301e4dc | |||
| c6a988bc92 | |||
| fe02ce7b64 | |||
| df012dec90 | |||
| 5969425a6f | |||
| b17b1e6463 | |||
| c99e61c402 | |||
| f4aaec8b3c | |||
| adc3e6118b | |||
| d933493c31 | |||
| f91d591b03 | |||
| af6deb99cd | |||
| c5d285f755 | |||
| b1eb706925 | |||
| 5a01634ca3 | |||
| 487676ce13 | |||
| 0280ca7b1a | |||
| b0ccab41e1 | |||
| ccda3a3325 | |||
| 4b7eeaa6e0 | |||
| 2951f0030d | |||
| acc2df29de | |||
| 431afd618f | |||
| 6d5e5bd692 | |||
| ffd2e8149e | |||
| 2ab92dc13e | |||
| 7f6a8bc87e | |||
| b1ff163a28 | |||
| 440d55ccb8 | |||
| 285532d431 | |||
| f2c47459f8 | |||
| 49703f08e5 | |||
| 1d0aa9573b | |||
| b2014cf88b | |||
| a0e0078bad | |||
| 5ebca21c32 | |||
| 970aea90c9 | |||
| 5f9341ad8e | |||
| 574c0983f5 | |||
| be6c7c7ac8 | |||
| 4fd31f276b | |||
| 224db7da57 | |||
| 1413e1aff4 | |||
| 68b669d3ff | |||
| 29f44c67c8 | |||
| 3332d2288f | |||
| 34702196e1 | |||
| d45c8d1e9b | |||
| bc19825ada | |||
| 72bb640239 | |||
| aacee4a94c | |||
| 3ab37d5c5d | |||
| 3dd3ecf8f1 | |||
| a3831827d0 | |||
| 6aa6c957ee | |||
| 9e12412f5f | |||
| 0f9eb31740 | |||
| 6be8ab581d | |||
| d8425bc9c5 | |||
| 0ddcb9c00a | |||
| 6d43623f45 | |||
| 196cd88010 | |||
| 7f37df4227 | |||
| 985a12a63b | |||
| 9058c0e9fc | |||
| 4b0cd32d12 | |||
| 6cb51bc55d | |||
| 119e3c31c9 | |||
| 56164c25d0 | |||
| c66e5f9fcd | |||
| 12700e1754 | |||
| 50db6f0765 | |||
| 64e8adddfd | |||
| 729a4d64e9 | |||
| 6f12504588 | |||
| 271459f820 | |||
| 5000c95d01 | |||
| f477733483 | |||
| 54e1681c5e | |||
| 2f6d9a156a | |||
| 10dd89ae62 | |||
| d01bfbec96 | |||
| 5eb4aa3a0e | |||
| 0b9933df1e | |||
| 76d12d6062 | |||
| cf10faf61c | |||
| 1cbf9c1cd9 | |||
| 25e361b9f9 | |||
| b88ee33ae4 | |||
| 684964da4f | |||
| 8a3841f638 | |||
| f642e42eea | |||
| bbef854c02 | |||
| e50611666e | |||
| eae4f721e8 | |||
| 12fed34e10 | |||
| 56350b54ee | |||
| 10b7644bb7 | |||
| d79db3bd3f | |||
| 1588461633 | |||
| dd999b60a4 | |||
| 3abee72fee | |||
| b5577b1d37 | |||
| e15ffb5820 | |||
| 4d9a5ed4a1 | |||
| b2bc1debe0 | |||
| 590ff67fcb | |||
| 209e1adf53 | |||
| 53c08484a3 | |||
| c6ca832737 | |||
| 2c399a078e | |||
| 8c519f13da | |||
| 853fdc70a2 | |||
| dc4f811eaa | |||
| d23c8b4a2a | |||
| 00c0dcc81d | |||
| f77feefdb8 | |||
| 2e346e5369 | |||
| 17023f6b55 | |||
| 3bb29abe32 | |||
| ac42d38b7a | |||
| d9c0130b59 | |||
| 4925d5a5e7 | |||
| dd9a7cf5d7 | |||
| 5ee3874b7e | |||
| 17dd77cc96 | |||
| 6c546b5290 | |||
| a853fc2026 | |||
| 92c9ac3826 | |||
| 78c2ced43b | |||
| 72f850f675 | |||
| bc326489ab | |||
| ea5e7d9563 | |||
| 5b5b48fc06 | |||
| 1548ba0b8d | |||
| 26b1c4244f | |||
| 8a4e981931 | |||
| 6163c3f08e | |||
| e17f381b1f | |||
| 316537eb4d | |||
| b2c421600c | |||
| 787f6ef528 | |||
| 8578547467 | |||
| 51ab6d6da1 | |||
| 3a8b5de8f7 | |||
| faf08f6513 | |||
| 06c69d23be | |||
| 1bcadd5f8e | |||
| 524195d729 | |||
| 0c25d277ef | |||
| 27b0348e1d | |||
| ea5aa68769 | |||
| 99fefb168a | |||
| 49457505cd | |||
| bfc948a9b4 | |||
| c407206627 | |||
| 39d3bf97d1 | |||
| 79be392775 | |||
| be4c6b8e16 | |||
| c8e193e70d | |||
| 35859ffc3f | |||
| 0d81ada5a8 | |||
| b68cf0fb45 | |||
| 9ed2078e92 | |||
| 1f002159e2 | |||
| 5bc690fcd9 | |||
| bab9adce24 | |||
| e47eddaa85 | |||
| 8ef1f2e395 | |||
| 541131bbc6 | |||
| 7626eecbf6 | |||
| 49afa75929 | |||
| 5c5265ede2 | |||
| 42d61d8089 | |||
| 01eb50078e | |||
| eef021f472 | |||
| 6f3fd785e7 | |||
| 7cd8a645a8 | |||
| 9671348dc1 | |||
| 7a3e68d656 | |||
| 42c7ec2f75 | |||
| 32c3ce5c98 | |||
| e55a3a82fc | |||
| 305da86f97 | |||
| c23d1d11b5 | |||
| a3cabd5f36 | |||
| 66ac4716f2 | |||
| 3cd1aaeb03 | |||
| 4eb940ffe7 | |||
| 61800b23e2 | |||
| 0283662931 | |||
| d125eb56b3 | |||
| 956d68695c | |||
| 83d81b64b3 | |||
| a4ea82c04b | |||
| 8387fe0d5b | |||
| cfd5b388f1 | |||
| 4ee6e38a94 | |||
| 3f87752d1f | |||
| b91548eef4 | |||
| 1195417b97 | |||
| 67f347197e | |||
| 8c2dfa9140 | |||
| a3fbfe0bff | |||
| 99db1b1cc3 | |||
| 638a7f0ab4 | |||
| 806a69fef3 | |||
| 8ba2e9e34c | |||
| e7e54bc19e | |||
| 9fb980599f | |||
| 28dfef985c | |||
| 1e416b9f8a | |||
| 784bcb0aa5 | |||
| 9642df2d9d | |||
| 37340838b6 | |||
| d1a8dd7817 | |||
| 8919a3937a | |||
| 5032030f8d | |||
| b30cb5d7dc | |||
| 7113c4622b | |||
| bd8caff0b7 | |||
| 519bf3184a | |||
| 79c77ce3a3 | |||
| 2f40f795ee | |||
| b16f92314b | |||
| ad22244388 | |||
| 02f89a9c9d | |||
| d286c040a5 | |||
| 99f5b8ebc1 | |||
| 9ac33f9b9b | |||
| 6ab1fcaf89 | |||
| 2e3d0cc57c | |||
| 1e44115461 | |||
| 8d57063e9d | |||
| 104d646c7c | |||
| 9ddbf79d9e | |||
| a9ec4c5c28 | |||
| 914d860197 | |||
| 23b3e5c731 | |||
| cdf04c2402 | |||
| 3535e44337 | |||
| 0b245d3885 | |||
| 4fda6cbcde | |||
| 2bbba897ec | |||
| 0cfb94b0ba | |||
| 3f7ac93932 | |||
| 96c36b4f6a | |||
| febd6d32cf | |||
| cbd8699801 | |||
| 1ee1d61c74 | |||
| 74400aacc6 | |||
| 9f55c3605a | |||
| 8d61e8179f | |||
| f250889c3e | |||
| 00f67d86d6 | |||
| 7b8b9cfd2b | |||
| 5e7a67834b | |||
| 3c85602ab1 | |||
| 66d0d0e213 | |||
| 1f68faddb9 | |||
| db06f06c96 | |||
| 5b580d2a17 | |||
| 083dd7d1a3 | |||
| 03bd4755ce | |||
| 79e973e9c8 | |||
| 29d37075dd | |||
| f311bac580 | |||
| beb7a4c992 | |||
| 4d86fb319b | |||
| 5ea4f46de7 | |||
| 1d8d5251ae | |||
| 54c8217808 | |||
| ba623903e0 | |||
| 907af3ae18 | |||
| 24ae8a6b66 | |||
| 1646a5cdd2 | |||
| 814f17d10b | |||
| 40fe105903 | |||
| 42a347d8b1 | |||
| 5450e5dac4 | |||
| 1368259a1e | |||
| 81a495f267 | |||
| 693a4a6633 | |||
| 82786c580a | |||
| e19743f705 | |||
| 9166a57930 | |||
| 8d8304e859 | |||
| 6ddc23e2aa | |||
| 2fc491015e | |||
| bb0afe1fa7 | |||
| eb46f8c776 | |||
| bd511f97cb | |||
| 560bf40e4b | |||
| 4f4652c1cd | |||
| 60e13c2a1d | |||
| 1fe0dc4d16 | |||
| 181a1e3c0a | |||
| 6bba771856 | |||
| 921f1f42fb | |||
| eeb1f27fa4 | |||
| 9ce21ad152 | |||
| c036929cfe | |||
| 21591a3a89 | |||
| a2e9a41a7e | |||
| 0902349130 | |||
| f900db8e10 | |||
| 0fa9b344e0 | |||
| f48ef3d17b | |||
| 40801d0a06 | |||
| c6ccf1cf21 | |||
| d311992983 | |||
| b4babbe2c7 | |||
| 0719f4c99e | |||
| eb5de15033 | |||
| b229486d3b | |||
| 33b8d3e219 | |||
| 230256793f | |||
| 540ef96745 | |||
| 1baf30a671 | |||
| 5e93840e48 | |||
| 73a5908039 | |||
| 8429148273 | |||
| 17f40e2180 | |||
| 0345b12379 | |||
| 163b0de874 | |||
| 1661caed05 | |||
| 1089aeab9e | |||
| 1a0d3eeb1b | |||
| c27636529d | |||
| ac85488245 | |||
| 433e98b645 | |||
| 873654669e | |||
| 8b96f218d5 | |||
| 52cbff0e15 | |||
| 32de8d9ccb | |||
| e06da886f7 | |||
| cdd5ad3a8e | |||
| 9bee0e460b | |||
| 60c8ace443 | |||
| 933aaeaf25 | |||
| ca43c77525 | |||
| b204182c13 | |||
| dfb4165a12 | |||
| 26a54cd228 | |||
| e229f14121 | |||
| ee6cdfb391 | |||
| 58354e563c | |||
| 48f1bf0918 | |||
| 0ffe1f2cef | |||
| 8fe1adacf3 | |||
| 30245c5d8a | |||
| 67a5225bde | |||
| 58c3d7087b | |||
| c408687af7 | |||
| 33b284afc0 | |||
| 78004bdfb5 | |||
| 640950d4c8 | |||
| 998de0f9a0 | |||
| ce6e515309 | |||
| e054c8fc55 | |||
| 9fa1c2eb3e | |||
| 64a314c19e | |||
| 40be42406c | |||
| bca0eefc83 | |||
| 662ca19f8f | |||
| 65f9d482f3 | |||
| 7058d5cb93 | |||
| f7b13c69e9 | |||
| 9f21b1a097 | |||
| 53983002b6 | |||
| d3c0914ac1 | |||
| 7eb9694221 | |||
| b8f0d37420 | |||
| 1d5b1c239c | |||
| d1ebc08478 | |||
| 5074a91669 | |||
| 40e56c4040 | |||
| 3b0ed9310a | |||
| f92dc6a1ad | |||
| 0040b76a19 | |||
| 6c6fbabf14 | |||
| 8c2d868093 | |||
| 96c9e74c6f | |||
| d947437c10 | |||
| 83df29d832 | |||
| 607afe864a | |||
| a97cee84f3 | |||
| ad64384891 | |||
| f75c73d739 | |||
| 418bcddc95 | |||
| 61192b998a | |||
| 5ea2b09dc3 | |||
| 5bfc2c4a9b | |||
| 8ec47c3894 | |||
| f4423638a2 | |||
| 7b3444308b | |||
| 5ec9916817 | |||
| be1a70299f | |||
| 8685aa371a | |||
| 0224faa184 | |||
| 8546e326cf | |||
| 9956fd2f04 | |||
| 4f669ca82f | |||
| 1cd3881aa8 | |||
| e634513f7b | |||
| 7b6cde6ae3 | |||
| 18146fdf9e | |||
| 2c80c35b21 | |||
| 54b73769b8 | |||
| f7983ee4d9 | |||
| 9eace86aee | |||
| 2fbb58eb2b | |||
| 187d04e3a1 | |||
| d5d7d73440 | |||
| b747e281b7 | |||
| e024d50476 | |||
| a6ba48c075 | |||
| e19375387d | |||
| a9f68187d4 | |||
| 4d754fa78d | |||
| 6d07ab2bc5 | |||
| a60b00c440 | |||
| d0f3f303b6 | |||
| 4fc8acce8c | |||
| 0797a4f99d | |||
| db453c8038 | |||
| c776c34cfd | |||
| 170b39fde6 | |||
| fc27a66825 | |||
| 06b49c140e | |||
| 3ab45c91e1 | |||
| 6660161cec | |||
| 8c6e207008 | |||
| 4180e3c540 | |||
| a218cd0ffb | |||
| 31c8de5bbe | |||
| faac7f365f | |||
| 8cc4332b10 | |||
| abd30da102 | |||
| f4701ff72a | |||
| 393d7885a1 | |||
| b4c6d3c975 | |||
| 3ce440a28d | |||
| cb4ab6a99f | |||
| 6c39d7b1ee | |||
| 840bd52671 | |||
| f3d70c7ea1 | |||
| 22b8528ee1 | |||
| 900e302df7 | |||
| be03d8e137 | |||
| d338a9639a | |||
| 235972f3da | |||
| ae822881e7 | |||
| 26f75c71ba | |||
| ad2aa2eff5 | |||
| 52689049ae | |||
| 1a29541140 | |||
| ece17eecef | |||
| 2b353094eb | |||
| 70305aa501 | |||
| 22dc504ad0 | |||
| ef9402d403 | |||
| f7e29532f7 | |||
| 60770683b6 | |||
| 051bbbc64f | |||
| 206eef964c | |||
| b8b7adff17 | |||
| 42c68d362e | |||
| 1df3967889 | |||
| dae87e26a3 | |||
| f193341eae | |||
| d21a042ad8 | |||
| b16b9e813d | |||
| a79fe1f350 | |||
| 14f99875fb | |||
| 2d2890b34f | |||
| 66d8cafd76 | |||
| 468ccbf2b7 | |||
| 6b6668f73b | |||
| 5eda05f007 | |||
| 1d6a8bd851 | |||
| e500af1ed4 | |||
| 7ee39fd4ed | |||
| 9ba6c83821 | |||
| 4243622414 | |||
| 8374a4f5bf | |||
| 5d851141cb | |||
| 26ac081182 | |||
| 64e0d9a4d5 | |||
| 269a6cad80 | |||
| 806ebdb8a8 | |||
| ed0016fd2b | |||
| a3fb24993d | |||
| 9c26598831 | |||
| 7634f153b7 | |||
| cabc4da3ac | |||
| 6419539305 | |||
| 97e1178525 | |||
| 04faf12c14 | |||
| e7aa79cc9f | |||
| f862e3f37f | |||
| ce051af5bb | |||
| dcb9f1c840 | |||
| 83d0fe9f09 | |||
| 7789ba4d36 | |||
| 80101b04e2 | |||
| 310d4d1b57 | |||
| 126c0c6ad6 | |||
| 6a43b44087 | |||
| 6772b32609 | |||
| db2f0b8c5c | |||
| 1376004197 | |||
| 1625989c48 | |||
| f7863bd686 | |||
| 883565905a | |||
| 786780ce9b | |||
| d2e9fed62d | |||
| 0e5a4fb098 | |||
| f223f042de | |||
| cd413825ed | |||
| a851c272cd | |||
| 412077b418 | |||
| 4df6f8a50e | |||
| 44bf4b1cc1 | |||
| 136e8dd7c5 | |||
| 024b2b04a4 | |||
| 5bbf210394 | |||
| 039c62b09b | |||
| 07f0aa45ef | |||
| de11f44309 | |||
| acee96ef17 | |||
| 4ed49f9dfa | |||
| f68b585f8f | |||
| 13ddc979ec | |||
| b0b7ac3efb | |||
| 62f8525dd5 | |||
| 1347355cb1 | |||
| f847c4a414 | |||
| 776fa924e3 | |||
| 8872466968 | |||
| b620038d98 | |||
| a248367b15 | |||
| c159ec4a9a | |||
| 5359a7670f | |||
| 7e1c7a6de2 | |||
| 91e19bbdd3 | |||
| e61daaee2d | |||
| 8caab1fd92 | |||
| cd944882c3 | |||
| 888d9591fe | |||
| 833808c5de | |||
| d731956b06 | |||
| 40449ae4de | |||
| 44ad61f004 | |||
| 74865d53f8 | |||
| 373415261e | |||
| d0f653da9a | |||
| cbb8319cfa | |||
| 0e467a6024 | |||
| e505916525 | |||
| 31f09ab117 | |||
| 09e5e0f9b3 | |||
| 773cada57a | |||
| 403947ed6d | |||
| d9d08a725c | |||
| e2ed75af94 | |||
| dd19afce92 | |||
| 5b5deac7d9 | |||
| 3f3ee3456e | |||
| 3e2f2fc02e | |||
| c5a3003cf7 | |||
| 4c6dcddf11 | |||
| b314e500cd | |||
| b83e6ad13f | |||
| fee401cfdf | |||
| fa14f62198 | |||
| 5526665494 | |||
| 6249af996a | |||
| e51a1b365e | |||
| f53f00d126 | |||
| ab8fbaf21d | |||
| 63d8bcf882 | |||
| e4e16bc6a6 | |||
| e4c21c4d5c | |||
| d9e6dbf889 | |||
| 5f4469e306 | |||
| 16f6365b18 | |||
| cdab54bf51 | |||
| b6fea0c856 | |||
| 6f186932cc | |||
| de544ec725 | |||
| a6c0d2c6f1 | |||
| 437d956cad | |||
| 43ba0b9da6 | |||
| fe1e2c3b62 | |||
| bbe7e5f96d | |||
| 2bfad9fc65 | |||
| 9f24c18f7f | |||
| a2d1e5ea03 | |||
| b082e4e988 | |||
| 45fbce0c2a | |||
| ff7006e232 | |||
| c68fdef0e4 | |||
| 4c60e4edc1 | |||
| f2e4e59f8d | |||
| 898205b5b1 | |||
| 8ec6862861 | |||
| c3ba6322d8 | |||
| e589828425 | |||
| c909aa161b | |||
| 5cee7d44f1 | |||
| 973b323a99 | |||
| d55954a363 | |||
| adca151e4f | |||
| 43513049be | |||
| a5ca41131b | |||
| 2ea3810980 | |||
| c9cb54e8b2 | |||
| 23dd3db50b | |||
| 179c46914d | |||
| ddb18e6c21 | |||
| d2e147ba51 | |||
| b63033f846 | |||
| 677e1101e6 | |||
| 3abc2ccfbb | |||
| 827d28f1cd | |||
| 59d12a1bbe | |||
| 8134172301 | |||
| 8d983aa561 | |||
| bb3085ef84 | |||
| 78b1650bdb | |||
| 5edc72d475 | |||
| 1df32c9dfa | |||
| bedb1ff7f9 | |||
| fbf12f16b5 | |||
| 22a5f34e1f | |||
| e1a8b08619 | |||
| 466d30fb74 | |||
| 7a1c6d2918 | |||
| 9fcc0dc8e7 | |||
| 286f244caf | |||
| 52ebeab90b | |||
| 6be7591332 | |||
| 73325aaefb | |||
| 0adcc6a8d1 | |||
| 93fb6acd1d | |||
| 77d52b323d | |||
| ca8e8bf6ef | |||
| 9acac86ed5 | |||
| ba5007219d | |||
| ec046fd599 | |||
| 5452ff1153 | |||
| d03b1e0608 | |||
| 53c88ad5af | |||
| 21585ca565 | |||
| 2fabee647a | |||
| cf4632045c | |||
| ec75afbc44 | |||
| c7b298c46f | |||
| 3406bb5a4a | |||
| e9427b4694 | |||
| 517a1f1835 | |||
| 6e735bbd1e | |||
| 5a148aa3b9 | |||
| b4c362da37 | |||
| 575ae164c8 | |||
| a9606bfb4e | |||
| b5ec04723d | |||
| 51cc08411f | |||
| d75034deae | |||
| 4ce23debb6 | |||
| 063706a7bf | |||
| eb41a3655f | |||
| a84476187f | |||
| 70b603d601 | |||
| c9709f2698 | |||
| be4f479afd | |||
| a251465dbc | |||
| 9b7051f2be | |||
| 8bfdef79c4 | |||
| f2c2bf81b3 | |||
| 8b5247b9bb | |||
| 075b1dc0eb | |||
| 42e1f0478d | |||
| d4653678b2 | |||
| a4ce752e58 | |||
| f6649398ef | |||
| 02859cc270 | |||
| 4ed30e082f | |||
| d3985dd129 | |||
| ac70821fea | |||
| 38b273a1c9 | |||
| eb5c88fbb2 | |||
| 1102952b47 | |||
| 5ad6cadcee | |||
| 5d236808d6 | |||
| ada9243e84 | |||
| ad4e1c1b5b | |||
| c5105c29b0 | |||
| f689b0f3b2 | |||
| 730f614024 | |||
| 2e4eb17a48 | |||
| 55d2a1f09b | |||
| e3a5bcb907 | |||
| d56567c9de | |||
| d7c381e05d | |||
| 1d23dd2426 | |||
| 86ce00adf9 | |||
| e1eef013a8 | |||
| d20046c866 | |||
| 2df452fe5b | |||
| c31bfd8b94 | |||
| f443675f4f | |||
| a44bd8849d | |||
| 274c887140 | |||
| 44973ebd00 | |||
| 88f74163ff | |||
| 6cd57dd426 | |||
| 481e866011 | |||
| a78450ba79 | |||
| 9fcdeca692 | |||
| 8e10c56304 | |||
| 591f600b11 | |||
| af03e55a73 | |||
| 1462cddd1e | |||
| aac569a925 | |||
| d19ac1fd15 | |||
| 410a23751b | |||
| 8190507e8c | |||
| 645f74f39d | |||
| acdf0912d4 | |||
| 32e4e91ab8 | |||
| b59ca329f3 | |||
| beb9099bdc | |||
| a013d95f0f | |||
| 9d5daf0015 | |||
| 1146307736 | |||
| c25eaadc1d | |||
| 50f6e76ab9 | |||
| c3637e731f | |||
| c31eef6b82 | |||
| 802683b765 | |||
| 335cf93970 | |||
| 041954c067 | |||
| 2da44c3699 | |||
| 65e53092e8 | |||
| 0203aa2b6e | |||
| f83fe28a18 | |||
| e487b45898 | |||
| 4a94eab393 | |||
| 5ff7e6ef0e | |||
| 0c99f41bd9 | |||
| bcac810f71 | |||
| feae105426 | |||
| d46b9c54b1 | |||
| d0b7162b6a | |||
| c16660254b | |||
| bbe0d52740 | |||
| 65117ebdd7 | |||
| 445d55e800 | |||
| dbce751464 | |||
| b8640d903f | |||
| 6e7560ee77 | |||
| efa26a067f | |||
| f7cf7c198d | |||
| d32f4fc38e | |||
| 0c8b8647f3 | |||
| c38dbbb1d7 | |||
| 98cec15625 | |||
| bad9828f47 | |||
| 18f91ddffa | |||
| 335745d365 | |||
| 4204b31398 | |||
| 029a568645 | |||
| 6b2f1fcd3e | |||
| 3bdc610249 | |||
| c03a70fde2 | |||
| c77645e173 | |||
| 6531002d61 | |||
| fea808ca5f | |||
| 59ba408070 | |||
| 3a8931feef | |||
| 7ebd8f6bf5 | |||
| 73883979c6 | |||
| d8935b46b3 | |||
| 1505d04622 | |||
| 3b690cc31f | |||
| b3814920bd | |||
| 8bf1f828b9 | |||
| 911e69e3bc | |||
| 77b4a1eaf6 | |||
| 2ed12c429e | |||
| e578c52fdf | |||
| 5ff4e3c4ea | |||
| 1c2e7256c1 | |||
| 7a51ca225a | |||
| 8d242d8072 | |||
| 543b068efa | |||
| eadbd5e821 | |||
| c771f5a13c | |||
| f13b3111c3 | |||
| bb2714ff24 | |||
| 54c42b1fc2 | |||
| 1f42f933f0 | |||
| 6c533d225b | |||
| 67a89d1289 | |||
| 0b9bef2f38 | |||
| 1c4649409d | |||
| e1daaf07ce | |||
| cd21f2d1d7 | |||
| 836e41064f | |||
| 4658e4c739 | |||
| 7993a7cdb0 | |||
| 567cb7d737 | |||
| 985128b537 | |||
| cd35568e04 | |||
| f612509cac | |||
| f415190483 | |||
| e00733a346 | |||
| aafafa0e73 | |||
| c87c9af12e | |||
| 622215715f | |||
| c87250c028 | |||
| d6b6d700a5 | |||
| 9d4e2c98a3 | |||
| 08094c2660 | |||
| fda82c82b0 | |||
| f0eabd4ea0 | |||
| 5a4d812146 | |||
| bf156a13bd | |||
| 53d276a8fc | |||
| 978fa54518 | |||
| 31f82e58b5 | |||
| 5967f66e6d | |||
| bea81b54ca | |||
| 6fd403bdca | |||
| 3d673d9d40 | |||
| cac949de56 | |||
| dffa152698 | |||
| 7796298fca | |||
| 5291bfe8d4 | |||
| b364313297 | |||
| 2bef608e07 | |||
| aee13ad909 | |||
| 9d82eba599 | |||
| 4852beb390 | |||
| 522c2f61c0 | |||
| d331396afe | |||
| c725cee044 | |||
| 367ef4ecb2 | |||
| c3a64facd5 | |||
| 2671781e1b | |||
| 9291fa68aa | |||
| 6ebb3659f4 | |||
| 109696e965 | |||
| b86bbd370c | |||
| 1575a4fb1a | |||
| e2f500be90 | |||
| 284b00a826 | |||
| 66180e19b5 | |||
| 1531462d22 | |||
| e17cd1f298 | |||
| 13092e9f80 | |||
| 8133b8b9dd | |||
| e25aafac6d | |||
| 037c7beb1b | |||
| faac4dfc30 | |||
| 469a088a4d | |||
| 9c854f727f | |||
| 8f6e5bd24b | |||
| 992f91cf4c | |||
| 0c61d4c978 | |||
| 72d32edf9a | |||
| dde39def9e | |||
| 6b43007c44 | |||
| 876558cf77 | |||
| b35a146edf | |||
| 86cf6a9908 | |||
| b0f7288ac0 | |||
| 32fcc1a8fb | |||
| eb4d125eaf | |||
| 87e5cced3c | |||
| bcd9ee2d48 | |||
| edf3d87458 | |||
| 0c0c353ecc | |||
| aaa4c8f899 | |||
| 57e3565c11 | |||
| fbcf72d762 | |||
| ca8daa836e | |||
| c2ccdbec9d | |||
| 69aee36e75 | |||
| 063f5c3b55 | |||
| 6d1b8ca65e | |||
| 03899d4d9c | |||
| 2b84af977d | |||
| e15b180322 | |||
| f070030f6b | |||
| 330ac66b38 | |||
| 12a9e650af | |||
| 1e5ccd811e | |||
| 2902ee6ad5 | |||
| 90ce4fec2c | |||
| a7ab26d08d | |||
| dcc396afb7 | |||
| 3f1722d54d | |||
| c79658afbb | |||
| 6f84ebb323 | |||
| 54c8d62243 | |||
| 83e6476408 | |||
| 03f317ffdb | |||
| 3f67c7c74a | |||
| b8b4660563 | |||
| 3d42bfd479 | |||
| c4ebbaba74 | |||
| 2ae193092d | |||
| 7e1d52ff00 | |||
| c98f43b984 | |||
| e93f128a7a | |||
| 71d8e7edd2 | |||
| 48f4298a8d | |||
| 1c15beadc7 | |||
| 2c1600ddfb | |||
| 298f7d9d52 | |||
| 105f0bfde2 | |||
| cf3a78e114 | |||
| 9cc5f0f889 | |||
| 81e588a896 | |||
| e30db9ee77 | |||
| 235be757f8 | |||
| e31d26a887 | |||
| 2293ba02ea | |||
| 7188997dd1 | |||
| 31cfb09468 | |||
| b76f2e2008 | |||
| 4b90972341 | |||
| f4ff34224c | |||
| 877c9c4ff9 | |||
| ac0b7291dd | |||
| 491ef6085a | |||
| 3cedef4974 | |||
| 22ab04bd2b | |||
| e5a080aebe | |||
| c560cc5add | |||
| 0d27bc323b | |||
| c71d3cef18 | |||
| 4e2d8daa3a | |||
| d0f51da0af | |||
| aeb73bca27 | |||
| f239b03291 | |||
| 297c2965f4 | |||
| daddf4d98e | |||
| e05f9bfebf | |||
| ef46aeae6f | |||
| 7edb3fd856 | |||
| 43b79778ea | |||
| 37f1f53b56 | |||
| 67bd1cdcd9 | |||
| 506385e5a2 | |||
| 2d4586b1c4 | |||
| 1476b9cb9c | |||
| 7bdde68ece | |||
| 50f92f55e2 | |||
| 370db62bf0 | |||
| 65f34f1d31 | |||
| 00a3908abb | |||
| 32034d590a | |||
| 3635fb3910 | |||
| d2ecfe5491 | |||
| 1f759dce5b | |||
| ae41c6038b | |||
| f41f7eb2ad | |||
| d04f383161 | |||
| cb989d7489 | |||
| b5cba19d26 | |||
| b7271d7a46 | |||
| 768bdc2cc2 | |||
| a0a093e260 | |||
| 0b2a7fdc15 | |||
| f1876e20f8 | |||
| 7d6a6e53f7 | |||
| 6b765a1f77 | |||
| 3b3c93dd53 | |||
| 521083a309 | |||
| 6d35325601 | |||
| 3c65f37d84 | |||
| d75dd058d6 | |||
| 40475e02ec | |||
| f6ea9c1300 | |||
| 902359f24e | |||
| bb4d5f1e93 | |||
| 1dec3f000e | |||
| 6d89814795 | |||
| f339bc9f7f | |||
| bb80bc0c07 | |||
| 96677ff8bf | |||
| c7b6a6df79 | |||
| 8bb7e8bfb2 | |||
| 02ab343e22 | |||
| 4d875a18de | |||
| cff2336923 | |||
| 0e96bfdfa3 | |||
| a24ef48ad1 | |||
| fe9c4f3391 | |||
| 6cbb0739f8 | |||
| 79ebabfcfb | |||
| 0c8e3262fe | |||
| c24a040c19 | |||
| 4f39cb8dfa | |||
| cdd2816642 | |||
| 27b6dfa4d2 | |||
| 204cbd0209 | |||
| b7980aad5a | |||
| e175729e2c | |||
| c26ad4c807 | |||
| 4372adc703 | |||
| 8a0c2b9b13 | |||
| 4443a1c030 | |||
| 39a02235cf | |||
| db89561480 | |||
| a4cbb11693 | |||
| 1ceeacc526 | |||
| b59052cc43 | |||
| b5d8161bc2 | |||
| b497eda26e | |||
| 44019e1042 | |||
| fd0e1da4a2 | |||
| f6c67b475a | |||
| ea18a5ad15 | |||
| 4d0cd3f497 | |||
| 7dbdeaebe0 | |||
| 2085bcceb6 | |||
| c09c962cb6 | |||
| 9108459ae4 | |||
| 992bac0b1f | |||
| ebd6917a1d | |||
| 3e079e3b80 | |||
| 2ca20be197 | |||
| 17f23f3751 | |||
| 8e3d699856 | |||
| f1a168fa53 | |||
| 3575113655 | |||
| fe9dd7d23f | |||
| 9feb9d04b3 | |||
| 5419b1439a | |||
| 6f8fbe3f09 | |||
| e4489343fe | |||
| d9f4a5793d | |||
| 70fcdc9ebb | |||
| 78e7a81638 | |||
| 58e82d5dbd | |||
| 06d15be43a | |||
| e1e7011853 | |||
| eff7645035 | |||
| eb75e52278 | |||
| 15e6148272 | |||
| ccd448a675 | |||
| db54c019ad | |||
| b762b4d72c | |||
| 2f8faa839d | |||
| 831c325c63 | |||
| f4f73078c5 | |||
| f7d43ad5af | |||
| a77c777980 | |||
| a34db7449e | |||
| 0283bd2f97 | |||
| a8de2f8ae7 | |||
| d5dee75df3 | |||
| 6b7631ed5e | |||
| 79cb5c0631 | |||
| 7d9901540f | |||
| e979e9c9fb | |||
| de719df6fe | |||
| 38d7f91ea0 | |||
| a20a429e8c | |||
| 9b63fb4ee2 | |||
| 099efdbc1d | |||
| af9120fc7a | |||
| 798a48aa96 | |||
| 462e22a3b0 | |||
| 4e432ed03f | |||
| 1b56c0f191 | |||
| 94cbeba495 | |||
| dfa74a69f7 | |||
| 9e1e4eeec2 | |||
| 962f8233b0 | |||
| 31923d511e | |||
| fdbb8300d3 | |||
| 203d8bca57 | |||
| 74c331eaf7 | |||
| 54365528a8 | |||
| bc174f7054 | |||
| 221e068bac | |||
| 1bdceeecf4 | |||
| a6824d9cd0 | |||
| fe03f9942b | |||
| 4c196922fb | |||
| 2a9a513d85 | |||
| 2bcea27ecd | |||
| fb7341f1f7 | |||
| f327daa12d | |||
| 2872b9fbf9 | |||
| cedd5c9c96 | |||
| 60e6aa9b54 | |||
| 541f482518 | |||
| 4019b7939a | |||
| 013b9c4c7c | |||
| 79addfda42 | |||
| 8546bda471 | |||
| 0770f174a1 | |||
| 5f4a89cecc | |||
| cbe0b1c5a6 | |||
| 0af193c505 | |||
| fdcfcc77a0 | |||
| 06d166d0d7 | |||
| b1b3e39fcd | |||
| 5ec025a3b9 | |||
| 58b7fbcf75 | |||
| be053d47e4 | |||
| fae1981161 | |||
| fd95549de9 | |||
| ff10bc05ec | |||
| eb8cd53de2 | |||
| 3fc863561a | |||
| 131cd94495 | |||
| f3a90a63b6 | |||
| 2494173aec | |||
| 866eb6241b | |||
| 86b3df1941 | |||
| e87f6d56f5 | |||
| acc890730f | |||
| b0707739fd | |||
| 251dd1fe45 | |||
| b9f3dc65e0 | |||
| 238ad7ce51 | |||
| 99fd5fca4d | |||
| 8eda77b76d | |||
| 81ac240ac8 | |||
| 6109798fab | |||
| 95715a007d | |||
| b33ec201ac | |||
| b53fbaf5b3 | |||
| 1e03a2e553 | |||
| fda7c6f67a | |||
| fabb7982ff | |||
| cbf206be60 | |||
| aa0c282205 | |||
| 9759365329 | |||
| e3738f6422 | |||
| 9746d169f9 | |||
| 2e6d03ff00 | |||
| f7b7d3d65e | |||
| 4037cf11aa | |||
| 02aeb321ce | |||
| 0012619257 | |||
| 6f3ade0d94 | |||
| cf572f328a | |||
| d1ce36038c | |||
| b382351c7b | |||
| 4e5e862f58 | |||
| ab84835362 | |||
| 41ce8489dc | |||
| ef3faf5832 | |||
| edf089ec9e | |||
| 0ae9a3605c | |||
| 7f9c4e52ac | |||
| 35947f96a8 | |||
| b0f91f1eea | |||
| 13dfca1749 | |||
| 9d9cd8a362 | |||
| 5e5c41fda5 | |||
| 3ebdc52b3e | |||
| af54f48cec | |||
| 8656059151 | |||
| a6d38f2458 | |||
| 085bdf5cfa | |||
| 6883bcacee | |||
| 2ecc6e0368 | |||
| 8fb5ca2fe1 | |||
| e40345095f | |||
| ffc0c7bb7b | |||
| 58fadc8928 | |||
| d96a607c04 | |||
| 2ea2c8c05f | |||
| b15f514018 | |||
| 05a33a0ec9 | |||
| 747d266742 | |||
| 522d30545b | |||
| 6135a44a8d | |||
| 7c7d646792 | |||
| 4a36fd2ec3 | |||
| b1bcc287cb | |||
| 6f5868a9d7 | |||
| 75863441f4 | |||
| 9763cb00e5 | |||
| 521599ef39 | |||
| 1921a64f4b | |||
| 6b73f5d555 | |||
| e0408f30ba | |||
| dca44fa093 | |||
| bbacb76581 | |||
| 1da8617a53 | |||
| e5967f7e9d | |||
| 65d84f9e9d | |||
| 93e9498b41 | |||
| 95332d5db9 | |||
| 9864792bbf | |||
| ca9d1eed7a | |||
| 38e867c917 | |||
| 1ff6daaa27 | |||
| 3ee1dbb8a5 | |||
| b4571d5c98 | |||
| 29d49d72f9 | |||
| 81de0fc7e4 | |||
| 9d5d266d2a | |||
| b97935299f | |||
| 32a7ea1c16 | |||
| 9fd95e6a1e | |||
| 61ba83c775 | |||
| 6369fed5fc | |||
| 42f4d1477d | |||
| 609ac9c9a2 | |||
| 79f2367472 | |||
| dfc9362084 | |||
| 487b469603 | |||
| 19e1df1e5d | |||
| fc55010888 | |||
| 56a36aa595 | |||
| 0b3158fdd5 | |||
| 896cd950e9 | |||
| af5e1b805f | |||
| 3f9943270c | |||
| 902d246d1a | |||
| 785bee2b39 | |||
| 4b335db31c | |||
| 4bef527ebb | |||
| 8273031d7e | |||
| dbf69bcd98 | |||
| 24d3096752 | |||
| 9a3754fbf8 | |||
| c2a95a13fe | |||
| b46466ac96 | |||
| 06991ddb17 | |||
| 4b3f4a868a | |||
| 014eff3534 | |||
| c01b7ddb59 | |||
| 0ff700849f | |||
| 5c695dea20 | |||
| c7ee4ca4db | |||
| c3da026b33 | |||
| 98da4e1791 | |||
| 8626b6d9f2 | |||
| 80c5331a5d | |||
| 39dc5c8160 | |||
| f3002e4fb6 | |||
| c451823c2b | |||
| b37cffd704 | |||
| 2af91dbf2a | |||
| f2551318fc | |||
| 22eb84f944 | |||
| 1ece0915f1 | |||
| 87853a2015 | |||
| 46a1b74799 | |||
| 0f6e7e5eab | |||
| 5dfa9615d2 | |||
| 1bde777bee | |||
| fa4f5df3e7 | |||
| 8a3c3810e0 | |||
| 144532530a | |||
| 0f1129e19b | |||
| 1f74580f15 | |||
| f93ba9970c | |||
| f87a3d0892 | |||
| c661ad67d0 | |||
| ce4dc9e3fa | |||
| 3d2c6e6032 | |||
| 6000a0cfe3 | |||
| b80c60997a | |||
| 35e45f0df1 | |||
| e65f5b9f78 | |||
| 5969f71e67 | |||
| b1307863eb | |||
| 9d0abe993b | |||
| c53bb7cf67 | |||
| 0cea26c628 | |||
| 610c919c72 | |||
| 2c35f94f7c | |||
| cd9a3870b3 | |||
| e11373f23a | |||
| f591635fc1 | |||
| 474b57ca61 | |||
| 8671887abc | |||
| 8274d1baa5 | |||
| bde601bfff | |||
| a2e0951042 | |||
| 3c9a8a38dd | |||
| 4c067fd39f | |||
| 5c251ee774 | |||
| ddda691552 | |||
| ba73090d53 | |||
| a080b606ab | |||
| 7c0f43c8a3 | |||
| 4fad1aee6b | |||
| 19aec5bc8d | |||
| 33ee60736c | |||
| c1bccb970f | |||
| 481cc029fa | |||
| bdaf58a3c4 | |||
| 0f64671dc0 | |||
| 60f055f293 | |||
| c67a9215e3 | |||
| a0e9df6d6d | |||
| 8341749c04 | |||
| 66d1886663 | |||
| 710e1fc278 | |||
| 4cf98584da | |||
| 3fb3cee423 | |||
| 2d1504a057 | |||
| 4fcfd089d8 | |||
| 04422a4637 | |||
| 37e6548246 | |||
| a761989f3e | |||
| acaa8b1731 | |||
| 082f47663d | |||
| 92f42154d5 | |||
| fc1084ce33 | |||
| adc3ab7e0a | |||
| dcc8c56969 | |||
| 0b3472d227 | |||
| b50121ad0b | |||
| dfddfc3e06 | |||
| 34ec6210c6 | |||
| daaef316e9 | |||
| cdac12bb2f | |||
| 3ab99647aa | |||
| 529482a83e | |||
| 29906ec057 | |||
| 9296ba7492 | |||
| 821c6d807d | |||
| 991b741cbe | |||
| 2559f0e822 | |||
| 8bb1ed3e95 | |||
| 56ba3fcb92 | |||
| e99a20a120 | |||
| f1a25b21a6 | |||
| cf9595ce58 | |||
| 7feece597a | |||
| fa16c782ca | |||
| a03d0b6a4a | |||
| dff76b8912 | |||
| cffea9a9bc | |||
| 43fee42198 | |||
| 5cd3968929 | |||
| 65dcae79f8 | |||
| 2b3b75a4a5 | |||
| 26b395110c | |||
| e11b3becfd | |||
| 73fa937f5c | |||
| 6ebe2e54ac | |||
| fb29a11cc9 | |||
| a9e06cbf92 | |||
| 93017c044d | |||
| c223ddbb9a | |||
| f00aeacb8b | |||
| 5b49071d6b | |||
| 17053a882b | |||
| 5e723d31a4 | |||
| 3283bd8b75 | |||
| 770d3c0015 | |||
| d15dfafd5d | |||
| 545c13d55c | |||
| e2099ac9ca | |||
| c937583a50 | |||
| 43c7a19849 | |||
| 83543487e7 | |||
| 434b259525 | |||
| add8efaba8 | |||
| 12ed79ca60 | |||
| 1e863382d3 | |||
| bad3504d4a | |||
| d94f207162 | |||
| 2c4b7781a4 | |||
| 4574c6ff07 | |||
| 7b5043e87b | |||
| a06f3ac5da | |||
| 721346a40a | |||
| f252871fb8 | |||
| 107196122c | |||
| 563c02d8da | |||
| 765934970a | |||
| 9cbdfda829 | |||
| c1fbc8cd68 | |||
| a92107cc47 | |||
| 3e84e116e8 | |||
| 7c0cdd169e | |||
| 424fd96615 | |||
| ebfcea88da | |||
| 3c7eb2f5e2 | |||
| 936167972f | |||
| 7f6070c960 | |||
| 0aea9c129c | |||
| d20fb7daa8 | |||
| a619f8a2fe | |||
| 0acb858d7b | |||
| e459be76fe | |||
| c4c59ccc75 | |||
| c820315409 | |||
| 2a19b61b7a | |||
| e1cf64ae16 | |||
| d3c2f8eb43 | |||
| a00453c83a | |||
| 2eb0e54909 | |||
| ac87bc57e9 | |||
| 2b8ea857f0 | |||
| 11c52114b2 | |||
| f55f9b4dd3 | |||
| cdd369ea98 | |||
| f2aab9f476 | |||
| 2619dc3556 | |||
| 1bbed351ba | |||
| 4cfb2644c6 | |||
| 5b85c7ad39 | |||
| ab3a3156f2 | |||
| 28a582025e | |||
| 8451a83a3a | |||
| 92c8dcc666 | |||
| da68b02e1e | |||
| 2d7729dbe9 | |||
| 6467edb843 | |||
| 1f01b3a9ff | |||
| 8aa1f8926d | |||
| ace363fa35 | |||
| 919f70a5fd | |||
| 9d6ad771a3 | |||
| bafccb20c6 | |||
| cef30c2af0 | |||
| ca58056a75 | |||
| 03e2e99498 | |||
| fba7afc4e9 | |||
| d7dda336ec | |||
| 55d4395160 | |||
| f7d5baa6d0 | |||
| 6ff509d263 | |||
| 57778981a7 | |||
| 6ac3bc564f | |||
| 82d08e2153 | |||
| 6212cd77e8 | |||
| 170034f943 | |||
| e639a8f9f1 | |||
| 9d10c45dac | |||
| b84159f2f1 | |||
| 49f26b4049 | |||
| 0e7e44cee2 | |||
| 36e769502c | |||
| a4b6580247 | |||
| 84fb1c5127 | |||
| ddfd0fb81d | |||
| 37edbf5824 | |||
| e15212bf49 | |||
| 6a0cc1b1f3 | |||
| 0e8339c701 | |||
| 5d71a4dbde | |||
| 0b78030c59 | |||
| 24237c16bf | |||
| c48da5dea7 | |||
| 6702ca10a1 | |||
| 4b44bd5e61 | |||
| 8a55beda92 | |||
| a12b824339 | |||
| c4a743189e | |||
| 85f9ef35f6 | |||
| 6de220e38a | |||
| 0d455d8c2f | |||
| f7b0b44ef6 | |||
| 81282a9c88 | |||
| a9b302e38d | |||
| 1fe4c367f7 | |||
| 2de7583900 | |||
| 356703c83e | |||
| 1cae709b2b | |||
| 46a492248f | |||
| d876ea6711 | |||
| b40b4c3cfd | |||
| 44980d6c46 | |||
| 442f9647a2 | |||
| a06ef07178 | |||
| 0c2ea5da4c | |||
| 45814ceb49 | |||
| 41f4617e66 | |||
| a463711b03 | |||
| 3a147141b1 | |||
| aea1c13bd3 | |||
| 9cc4c017ae | |||
| 88022747be | |||
| ebb292a2f7 | |||
| 818998259d | |||
| 36b02c2cec | |||
| e6195ade3e | |||
| 231a875bb4 | |||
| 378c777a38 | |||
| 8ef63916ef | |||
| f32ecdf5f1 | |||
| 94739b9b8e | |||
| 023db4e04e | |||
| 5a4b95f5fe | |||
| b091657b5c | |||
| f7bf5c9328 | |||
| 86e521b9aa | |||
| e08cf57b72 | |||
| 9e06cb9a83 | |||
| c65e8622b8 | |||
| 7795efeb7a | |||
| e725e0020e | |||
| 8478ce25f1 | |||
| 22cdac6210 | |||
| 3422a1a59f | |||
| f807b8cb46 | |||
| e1e510ce1e | |||
| 36bc3ff22d | |||
| 1db1ffde99 | |||
| 7984b625ba | |||
| bb22f062ed | |||
| a3086e6a5b | |||
| 1eb9bd34fd | |||
| cff7baaaad | |||
| 47af700375 | |||
| eb7f53a1e3 | |||
| d23792fda2 | |||
| b5cbb8e450 | |||
| fc037b4518 | |||
| c04921f42b | |||
| 8af3463668 | |||
| 094565ccd6 | |||
| 07b9769504 | |||
| 566b12f5d1 | |||
| a560999d13 | |||
| a818a3d293 | |||
| 4d68a174cb | |||
| 905219e523 | |||
| c675b87040 | |||
| e2dadfdc40 | |||
| a66f9aa04d | |||
| 863e74dd2e | |||
| aebb07c5cc | |||
| 677fec0a0b | |||
| 61f06faaf5 | |||
| fcf8309c23 | |||
| 76b19a4980 | |||
| ed1a9fc7aa | |||
| b71e30755d | |||
| fe196d1b20 | |||
| 0b152a3cb8 | |||
| 1a0e096ddb | |||
| bf040d4c42 | |||
| 3e2101aa5b | |||
| 44f11b38e7 | |||
| 06f8514bc1 | |||
| d9a9f1c25c | |||
| e77f7244ba | |||
| 09779cd1e1 | |||
| 11024168db | |||
| 304914513e | |||
| 03d0efcfc6 | |||
| 0c2bdc9146 | |||
| 188450b0c0 | |||
| ddf6bbfa46 | |||
| 2c7c98a152 | |||
| d31ac75718 | |||
| 4b28c659de | |||
| 00b937e52a | |||
| 68f333fb87 | |||
| 085b4d9319 | |||
| b8edd14f39 | |||
| 61a19d694b | |||
| aa96859798 | |||
| abf015f485 | |||
| 0b9a02afde | |||
| e332ce28f8 | |||
| 08e779f9f1 | |||
| a53b6cd28f | |||
| 47ebab237b | |||
| 5a5af60f97 | |||
| 50cc17c7cb | |||
| a1e504c138 | |||
| 4cc413047c | |||
| 2397097e4d | |||
| c88f959ec9 | |||
| 0b2e28b62d | |||
| c7f2ead696 | |||
| b454e02d01 | |||
| 47df2ffc3e | |||
| d18e431e2f | |||
| 0a147d2db7 | |||
| ccdc933064 | |||
| 023f2d4569 | |||
| 06a7371d2b | |||
| 626f5d3487 | |||
| 8cd3b9fe2e | |||
| 716c35d52a | |||
| 8cc0f3918b | |||
| 98b51f0799 | |||
| 81d6dad548 | |||
| 41bc11cf96 | |||
| 721dbe415a | |||
| f5c0b51428 | |||
| 892c6ad80c | |||
| a47805e494 | |||
| 9dd49054d1 | |||
| f5d1d1a0b7 | |||
| d75fcb7fec | |||
| 826be45b6a | |||
| d35d9c17c5 | |||
| f9553e7d44 | |||
| 638c9720cf | |||
| 08a190882f | |||
| bfa7530a39 | |||
| 6c74148c27 | |||
| 480cad0fed | |||
| 32be489136 | |||
| 11801d8e2e | |||
| 1b280a2940 | |||
| 424890a1e1 | |||
| 5f85219495 | |||
| a63d687f1c | |||
| f2d6c3ad83 | |||
| 0b6941d5ce | |||
| 048696ee93 | |||
| ae51676471 | |||
| 27a405fb1d | |||
| e2cf65b591 | |||
| 9749fc817d | |||
| e6600f2c43 | |||
| a664931e7a | |||
| a2ba965600 | |||
| 65255dbb50 | |||
| a5cb8761a5 | |||
| e3930e0717 | |||
| afd59e9933 | |||
| 8087524bef | |||
| 605440812f | |||
| b10c6eb615 | |||
| a96264ff6a | |||
| deb3893820 | |||
| 9b1d822b5b | |||
| f933fb705c | |||
| 5cd61c4c02 | |||
| 586fa70eac | |||
| 9b420ad33f | |||
| 5891290672 | |||
| 72a7b51d47 | |||
| 2943e0e58d | |||
| 5abce916a8 | |||
| 89d4be8a0a | |||
| b7113bda23 | |||
| 0088929622 | |||
| b3468cf7f2 | |||
| f88c5c8528 | |||
| 687fdda7f7 | |||
| aec51e514c | |||
| 308d4600ef | |||
| 50a5fa15bb | |||
| 7d96a57d73 | |||
| 162ebfd4e0 | |||
| a586a92d5e | |||
| 3df20a924f | |||
| ddcf466e4e | |||
| 5d10cbf18d | |||
| 918ea59b9a | |||
| 8d9afa7592 | |||
| 95e05336c2 | |||
| a188385817 | |||
| 0a6baf331b | |||
| 0e29e052d5 | |||
| d8d255980e | |||
| dc5a5fa543 | |||
| 8638fc91ff | |||
| 96a0900edc | |||
| abea80e3ab | |||
| 42dfe936b7 | |||
| 8385bcc2d7 | |||
| 9b8f60b64b | |||
| b9dab77c8b | |||
| 474114236e | |||
| 238b0b421a | |||
| 8abe62886a | |||
| 78cc9cffe4 | |||
| 59a5dd713f | |||
| a39024ff03 | |||
| 72bfbd93a8 | |||
| c9a3e3d9d2 | |||
| 8387708901 | |||
| b565b4b3b9 | |||
| 893dcd4f24 | |||
| d613aa8f3e | |||
| 5750bb7067 | |||
| 0e07ae6ce8 | |||
| 02b6351e13 | |||
| 78367af830 | |||
| dc05cd481f | |||
| 7daad5477a | |||
| 45cdfbfae8 | |||
| 4159534a64 | |||
| 3fb5c38571 | |||
| 59f80ebc47 | |||
| 198a97b00c | |||
| 3ea4e917e8 | |||
| 60ad077172 | |||
| 356ad28e41 | |||
| e241141220 | |||
| 14bb1b467a | |||
| 2bbea6fd3f | |||
| 48aef25b3f | |||
| 8e50e5dee3 | |||
| d5d54d4d3b | |||
| 412e8a32dd | |||
| 0f82cf409b | |||
| 79df39acab | |||
| d00177a9b6 | |||
| 8786bae7dc | |||
| 4b3f8ca361 | |||
| 03183218f7 | |||
| 95b6db57e1 | |||
| bbe0c2457b | |||
| c894c53e69 | |||
| 5b3fb7387d | |||
| feac310caf | |||
| d67ec3feb3 | |||
| cf8abb4528 | |||
| d66de26de4 | |||
| e5a7ada3cf | |||
| b76fdd7fe4 | |||
| 7edf3f6147 | |||
| 2143dff2ae | |||
| 32c714d1b6 | |||
| 84e699ee24 | |||
| 7fdb572b8b | |||
| 91ffb0820a | |||
| cfd3b66be9 | |||
| 75c4f9dea8 | |||
| a76a32230d | |||
| 0730f5ff4f | |||
| c43d0a684c | |||
| 66f1eda6cf | |||
| bf4d191c8b | |||
| d76d56fcce | |||
| 251b0c58de | |||
| 073cca4e8e | |||
| a4ad99f189 | |||
| d37b910889 | |||
| be69244e8d | |||
| 617f74a225 | |||
| a2710ddc25 | |||
| 70101bfa7a | |||
| 203f2984d7 | |||
| 1d510e76b8 | |||
| 64244af2cc | |||
| 35e109882e | |||
| 18a32ffb0b | |||
| a5af3ba0cb | |||
| 83bd39a9a8 | |||
| cc0657aaa8 | |||
| 965dc2cb47 | |||
| 9c4cbe17a2 | |||
| 835fcfa4ea | |||
| 932780c578 | |||
| 37f160a452 | |||
| f80b706fc3 | |||
| f78cbed4d8 | |||
| e0b12c78ff | |||
| e7cf814a59 | |||
| 865c45593b | |||
| 62e6f109c7 | |||
| 60be8ed022 | |||
| c157882900 | |||
| a23c211a65 | |||
| 293ed6bd7e | |||
| 13ddd7c5f9 | |||
| 0de015fc8b | |||
| d34fedae01 | |||
| 7c623fc467 | |||
| 359079c3e6 | |||
| ba72fa3f05 | |||
| 23caab5b06 | |||
| b506bd15a5 | |||
| d0d9d68fe6 | |||
| 88134ac130 | |||
| 3d8a5196a0 | |||
| c4fb138ae8 | |||
| 759cfdaabd | |||
| 3d9620abb0 | |||
| 420b0394a7 | |||
| 84bb4c8b07 | |||
| 310dbb61ee | |||
| 9b536af9e6 | |||
| c2ca1ea1e5 | |||
| ada4b226de | |||
| 67f956d4a0 | |||
| f68af7dcf2 | |||
| be1b6f8edc | |||
| 1150f62927 | |||
| b4c7a521b4 | |||
| 5d083a1536 | |||
| 2f5ed3aead | |||
| 2951df0cd9 | |||
| ec22070957 | |||
| 0e36f03954 | |||
| 57309ae3d5 | |||
| 7545194f97 | |||
| 4bb0918e27 | |||
| 64e5449ab3 | |||
| a0eeb17d73 | |||
| c021dd03d3 | |||
| 2f1683b26a | |||
| c99939f435 | |||
| efad8bac3c | |||
| eaf68fa463 | |||
| 9475f2e56c | |||
| 2e0c067cd2 | |||
| 59a6043549 | |||
| 840a7b7c73 | |||
| 61e322678b | |||
| 04acd08ad2 | |||
| f3bf4faee0 | |||
| c3603ba220 | |||
| a3a52fd12c | |||
| 7c4756da81 | |||
| f4fe03c790 | |||
| a748bb9352 | |||
| d24fb6ed48 | |||
| 021dc5b82c | |||
| 9339b78f80 | |||
| 8449f8580d | |||
| 0948c5bc69 | |||
| 857589b365 | |||
| c39b1bf823 | |||
| 545aa50898 | |||
| 298006a4b9 | |||
| 903a4131ab | |||
| a0ec0ddb14 | |||
| 46eb876f9b | |||
| 8374c3941a | |||
| 8cec14c2ff | |||
| c716f105bd | |||
| 737be1f571 | |||
| 6f17c700b8 | |||
| 04d79f9117 | |||
| 8145808c43 | |||
| 86d5626d92 | |||
| 071ef281c1 | |||
| 70760e4ede | |||
| 8a32fd36ad |
@@ -0,0 +1,36 @@
|
|||||||
|
# 后端规则
|
||||||
|
|
||||||
|
主包:`packages/ui/certd-server`。后端使用 Node.js、ESM、TypeScript、MidwayJS 3、Koa、TypeORM,默认 better-sqlite3,同时支持 PostgreSQL 和 MySQL,并通过 `@certd/midway-flyway-js` 使用类似 Flyway 的 SQL 迁移机制。
|
||||||
|
|
||||||
|
详细入口、模块和验证命令见 `.codex/repo-map.md`。
|
||||||
|
|
||||||
|
## 默认开发配置
|
||||||
|
|
||||||
|
- HTTP 端口:`7001`
|
||||||
|
- HTTPS 端口:`7002`
|
||||||
|
- 默认 SQLite 数据库:`./data/db.sqlite`
|
||||||
|
- 默认文件根目录:`./data/files`
|
||||||
|
|
||||||
|
## 数据与迁移
|
||||||
|
|
||||||
|
- 后端使用 TypeORM 实体加 SQL 迁移。
|
||||||
|
- 重点查看 `packages/ui/certd-server/src/modules/**/entity/*.ts` 和 `packages/ui/certd-server/db/migration/*.sql`。
|
||||||
|
- 默认配置中 `synchronize: false`,涉及表结构变更时应添加或更新迁移脚本,不要依赖 TypeORM 自动同步。
|
||||||
|
|
||||||
|
## 文件上传
|
||||||
|
|
||||||
|
使用 `/basic/file/upload` 上传文件后,接口返回的是临时缓存 key。业务保存表单或设置时,后端必须调用 `FileService.saveFile(userId, key, "public" | "private")` 转成永久文件 key 后再入库/入设置;不要直接保存 `tmpfile_key_...`,否则后续回显或下载会失效。
|
||||||
|
|
||||||
|
## Service 与事务
|
||||||
|
|
||||||
|
- 后端方法参数超过 3 个时,尽量改为对象参数传入。
|
||||||
|
- 需要传入 `manager` / `EntityManager` 做事务传播的方法,必须使用对象参数,不要把 `manager` 作为位置参数藏在参数列表末尾。
|
||||||
|
- 后端 service 层只有存在事务链路传播需求时才定义 `ctx`,不要为了将来可能需要而提前给普通方法加 `ctx`。
|
||||||
|
- 事务链路方法统一采用 `method(ctx, req)` 形式,`ctx` 放第一位并承载 `manager?: EntityManager` 等横切上下文,业务参数放在 `req` 对象里,例如 `settleCommission({ manager }, { tradeId, userId, amount })`。
|
||||||
|
- 无事务链路需求的普通查询、纯函数和简单私有方法继续使用明确参数。
|
||||||
|
- service 内部需要根据事务上下文选择 Repository 时,优先使用 `BaseService.getRepo(ctx, EntityClass)`;不要在业务方法里反复写 `ctx.manager?.getRepository(Entity) || this.xxxRepository`。
|
||||||
|
- 拿到 repo 后 save/update/delete/find 都能做,不需要再包一层 `saveEntity` 之类的单一用途方法。
|
||||||
|
- service 拼接用户与项目范围查询条件时,优先使用 `BaseService.buildUserProjectQuery(userId, projectId)`;不要直接写 `{ userId, projectId }`,否则 `projectId` 为空时可能把 `null`/`undefined` 带入 TypeORM 条件,导致查询或 update 不符合预期。
|
||||||
|
- `ctx` 类型统一从 `BaseService` 导出的 `ServiceContext` 复用,不要在每个 service 里重复定义。
|
||||||
|
- 需要“有事务则复用、无事务则开启”时,使用 `BaseService.transactionWithCtx(ctx, callback)`:`ctx.manager` 存在则直接执行 callback,否则自动 `this.transaction()`。不要在业务代码里手写 `if (ctx.manager) { ... } else { await this.transaction(...) }`。
|
||||||
|
- 新增方法注意不要与 `BaseService` 基类方法签名冲突,例如 `delete(id)` vs `BaseService.delete(ids, where?)`,ts-node 下会直接 TS2416 编译报错。冲突时改用具体名称如 `deleteById`。
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# 代码风格规则
|
||||||
|
|
||||||
|
## 基本原则
|
||||||
|
|
||||||
|
- 中文 README 在部分 PowerShell 环境中可能显示乱码;`README_en.md` 可读性更好,且包含同样的高层项目说明。
|
||||||
|
- 根包管理器是 pnpm,不要引入 npm/yarn lockfile。
|
||||||
|
- 优先沿用现有模块、插件、服务模式,再考虑新增抽象。
|
||||||
|
- 注意本地数据和配置里可能包含凭据、证书材料等敏感信息。
|
||||||
|
|
||||||
|
## 注释
|
||||||
|
|
||||||
|
本仓库代码注释优先使用中文,尤其是解释业务规则、兼容逻辑、协议细节和隐藏风险时;除非文件已有明确英文注释风格或引用外部英文术语,否则不要新增英文说明性注释。
|
||||||
|
|
||||||
|
## 可读性
|
||||||
|
|
||||||
|
代码可读性优先于短写法。遇到包含业务分支的复杂三元表达式、内联对象、链式调用或条件组合时,优先拆成命名清晰的中间变量、独立分支或小函数,让读代码的人能一眼看出业务意图;不要为了少写几行把逻辑压成难读的一坨。
|
||||||
|
|
||||||
|
在对象字面量、查询条件或函数参数里不要内联调用多层 helper,例如 `{ domain, ...this.buildUserProjectQuery(userId, projectId) }`。应先用命名变量承接结果,例如 `const userProjectQuery = this.buildUserProjectQuery(userId, projectId)`,再在对象里展开 `...userProjectQuery`,让条件构造和业务字段都更容易阅读。
|
||||||
|
|
||||||
|
## DRY
|
||||||
|
|
||||||
|
遵守 DRY 原则:同一业务规则、字段转换、权限判断、Repository 选择、事务传播、金额计算等逻辑不要在多个地方复制粘贴。第二次出现时可以先保持清晰,第三次出现前应优先抽成局部 helper、service 方法或已有公共工具;抽象要服务于减少真实重复和降低修改风险,不要为了形式上的“复用”制造过度设计。
|
||||||
|
|
||||||
|
## 单一职责
|
||||||
|
|
||||||
|
遵守单一职责原则:一个方法只负责一个清晰的业务步骤或技术步骤。流程编排方法可以串联多个步骤,但具体的校验、计算、持久化、状态变更、展示数据组装应尽量拆到命名明确的小方法中;不要让一个方法同时承担查询、校验、计算、写库、格式化返回等过多职责。
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# 前端规则
|
||||||
|
|
||||||
|
主包:`packages/ui/certd-client`。前端使用 Vue 3、Vite、TypeScript、Ant Design Vue、Fast Crud、Pinia、vue-router、vue-i18n、Tailwind/Windi 相关样式工具。
|
||||||
|
|
||||||
|
详细入口、路由、状态、API、视图、locale 和验证命令见 `.codex/repo-map.md`。
|
||||||
|
|
||||||
|
## 禁跑命令
|
||||||
|
|
||||||
|
- 不要运行前端 `pnpm tsc` / `vue-tsc`:当前依赖组合中 `vue-tsc@1.8.27` 会直接抛内部错误 `Search string not found: "/supportedTSExtensions = .*(?=;)/"`,不是有效的项目类型检查结果。
|
||||||
|
- 前端暂不跑单元测试;当前 `test:unit` 只是占位脚本。
|
||||||
|
|
||||||
|
## 格式化与校验
|
||||||
|
|
||||||
|
前端 TS/Vue/locale 等文件改动后,优先只对本次改动文件运行项目现有自动格式化/修复:
|
||||||
|
|
||||||
|
- Prettier:`packages\ui\certd-client\node_modules\.bin\prettier.cmd --write <files>`
|
||||||
|
- ESLint:`packages\ui\certd-client\node_modules\.bin\eslint.cmd --fix <files>`
|
||||||
|
|
||||||
|
不要为了格式化无关文件而扩大 diff。项目保留了 `tslint` 依赖,但当前主要使用 ESLint + Prettier。
|
||||||
|
|
||||||
|
## Fast Crud 页面
|
||||||
|
|
||||||
|
- 列表管理、后台管理、记录查询、CRUD 表格类页面,默认优先使用 Fast Crud(`@fast-crud/fast-crud`、`fs-crud`、`useFs`、`createCrudOptions`)实现。
|
||||||
|
- 只有轻量只读展示、强交互自定义界面或已有页面模式明确不适合 Fast Crud 时,才手写 `a-table` / 自定义列表,并在回复中说明原因。
|
||||||
|
- 开发或重构这类页面前,先读取 `.trae/skills/fast-crud-page-dev/SKILL.md`,按仓库内 Fast Crud 页面拆分与验证方式实现。
|
||||||
|
- 页面内嵌 Fast Crud 表格时,要显式给外层容器稳定高度或 `flex: 1; min-height: 0` 的撑满链路;Fast Crud 依赖外部元素高度,不能只依赖表格默认高度。
|
||||||
|
- 后台管理列表里展示或筛选用户字段时,优先参考 `packages/ui/certd-client/src/views/sys/suite/user-suite/crud.tsx` 的 `userId` 字段模式:前端使用 `table-select` + `/sys/authority/user/getSimpleUserByIds` 字典回显和搜索;不要为了展示用户名让后端列表接口额外 `fillSimpleUser` / `userDisplay`,除非该接口本身就是用户端业务列表且已有明确模式。
|
||||||
|
|
||||||
|
## 对话框
|
||||||
|
|
||||||
|
前端对话框里只做纯确认时可以使用 `Modal.confirm`;只要对话框里有字段输入、表单校验或提交字段,统一使用 `useFormDialog` / `openFormDialog`,不要在 `Modal.confirm` 的 `content` 里手写输入框。
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# 流水线与插件规则
|
||||||
|
|
||||||
|
项目最关键的架构概念是证书流水线。核心导出、关键抽象、插件目录和共享 helper 位置见 `.codex/repo-map.md`。
|
||||||
|
|
||||||
|
插件是核心能力,不是边缘功能。新增服务商、DNS 验证、证书部署、通知方式等能力,通常应该放在插件包里,或放在 `packages/ui/certd-server/src/plugins/<plugin-name>/` 下。
|
||||||
|
|
||||||
|
## 改动归属
|
||||||
|
|
||||||
|
修改证书申请、验证、部署或通知行为时,先判断改动属于哪里:
|
||||||
|
|
||||||
|
- ACME client 代码
|
||||||
|
- pipeline 核心抽象
|
||||||
|
- 后端 module/service/entity/controller
|
||||||
|
- 某个具体插件实现
|
||||||
|
- 前端 view/form/schema
|
||||||
|
|
||||||
|
如果只是某个服务商或部署目标的问题,不要轻易修改共享 pipeline/core 行为,除非确实是可复用的公共能力。
|
||||||
|
|
||||||
|
## ACME / EAB
|
||||||
|
|
||||||
|
- 公共 EAB(尤其是 Google EAB)可能只能创建一次 ACME 账号。要跨用户复用公共 EAB,应保存并复用同一个 ACME account private key;`accountUrl` 如果存到 `userContext` 里,只能视为当前用户缓存,因为 `userContext` 跟用户 id 走。
|
||||||
|
- ACME 协议的 `newAccount` 支持 `onlyReturnExisting`。使用同一个 account private key 调用 `newAccount({ onlyReturnExisting: true })` 可以取回已创建账号的 URL,且不会再次消费 EAB。
|
||||||
|
- 修改 EAB 的 `kid` 后,应重新生成绑定该 `kid` 的 account private key;否则应阻止继续申请并提示用户刷新账号私钥。
|
||||||
|
|
||||||
|
## 插件开发技能
|
||||||
|
|
||||||
|
仓库内置了 Certd 插件开发技能,供 Trae 和 Codex 共用:
|
||||||
|
|
||||||
|
- Trae 入口:`.trae/skills`
|
||||||
|
- Codex 入口:`.codex/skills`
|
||||||
|
|
||||||
|
其中 `.codex/skills` 是指向 `.trae/skills` 的目录链接,不要复制出第二份技能内容。更新技能时只维护 `.trae/skills` 下的原始文件,Codex 会通过 `.codex/skills` 读取同一份内容。
|
||||||
|
|
||||||
|
当前技能包括:
|
||||||
|
|
||||||
|
- `access-plugin-dev`:开发 Access 授权插件
|
||||||
|
- `dns-provider-dev`:开发 DNS Provider 插件
|
||||||
|
- `fast-crud-page-dev`:开发或重构前端 Fast Crud 列表管理页面
|
||||||
|
- `task-plugin-dev`:开发 Task 部署任务插件
|
||||||
|
- `plugin-converter`:将插件转换为 YAML 配置
|
||||||
|
|
||||||
|
做插件相关任务时,先读取对应技能目录下的 `SKILL.md`,再进入具体实现。若用户在插件开发中指出更好的做法,应总结并更新对应技能。
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# 测试与验证规则
|
||||||
|
|
||||||
|
实现新功能或修复行为缺陷前,先补对应单元测试,并先运行测试确认它处于失败状态;再实现功能或修复代码,反复运行聚焦单元测试直到通过。若某项改动确实不适合先写单元测试,应在回复中说明原因和替代验证方式。
|
||||||
|
|
||||||
|
后补单元测试时,应先基于对正确行为的实际预期编写测试,而不是为了迎合现有实现改写预期;如果运行后出现红灯,且通过测试需要修改已有实现,应先向用户确认这是确实的 bug,还是原本需求/既有行为就是如此;确认后再修改原始实现,避免把测试补充变成未经确认的行为改动。
|
||||||
|
|
||||||
|
## 后端单测
|
||||||
|
|
||||||
|
- 后端纯单元测试用例放在 `src` 目录内,并尽量与被测文件相邻,例如 `src/utils/random.test.ts`。
|
||||||
|
- 对应 `test:unit` 只跑 `src/**/*.test.ts`,构建/打包配置应排除这些 `*.test.ts` 文件。
|
||||||
|
- 单元测试需要 mock ESM 静态 import 时,优先使用 `esmock`,不要为了测试把业务代码改成构造函数注入或把逻辑挪到调用方。
|
||||||
|
- 各包 `test:unit` 脚本应显式设置 `NODE_ENV=unittest`。
|
||||||
|
|
||||||
|
## 运行方式
|
||||||
|
|
||||||
|
单个 monorepo 包运行单元测试时,优先使用 `corepack pnpm --dir <包目录> test:unit`,例如:
|
||||||
|
|
||||||
|
- `corepack pnpm --dir packages\ui\certd-server test:unit`
|
||||||
|
- `corepack pnpm --dir packages\core\basic test:unit`
|
||||||
|
- `corepack pnpm --dir packages\plugins\plugin-lib test:unit`
|
||||||
|
|
||||||
|
也可以用包名过滤,例如 `corepack pnpm --filter @certd/ui-server test:unit`。
|
||||||
|
|
||||||
|
前端 `packages\ui\certd-client` 暂时不跑单元测试。前端改动优先使用 Prettier/ESLint 做改动文件验证。
|
||||||
|
|
||||||
|
优先对改动包运行聚焦测试;只有跨包影响明显时再考虑全 monorepo 构建。
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
# Certd 仓库地图
|
||||||
|
|
||||||
|
本文档由 Codex 子智能体只读探索后整理,用于后续开发时快速定位代码。进入仓库仍应先读取根目录 `AGENTS.md`,本文件只作为导航补充。
|
||||||
|
|
||||||
|
## 顶层结构
|
||||||
|
|
||||||
|
Certd 是 pnpm + lerna-lite monorepo。
|
||||||
|
|
||||||
|
- `package.json`:根脚本与 workspace 元信息
|
||||||
|
- `pnpm-workspace.yaml`:workspace 匹配规则,包含 `packages/**`、`packages/ui/**`
|
||||||
|
- `lerna.json`:lerna-lite 配置
|
||||||
|
- `docs`:VitePress 文档站
|
||||||
|
- `docker`:Docker 安装和运行相关文件
|
||||||
|
- `packages/core/acme-client`:ACME 协议客户端
|
||||||
|
- `packages/core/basic`:共享基础工具
|
||||||
|
- `packages/core/pipeline`:流水线核心抽象、插件模型、执行上下文
|
||||||
|
- `packages/libs`:共享集成库
|
||||||
|
- `packages/plugins/plugin-lib`:证书、DNS Provider、格式转换等插件共享能力
|
||||||
|
- `packages/plugins/plugin-cert`:证书插件包入口
|
||||||
|
- `packages/ui/certd-server`:后端 Midway 服务
|
||||||
|
- `packages/ui/certd-client`:前端 Vue/Vite 管理台
|
||||||
|
- `packages/pro`:商业版独立 Git 工作区,需在该目录内单独检查状态
|
||||||
|
|
||||||
|
运行时或生成产物通常包括根目录 `node_modules`、`logs`、`output`、`lerna-debug.log`、`tmp-certd-client-vite*.log`,以及后端 `packages/ui/certd-server/data`、`packages/ui/certd-server/logs`、各包 `dist`、插件 metadata/yaml 导出结果。
|
||||||
|
|
||||||
|
## 常用验证
|
||||||
|
|
||||||
|
- 根目录启动后端生产模式:`pnpm run start:server`
|
||||||
|
- 后端开发服务:`corepack pnpm --dir packages\ui\certd-server dev`
|
||||||
|
- 后端聚焦单测:`corepack pnpm --dir packages\ui\certd-server test:unit`
|
||||||
|
- 后端完整测试:`corepack pnpm --dir packages\ui\certd-server test`
|
||||||
|
- 后端构建:`corepack pnpm --dir packages\ui\certd-server build`
|
||||||
|
- 前端开发服务:`corepack pnpm --dir packages\ui\certd-client dev`
|
||||||
|
- 前端构建:`corepack pnpm --dir packages\ui\certd-client build`
|
||||||
|
- 前端改动文件格式化:`packages\ui\certd-client\node_modules\.bin\prettier.cmd --write <files>`
|
||||||
|
- 前端改动文件 ESLint 修复:`packages\ui\certd-client\node_modules\.bin\eslint.cmd --fix <files>`
|
||||||
|
|
||||||
|
不要主动运行 `pnpm install`。前端不要运行 `pnpm tsc` / `vue-tsc`,当前依赖组合中 `vue-tsc@1.8.27` 会抛无效内部错误;前端 `test:unit` 也只是占位。
|
||||||
|
|
||||||
|
## 后端地图
|
||||||
|
|
||||||
|
主包:`packages/ui/certd-server`。
|
||||||
|
|
||||||
|
- `bootstrap.js`:Midway 启动入口,使用 `@midwayjs/bootstrap`
|
||||||
|
- `src/configuration.ts`:Midway 主配置,注册组件和全局中间件
|
||||||
|
- `src/config/config.default.ts`:端口、HTTPS、静态文件、cron、TypeORM、Flyway、上传、JWT、Swagger 默认配置
|
||||||
|
- `src/config/loader.ts`:读取 `.env`、`.env.<env>.yaml`,支持 `certd_` 前缀环境变量覆盖嵌套配置
|
||||||
|
- `src/modules`:业务模块根目录,例如 `basic`、`cert`、`cname`、`cron`、`login`、`monitor`、`open`、`pipeline`、`plugin`、`suite`、`sys`
|
||||||
|
- `src/controller`:API 入口,按 `basic`、`user`、`sys`、`openapi` 分组
|
||||||
|
- `db/migration`:SQL 迁移目录,TypeORM `synchronize: false`,表结构变更应配套迁移 SQL
|
||||||
|
|
||||||
|
测试使用 Mocha + Node `assert/strict`,纯单测放在 `src/**/*.test.ts`,尽量与被测文件相邻。可参考 `src/utils/random.test.ts`、`src/controller/basic/app-controller.test.ts`、`src/modules/pipeline/service/pipeline-service.test.ts`。
|
||||||
|
|
||||||
|
## 前端地图
|
||||||
|
|
||||||
|
主包:`packages/ui/certd-client`。
|
||||||
|
|
||||||
|
- `vite.config.ts`:Vite 配置,dev 端口 `3008`,`/api`、`/certd/api` 代理到后端 `127.0.0.1:7001`
|
||||||
|
- `src/main.ts`:Vue 启动入口,注册 AntDV、Vben、router、全局组件、插件和偏好设置
|
||||||
|
- `src/App.vue`:根组件,包含 `AConfigProvider`、`FsFormProvider`、`router-view`
|
||||||
|
- `src/router/index.ts`、`src/router/resolve.ts`:路由入口,使用 `createWebHashHistory`
|
||||||
|
- `src/router/source/modules/certd.ts`:Certd 主业务路由
|
||||||
|
- `src/store`:Pinia store,主要有 `user`、`project`、`settings`、`plugin`
|
||||||
|
- `src/api/service.ts`:Axios 封装
|
||||||
|
- `src/api/tools.ts`:错误与响应工具
|
||||||
|
- `src/views/certd`:核心业务视图,例如 `pipeline`、`cert`、`monitor`、`access`、`notification`、`open`、`project`、`suite`、`wallet`
|
||||||
|
- `src/locales`:国际化入口与语言包
|
||||||
|
|
||||||
|
列表管理、后台管理、记录查询、CRUD 表格页面优先使用 Fast Crud。开发前读取 `.trae/skills/fast-crud-page-dev/SKILL.md`。常见拆分是 `api.ts`、`crud.tsx`、`index.vue`。可参考 `src/views/certd/access`、`src/views/sys/suite/user-suite/crud.tsx`、`src/views/certd/wallet/index.vue`。内嵌 `fs-crud` 时要给外层稳定高度或完整 `flex: 1; min-height: 0` 链路。
|
||||||
|
|
||||||
|
## 流水线与插件地图
|
||||||
|
|
||||||
|
核心入口:`packages/core/pipeline/src/index.ts`,导出 `core`、`dt`、`access`、`registry`、`plugin`、`context`、`decorator`、`service`、`notification`。
|
||||||
|
|
||||||
|
- `packages/core/pipeline/src/plugin`:任务插件抽象,例如 `AbstractTaskPlugin`、`IsTaskPlugin`、`TaskInput`、`pluginRegistry`
|
||||||
|
- `packages/core/pipeline/src/access`:授权插件抽象,例如 `BaseAccess`、`IsAccess`、`AccessInput`、`accessRegistry`
|
||||||
|
- `packages/core/pipeline/src/dt/pipeline.ts`:`Pipeline`、`Stage`、`Task`、`RunStrategy` 等流水线数据结构
|
||||||
|
- `packages/core/pipeline/src/core`:执行器、上下文、运行历史、文件存储等
|
||||||
|
- `packages/core/pipeline/src/service`:CNAME、事件、配置、邮件、URL 等 pipeline service 接口
|
||||||
|
- `packages/ui/certd-server/src/plugins`:后端内置服务商、DNS、部署、通知等插件
|
||||||
|
- `packages/ui/certd-server/src/plugins/plugin-cert`:证书申请核心插件
|
||||||
|
- `packages/ui/certd-server/src/plugins/plugin-lib`:后端插件 helper/access
|
||||||
|
- `packages/plugins/plugin-lib/src/cert`:`CertReader`、`CertConverter`、DNS Provider 公共能力
|
||||||
|
- `packages/plugins/plugin-lib/src/cert/dns-provider`:`AbstractDnsProvider`、`dnsProviderRegistry`、`DomainParser`
|
||||||
|
|
||||||
|
插件开发技能入口:
|
||||||
|
|
||||||
|
- `.trae/skills/dns-provider-dev/SKILL.md`:DNS Provider 插件
|
||||||
|
- `.trae/skills/task-plugin-dev/SKILL.md`:Task 部署任务插件
|
||||||
|
- `.trae/skills/access-plugin-dev/SKILL.md`:Access 授权插件
|
||||||
|
- `.trae/skills/plugin-converter/SKILL.md`:插件转 YAML 配置
|
||||||
|
|
||||||
|
改动归属判断:
|
||||||
|
|
||||||
|
- ACME 协议、EAB、账号、订单、挑战流程:优先看 `packages/core/acme-client` 或 `packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/acme.ts`
|
||||||
|
- 流水线执行、任务生命周期、输入输出、注册机制:看 `packages/core/pipeline`
|
||||||
|
- 单个云厂商 DNS 验证、证书部署、API 调用失败:改对应 `packages/ui/certd-server/src/plugins/plugin-xxx`
|
||||||
|
- 通用证书读取、DNS Provider 公共能力、格式转换:改 `packages/plugins/plugin-lib`
|
||||||
|
- 后端业务数据、接口、实体、权限、迁移:改 `packages/ui/certd-server/src/modules` 与 `src/controller`
|
||||||
|
- 表单、列表、插件配置 UI:改 `packages/ui/certd-client/src/views/certd` 及对应 `src/api`
|
||||||
|
|
||||||
|
原则:如果只是单个服务商或部署目标的问题,不动共享 pipeline/core;只有可复用的公共语义或跨插件一致行为,才考虑上移到 `packages/core/pipeline` 或 `packages/plugins/plugin-lib`。
|
||||||
|
|
||||||
|
## Git 注意事项
|
||||||
|
|
||||||
|
子智能体探索时根仓库 `git status --short` 为空。`packages/pro` 也是独立仓库且当时未显示未提交改动,但曾出现无法删除 `packages/pro/.git/index.lock` 的警告;后续操作 pro 仓库前应先检查该锁文件或占用状态。
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../.trae/skills
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#
|
||||||
|
# http://editorconfig.org
|
||||||
|
#
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: greper
|
||||||
|
buy_me_a_coffee: greper
|
||||||
|
custom: ['https://afdian.com/a/greper']
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
name: Plugin Apply
|
||||||
|
about: 部署插件申请支持
|
||||||
|
title: '[Plugin] '
|
||||||
|
labels: feature
|
||||||
|
---
|
||||||
|
|
||||||
|
> > 感谢您支持certd,请按如下规范提交issue
|
||||||
|
> > 如果有条件,请尽量在[github上提交](https://github.com/certd/certd/issues)
|
||||||
|
|
||||||
|
# 新部署插件申请支持
|
||||||
|
|
||||||
|
## 1. 需求描述
|
||||||
|
|
||||||
|
`请在此处简要描述你的需求`
|
||||||
|
|
||||||
|
## 2. 要部署证书应用的信息
|
||||||
|
|
||||||
|
1. 应用名称:
|
||||||
|
|
||||||
|
2. 应用网址/项目地址/官方网站:
|
||||||
|
|
||||||
|
3. 管理证书界面截图(或者手动部署证书方式介绍及截图):
|
||||||
|
|
||||||
|
4. 是否有API接口,接口地址:
|
||||||
|
|
||||||
|
5. 如果没有API接口,网页登录是否需要验证码:
|
||||||
|
|
||||||
|
6. 是否可以提供测试账号?(如果可以请留下联系方式或者加作者好友)
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
name: DNS Provider Apply
|
||||||
|
about: 域名提供商申请支持
|
||||||
|
title: '[DNS] '
|
||||||
|
labels: feature
|
||||||
|
---
|
||||||
|
|
||||||
|
> 感谢您支持certd,请按如下规范提交issue
|
||||||
|
> 如果有条件,请尽量在[github上提交](https://github.com/certd/certd/issues)
|
||||||
|
|
||||||
|
# 新域名提供商支持申请
|
||||||
|
|
||||||
|
## 1. 基本信息
|
||||||
|
|
||||||
|
请填写如下内容:
|
||||||
|
|
||||||
|
1. 域名提供商名称:
|
||||||
|
|
||||||
|
2. 管理页面地址:
|
||||||
|
|
||||||
|
3. 是否有API接口,接口地址:
|
||||||
|
|
||||||
|
4. 如果没有API接口,网页登录是否有验证码:
|
||||||
|
|
||||||
|
5. 是否可以提供测试账号?(如果可以请留下联系方式或者加作者好友)
|
||||||
|
|
||||||
|
## 2. 截图
|
||||||
|
|
||||||
|
`域名管理页面截图`
|
||||||
@@ -1,21 +1,32 @@
|
|||||||
> 感谢您支持certd,请按如下规范提交issue
|
---
|
||||||
|
name: Bug Report
|
||||||
|
about: 错误或问题报告
|
||||||
|
title: '[BUG] '
|
||||||
|
labels: bug
|
||||||
|
---
|
||||||
|
|
||||||
|
> 感谢您支持certd,请按如下规范提交issue
|
||||||
> 如果有条件,请尽量在[github上提交](https://github.com/certd/certd/issues)
|
> 如果有条件,请尽量在[github上提交](https://github.com/certd/certd/issues)
|
||||||
|
|
||||||
|
# bug提交
|
||||||
|
|
||||||
|
## 1、问题描述
|
||||||
|
|
||||||
## 一、问题描述
|
|
||||||
`请在此处简要描述你所遇到的问题,必要时请贴出相关截图辅助理解和定位`
|
`请在此处简要描述你所遇到的问题,必要时请贴出相关截图辅助理解和定位`
|
||||||
|
|
||||||
### 复现步骤
|
### 2、复现步骤
|
||||||
|
|
||||||
`请描述复现问题的详细步骤`
|
`请描述复现问题的详细步骤`
|
||||||
`如果非示例页面的问题,最好能提供最小复现示例的代码、或者仓库链接`
|
`如果非示例页面的问题,最好能提供最小复现示例的代码、或者仓库链接`
|
||||||
|
|
||||||
|
### 3.报错截图
|
||||||
|
|
||||||
### 报错截图
|
|
||||||
`请贴出报错日志截图`
|
`请贴出报错日志截图`
|
||||||
|
|
||||||
### 效果截图
|
### 4、效果截图
|
||||||
|
|
||||||
`请贴出效果截图`
|
`请贴出效果截图`
|
||||||
#### 1. 期望效果
|
|
||||||
|
|
||||||
#### 2. 实际效果
|
#### 4.1. 期望效果
|
||||||
|
|
||||||
|
#### 4.2. 实际效果
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
name: Feature Request
|
||||||
|
about: 新需求、新特性申请支持
|
||||||
|
title: '[Feature] '
|
||||||
|
labels: feature
|
||||||
|
---
|
||||||
|
|
||||||
|
> > 感谢您支持certd,请按如下规范提交issue
|
||||||
|
> > 如果有条件,请尽量在[github上提交](https://github.com/certd/certd/issues)
|
||||||
|
|
||||||
|
# 新特性申请
|
||||||
|
|
||||||
|
> 注意:这里仅供如果是要申请新的部署插件,请提交插件申请
|
||||||
|
|
||||||
|
## 1. 需求描述,需求背景
|
||||||
|
|
||||||
|
`请在此处简要描述你所遇到的问题,必要时请贴出相关截图辅助理解`
|
||||||
|
|
||||||
|
## 2. 期望效果
|
||||||
|
|
||||||
|
`必要时可以截图描述你的期望效果`
|
||||||
|
|
||||||
|
## 3. 你的解决方案
|
||||||
|
|
||||||
|
`如果你有解决方案,请描述你的方案`
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
name: build-image-for-test
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: ['v2-dev']
|
|
||||||
paths:
|
|
||||||
- "build-dev.trigger"
|
|
||||||
|
|
||||||
# schedule:
|
|
||||||
# - # 国际时间 19:17 执行,北京时间3:17 ↙↙↙ 改成你想要每天自动执行的时间
|
|
||||||
# - cron: '17 19 * * *'
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-certd-image:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout Code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
ref: v2-dev
|
|
||||||
|
|
||||||
- name: get_certd_version
|
|
||||||
id: get_certd_version
|
|
||||||
uses: actions/github-script@v6
|
|
||||||
with:
|
|
||||||
result-encoding: string
|
|
||||||
script: |
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const pnpmWorkspace = "./pnpm-workspace.yaml";
|
|
||||||
fs.unlinkSync(pnpmWorkspace)
|
|
||||||
const jsonFilePath = "./packages/ui/certd-server/package.json";
|
|
||||||
const jsonContent = fs.readFileSync(jsonFilePath, 'utf-8');
|
|
||||||
const pkg = JSON.parse(jsonContent)
|
|
||||||
console.log("certd_version:",pkg.version);
|
|
||||||
return pkg.version
|
|
||||||
# - name: Use Node.js
|
|
||||||
# uses: actions/setup-node@v4
|
|
||||||
# with:
|
|
||||||
# node-version: 18
|
|
||||||
# cache: 'npm'
|
|
||||||
# working-directory: ./packages/ui/certd-client
|
|
||||||
- run: |
|
|
||||||
npm install -g pnpm@8.15.7
|
|
||||||
pnpm install
|
|
||||||
npm run build
|
|
||||||
working-directory: ./packages/ui/certd-client
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Login to aliyun container Registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: registry.cn-shenzhen.aliyuncs.com
|
|
||||||
username: ${{ secrets.aliyun_cs_username }}
|
|
||||||
password: ${{ secrets.aliyun_cs_password }}
|
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.dockerhub_username }}
|
|
||||||
password: ${{ secrets.dockerhub_password }}
|
|
||||||
|
|
||||||
- name: Build default platforms
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
push: true
|
|
||||||
context: ./packages/ui/
|
|
||||||
tags: |
|
|
||||||
registry.cn-shenzhen.aliyuncs.com/handsfree/certd-dev:latest
|
|
||||||
greper/certd-dev:latest
|
|
||||||
|
|
||||||
@@ -3,13 +3,14 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: ['v2-dev']
|
branches: ['v2-dev']
|
||||||
paths:
|
paths:
|
||||||
- "build.trigger"
|
- 'trigger/build.trigger'
|
||||||
|
workflow_dispatch: # 添加手动触发
|
||||||
# schedule:
|
# schedule:
|
||||||
# - # 国际时间 19:17 执行,北京时间3:17 ↙↙↙ 改成你想要每天自动执行的时间
|
# - # 国际时间 19:17 执行,北京时间3:17 ↙↙↙ 改成你想要每天自动执行的时间
|
||||||
# - cron: '17 19 * * *'
|
# - cron: '17 19 * * *'
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-certd-image:
|
build-certd-image:
|
||||||
@@ -20,7 +21,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
lfs: true
|
lfs: true
|
||||||
|
ref: 'v2-dev'
|
||||||
- name: get_certd_version
|
- name: get_certd_version
|
||||||
id: get_certd_version
|
id: get_certd_version
|
||||||
uses: actions/github-script@v6
|
uses: actions/github-script@v6
|
||||||
@@ -36,14 +37,14 @@ jobs:
|
|||||||
const pkg = JSON.parse(jsonContent)
|
const pkg = JSON.parse(jsonContent)
|
||||||
console.log("certd_version:",pkg.version);
|
console.log("certd_version:",pkg.version);
|
||||||
return pkg.version
|
return pkg.version
|
||||||
# - name: Use Node.js
|
# - name: Use Node.js
|
||||||
# uses: actions/setup-node@v4
|
# uses: actions/setup-node@v4
|
||||||
# with:
|
# with:
|
||||||
# node-version: 18
|
# node-version: 18
|
||||||
# cache: 'npm'
|
# cache: 'npm'
|
||||||
# working-directory: ./packages/ui/certd-client
|
# working-directory: ./packages/ui/certd-client
|
||||||
- run: |
|
- run: |
|
||||||
npm install -g pnpm@8.15.7
|
npm install -g pnpm@10.33.4
|
||||||
pnpm install
|
pnpm install
|
||||||
npm run build
|
npm run build
|
||||||
working-directory: ./packages/ui/certd-client
|
working-directory: ./packages/ui/certd-client
|
||||||
@@ -61,6 +62,13 @@ jobs:
|
|||||||
username: ${{ secrets.aliyun_cs_username }}
|
username: ${{ secrets.aliyun_cs_username }}
|
||||||
password: ${{ secrets.aliyun_cs_password }}
|
password: ${{ secrets.aliyun_cs_password }}
|
||||||
|
|
||||||
|
- name: Login to GitHub Packages
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
@@ -74,31 +82,4 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
context: ./packages/ui/
|
context: ./packages/ui/
|
||||||
tags: |
|
tags: |
|
||||||
registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
|
||||||
registry.cn-shenzhen.aliyuncs.com/handsfree/certd:${{steps.get_certd_version.outputs.result}}
|
registry.cn-shenzhen.aliyuncs.com/handsfree/certd:${{steps.get_certd_version.outputs.result}}
|
||||||
greper/certd:latest
|
|
||||||
greper/certd:${{steps.get_certd_version.outputs.result}}
|
|
||||||
|
|
||||||
# - name: Build armv7
|
|
||||||
# uses: docker/build-push-action@v6
|
|
||||||
# with:
|
|
||||||
# platforms: linux/arm/v7
|
|
||||||
# push: true
|
|
||||||
# context: ./packages/ui/
|
|
||||||
# tags: |
|
|
||||||
# registry.cn-shenzhen.aliyuncs.com/handsfree/certd:armv7
|
|
||||||
# registry.cn-shenzhen.aliyuncs.com/handsfree/certd:${{steps.get_certd_version.outputs.result}}-armv7
|
|
||||||
# greper/certd:armv7
|
|
||||||
# greper/certd:${{steps.get_certd_version.outputs.result}}-armv7
|
|
||||||
|
|
||||||
# - name: Build agent
|
|
||||||
# uses: docker/build-push-action@v6
|
|
||||||
# with:
|
|
||||||
# platforms: linux/amd64,linux/arm64
|
|
||||||
# push: true
|
|
||||||
# context: ./packages/ui/agent/
|
|
||||||
# tags: |
|
|
||||||
# registry.cn-shenzhen.aliyuncs.com/handsfree/certd-agent:latest
|
|
||||||
# registry.cn-shenzhen.aliyuncs.com/handsfree/certd-agent:${{steps.get_certd_version.outputs.result}}
|
|
||||||
# greper/certd-agent:latest
|
|
||||||
# greper/certd-agent:${{steps.get_certd_version.outputs.result}}
|
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: ['v2-dev']
|
branches: ['v2-dev']
|
||||||
paths:
|
paths:
|
||||||
- "deploy.trigger"
|
- 'trigger/deploy.trigger'
|
||||||
workflow_run:
|
workflow_run:
|
||||||
workflows: [ "build-image" ]
|
workflows: ['build-image']
|
||||||
types:
|
types:
|
||||||
- completed
|
- completed
|
||||||
|
workflow_dispatch: # 添加手动触发
|
||||||
|
|
||||||
# schedule:
|
# schedule:
|
||||||
# - # 国际时间 19:17 执行,北京时间3:17 ↙↙↙ 改成你想要每天自动执行的时间
|
# - # 国际时间 19:17 执行,北京时间3:17 ↙↙↙ 改成你想要每天自动执行的时间
|
||||||
@@ -18,12 +19,14 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
deploy-certd-demo:
|
deploy-certd-demo:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: v2-dev
|
ref: v2-dev
|
||||||
|
|
||||||
- name: get_certd_version
|
- name: get_certd_version
|
||||||
id: get_certd_version
|
id: get_certd_version
|
||||||
uses: actions/github-script@v6
|
uses: actions/github-script@v6
|
||||||
@@ -41,9 +44,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
time: '10' # for 60 seconds
|
time: '10' # for 60 seconds
|
||||||
- name: deploy-certd-demo
|
- name: deploy-certd-demo
|
||||||
uses: tyrrrz/action-http-request@master
|
uses: tyrrrz/action-http-request@prime
|
||||||
with:
|
with:
|
||||||
url: http://flow-openapi.aliyun.com/pipeline/webhook/lzCzlGrLCOHQaTMMt0mG
|
# 通过webhook 触发 certd-demo来部署
|
||||||
|
url: ${{ secrets.WEBHOOK_CERTD_DEMO }}
|
||||||
method: POST
|
method: POST
|
||||||
headers: |
|
headers: |
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
@@ -53,15 +57,3 @@ jobs:
|
|||||||
}
|
}
|
||||||
retry-count: 3
|
retry-count: 3
|
||||||
retry-delay: 5000
|
retry-delay: 5000
|
||||||
|
|
||||||
- name: deploy-certd-doc
|
|
||||||
uses: tyrrrz/action-http-request@master
|
|
||||||
with:
|
|
||||||
url: http://flow-openapi.aliyun.com/pipeline/webhook/IiSxLDp9aOhgDUxJPytv
|
|
||||||
method: POST
|
|
||||||
body: |
|
|
||||||
{}
|
|
||||||
headers: |
|
|
||||||
Content-Type: application/json
|
|
||||||
retry-count: 3
|
|
||||||
retry-delay: 5000
|
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
name: publish-atomgit
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ['v2-dev']
|
||||||
|
paths:
|
||||||
|
- 'trigger/publish.trigger'
|
||||||
|
workflow_run:
|
||||||
|
workflows: ['build-image-for-release']
|
||||||
|
types:
|
||||||
|
- completed
|
||||||
|
workflow_dispatch: # 添加手动触发
|
||||||
|
# schedule:
|
||||||
|
# - # 国际时间 19:17 执行,北京时间3:17 ↙↙↙ 改成你想要每天自动执行的时间
|
||||||
|
# - cron: '17 19 * * *'
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish-atomgit:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: |
|
||||||
|
github.event_name == 'workflow_dispatch' ||
|
||||||
|
(github.event.workflow_run.conclusion == 'success')
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
lfs: true
|
||||||
|
ref: 'v2-dev'
|
||||||
|
|
||||||
|
- name: get_certd_version
|
||||||
|
id: get_certd_version
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
result-encoding: string
|
||||||
|
script: |
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const pnpmWorkspace = "./pnpm-workspace.yaml";
|
||||||
|
fs.unlinkSync(pnpmWorkspace)
|
||||||
|
const jsonFilePath = "./packages/ui/certd-server/package.json";
|
||||||
|
const jsonContent = fs.readFileSync(jsonFilePath, 'utf-8');
|
||||||
|
const pkg = JSON.parse(jsonContent)
|
||||||
|
console.log("certd_version:",pkg.version);
|
||||||
|
return pkg.version
|
||||||
|
- run: |
|
||||||
|
npm install -g pnpm@10.33.4
|
||||||
|
pnpm install
|
||||||
|
npm run build
|
||||||
|
working-directory: ./packages/ui/certd-client
|
||||||
|
|
||||||
|
- name: publish_to_atomgit
|
||||||
|
id: publish_to_atomgit
|
||||||
|
run: |
|
||||||
|
rootDir=$(pwd)
|
||||||
|
rm -rf ./packages/ui/certd-client/dist/**/*.gz
|
||||||
|
cd ./packages/ui/certd-client/dist && zip -r ../ui.zip .
|
||||||
|
cd $rootDir
|
||||||
|
export ATOMGIT_TOKEN=${{ secrets.ATOMGIT_TOKEN }}
|
||||||
|
pnpm install
|
||||||
|
npm run publish_to_atomgit
|
||||||
|
working-directory: ./
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
name: publish-gitee
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ['v2-dev']
|
||||||
|
paths:
|
||||||
|
- 'trigger/publish.trigger'
|
||||||
|
workflow_run:
|
||||||
|
workflows: ['build-image-for-release']
|
||||||
|
types:
|
||||||
|
- completed
|
||||||
|
workflow_dispatch: # 添加手动触发
|
||||||
|
# schedule:
|
||||||
|
# - # 国际时间 19:17 执行,北京时间3:17 ↙↙↙ 改成你想要每天自动执行的时间
|
||||||
|
# - cron: '17 19 * * *'
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish-gitee:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: |
|
||||||
|
github.event_name == 'workflow_dispatch' ||
|
||||||
|
(github.event.workflow_run.conclusion == 'success')
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
lfs: true
|
||||||
|
ref: 'v2-dev'
|
||||||
|
|
||||||
|
- name: publish_to_gitee
|
||||||
|
id: publish_to_gitee
|
||||||
|
run: |
|
||||||
|
export GITEE_TOKEN=${{ secrets.GITEE_TOKEN }}
|
||||||
|
rm -rf ./pnpm*.yaml
|
||||||
|
npm install -g pnpm@10.33.4
|
||||||
|
pnpm install
|
||||||
|
npm run publish_to_gitee
|
||||||
|
working-directory: ./
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
name: publish-github
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ['v2-dev']
|
||||||
|
paths:
|
||||||
|
- 'trigger/publish.trigger'
|
||||||
|
workflow_run:
|
||||||
|
workflows: ['build-image-for-release']
|
||||||
|
types:
|
||||||
|
- completed
|
||||||
|
workflow_dispatch: # 添加手动触发
|
||||||
|
# schedule:
|
||||||
|
# - # 国际时间 19:17 执行,北京时间3:17 ↙↙↙ 改成你想要每天自动执行的时间
|
||||||
|
# - cron: '17 19 * * *'
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish-github:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: |
|
||||||
|
github.event_name == 'workflow_dispatch' ||
|
||||||
|
(github.event.workflow_run.conclusion == 'success')
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
lfs: true
|
||||||
|
ref: 'v2-dev'
|
||||||
|
|
||||||
|
- name: publish_to_github
|
||||||
|
id: publish_to_github
|
||||||
|
run: |
|
||||||
|
export GITHUB_TOKEN=${{ secrets.GH_TOKEN }}
|
||||||
|
rm -rf ./pnpm*.yaml
|
||||||
|
npm install -g pnpm@10.33.4
|
||||||
|
pnpm install
|
||||||
|
npm run publish_to_github
|
||||||
|
working-directory: ./
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
name: build-image-for-release
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ['v2-dev']
|
||||||
|
paths:
|
||||||
|
- 'trigger/release.trigger'
|
||||||
|
workflow_dispatch: # 添加手动触发
|
||||||
|
# workflow_run:
|
||||||
|
# workflows: [ "deploy-demo" ]
|
||||||
|
# types:
|
||||||
|
# - completed
|
||||||
|
|
||||||
|
# schedule:
|
||||||
|
# - # 国际时间 19:17 执行,北京时间3:17 ↙↙↙ 改成你想要每天自动执行的时间
|
||||||
|
# - cron: '17 19 * * *'
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-certd-image:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
lfs: true
|
||||||
|
ref: 'v2-dev'
|
||||||
|
|
||||||
|
- name: get_certd_version
|
||||||
|
id: get_certd_version
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
result-encoding: string
|
||||||
|
script: |
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const pnpmWorkspace = "./pnpm-workspace.yaml";
|
||||||
|
fs.unlinkSync(pnpmWorkspace)
|
||||||
|
const jsonFilePath = "./packages/ui/certd-server/package.json";
|
||||||
|
const jsonContent = fs.readFileSync(jsonFilePath, 'utf-8');
|
||||||
|
const pkg = JSON.parse(jsonContent)
|
||||||
|
console.log("certd_version:",pkg.version);
|
||||||
|
return pkg.version
|
||||||
|
# - name: Use Node.js
|
||||||
|
# uses: actions/setup-node@v4
|
||||||
|
# with:
|
||||||
|
# node-version: 18
|
||||||
|
# cache: 'npm'
|
||||||
|
# working-directory: ./packages/ui/certd-client
|
||||||
|
- run: |
|
||||||
|
npm install -g pnpm@10.33.4
|
||||||
|
pnpm install
|
||||||
|
npm run build
|
||||||
|
working-directory: ./packages/ui/certd-client
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to aliyun container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: registry.cn-shenzhen.aliyuncs.com
|
||||||
|
username: ${{ secrets.aliyun_cs_username }}
|
||||||
|
password: ${{ secrets.aliyun_cs_password }}
|
||||||
|
|
||||||
|
- name: Login to GitHub Packages
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.dockerhub_username }}
|
||||||
|
password: ${{ secrets.dockerhub_password }}
|
||||||
|
|
||||||
|
- name: Build default platforms
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
context: ./packages/ui/
|
||||||
|
tags: |
|
||||||
|
registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
||||||
|
registry.cn-shenzhen.aliyuncs.com/handsfree/certd:${{steps.get_certd_version.outputs.result}}
|
||||||
|
greper/certd:latest
|
||||||
|
greper/certd:${{steps.get_certd_version.outputs.result}}
|
||||||
|
ghcr.io/${{ github.repository }}:latest
|
||||||
|
ghcr.io/${{ github.repository }}:${{steps.get_certd_version.outputs.result}}
|
||||||
|
- name: Build armv7
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
platforms: linux/arm/v7
|
||||||
|
push: true
|
||||||
|
context: ./packages/ui/
|
||||||
|
tags: |
|
||||||
|
registry.cn-shenzhen.aliyuncs.com/handsfree/certd:armv7
|
||||||
|
registry.cn-shenzhen.aliyuncs.com/handsfree/certd:${{steps.get_certd_version.outputs.result}}-armv7
|
||||||
|
greper/certd:armv7
|
||||||
|
greper/certd:${{steps.get_certd_version.outputs.result}}-armv7
|
||||||
|
ghcr.io/${{ github.repository }}:armv7
|
||||||
|
ghcr.io/${{ github.repository }}:${{steps.get_certd_version.outputs.result}}-armv7
|
||||||
|
|
||||||
|
# - name: Build agent
|
||||||
|
# uses: docker/build-push-action@v6
|
||||||
|
# with:
|
||||||
|
# platforms: linux/amd64,linux/arm64
|
||||||
|
# push: true
|
||||||
|
# context: ./packages/ui/agent/
|
||||||
|
# tags: |
|
||||||
|
# registry.cn-shenzhen.aliyuncs.com/handsfree/certd-agent:latest
|
||||||
|
# registry.cn-shenzhen.aliyuncs.com/handsfree/certd-agent:${{steps.get_certd_version.outputs.result}}
|
||||||
|
# greper/certd-agent:latest
|
||||||
|
# greper/certd-agent:${{steps.get_certd_version.outputs.result}}
|
||||||
|
- name: deploy-certd-doc
|
||||||
|
uses: tyrrrz/action-http-request@prime
|
||||||
|
with:
|
||||||
|
url: ${{ secrets.WEBHOOK_CERTD_DOC }}
|
||||||
|
method: POST
|
||||||
|
body: |
|
||||||
|
{
|
||||||
|
"CERTD_VERSION": "1.0.0"
|
||||||
|
}
|
||||||
|
headers: |
|
||||||
|
Content-Type: application/json
|
||||||
|
retry-count: 3
|
||||||
|
retry-delay: 5000
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
name: sync-to-atomgit-dev
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ['v2-dev']
|
||||||
|
# schedule:
|
||||||
|
# - # 国际时间 19:17 执行,北京时间3:17 ↙↙↙ 改成你想要每天自动执行的时间
|
||||||
|
# - cron: '17 19 * * *'
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
sync:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout work repo # 1. 检出当前仓库(certd-sync-work)
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
lfs: true
|
||||||
|
ref: v2-dev
|
||||||
|
- name: Set git user # 2. 给git命令设置用户名和邮箱,↙↙↙ 改成你的name和email
|
||||||
|
run: |
|
||||||
|
git config --global user.name "xiaojunnuo"
|
||||||
|
git config --global user.email "xiaojunnuo@qq.com"
|
||||||
|
|
||||||
|
- name: Set git token # 3. 给git命令设置token,用于push到目标仓库
|
||||||
|
uses: de-vri-es/setup-git-credentials@v2
|
||||||
|
with: # token 格式为: username:password
|
||||||
|
credentials: https://greper:${{secrets.ATOMGIT_TOKEN}}@atomgit.com
|
||||||
|
|
||||||
|
- name: push to atomgit # 4. 执行同步
|
||||||
|
run: |
|
||||||
|
git remote add upstream https://atomgit.com/certd/certd
|
||||||
|
git push --set-upstream upstream v2-dev
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
name: sync-to-atomgit
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ['v2']
|
||||||
|
# schedule:
|
||||||
|
# - # 国际时间 19:17 执行,北京时间3:17 ↙↙↙ 改成你想要每天自动执行的时间
|
||||||
|
# - cron: '17 19 * * *'
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
sync:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout work repo # 1. 检出当前仓库(certd-sync-work)
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
lfs: true
|
||||||
|
ref: v2
|
||||||
|
- name: Set git user # 2. 给git命令设置用户名和邮箱,↙↙↙ 改成你的name和email
|
||||||
|
run: |
|
||||||
|
git config --global user.name "xiaojunnuo"
|
||||||
|
git config --global user.email "xiaojunnuo@qq.com"
|
||||||
|
|
||||||
|
- name: Set git token # 3. 给git命令设置token,用于push到目标仓库
|
||||||
|
uses: de-vri-es/setup-git-credentials@v2
|
||||||
|
with: # token 格式为: username:password
|
||||||
|
credentials: https://greper:${{secrets.ATOMGIT_TOKEN}}@atomgit.com
|
||||||
|
|
||||||
|
- name: push to atomgit # 4. 执行同步
|
||||||
|
run: |
|
||||||
|
git remote add upstream https://atomgit.com/certd/certd
|
||||||
|
git push --set-upstream upstream v2
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
name: sync-to-cnb-dev
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ['v2-dev']
|
||||||
|
# schedule:
|
||||||
|
# - # 国际时间 19:17 执行,北京时间3:17 ↙↙↙ 改成你想要每天自动执行的时间
|
||||||
|
# - cron: '17 19 * * *'
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
sync:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout work repo # 1. 检出当前仓库(certd-sync-work)
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
lfs: true
|
||||||
|
ref: v2-dev
|
||||||
|
- name: Set git user # 2. 给git命令设置用户名和邮箱,↙↙↙ 改成你的name和email
|
||||||
|
run: |
|
||||||
|
git config --global user.name "xiaojunnuo"
|
||||||
|
git config --global user.email "xiaojunnuo@qq.com"
|
||||||
|
|
||||||
|
- name: Set git token # 3. 给git命令设置token,用于push到目标仓库
|
||||||
|
uses: de-vri-es/setup-git-credentials@v2
|
||||||
|
with: # token 格式为: username:password
|
||||||
|
credentials: https://cnb:${{secrets.CNB_TOKEN}}@cnb.cool
|
||||||
|
|
||||||
|
- name: push to cnb # 4. 执行同步
|
||||||
|
run: |
|
||||||
|
git remote add upstream https://cnb.cool/certd/certd.git
|
||||||
|
git push --set-upstream upstream v2-dev
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
name: sync-to-cnb
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ['v2']
|
||||||
|
# schedule:
|
||||||
|
# - # 国际时间 19:17 执行,北京时间3:17 ↙↙↙ 改成你想要每天自动执行的时间
|
||||||
|
# - cron: '17 19 * * *'
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
sync:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout work repo # 1. 检出当前仓库(certd-sync-work)
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
lfs: true
|
||||||
|
- name: Set git user # 2. 给git命令设置用户名和邮箱,↙↙↙ 改成你的name和email
|
||||||
|
run: |
|
||||||
|
git config --global user.name "xiaojunnuo"
|
||||||
|
git config --global user.email "xiaojunnuo@qq.com"
|
||||||
|
|
||||||
|
- name: Set git token # 3. 给git命令设置token,用于push到目标仓库
|
||||||
|
uses: de-vri-es/setup-git-credentials@v2
|
||||||
|
with: # token 格式为: username:password
|
||||||
|
credentials: https://cnb:${{secrets.CNB_TOKEN}}@cnb.cool
|
||||||
|
|
||||||
|
- name: push to cnb # 4. 执行同步
|
||||||
|
run: |
|
||||||
|
git remote add upstream https://cnb.cool/certd/certd.git
|
||||||
|
git push --set-upstream upstream v2
|
||||||
@@ -12,23 +12,23 @@ jobs:
|
|||||||
sync:
|
sync:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout work repo # 1. 检出当前仓库(certd-sync-work)
|
- name: Checkout work repo # 1. 检出当前仓库(certd-sync-work)
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
lfs: true
|
lfs: true
|
||||||
- name: Set git user # 2. 给git命令设置用户名和邮箱,↙↙↙ 改成你的name和email
|
ref: v2-dev
|
||||||
|
- name: Set git user # 2. 给git命令设置用户名和邮箱,↙↙↙ 改成你的name和email
|
||||||
run: |
|
run: |
|
||||||
git config --global user.name "xiaojunnuo"
|
git config --global user.name "xiaojunnuo"
|
||||||
git config --global user.email "xiaojunnuo@qq.com"
|
git config --global user.email "xiaojunnuo@qq.com"
|
||||||
|
|
||||||
- name: Set git token # 3. 给git命令设置token,用于push到目标仓库
|
- name: Set git token # 3. 给git命令设置token,用于push到目标仓库
|
||||||
uses: de-vri-es/setup-git-credentials@v2
|
uses: de-vri-es/setup-git-credentials@v2
|
||||||
with: # token 格式为: username:password
|
with: # token 格式为: username:password
|
||||||
credentials: https://${{secrets.PUSH_TOKEN_GITEE}}@gitee.com
|
credentials: https://${{secrets.PUSH_TOKEN_GITEE}}@gitee.com
|
||||||
|
|
||||||
- name: push to gitee # 4. 执行同步
|
- name: push to gitee # 4. 执行同步
|
||||||
run: |
|
run: |
|
||||||
git remote add upstream https://gitee.com/certd/certd
|
git remote add upstream https://gitee.com/certd/certd
|
||||||
git push --set-upstream upstream v2-dev
|
git push --set-upstream upstream v2-dev
|
||||||
|
|
||||||
|
|||||||
@@ -12,23 +12,22 @@ jobs:
|
|||||||
sync:
|
sync:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout work repo # 1. 检出当前仓库(certd-sync-work)
|
- name: Checkout work repo # 1. 检出当前仓库(certd-sync-work)
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
lfs: true
|
lfs: true
|
||||||
- name: Set git user # 2. 给git命令设置用户名和邮箱,↙↙↙ 改成你的name和email
|
- name: Set git user # 2. 给git命令设置用户名和邮箱,↙↙↙ 改成你的name和email
|
||||||
run: |
|
run: |
|
||||||
git config --global user.name "xiaojunnuo"
|
git config --global user.name "xiaojunnuo"
|
||||||
git config --global user.email "xiaojunnuo@qq.com"
|
git config --global user.email "xiaojunnuo@qq.com"
|
||||||
|
|
||||||
- name: Set git token # 3. 给git命令设置token,用于push到目标仓库
|
- name: Set git token # 3. 给git命令设置token,用于push到目标仓库
|
||||||
uses: de-vri-es/setup-git-credentials@v2
|
uses: de-vri-es/setup-git-credentials@v2
|
||||||
with: # token 格式为: username:password
|
with: # token 格式为: username:password
|
||||||
credentials: https://${{secrets.PUSH_TOKEN_GITEE}}@gitee.com
|
credentials: https://${{secrets.PUSH_TOKEN_GITEE}}@gitee.com
|
||||||
|
|
||||||
- name: push to gitee # 4. 执行同步
|
- name: push to gitee # 4. 执行同步
|
||||||
run: |
|
run: |
|
||||||
git remote add upstream https://gitee.com/certd/certd
|
git remote add upstream https://gitee.com/certd/certd
|
||||||
git push --set-upstream upstream v2
|
git push --set-upstream upstream v2
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
./packages/core/lego
|
./packages/core/lego
|
||||||
# IntelliJ project files
|
# IntelliJ project files
|
||||||
.vscode/
|
|
||||||
node_modules/
|
node_modules/
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
@@ -17,6 +16,7 @@ gen
|
|||||||
/test/*.private.*
|
/test/*.private.*
|
||||||
|
|
||||||
/*.log
|
/*.log
|
||||||
|
nohup.out
|
||||||
|
|
||||||
/packages/ui/*/.idea
|
/packages/ui/*/.idea
|
||||||
/packages/ui/*/node_modules
|
/packages/ui/*/node_modules
|
||||||
@@ -29,5 +29,13 @@ test/**/*.js
|
|||||||
/packages/ui/certd-server/data/db.sqlite
|
/packages/ui/certd-server/data/db.sqlite
|
||||||
/packages/ui/certd-server/data/keys.yaml
|
/packages/ui/certd-server/data/keys.yaml
|
||||||
/packages/pro/
|
/packages/pro/
|
||||||
|
test.js
|
||||||
|
.history
|
||||||
|
/logs
|
||||||
|
.pnpm-lock.yaml
|
||||||
|
pnpm-lock.yaml
|
||||||
|
.studio/
|
||||||
|
|
||||||
|
# Certd 推广报告,仅本地使用
|
||||||
|
/popularize/
|
||||||
|
|
||||||
test.js
|
|
||||||
@@ -1,2 +1,6 @@
|
|||||||
link-workspace-packages=deep
|
link-workspace-packages=deep
|
||||||
prefer-workspace-packages=true
|
prefer-workspace-packages=true
|
||||||
|
better_sqlite3_binary_host=https://registry.npmmirror.com/-/binary/better-sqlite3
|
||||||
|
better_sqlite3_binary_host_mirror=https://registry.npmmirror.com/-/binary/better-sqlite3
|
||||||
|
better-sqlite3_binary_host=https://registry.npmmirror.com/-/binary/better-sqlite3
|
||||||
|
better-sqlite3_binary_host_mirror=https://registry.npmmirror.com/-/binary/better-sqlite3
|
||||||
@@ -0,0 +1,418 @@
|
|||||||
|
---
|
||||||
|
name: access-plugin-dev
|
||||||
|
description: 用于开发 Certd 系统中的 Access 插件,存储用户第三方应用授权数据并对接实现第三方 API 接口。当用户需要创建授权插件、实现第三方API接口、添加新的授权方式或修改现有 Access 插件时触发。
|
||||||
|
version: 1.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# Access 插件开发技能
|
||||||
|
|
||||||
|
## 角色定义
|
||||||
|
|
||||||
|
你是一名 Certd 插件开发专家,擅长创建和实现 Access 类型的插件,熟悉 TypeScript 编程和 Certd 插件开发规范。
|
||||||
|
|
||||||
|
## 核心指令
|
||||||
|
|
||||||
|
请严格按照以下步骤执行任务:
|
||||||
|
|
||||||
|
1. **导入必要的依赖**
|
||||||
|
|
||||||
|
- 导入 `AccessInput`, `BaseAccess`, `IsAccess`, `Pager`, `PageRes`, `PageSearch` 等必要的类型和装饰器
|
||||||
|
- 导入 `DomainRecord` 等相关类型
|
||||||
|
|
||||||
|
2. **使用 @IsAccess 注解注册插件**
|
||||||
|
|
||||||
|
- 配置插件的唯一标识、标题、图标和描述
|
||||||
|
- 继承 `BaseAccess` 类
|
||||||
|
|
||||||
|
3. **定义授权属性**
|
||||||
|
|
||||||
|
- 使用 `@AccessInput` 注解定义授权属性
|
||||||
|
- 配置属性的标题、默认值、组件类型和验证规则
|
||||||
|
- 对于敏感信息,设置 `encrypt: true` 进行加密
|
||||||
|
|
||||||
|
4. **实现测试方法**
|
||||||
|
|
||||||
|
- 添加测试按钮配置
|
||||||
|
- 实现 `onTestRequest` 方法,用于测试接口调用是否正常
|
||||||
|
|
||||||
|
5. **实现 API 方法**
|
||||||
|
|
||||||
|
- 实现必要的 API 方法,如 `GetDomainList`
|
||||||
|
- 封装统一的 API 请求方法 `doRequest`,处理错误和日志
|
||||||
|
|
||||||
|
6. **遵循开发最佳实践**
|
||||||
|
- 使用 `this.ctx.logger` 输出日志
|
||||||
|
- 统一处理错误,抛出明确的错误信息
|
||||||
|
- 实现代码复用,避免重复逻辑
|
||||||
|
|
||||||
|
## 输出规范
|
||||||
|
|
||||||
|
- 必须包含完整的插件实现代码,包括所有必要的导入语句
|
||||||
|
- 代码必须包含详细的注释说明,解释每个步骤的目的和实现细节
|
||||||
|
- 提供完整的示例代码,展示插件的使用方法,包括不同类型的授权方式
|
||||||
|
- 代码块必须使用正确的语法高亮,确保代码可读性
|
||||||
|
- 包含开发技巧和注意事项,帮助开发者避免常见错误
|
||||||
|
- 输出内容必须结构清晰,使用适当的标题和列表格式
|
||||||
|
|
||||||
|
## 示例
|
||||||
|
|
||||||
|
### 示例 1: 基本 Access 插件
|
||||||
|
|
||||||
|
#### 用户输入
|
||||||
|
|
||||||
|
创建一个 Access 插件,用于存储第三方应用的授权信息并提供 API 接口。
|
||||||
|
|
||||||
|
#### 你的回答
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { AccessInput, BaseAccess, IsAccess, Pager, PageRes, PageSearch } from '@certd/pipeline';
|
||||||
|
import { DomainRecord } from '@certd/plugin-lib';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 这个注解将注册一个授权配置
|
||||||
|
* 在certd的后台管理系统中,用户可以选择添加此类型的授权
|
||||||
|
*/
|
||||||
|
@IsAccess({
|
||||||
|
name: 'demo',
|
||||||
|
title: '授权插件示例',
|
||||||
|
icon: 'clarity:plugin-line', //插件图标
|
||||||
|
desc: '这是一个示例授权插件,用于演示如何实现一个授权插件',
|
||||||
|
})
|
||||||
|
export class DemoAccess extends BaseAccess {
|
||||||
|
/**
|
||||||
|
* 授权属性配置
|
||||||
|
*/
|
||||||
|
@AccessInput({
|
||||||
|
title: '授权方式',
|
||||||
|
value: 'apiKey', //默认值
|
||||||
|
component: {
|
||||||
|
name: 'a-select', //基于antdv的输入组件
|
||||||
|
vModel: 'value', // v-model绑定的属性名
|
||||||
|
options: [
|
||||||
|
//组件参数
|
||||||
|
{
|
||||||
|
label: 'API密钥(推荐)',
|
||||||
|
value: 'apiKey',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '账号密码',
|
||||||
|
value: 'account',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
placeholder: 'demoKeyId',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
apiType = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 授权属性配置
|
||||||
|
*/
|
||||||
|
@AccessInput({
|
||||||
|
title: '密钥Id',
|
||||||
|
component: {
|
||||||
|
name: 'a-input',
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: 'demoKeyId',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
demoKeyId = '';
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: '密钥', //标题
|
||||||
|
required: true, //text组件可以省略
|
||||||
|
encrypt: true, //该属性是否需要加密
|
||||||
|
})
|
||||||
|
demoKeySecret = '';
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: '测试',
|
||||||
|
component: {
|
||||||
|
name: 'api-test',
|
||||||
|
action: 'TestRequest',
|
||||||
|
},
|
||||||
|
helper: '点击测试接口是否正常',
|
||||||
|
})
|
||||||
|
testRequest = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会通过上面的testRequest参数在ui界面上生成测试按钮,供用户测试接口调用是否正常
|
||||||
|
*/
|
||||||
|
async onTestRequest() {
|
||||||
|
await this.GetDomainList({});
|
||||||
|
return 'ok';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* api接口示例 获取域名列表
|
||||||
|
*/
|
||||||
|
async GetDomainList(req: PageSearch): Promise<PageRes<DomainRecord>> {
|
||||||
|
//输出日志必须使用ctx.logger
|
||||||
|
this.ctx.logger.info(`获取域名列表,req:${JSON.stringify(req)}`);
|
||||||
|
const pager = new Pager(req);
|
||||||
|
const resp = await this.doRequest({
|
||||||
|
action: 'ListDomains',
|
||||||
|
data: {
|
||||||
|
domain: req.searchKey,
|
||||||
|
offset: pager.getOffset(),
|
||||||
|
limit: pager.pageSize,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const total = resp?.TotalCount || 0;
|
||||||
|
let list = resp?.DomainList?.map(item => {
|
||||||
|
item.domain = item.Domain;
|
||||||
|
item.id = item.DomainId;
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
total,
|
||||||
|
list,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用api调用方法, 具体如何构造请求体,需参考对应应用的API文档
|
||||||
|
*/
|
||||||
|
async doRequest(req: { action: string; data?: any }) {
|
||||||
|
const res = await this.ctx.http.request({
|
||||||
|
url: 'https://api.demo.cn/api/',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
Action: req.action,
|
||||||
|
Body: req.data,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.Code !== 0) {
|
||||||
|
//异常处理
|
||||||
|
throw new Error(res.Message || '请求失败');
|
||||||
|
}
|
||||||
|
return res.Resp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例 2: 支持 OAuth 授权的 Access 插件
|
||||||
|
|
||||||
|
#### 用户输入
|
||||||
|
|
||||||
|
创建一个支持 OAuth 授权方式的 Access 插件。
|
||||||
|
|
||||||
|
#### 你的回答
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { AccessInput, BaseAccess, IsAccess, Pager, PageRes, PageSearch } from '@certd/pipeline';
|
||||||
|
import { DomainRecord } from '@certd/plugin-lib';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAuth 授权插件示例
|
||||||
|
*/
|
||||||
|
@IsAccess({
|
||||||
|
name: 'oauth-demo',
|
||||||
|
title: 'OAuth授权插件示例',
|
||||||
|
icon: 'clarity:plugin-line',
|
||||||
|
desc: '这是一个支持OAuth授权的插件示例',
|
||||||
|
})
|
||||||
|
export class OAuthDemoAccess extends BaseAccess {
|
||||||
|
@AccessInput({
|
||||||
|
title: '授权方式',
|
||||||
|
value: 'oauth',
|
||||||
|
component: {
|
||||||
|
name: 'a-select',
|
||||||
|
vModel: 'value',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'OAuth授权',
|
||||||
|
value: 'oauth',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'API密钥',
|
||||||
|
value: 'apiKey',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
authType = '';
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: '客户端ID',
|
||||||
|
component: {
|
||||||
|
name: 'a-input',
|
||||||
|
placeholder: 'Client ID',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
clientId = '';
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: '客户端密钥',
|
||||||
|
required: true,
|
||||||
|
encrypt: true,
|
||||||
|
})
|
||||||
|
clientSecret = '';
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: '授权回调地址',
|
||||||
|
component: {
|
||||||
|
name: 'a-input',
|
||||||
|
placeholder: 'https://your-domain.com/callback',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
redirectUri = '';
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: 'AccessToken',
|
||||||
|
required: true,
|
||||||
|
encrypt: true,
|
||||||
|
})
|
||||||
|
accessToken = '';
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: 'RefreshToken',
|
||||||
|
encrypt: true,
|
||||||
|
})
|
||||||
|
refreshToken = '';
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: '测试',
|
||||||
|
component: {
|
||||||
|
name: 'api-test',
|
||||||
|
action: 'TestOAuth',
|
||||||
|
},
|
||||||
|
helper: '点击测试OAuth授权是否正常',
|
||||||
|
})
|
||||||
|
testOAuth = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试OAuth授权
|
||||||
|
*/
|
||||||
|
async onTestOAuth() {
|
||||||
|
try {
|
||||||
|
// 测试AccessToken是否有效
|
||||||
|
const result = await this.doOAuthRequest('GET', '/api/user/profile');
|
||||||
|
this.ctx.logger.info('OAuth测试成功:', result);
|
||||||
|
return 'OAuth授权测试成功';
|
||||||
|
} catch (error) {
|
||||||
|
this.ctx.logger.error('OAuth测试失败:', error);
|
||||||
|
throw new Error('OAuth授权测试失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAuth API请求方法
|
||||||
|
*/
|
||||||
|
async doOAuthRequest(method: string, endpoint: string, data?: any) {
|
||||||
|
const res = await this.ctx.http.request({
|
||||||
|
url: `https://api.oauth-demo.com${endpoint}`,
|
||||||
|
method,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${this.accessToken}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error(`API请求失败: ${res.status} ${res.statusText}`);
|
||||||
|
}
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新AccessToken
|
||||||
|
*/
|
||||||
|
async refreshAccessToken() {
|
||||||
|
if (!this.refreshToken) {
|
||||||
|
throw new Error('没有提供RefreshToken');
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await this.ctx.http.request({
|
||||||
|
url: 'https://api.oauth-demo.com/oauth/token',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
grant_type: 'refresh_token',
|
||||||
|
refresh_token: this.refreshToken,
|
||||||
|
client_id: this.clientId,
|
||||||
|
client_secret: this.clientSecret,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data.access_token) {
|
||||||
|
this.accessToken = res.data.access_token;
|
||||||
|
if (res.data.refresh_token) {
|
||||||
|
this.refreshToken = res.data.refresh_token;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
throw new Error('刷新AccessToken失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取域名列表
|
||||||
|
*/
|
||||||
|
async GetDomainList(req: PageSearch): Promise<PageRes<DomainRecord>> {
|
||||||
|
try {
|
||||||
|
const res = await this.doOAuthRequest('GET', '/api/domains', {
|
||||||
|
search: req.searchKey,
|
||||||
|
page: req.page,
|
||||||
|
pageSize: req.pageSize,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: res.total,
|
||||||
|
list: res.items.map((item: any) => ({
|
||||||
|
id: item.id,
|
||||||
|
domain: item.domain,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
// 尝试刷新AccessToken并重试
|
||||||
|
if (error.message.includes('401')) {
|
||||||
|
await this.refreshAccessToken();
|
||||||
|
const res = await this.doOAuthRequest('GET', '/api/domains', {
|
||||||
|
search: req.searchKey,
|
||||||
|
page: req.page,
|
||||||
|
pageSize: req.pageSize,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: res.total,
|
||||||
|
list: res.items.map((item: any) => ({
|
||||||
|
id: item.id,
|
||||||
|
domain: item.domain,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **插件命名**:插件名称应简洁明了,反映其功能。
|
||||||
|
2. **属性加密**:对于敏感信息(如密钥),应设置 `encrypt: true`。
|
||||||
|
3. **日志输出**:必须使用 `this.ctx.logger` 输出日志,而不是 `console`,参数文本化,不要传对象,否则会输出`[object Object]}`。
|
||||||
|
4. **错误处理**:API 调用失败时应抛出明确的错误信息。
|
||||||
|
5. **测试方法**:实现 `onTestRequest` 方法,以便用户可以测试授权是否正常。
|
||||||
|
6. **统一接口调用**:封装统一的 API 请求方法,避免重复编写错误处理逻辑。
|
||||||
|
|
||||||
|
## 开发技巧
|
||||||
|
|
||||||
|
### 实现统一的 API 请求封装
|
||||||
|
|
||||||
|
**好处:**
|
||||||
|
|
||||||
|
- **代码复用**:避免在每个 API 方法中重复编写相同的 header 设置和错误处理逻辑
|
||||||
|
- **错误处理一致**:统一捕获和处理各种错误情况,确保错误信息格式统一
|
||||||
|
- **日志记录完善**:集中记录详细的错误信息,便于调试和问题排查
|
||||||
|
- **接口调用简化**:调用方只需关注业务逻辑,无需关心底层请求细节
|
||||||
|
- **易于维护**:统一修改 API 调用方式时,只需修改一处代码
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
我需要开发一个 Access 插件,用于存储和管理第三方应用的授权信息。请指导我如何实现。
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
# Access 插件开发指南
|
||||||
|
|
||||||
|
## 开发步骤
|
||||||
|
|
||||||
|
### 1. 导入必要的依赖
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { AccessInput, BaseAccess, IsAccess, Pager, PageRes, PageSearch } from '@certd/pipeline';
|
||||||
|
import { DomainRecord } from '@certd/plugin-lib';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 使用 @IsAccess 注解注册插件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@IsAccess({
|
||||||
|
name: 'demo', // 插件唯一标识
|
||||||
|
title: '授权插件示例', // 插件标题
|
||||||
|
icon: 'clarity:plugin-line', // 插件图标
|
||||||
|
desc: '这是一个示例授权插件,用于演示如何实现一个授权插件', // 插件描述
|
||||||
|
})
|
||||||
|
export class DemoAccess extends BaseAccess {
|
||||||
|
// 插件实现...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 定义授权属性
|
||||||
|
|
||||||
|
使用 `@AccessInput` 注解定义授权属性:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@AccessInput({
|
||||||
|
title: '授权方式',
|
||||||
|
value: 'apiKey', // 默认值
|
||||||
|
component: {
|
||||||
|
name: "a-select", // 基于 antdv 的输入组件
|
||||||
|
vModel: "value", // v-model 绑定的属性名
|
||||||
|
options: [ // 组件参数
|
||||||
|
{ label: "API密钥(推荐)", value: "apiKey" },
|
||||||
|
{ label: "账号密码", value: "account" },
|
||||||
|
],
|
||||||
|
placeholder: 'demoKeyId',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
apiType = '';
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: '密钥Id',
|
||||||
|
component: {
|
||||||
|
name:"a-input",
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: 'demoKeyId',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
demoKeyId = '';
|
||||||
|
|
||||||
|
@AccessInput({
|
||||||
|
title: '密钥',//标题
|
||||||
|
required: true, //text组件可以省略
|
||||||
|
encrypt: true, //该属性是否需要加密
|
||||||
|
})
|
||||||
|
demoKeySecret = '';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 实现测试方法
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@AccessInput({
|
||||||
|
title: "测试",
|
||||||
|
component: {
|
||||||
|
name: "api-test",
|
||||||
|
action: "TestRequest"
|
||||||
|
},
|
||||||
|
helper: "点击测试接口是否正常"
|
||||||
|
})
|
||||||
|
testRequest = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会通过上面的testRequest参数在ui界面上生成测试按钮,供用户测试接口调用是否正常
|
||||||
|
*/
|
||||||
|
async onTestRequest() {
|
||||||
|
await this.GetDomainList({});
|
||||||
|
return "ok"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 实现 API 方法
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
/**
|
||||||
|
* 获api接口示例 取域名列表,
|
||||||
|
*/
|
||||||
|
async GetDomainList(req: PageSearch): Promise<PageRes<DomainRecord>> {
|
||||||
|
//输出日志必须使用ctx.logger
|
||||||
|
this.ctx.logger.info(`获取域名列表,req:${JSON.stringify(req)}`);
|
||||||
|
const pager = new Pager(req);
|
||||||
|
const resp = await this.doRequest({
|
||||||
|
action: "ListDomains",
|
||||||
|
data: {
|
||||||
|
domain: req.searchKey,
|
||||||
|
offset: pager.getOffset(),
|
||||||
|
limit: pager.pageSize,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const total = resp?.TotalCount || 0;
|
||||||
|
let list = resp?.DomainList?.map((item) => {
|
||||||
|
item.domain = item.Domain;
|
||||||
|
item.id = item.DomainId;
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
total,
|
||||||
|
list
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用api调用方法, 具体如何构造请求体,需参考对应应用的API文档
|
||||||
|
*/
|
||||||
|
async doRequest(req: { action: string, data?: any }) {
|
||||||
|
const res = await this.ctx.http.request({
|
||||||
|
url: "https://api.demo.cn/api/",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
Action: req.action,
|
||||||
|
Body: req.data
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.Code !== 0) {
|
||||||
|
//异常处理
|
||||||
|
throw new Error(res.Message || "请求失败");
|
||||||
|
}
|
||||||
|
return res.Resp;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **插件命名**:插件名称应简洁明了,反映其功能。
|
||||||
|
2. **属性加密**:对于敏感信息(如密钥),应设置 `encrypt: true`。
|
||||||
|
3. **日志输出**:必须使用 `this.ctx.logger` 输出日志,而不是 `console`。
|
||||||
|
4. **错误处理**:API 调用失败时应抛出明确的错误信息。
|
||||||
|
5. **测试方法**:实现 `onTestRequest` 方法,以便用户可以测试授权是否正常。
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
你是一名资深nodejs工程师,擅长开发Certd开源系统的任务插件。
|
||||||
|
certd是一款全自动证书申请部署管理工具,基于流水线的方式,通过里面申请证书插件申请证书,然后将证书传递给下一个部署任务插件,不同的部署任务插件将证书部署到用户的各个应用系统当中。
|
||||||
|
|
||||||
|
certd插件分成以下几种类型:
|
||||||
|
Access:存储用户的第三放应用的授权数据,比如用户名密码,accessSecret 或 accessToken等。同时它里面的方法还负责对接第三方的api接口
|
||||||
|
Task: 部署任务插件,它继承AbstractTaskPlugin类,被流水线调用execute方法,将证书部署到对应的应用上
|
||||||
|
DnsProvider: DNS提供商插件,它用于在ACME申请证书时给域名添加txt解析记录。
|
||||||
|
|
||||||
|
注意事项:
|
||||||
|
1、使用技能:在开始工作前,请阅读并加载.trae/skills下面的技能,根据skills进行相应的插件开发
|
||||||
|
2、迭代技能:当开发过程用户提醒你更好的做法时,你需要总结经验,更新相应的skills,让skills越来越完善,能够在以后得新插件开发中具备指导意义。
|
||||||
|
3、一般调用的api接口文档会比较复杂,你不知道接口是什么时,请务必询问用户,让用户提供API接口文档
|
||||||
|
4、完成开发后无需测试,通知用户自己去测试
|
||||||
@@ -0,0 +1,456 @@
|
|||||||
|
---
|
||||||
|
name: dns-provider-dev
|
||||||
|
description: 用于开发 Certd 系统中的 DNS Provider 插件,在 ACME 申请证书时给域名添加 TXT 解析记录以验证域名所有权。当用户需要创建DNS提供商插件、实现DNS解析、ACME证书验证或修改现有 DNS Provider 插件时触发。
|
||||||
|
version: 1.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# DNS Provider 插件开发技能
|
||||||
|
|
||||||
|
## 角色定义
|
||||||
|
|
||||||
|
你是一名 Certd 插件开发专家,擅长创建和实现 DNS Provider 类型的插件,熟悉 TypeScript 编程和 Certd 插件开发规范。
|
||||||
|
|
||||||
|
## 核心指令
|
||||||
|
|
||||||
|
请严格按照以下步骤执行任务:
|
||||||
|
|
||||||
|
1. **导入必要的依赖**
|
||||||
|
|
||||||
|
- 导入 `AbstractDnsProvider`, `CreateRecordOptions`, `IsDnsProvider`, `RemoveRecordOptions` 等必要的类型和装饰器
|
||||||
|
- 导入对应的 Access 插件类型
|
||||||
|
|
||||||
|
2. **定义记录数据结构**
|
||||||
|
|
||||||
|
- 定义适合对应云平台的记录数据结构
|
||||||
|
- 至少包含 id 字段,用于后续删除记录
|
||||||
|
|
||||||
|
3. **使用 @IsDnsProvider 注解注册插件**
|
||||||
|
|
||||||
|
- 配置插件的唯一标识、标题、描述、图标
|
||||||
|
- 指定对应的云平台的 access 类型名称
|
||||||
|
- 设置排序值(可选)
|
||||||
|
- 继承 `AbstractDnsProvider` 类
|
||||||
|
|
||||||
|
4. **实现 onInstance 方法**
|
||||||
|
|
||||||
|
- 获取并保存对应的 Access 实例
|
||||||
|
- 执行初始化操作
|
||||||
|
|
||||||
|
5. **实现 createRecord 方法**
|
||||||
|
|
||||||
|
- 解析传入的参数(fullRecord, value, type, domain)
|
||||||
|
- 记录操作开始日志
|
||||||
|
- 调用云平台 API 创建 TXT 类型的 DNS 解析记录
|
||||||
|
- 处理可能的错误:网络错误、API调用失败、授权失败等
|
||||||
|
- 记录操作结果日志
|
||||||
|
- 返回创建的记录信息,用于后续删除操作
|
||||||
|
|
||||||
|
6. **实现 removeRecord 方法**
|
||||||
|
|
||||||
|
- 解析传入的参数和之前创建的记录信息
|
||||||
|
- 记录操作开始日志
|
||||||
|
- 调用云平台 API 删除 TXT 类型的 DNS 解析记录
|
||||||
|
- 处理可能的错误:网络错误、API调用失败、记录不存在等
|
||||||
|
- 记录操作结果日志
|
||||||
|
|
||||||
|
7. **实现 getDomainListPage 方法**
|
||||||
|
|
||||||
|
- 实现获取域名列表的方法
|
||||||
|
- 支持分页查询
|
||||||
|
- 处理可能的错误:网络错误、API调用失败、授权失败等
|
||||||
|
- 返回标准化的域名列表格式
|
||||||
|
|
||||||
|
8. **实例化插件**
|
||||||
|
- 实例化插件,确保插件被注册
|
||||||
|
|
||||||
|
## 输出规范
|
||||||
|
|
||||||
|
- 必须包含完整的插件实现代码
|
||||||
|
- 代码必须包含详细的注释说明
|
||||||
|
- 提供完整的示例代码,展示插件的使用方法
|
||||||
|
- 包含开发注意事项
|
||||||
|
|
||||||
|
## 示例
|
||||||
|
|
||||||
|
### 示例 1: 基本 DNS Provider 插件
|
||||||
|
|
||||||
|
#### 用户输入
|
||||||
|
|
||||||
|
创建一个 DNS Provider 插件,用于在 ACME 申请证书时添加和删除 TXT 解析记录。
|
||||||
|
|
||||||
|
#### 你的回答
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Pager, PageRes, PageSearch } from '@certd/pipeline';
|
||||||
|
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions, DomainRecord } from '@certd/plugin-cert';
|
||||||
|
import { DemoAccess } from './access.js';
|
||||||
|
|
||||||
|
type DemoRecord = {
|
||||||
|
// 这里定义 Record 记录的数据结构,跟对应云平台接口返回值一样即可
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 这里通过 IsDnsProvider 注册一个 dnsProvider
|
||||||
|
@IsDnsProvider({
|
||||||
|
name: 'demo',
|
||||||
|
title: 'Dns提供商Demo',
|
||||||
|
desc: 'dns provider示例',
|
||||||
|
icon: 'clarity:plugin-line',
|
||||||
|
// 这里是对应的云平台的 access 类型名称
|
||||||
|
accessType: 'demo',
|
||||||
|
order: 99,
|
||||||
|
})
|
||||||
|
export class DemoDnsProvider extends AbstractDnsProvider<DemoRecord> {
|
||||||
|
access!: DemoAccess;
|
||||||
|
|
||||||
|
async onInstance() {
|
||||||
|
this.access = this.ctx.access as DemoAccess;
|
||||||
|
// 也可以通过 ctx 成员变量传递 context
|
||||||
|
this.logger.debug('access', this.access);
|
||||||
|
// 初始化的操作
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 dns 解析记录,用于验证域名所有权
|
||||||
|
*/
|
||||||
|
async createRecord(options: CreateRecordOptions): Promise<any> {
|
||||||
|
/**
|
||||||
|
* options 参数说明
|
||||||
|
* fullRecord: '_acme-challenge.example.com',
|
||||||
|
* value: 一串 uuid
|
||||||
|
* type: 'TXT',
|
||||||
|
* domain: 'example.com'
|
||||||
|
*/
|
||||||
|
const { fullRecord, value, type, domain } = options;
|
||||||
|
this.logger.info('添加域名解析:', fullRecord, value, type, domain);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 调用创建 dns 解析记录的对应的云端接口,创建 txt 类型的 dns 解析记录
|
||||||
|
// 请根据实际接口情况调用,例如:
|
||||||
|
// const createDnsRecordUrl = "xxx"
|
||||||
|
// const record = this.http.post(createDnsRecordUrl,{
|
||||||
|
// // 授权参数
|
||||||
|
// // 创建 dns 解析记录的参数
|
||||||
|
// })
|
||||||
|
// // 返回本次创建的 dns 解析记录,这个记录会在删除的时候用到
|
||||||
|
// return record
|
||||||
|
|
||||||
|
// 模拟返回
|
||||||
|
return { id: 'demo-record-id' };
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('创建DNS记录失败:', error);
|
||||||
|
throw new Error(`创建DNS记录失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除 dns 解析记录,清理申请痕迹
|
||||||
|
* @param options
|
||||||
|
*/
|
||||||
|
async removeRecord(options: RemoveRecordOptions<DemoRecord>): Promise<void> {
|
||||||
|
const { fullRecord, value, domain } = options.recordReq;
|
||||||
|
const record = options.recordRes;
|
||||||
|
this.logger.info('删除域名解析:', domain, fullRecord, value, record);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 这里调用删除 txt dns 解析记录接口
|
||||||
|
// 请根据实际接口情况调用,例如:
|
||||||
|
// const deleteDnsRecordUrl = "xxx"
|
||||||
|
// const res = this.http.delete(deleteDnsRecordUrl,{
|
||||||
|
// // 授权参数
|
||||||
|
// // 删除 dns 解析记录的参数
|
||||||
|
// })
|
||||||
|
|
||||||
|
this.logger.info('删除域名解析成功:', fullRecord, value);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('删除DNS记录失败:', error);
|
||||||
|
// 即使删除失败也不抛出异常,避免影响整个证书申请流程
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实现获取域名列表
|
||||||
|
*/
|
||||||
|
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
|
||||||
|
try {
|
||||||
|
const pager = new Pager(req);
|
||||||
|
const res = await this.http.request({
|
||||||
|
// 请求接口获取域名列表
|
||||||
|
});
|
||||||
|
const list =
|
||||||
|
res.Domains?.map(item => ({
|
||||||
|
id: item.Id,
|
||||||
|
domain: item.DomainName,
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
list,
|
||||||
|
total: res.Total,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('获取域名列表失败:', error);
|
||||||
|
return { list: [], total: 0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实例化这个 provider,将其自动注册到系统中
|
||||||
|
new DemoDnsProvider();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例 2: 阿里云 DNS Provider 插件
|
||||||
|
|
||||||
|
#### 用户输入
|
||||||
|
|
||||||
|
创建一个阿里云 DNS Provider 插件,用于在 ACME 申请证书时添加和删除 TXT 解析记录。
|
||||||
|
|
||||||
|
#### 你的回答
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions, PageSearch, PageRes, DomainRecord } from '@certd/plugin-cert';
|
||||||
|
import { AliyunAccess } from './aliyun-access.js';
|
||||||
|
import { Pager } from '@certd/pipeline';
|
||||||
|
|
||||||
|
type AliyunRecord = {
|
||||||
|
RecordId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 阿里云 DNS Provider 插件
|
||||||
|
@IsDnsProvider({
|
||||||
|
name: 'aliyun',
|
||||||
|
title: '阿里云DNS',
|
||||||
|
desc: '阿里云DNS提供商插件',
|
||||||
|
icon: 'clarity:plugin-line',
|
||||||
|
accessType: 'aliyun',
|
||||||
|
order: 10,
|
||||||
|
})
|
||||||
|
export class AliyunDnsProvider extends AbstractDnsProvider<AliyunRecord> {
|
||||||
|
access!: AliyunAccess;
|
||||||
|
|
||||||
|
async onInstance() {
|
||||||
|
this.access = this.ctx.access as AliyunAccess;
|
||||||
|
this.logger.debug('阿里云Access实例初始化成功');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 DNS 解析记录
|
||||||
|
*/
|
||||||
|
async createRecord(options: CreateRecordOptions): Promise<AliyunRecord> {
|
||||||
|
const { fullRecord, value, type, domain } = options;
|
||||||
|
this.logger.info('阿里云DNS: 添加解析记录', { fullRecord, value, type, domain });
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 提取主机记录
|
||||||
|
const hostRecord = fullRecord.replace(`.${domain}`, '');
|
||||||
|
|
||||||
|
// 调用阿里云 API 创建解析记录
|
||||||
|
const response = await this.access.doRequest({
|
||||||
|
action: 'AddDomainRecord',
|
||||||
|
data: {
|
||||||
|
DomainName: domain,
|
||||||
|
RR: hostRecord,
|
||||||
|
Type: type,
|
||||||
|
Value: value,
|
||||||
|
TTL: 600, // 10分钟
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.info('阿里云DNS: 解析记录创建成功', { RecordId: response.RecordId });
|
||||||
|
return { RecordId: response.RecordId };
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('阿里云DNS: 创建解析记录失败', error);
|
||||||
|
throw new Error(`阿里云DNS创建解析记录失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除 DNS 解析记录
|
||||||
|
*/
|
||||||
|
async removeRecord(options: RemoveRecordOptions<AliyunRecord>): Promise<void> {
|
||||||
|
const { fullRecord, value, domain } = options.recordReq;
|
||||||
|
const record = options.recordRes;
|
||||||
|
this.logger.info('阿里云DNS: 删除解析记录', { fullRecord, value, domain, RecordId: record.RecordId });
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 调用阿里云 API 删除解析记录
|
||||||
|
await this.access.doRequest({
|
||||||
|
action: 'DeleteDomainRecord',
|
||||||
|
data: {
|
||||||
|
RecordId: record.RecordId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.info('阿里云DNS: 解析记录删除成功', { RecordId: record.RecordId });
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('阿里云DNS: 删除解析记录失败', error);
|
||||||
|
// 即使删除失败也不抛出异常,避免影响整个证书申请流程
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取域名列表
|
||||||
|
*/
|
||||||
|
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
|
||||||
|
try {
|
||||||
|
const pager = new Pager(req);
|
||||||
|
const response = await this.access.doRequest({
|
||||||
|
action: 'DescribeDomains',
|
||||||
|
data: {
|
||||||
|
PageNumber: pager.page,
|
||||||
|
PageSize: pager.pageSize,
|
||||||
|
KeyWord: req.searchKey,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const list = response.Domains.Domain.map((domain: any) => ({
|
||||||
|
id: domain.DomainId,
|
||||||
|
domain: domain.DomainName,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
list,
|
||||||
|
total: response.TotalCount,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('阿里云DNS: 获取域名列表失败', error);
|
||||||
|
return { list: [], total: 0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实例化插件
|
||||||
|
new AliyunDnsProvider();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例 3: 腾讯云 DNS Provider 插件
|
||||||
|
|
||||||
|
#### 用户输入
|
||||||
|
|
||||||
|
创建一个腾讯云 DNS Provider 插件,用于在 ACME 申请证书时添加和删除 TXT 解析记录。
|
||||||
|
|
||||||
|
#### 你的回答
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions, PageSearch, PageRes, DomainRecord } from '@certd/plugin-cert';
|
||||||
|
import { TencentAccess } from './tencent-access.js';
|
||||||
|
import { Pager } from '@certd/pipeline';
|
||||||
|
|
||||||
|
type TencentRecord = {
|
||||||
|
RecordId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 腾讯云 DNS Provider 插件
|
||||||
|
@IsDnsProvider({
|
||||||
|
name: 'tencent',
|
||||||
|
title: '腾讯云DNS',
|
||||||
|
desc: '腾讯云DNS提供商插件',
|
||||||
|
icon: 'clarity:plugin-line',
|
||||||
|
accessType: 'tencent',
|
||||||
|
order: 20,
|
||||||
|
})
|
||||||
|
export class TencentDnsProvider extends AbstractDnsProvider<TencentRecord> {
|
||||||
|
access!: TencentAccess;
|
||||||
|
|
||||||
|
async onInstance() {
|
||||||
|
this.access = this.ctx.access as TencentAccess;
|
||||||
|
this.logger.debug('腾讯云Access实例初始化成功');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 DNS 解析记录
|
||||||
|
*/
|
||||||
|
async createRecord(options: CreateRecordOptions): Promise<TencentRecord> {
|
||||||
|
const { fullRecord, value, type, domain } = options;
|
||||||
|
this.logger.info('腾讯云DNS: 添加解析记录', { fullRecord, value, type, domain });
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 提取主机记录
|
||||||
|
const hostRecord = fullRecord.replace(`.${domain}`, '');
|
||||||
|
|
||||||
|
// 调用腾讯云 API 创建解析记录
|
||||||
|
const response = await this.access.doRequest({
|
||||||
|
action: 'CreateRecord',
|
||||||
|
data: {
|
||||||
|
Domain: domain,
|
||||||
|
SubDomain: hostRecord,
|
||||||
|
RecordType: type,
|
||||||
|
RecordValue: value,
|
||||||
|
TTL: 600, // 10分钟
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.info('腾讯云DNS: 解析记录创建成功', { RecordId: response.RecordId });
|
||||||
|
return { RecordId: response.RecordId };
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('腾讯云DNS: 创建解析记录失败', error);
|
||||||
|
throw new Error(`腾讯云DNS创建解析记录失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除 DNS 解析记录
|
||||||
|
*/
|
||||||
|
async removeRecord(options: RemoveRecordOptions<TencentRecord>): Promise<void> {
|
||||||
|
const { fullRecord, value, domain } = options.recordReq;
|
||||||
|
const record = options.recordRes;
|
||||||
|
this.logger.info('腾讯云DNS: 删除解析记录', { fullRecord, value, domain, RecordId: record.RecordId });
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 调用腾讯云 API 删除解析记录
|
||||||
|
await this.access.doRequest({
|
||||||
|
action: 'DeleteRecord',
|
||||||
|
data: {
|
||||||
|
RecordId: record.RecordId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.info('腾讯云DNS: 解析记录删除成功', { RecordId: record.RecordId });
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('腾讯云DNS: 删除解析记录失败', error);
|
||||||
|
// 即使删除失败也不抛出异常,避免影响整个证书申请流程
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取域名列表
|
||||||
|
*/
|
||||||
|
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
|
||||||
|
try {
|
||||||
|
const pager = new Pager(req);
|
||||||
|
const response = await this.access.doRequest({
|
||||||
|
action: 'DescribeDomains',
|
||||||
|
data: {
|
||||||
|
Offset: (pager.page - 1) * pager.pageSize,
|
||||||
|
Limit: pager.pageSize,
|
||||||
|
Keyword: req.searchKey,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const list = response.Domains.map((domain: any) => ({
|
||||||
|
id: domain.DomainId,
|
||||||
|
domain: domain.DomainName,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
list,
|
||||||
|
total: response.TotalCount,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('腾讯云DNS: 获取域名列表失败', error);
|
||||||
|
return { list: [], total: 0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实例化插件
|
||||||
|
new TencentDnsProvider();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **插件命名**:插件名称应简洁明了,反映其功能。
|
||||||
|
2. **accessType**:必须指定对应的云平台的 access 类型名称。
|
||||||
|
3. **记录结构**:定义适合对应云平台的记录数据结构,至少包含 id 字段用于删除记录。
|
||||||
|
4. **日志输出**:使用 `this.logger` 输出日志,而不是 `console`,参数文本化,不要传对象,否则会输出`[object Object]}`。
|
||||||
|
5. **错误处理**:API 调用失败时应抛出明确的错误信息。
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
我需要开发一个 DNS Provider 插件,用于在 ACME 申请证书时添加 TXT 解析记录。请指导我如何实现。
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
# DNS Provider 插件开发指南
|
||||||
|
|
||||||
|
## 开发步骤
|
||||||
|
|
||||||
|
### 1. 导入必要的依赖
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
|
||||||
|
import { DemoAccess } from './access.js';
|
||||||
|
import { isDev } from '../../utils/env.js';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 定义记录数据结构
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type DemoRecord = {
|
||||||
|
// 这里定义 Record 记录的数据结构,跟对应云平台接口返回值一样即可,一般是拿到 id 就行,用于删除 txt 解析记录,清理申请痕迹
|
||||||
|
// id:string
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 使用 @IsDnsProvider 注解注册插件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 这里通过 IsDnsProvider 注册一个 dnsProvider
|
||||||
|
@IsDnsProvider({
|
||||||
|
name: 'demo', // 插件唯一标识
|
||||||
|
title: 'Dns提供商Demo', // 插件标题
|
||||||
|
desc: 'dns provider示例', // 插件描述
|
||||||
|
icon: 'clarity:plugin-line', // 插件图标
|
||||||
|
// 这里是对应的云平台的 access 类型名称
|
||||||
|
accessType: 'demo',
|
||||||
|
order: 99, // 排序
|
||||||
|
})
|
||||||
|
export class DemoDnsProvider extends AbstractDnsProvider<DemoRecord> {
|
||||||
|
access!: DemoAccess;
|
||||||
|
|
||||||
|
async onInstance() {
|
||||||
|
this.access = this.ctx.access as DemoAccess;
|
||||||
|
// 也可以通过 ctx 成员变量传递 context
|
||||||
|
this.logger.debug('access', this.access);
|
||||||
|
// 初始化的操作
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插件实现...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 实现 createRecord 方法
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
/**
|
||||||
|
* 创建 dns 解析记录,用于验证域名所有权
|
||||||
|
*/
|
||||||
|
async createRecord(options: CreateRecordOptions): Promise<any> {
|
||||||
|
/**
|
||||||
|
* options 参数说明
|
||||||
|
* fullRecord: '_acme-challenge.example.com',
|
||||||
|
* value: 一串 uuid
|
||||||
|
* type: 'TXT',
|
||||||
|
* domain: 'example.com'
|
||||||
|
*/
|
||||||
|
const { fullRecord, value, type, domain } = options;
|
||||||
|
this.logger.info('添加域名解析:', fullRecord, value, type, domain);
|
||||||
|
|
||||||
|
// 调用创建 dns 解析记录的对应的云端接口,创建 txt 类型的 dns 解析记录
|
||||||
|
// 请根据实际接口情况调用,例如:
|
||||||
|
// const createDnsRecordUrl = "xxx"
|
||||||
|
// const record = this.http.post(createDnsRecordUrl,{
|
||||||
|
// // 授权参数
|
||||||
|
// // 创建 dns 解析记录的参数
|
||||||
|
// })
|
||||||
|
// // 返回本次创建的 dns 解析记录,这个记录会在删除的时候用到
|
||||||
|
// return record
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 实现 removeRecord 方法
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
/**
|
||||||
|
* 删除 dns 解析记录,清理申请痕迹
|
||||||
|
* @param options
|
||||||
|
*/
|
||||||
|
async removeRecord(options: RemoveRecordOptions<DemoRecord>): Promise<void> {
|
||||||
|
const { fullRecord, value, domain } = options.recordReq;
|
||||||
|
const record = options.recordRes;
|
||||||
|
this.logger.info('删除域名解析:', domain, fullRecord, value, record);
|
||||||
|
// 这里调用删除 txt dns 解析记录接口
|
||||||
|
// 请根据实际接口情况调用,例如:
|
||||||
|
|
||||||
|
// const deleteDnsRecordUrl = "xxx"
|
||||||
|
// const res = this.http.delete(deleteDnsRecordUrl,{
|
||||||
|
// // 授权参数
|
||||||
|
// // 删除 dns 解析记录的参数
|
||||||
|
// })
|
||||||
|
|
||||||
|
|
||||||
|
this.logger.info('删除域名解析成功:', fullRecord, value);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 实例化插件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 实例化这个 provider,将其自动注册到系统中
|
||||||
|
if (isDev()) {
|
||||||
|
// 你的实现 要去掉这个 if,不然生产环境将不会显示
|
||||||
|
new DemoDnsProvider();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **插件命名**:插件名称应简洁明了,反映其功能。
|
||||||
|
2. **accessType**:必须指定对应的云平台的 access 类型名称。
|
||||||
|
3. **记录结构**:定义适合对应云平台的记录数据结构,至少包含 id 字段用于删除记录。
|
||||||
|
4. **日志输出**:使用 `this.logger` 输出日志,而不是 `console`。
|
||||||
|
5. **错误处理**:API 调用失败时应抛出明确的错误信息。
|
||||||
|
6. **实例化**:生产环境中应移除 `if (isDev())` 条件,确保插件在生产环境中也能被注册。
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
---
|
||||||
|
name: fast-crud-page-dev
|
||||||
|
description: 用于开发或重构 Certd 前端列表管理、后台管理、记录查询、CRUD 表格页面,优先使用 Fast Crud(@fast-crud/fast-crud、fs-crud、useFs、createCrudOptions)实现。当用户要求列表页、管理页、审核页、记录页或表格 CRUD 页面时触发。
|
||||||
|
version: 1.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# Fast Crud 页面开发技能
|
||||||
|
|
||||||
|
## 角色定义
|
||||||
|
|
||||||
|
你是一名 Certd 前端列表管理页面开发专家,熟悉 Vue 3、Ant Design Vue、Fast Crud 和本仓库现有页面拆分方式。你的目标是让管理页面保持统一的表格、搜索、分页、操作列和弹窗体验。
|
||||||
|
|
||||||
|
## 核心规则
|
||||||
|
|
||||||
|
- 列表管理、后台管理、记录查询、审核记录、CRUD 表格类页面,默认优先使用 Fast Crud 实现。
|
||||||
|
- 只有轻量只读展示、强交互自定义界面、复杂可视化或已有页面模式明确不适合 Fast Crud 时,才手写 `a-table` / 自定义列表,并在回复中说明原因。
|
||||||
|
- 设置表单、概览卡片、向导流程等非列表主体可以保留自定义 Vue;如果同一功能同时包含设置和列表,优先拆成独立页面,或把设置放入对话框。
|
||||||
|
|
||||||
|
## 推荐文件拆分
|
||||||
|
|
||||||
|
- `api.ts`:封装接口请求,保持页面和 CRUD 配置里不直接散落 URL。
|
||||||
|
- `crud.tsx` / `crud-*.tsx`:导出 `createCrudOptions`,集中定义请求映射、搜索项、列、表单、操作列、工具栏和字典。
|
||||||
|
- `index.vue`:承载 `fs-page`、`fs-crud`、页面头部、弹窗和生命周期,使用 `useFs({ createCrudOptions, context })` 创建绑定。
|
||||||
|
|
||||||
|
## 实现流程
|
||||||
|
|
||||||
|
1. 先在 `packages/ui/certd-client/src/views` 下找 1-2 个相近 Fast Crud 页面,沿用它们的导入、布局、命名和权限写法。
|
||||||
|
2. 在 `index.vue` 中使用 `fs-crud ref="crudRef" v-bind="crudBinding"`,并在 `onMounted` 或 `onActivated` 时调用 `crudExpose.doRefresh()`;两个生命周期同时存在时只保留一个刷新入口,避免首次进入页面请求两次。
|
||||||
|
3. 在 `crud.tsx` 中配置 `request.pageRequest`、`columns`、`search`、`form`、`rowHandle`、`actionbar`、`toolbar` 等,接口分页参数和返回值按现有页面适配。
|
||||||
|
4. 操作按钮优先放在 Fast Crud 的 `rowHandle.buttons` 或 `actionbar.buttons` 中;审核、保存设置、批量操作等复杂交互可通过 `context` 调用 `index.vue` 中的方法。
|
||||||
|
5. 金额、状态、时间、枚举等字段优先复用项目已有组件、字典和格式化工具;避免在模板里重复堆格式化逻辑。
|
||||||
|
6. 表格查询条件使用 Fast Crud 的 `search` 配置;新增/编辑表单使用 Fast Crud 的 `form` 配置,复杂设置项可以用 Ant Design Vue 对话框承载。
|
||||||
|
7. 删除、审核通过、拒绝等危险操作必须保留确认弹窗和错误提示,成功后刷新当前 CRUD 列表。
|
||||||
|
8. 对话框里只做纯确认时可以使用 `Modal.confirm`;只要需要字段输入、表单校验或提交字段,统一使用 `useFormDialog` / `openFormDialog`,不要在 `Modal.confirm` 的 `content` 里手写输入框。
|
||||||
|
|
||||||
|
## crud 配置
|
||||||
|
|
||||||
|
const crudOptions ={
|
||||||
|
id: string, //表格唯一标识,同一个页面的多个表格的列设置和字段设置会根据id进行区分保存
|
||||||
|
request:{}, //http请求
|
||||||
|
columns:{ //字段配置
|
||||||
|
key:{ //字段key
|
||||||
|
column:{}, //对应table-column配置
|
||||||
|
form:{}, //表单中该字段的公共配置,viewForm、addForm、editForm、search会集成此配置,支持对应ui的form-item配置
|
||||||
|
viewForm:{}, //查看表单中该字段的配置,支持对应ui的form-item配置
|
||||||
|
addForm:{}, // 添加表单中该字段的配置,支持对应ui的form-item配置
|
||||||
|
editForm:{}, //编辑表单中该字段的配置,支持对应ui的form-item配置
|
||||||
|
search:{} //对应查询表单的form-item配置
|
||||||
|
}
|
||||||
|
},
|
||||||
|
search:{ //查询框配置 ,对应fs-search组件
|
||||||
|
options:{} //查询表单配置 ,对应el-from, a-form配置
|
||||||
|
},
|
||||||
|
actionbar:{}, //动作条,添加按钮,对应fs-actionbar组件
|
||||||
|
toolbar:{}, //工具条 ,对应fs-toolbar组件
|
||||||
|
table:{ //表格配置,对应fs-table
|
||||||
|
// 对应 el-table / a-table的配置
|
||||||
|
slots:{} // 对应el-table ,a-table的插槽
|
||||||
|
},
|
||||||
|
data:{}, //列表数据,无需配置,自动从pageRequest中获取
|
||||||
|
// 如果你要手动改变表格数据,可以通过crudBinding.value.data直接赋值修改表格数据
|
||||||
|
rowHandle:{}, //操作列配置,对应fs-row-handle
|
||||||
|
form:{ //表单的公共配置,对应el-form,a-form配置
|
||||||
|
wrapper:{} //表单外部容器(对话框)的配置,对应el-dialog,el-drawer,a-model,a-drawer的配置
|
||||||
|
},
|
||||||
|
viewForm:{}, //查看表单的独立配置
|
||||||
|
editForm:{}, //编辑表单的独立配置
|
||||||
|
addForm:{}, //添加表单的独立配置
|
||||||
|
pagination:{}, //分页配置 ,对应el-pagination / a-pagination
|
||||||
|
container:{}, //容器配置 ,对应fs-container
|
||||||
|
}
|
||||||
|
|
||||||
|
## 布局高度
|
||||||
|
|
||||||
|
- Fast Crud 表格依赖外部容器高度计算。虽然表格本身有默认约 200px 高度,但页面内嵌 `fs-crud` 时,为了获得稳定可用的列表区域,必须让外层容器提供明确高度或剩余高度。
|
||||||
|
- 独立列表页通常可直接让 `fs-page` / 页面内容区撑满;如果表格嵌在 tabs、详情页、上下分区或弹窗里,要从页面根容器到 `fs-crud` 建立完整的 flex 高度链路:父容器 `display: flex; flex-direction: column; min-height: 0`,中间内容区和 tab pane 使用 `flex: 1; min-height: 0`,`fs-crud` 本身也使用 `flex: 1; min-height: 0`。
|
||||||
|
- 有固定操作栏、统计区、说明区时,这些区域应 `flex: none`,把剩余空间交给表格区域。
|
||||||
|
- 修改嵌入式 Fast Crud 页面后,要检查空数据、少量数据和多页数据时表格高度、分页器和空状态是否仍在预期区域内。
|
||||||
|
|
||||||
|
## 列表导出
|
||||||
|
|
||||||
|
- 列表需要导出时,优先使用 Fast Crud 工具栏导出能力,不要另写一套导出按钮或后端接口,除非数据必须跨权限、跨分页或异步生成文件。
|
||||||
|
- 导出当前搜索条件下的数据时,在 `toolbar.export` 中设置 `dataFrom: "search"`,并显式打开导出按钮。
|
||||||
|
- 导出列必须输出 Excel 可读的纯文本或数字;不要直接导出对象、数组、VNode、进度条组件、开关组件、时间戳毫秒值等。
|
||||||
|
- 有隐藏但业务上需要导出的字段时,把字段定义为普通列并设置 `column.show: false`,再在 `columnFilter` 中对该字段返回 `true`。例如证书域名这类只用于导出的辅助列。
|
||||||
|
- 嵌套字段可以使用 `lastVars.certDomains` 这类 key;导出格式化时用安全取值函数读取嵌套值。
|
||||||
|
- `dataFormatter` 中统一格式化特殊字段:时间字段转 `YYYY-MM-DD HH:mm:ss`,日期类有效期转业务文案或 `YYYY-MM-DD`,枚举/开关转字典 label,数组转逗号分隔字符串,对象转明确的业务摘要。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { ColumnProps, DataFormatterContext } from "@fast-crud/fast-crud";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
function getRecordValue(row: any, key: string) {
|
||||||
|
return key.split(".").reduce((target, item) => target?.[item], row);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatListValue(value: any) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.join(",");
|
||||||
|
}
|
||||||
|
return value ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportColumnFilter(col: ColumnProps) {
|
||||||
|
if (!col.key || ["_index", "_selection", "rowHandle"].includes(col.key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (col.key === "lastVars.certDomains") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return col.show !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportDataFormatter(opts: DataFormatterContext) {
|
||||||
|
const { row, originalRow, col, exportCol } = opts;
|
||||||
|
const key = col.key;
|
||||||
|
const value = getRecordValue(originalRow, key);
|
||||||
|
|
||||||
|
if (key === "lastVars.certDomains") {
|
||||||
|
row[key] = formatListValue(value);
|
||||||
|
} else if (key.includes("Time") && value) {
|
||||||
|
row[key] = dayjs(value).format("YYYY-MM-DD HH:mm:ss");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (col.width) {
|
||||||
|
exportCol.width = col.width / 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
crudOptions: {
|
||||||
|
toolbar: {
|
||||||
|
buttons: {
|
||||||
|
export: { show: true },
|
||||||
|
},
|
||||||
|
export: {
|
||||||
|
dataFrom: "search",
|
||||||
|
columnFilter: exportColumnFilter,
|
||||||
|
dataFormatter: exportDataFormatter,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
"lastVars.certDomains": {
|
||||||
|
title: "证书域名",
|
||||||
|
type: "text",
|
||||||
|
column: {
|
||||||
|
show: false,
|
||||||
|
width: 260,
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
form: { show: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 内置 CRUD 按钮
|
||||||
|
|
||||||
|
只要在 `request` 中配置了 `addRequest`、`editRequest`、`delRequest`,Fast Crud 会自动在 `rowHandle` 渲染新增、编辑、删除按钮并完成对应操作,**不需要手写 `openDeleteConfirm`、`openEditDialog` 等方法**。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// crud.tsx
|
||||||
|
const addRequest = async ({ form }: AddReq) => await api.AddObj(form);
|
||||||
|
const editRequest = async ({ form, row }: EditReq) => {
|
||||||
|
form.id = row.id;
|
||||||
|
return await api.UpdateObj(form);
|
||||||
|
};
|
||||||
|
const delRequest = async ({ row }: DelReq) => await api.DelObj(row.id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
crudOptions: {
|
||||||
|
request: { pageRequest, addRequest, editRequest, delRequest },
|
||||||
|
rowHandle: {
|
||||||
|
buttons: {
|
||||||
|
view: { show: false }, // 不需要查看就隐藏
|
||||||
|
edit: {}, // 自动调用 editRequest
|
||||||
|
remove: {}, // 自动调用 delRequest,自带确认弹窗和错误提示
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- 删除按钮自带确认弹窗,不需要额外包装 `Modal.confirm`。
|
||||||
|
- 只有**自定义操作**(如禁用、审核、生成激活码)才需要在 `rowHandle.buttons` 中手写 `click` 处理方法。
|
||||||
|
- 如果不需要某列操作,直接把对应 key 去掉或设 `show: false`。
|
||||||
|
|
||||||
|
## compute 动态计算
|
||||||
|
|
||||||
|
当 `rowHandle.buttons` 的 `show`、`disabled` 等属性需要根据行数据动态决定时,**必须使用 `compute` 包裹**,不能直接传函数。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { compute } from "@fast-crud/fast-crud";
|
||||||
|
|
||||||
|
// WRONG: 直接传函数
|
||||||
|
show: ({ row }) => row.status === "unused"
|
||||||
|
|
||||||
|
// CORRECT: 用 compute 包裹
|
||||||
|
show: compute(({ row }) => row.status === "unused")
|
||||||
|
```
|
||||||
|
|
||||||
|
`compute` 基于 Vue 的 `computed`,但额外支持上下文参数。适用位置:
|
||||||
|
- `rowHandle.buttons` 的 `show`、`disabled` 等属性
|
||||||
|
- `columns.key.column` 的 `show`、`cellRender` 等
|
||||||
|
- `columns.key.form` / `search` 的表单字段属性
|
||||||
|
|
||||||
|
参考文档:http://fast-crud.docmirror.cn/guide/advance/compute.html
|
||||||
|
|
||||||
|
## 代码习惯
|
||||||
|
|
||||||
|
- 页面命名、API 命名、权限标识和路由结构要贴近同目录已有页面。
|
||||||
|
- CRUD 配置中不要写大段业务流程;复杂逻辑放回 `index.vue` 方法或 `api.ts`。
|
||||||
|
- 能用 `dict`、`compute`、`valueBuilder`、`valueResolve`、`component` 配置表达的表格/表单行为,不要改成手写模板。
|
||||||
|
- 保持列表页密度和操作入口克制,不要做营销式布局、嵌套卡片或大块说明文字。
|
||||||
|
- 如果页面有“设置 + 列表”,管理端优先拆成两个路由页面;用户端提现设置这类低频配置优先使用对话框保存。
|
||||||
|
|
||||||
|
## 验证方式
|
||||||
|
|
||||||
|
- 前端改动后,只对本次改动的 Vue / TS / TSX / locale 文件运行项目现有 Prettier / ESLint。
|
||||||
|
- 不运行 `vue-tsc` / `pnpm tsc`,因为当前依赖组合下 `vue-tsc` 已知会抛内部错误。
|
||||||
|
- 若只是新增或修改本 skill / 文档,不需要运行前端格式化和测试。
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
---
|
||||||
|
name: plugin-converter
|
||||||
|
description: 用于将 Certd 插件转换为 YAML 配置文件的命令行工具,支持分析单个插件文件、识别插件类型并生成对应的 YAML 配置。当用户需要生成插件配置、转换插件格式、批量处理插件或修改现有插件配置时触发。
|
||||||
|
version: 1.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# 插件转换工具技能
|
||||||
|
|
||||||
|
## 角色定义
|
||||||
|
|
||||||
|
你是一名 Certd 插件开发专家,擅长使用插件转换工具将 Certd 插件转换为 YAML 配置文件,熟悉命令行工具的使用和 Certd 插件开发规范。
|
||||||
|
|
||||||
|
## 核心指令
|
||||||
|
|
||||||
|
请严格按照以下步骤执行任务:
|
||||||
|
|
||||||
|
1. **定位工具位置**
|
||||||
|
|
||||||
|
- 工具位于 `trae/skills/convert-plugin-to-yaml.js`
|
||||||
|
|
||||||
|
2. **了解功能特性**
|
||||||
|
|
||||||
|
- 单个插件转换:支持指定单个插件文件进行转换
|
||||||
|
- 批量插件转换:支持指定目录批量转换多个插件
|
||||||
|
- 自动类型识别:自动识别插件类型(Access、Task、DNS Provider、Notification、Addon)
|
||||||
|
- 详细日志输出:提供详细的转换过程日志
|
||||||
|
- YAML 配置生成:生成标准的 YAML 配置文件
|
||||||
|
- 配置文件保存:自动将生成的配置保存到 `./metadata` 目录
|
||||||
|
- 自定义输出目录:支持指定自定义输出目录
|
||||||
|
- 格式化输出:支持格式化 YAML 输出
|
||||||
|
- 可复用函数:导出了可复用的函数,便于其他模块调用
|
||||||
|
|
||||||
|
3. **使用工具**
|
||||||
|
|
||||||
|
- 基本用法:`node trae/skills/convert-plugin-to-yaml.js <插件文件路径>`
|
||||||
|
- 批量转换:`node trae/skills/convert-plugin-to-yaml.js <目录路径>`
|
||||||
|
- 自定义输出目录:`node trae/skills/convert-plugin-to-yaml.js <插件文件路径> --output <输出目录>`
|
||||||
|
- 格式化输出:`node trae/skills/convert-plugin-to-yaml.js <插件文件路径> --format`
|
||||||
|
- 示例:
|
||||||
|
- 转换 Access 插件:`node trae/skills/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/plugin-demo/access.js`
|
||||||
|
- 转换 Task 插件:`node trae/skills/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/plugin-demo/plugins/plugin-test.js`
|
||||||
|
- 转换 DNS Provider 插件:`node trae/skills/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/plugin-demo/dns-provider.js`
|
||||||
|
- 批量转换插件:`node trae/skills/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/`
|
||||||
|
- 自定义输出目录:`node trae/skills/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/plugin-demo/access.js --output ./configs`
|
||||||
|
|
||||||
|
4. **理解转换过程**
|
||||||
|
|
||||||
|
- 加载插件模块:使用 `import()` 动态加载指定的插件文件
|
||||||
|
- 分析插件定义:检查模块导出的对象,寻找带有 `define` 属性的插件
|
||||||
|
- 识别插件类型:根据插件的继承关系或属性识别插件类型
|
||||||
|
- 生成 YAML 配置:基于插件定义生成标准的 YAML 配置
|
||||||
|
- 保存配置文件:将生成的配置保存到 `./metadata` 目录
|
||||||
|
|
||||||
|
5. **了解输出说明**
|
||||||
|
|
||||||
|
- 命令行输出:插件加载状态、插件导出的对象列表、插件类型识别结果、生成的 YAML 配置内容、配置文件保存路径
|
||||||
|
- 配置文件命名规则:`<插件类型>[_<子类型>]_<插件名称>.yaml`
|
||||||
|
|
||||||
|
6. **理解插件类型识别逻辑**
|
||||||
|
|
||||||
|
- DNS Provider:如果插件定义中包含 `accessType` 属性
|
||||||
|
- Task:如果插件继承自 `AbstractTaskPlugin`
|
||||||
|
- Notification:如果插件继承自 `BaseNotification`
|
||||||
|
- Access:如果插件继承自 `BaseAccess`
|
||||||
|
- Addon:如果插件继承自 `BaseAddon`
|
||||||
|
|
||||||
|
7. **遵循注意事项**
|
||||||
|
- 文件路径:插件文件路径可以是相对路径或绝对路径
|
||||||
|
- 文件格式:仅支持 `.js` 文件,不支持 `.ts` 文件(需要先编译)
|
||||||
|
- 依赖安装:执行前确保已安装所有必要的依赖
|
||||||
|
- 配置目录:如果 `./metadata` 目录不存在,工具会自动创建
|
||||||
|
- 错误处理:如果插件加载失败或识别失败,工具会输出错误信息但不会终止执行
|
||||||
|
|
||||||
|
## 输出规范
|
||||||
|
|
||||||
|
- 必须包含工具的使用方法和示例
|
||||||
|
- 必须包含转换过程的详细说明
|
||||||
|
- 必须包含输出说明和配置文件命名规则
|
||||||
|
- 必须包含插件类型识别逻辑
|
||||||
|
- 必须包含注意事项和故障排除建议
|
||||||
|
|
||||||
|
## 示例
|
||||||
|
|
||||||
|
### 示例 1: 转换单个 Access 插件
|
||||||
|
|
||||||
|
#### 用户输入
|
||||||
|
|
||||||
|
将 Access 插件转换为 YAML 配置文件。
|
||||||
|
|
||||||
|
#### 你的回答
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 转换 Access 插件
|
||||||
|
node trae/skills/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/plugin-demo/access.js
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 输出
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ node trae/skills/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/plugin-demo/access.js
|
||||||
|
开始转换插件: packages/ui/certd-server/src/plugins/plugin-demo/access.js
|
||||||
|
插件模块导出了 1 个对象: DemoAccess
|
||||||
|
处理插件: DemoAccess
|
||||||
|
插件类型: access
|
||||||
|
脚本路径: packages/ui/certd-server/src/plugins/plugin-demo/access.js
|
||||||
|
|
||||||
|
生成的 YAML 配置:
|
||||||
|
name: demo
|
||||||
|
title: 授权插件示例
|
||||||
|
desc: 这是一个示例授权插件,用于演示如何实现一个授权插件
|
||||||
|
icon: clarity:plugin-line
|
||||||
|
pluginType: access
|
||||||
|
type: builtIn
|
||||||
|
scriptFilePath: packages/ui/certd-server/src/plugins/plugin-demo/access.js
|
||||||
|
|
||||||
|
YAML 配置已保存到: ./metadata/access_demo.yaml
|
||||||
|
插件转换完成!
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例 2: 批量转换插件
|
||||||
|
|
||||||
|
#### 用户输入
|
||||||
|
|
||||||
|
批量转换目录中的所有插件为 YAML 配置文件。
|
||||||
|
|
||||||
|
#### 你的回答
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 批量转换插件
|
||||||
|
node trae/skills/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 输出
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ node trae/skills/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/
|
||||||
|
开始转换目录: packages/ui/certd-server/src/plugins/
|
||||||
|
|
||||||
|
正在处理文件: packages/ui/certd-server/src/plugins/plugin-demo/access.js
|
||||||
|
开始转换插件: packages/ui/certd-server/src/plugins/plugin-demo/access.js
|
||||||
|
插件模块导出了 1 个对象: DemoAccess
|
||||||
|
处理插件: DemoAccess
|
||||||
|
插件类型: access
|
||||||
|
脚本路径: packages/ui/certd-server/src/plugins/plugin-demo/access.js
|
||||||
|
|
||||||
|
生成的 YAML 配置:
|
||||||
|
name: demo
|
||||||
|
title: 授权插件示例
|
||||||
|
desc: 这是一个示例授权插件,用于演示如何实现一个授权插件
|
||||||
|
icon: clarity:plugin-line
|
||||||
|
pluginType: access
|
||||||
|
type: builtIn
|
||||||
|
scriptFilePath: packages/ui/certd-server/src/plugins/plugin-demo/access.js
|
||||||
|
|
||||||
|
YAML 配置已保存到: ./metadata/access_demo.yaml
|
||||||
|
插件转换完成!
|
||||||
|
|
||||||
|
正在处理文件: packages/ui/certd-server/src/plugins/plugin-demo/plugins/plugin-test.js
|
||||||
|
开始转换插件: packages/ui/certd-server/src/plugins/plugin-demo/plugins/plugin-test.js
|
||||||
|
插件模块导出了 1 个对象: DemoTest
|
||||||
|
处理插件: DemoTest
|
||||||
|
插件类型: task
|
||||||
|
脚本路径: packages/ui/certd-server/src/plugins/plugin-demo/plugins/plugin-test.js
|
||||||
|
|
||||||
|
生成的 YAML 配置:
|
||||||
|
name: DemoTest
|
||||||
|
title: Demo-测试插件
|
||||||
|
desc: 这是一个示例任务插件,用于演示如何实现一个任务插件
|
||||||
|
icon: clarity:plugin-line
|
||||||
|
pluginType: task
|
||||||
|
group: other
|
||||||
|
type: builtIn
|
||||||
|
scriptFilePath: packages/ui/certd-server/src/plugins/plugin-demo/plugins/plugin-test.js
|
||||||
|
|
||||||
|
YAML 配置已保存到: ./metadata/task_DemoTest.yaml
|
||||||
|
插件转换完成!
|
||||||
|
|
||||||
|
正在处理文件: packages/ui/certd-server/src/plugins/plugin-demo/dns-provider.js
|
||||||
|
开始转换插件: packages/ui/certd-server/src/plugins/plugin-demo/dns-provider.js
|
||||||
|
插件模块导出了 1 个对象: DemoDnsProvider
|
||||||
|
处理插件: DemoDnsProvider
|
||||||
|
插件类型: dns-provider
|
||||||
|
脚本路径: packages/ui/certd-server/src/plugins/plugin-demo/dns-provider.js
|
||||||
|
|
||||||
|
生成的 YAML 配置:
|
||||||
|
name: demo
|
||||||
|
title: Dns提供商Demo
|
||||||
|
desc: dns provider示例
|
||||||
|
icon: clarity:plugin-line
|
||||||
|
pluginType: dns-provider
|
||||||
|
accessType: demo
|
||||||
|
order: 99
|
||||||
|
type: builtIn
|
||||||
|
scriptFilePath: packages/ui/certd-server/src/plugins/plugin-demo/dns-provider.js
|
||||||
|
|
||||||
|
YAML 配置已保存到: ./metadata/dns-provider_demo.yaml
|
||||||
|
插件转换完成!
|
||||||
|
|
||||||
|
批量转换完成,共处理 3 个插件文件
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例 3: 自定义输出目录
|
||||||
|
|
||||||
|
#### 用户输入
|
||||||
|
|
||||||
|
将插件转换为 YAML 配置文件,并保存到自定义目录。
|
||||||
|
|
||||||
|
#### 你的回答
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 自定义输出目录
|
||||||
|
node trae/skills/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/plugin-demo/access.js --output ./configs
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 输出
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ node trae/skills/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/plugin-demo/access.js --output ./configs
|
||||||
|
开始转换插件: packages/ui/certd-server/src/plugins/plugin-demo/access.js
|
||||||
|
插件模块导出了 1 个对象: DemoAccess
|
||||||
|
处理插件: DemoAccess
|
||||||
|
插件类型: access
|
||||||
|
脚本路径: packages/ui/certd-server/src/plugins/plugin-demo/access.js
|
||||||
|
|
||||||
|
生成的 YAML 配置:
|
||||||
|
name: demo
|
||||||
|
title: 授权插件示例
|
||||||
|
desc: 这是一个示例授权插件,用于演示如何实现一个授权插件
|
||||||
|
icon: clarity:plugin-line
|
||||||
|
pluginType: access
|
||||||
|
type: builtIn
|
||||||
|
scriptFilePath: packages/ui/certd-server/src/plugins/plugin-demo/access.js
|
||||||
|
|
||||||
|
YAML 配置已保存到: ./configs/access_demo.yaml
|
||||||
|
插件转换完成!
|
||||||
|
```
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
|
||||||
|
1. **模块加载失败**
|
||||||
|
|
||||||
|
- 原因:插件文件依赖未安装或路径错误
|
||||||
|
- 解决:确保已安装所有依赖,检查文件路径是否正确
|
||||||
|
|
||||||
|
2. **插件类型识别失败**
|
||||||
|
|
||||||
|
- 原因:插件未正确继承基类或缺少必要的属性
|
||||||
|
- 解决:检查插件代码,确保正确继承对应的基类
|
||||||
|
|
||||||
|
3. **YAML 配置生成失败**
|
||||||
|
|
||||||
|
- 原因:插件定义格式不正确
|
||||||
|
- 解决:检查插件的 `define` 属性格式是否正确
|
||||||
|
|
||||||
|
4. **配置文件保存失败**
|
||||||
|
- 原因:权限不足或磁盘空间不足
|
||||||
|
- 解决:确保有足够的权限和磁盘空间
|
||||||
|
|
||||||
|
### 调试建议
|
||||||
|
|
||||||
|
- **查看详细日志**:工具会输出详细的转换过程日志,仔细查看日志信息
|
||||||
|
- **检查插件代码**:确保插件代码符合 Certd 插件开发规范
|
||||||
|
- **尝试简化插件**:如果转换失败,尝试创建一个最小化的插件示例进行测试
|
||||||
|
- **检查依赖版本**:确保使用的依赖版本与 Certd 兼容
|
||||||
|
|
||||||
|
## 代码结构
|
||||||
|
|
||||||
|
### 主要函数
|
||||||
|
|
||||||
|
1. **isPrototypeOf(value, cls)**:检查对象是否是指定类的原型
|
||||||
|
2. **loadSingleModule(filePath)**:加载单个插件模块
|
||||||
|
3. **convertSinglePlugin(pluginPath)**:分析单个插件并生成 YAML 配置
|
||||||
|
4. **main()**:主函数,处理命令行参数并执行转换
|
||||||
|
|
||||||
|
### 导出函数
|
||||||
|
|
||||||
|
工具导出了以下函数,便于其他模块调用:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
export {
|
||||||
|
convertSinglePlugin, // 转换单个插件
|
||||||
|
loadSingleModule, // 加载单个模块
|
||||||
|
isPrototypeOf, // 检查原型关系
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 应用场景
|
||||||
|
|
||||||
|
1. **插件开发**:在开发新插件时,快速生成配置文件
|
||||||
|
2. **插件调试**:查看插件的内部定义和配置
|
||||||
|
3. **插件管理**:批量转换现有插件为标准配置格式
|
||||||
|
4. **自动化构建**:集成到构建流程中,自动生成插件配置
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
我需要将一个插件转换为 YAML 配置文件。请指导我如何使用插件转换工具。
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
# 插件转换工具使用指南
|
||||||
|
|
||||||
|
## 工具说明
|
||||||
|
|
||||||
|
插件转换工具用于将单个 Certd 插件转换为 YAML 配置文件,方便插件的注册和管理。
|
||||||
|
|
||||||
|
## 工具位置
|
||||||
|
|
||||||
|
`.trae/skills/plugin-converter/resources/convert-plugin-to-yaml.js`
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 基本用法
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node .trae/skills/plugin-converter/resources/convert-plugin-to-yaml.js <插件文件路径>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例
|
||||||
|
|
||||||
|
#### 转换 Access 插件
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node .trae/skills/plugin-converter/resources/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/plugin-demo/access.js
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 转换 Task 插件
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node .trae/skills/plugin-converter/resources/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/plugin-demo/plugins/plugin-test.js
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 转换 DNS Provider 插件
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node .trae/skills/plugin-converter/resources/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/plugin-demo/dns-provider.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## 转换过程
|
||||||
|
|
||||||
|
1. **加载插件模块**:使用 `import()` 动态加载指定的插件文件
|
||||||
|
2. **分析插件定义**:检查模块导出的对象,寻找带有 `define` 属性的插件
|
||||||
|
3. **识别插件类型**:根据插件的继承关系或属性识别插件类型
|
||||||
|
4. **生成 YAML 配置**:基于插件定义生成标准的 YAML 配置
|
||||||
|
5. **保存配置文件**:将生成的配置保存到 `./metadata` 目录
|
||||||
|
|
||||||
|
## 输出说明
|
||||||
|
|
||||||
|
### 命令行输出
|
||||||
|
|
||||||
|
执行转换命令后,工具会输出以下信息:
|
||||||
|
|
||||||
|
- 插件加载状态
|
||||||
|
- 插件导出的对象列表
|
||||||
|
- 插件类型识别结果
|
||||||
|
- 生成的 YAML 配置内容
|
||||||
|
- 配置文件保存路径
|
||||||
|
|
||||||
|
### 配置文件命名规则
|
||||||
|
|
||||||
|
生成的配置文件命名规则为:
|
||||||
|
|
||||||
|
```
|
||||||
|
<插件类型>[_<子类型>]_<插件名称>.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
- `access_demo.yaml`(Access 插件)
|
||||||
|
- `deploy_DemoTest.yaml`(Task 插件)
|
||||||
|
- `dnsProvider_demo.yaml`(DNS Provider 插件)
|
||||||
|
|
||||||
|
## 示例输出
|
||||||
|
|
||||||
|
### 转换 Access 插件示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ node .trae/skills/plugin-converter/resources/convert-plugin-to-yaml.js packages/ui/certd-server/src/plugins/plugin-demo/access.js
|
||||||
|
开始转换插件: packages/ui/certd-server/src/plugins/plugin-demo/access.js
|
||||||
|
插件模块导出了 1 个对象: DemoAccess
|
||||||
|
处理插件: DemoAccess
|
||||||
|
插件类型: access
|
||||||
|
脚本路径: packages/ui/certd-server/src/plugins/plugin-demo/access.js
|
||||||
|
|
||||||
|
生成的 YAML 配置:
|
||||||
|
name: demo
|
||||||
|
title: 授权插件示例
|
||||||
|
desc: 这是一个示例授权插件,用于演示如何实现一个授权插件
|
||||||
|
icon: clarity:plugin-line
|
||||||
|
pluginType: access
|
||||||
|
type: builtIn
|
||||||
|
scriptFilePath: packages/ui/certd-server/src/plugins/plugin-demo/access.js
|
||||||
|
|
||||||
|
YAML 配置已保存到: ./metadata/access_demo.yaml
|
||||||
|
插件转换完成!
|
||||||
|
```
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
// 转换单个插件为 YAML 配置的技能脚本
|
||||||
|
|
||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
import { pathToFileURL } from 'node:url';
|
||||||
|
import * as yaml from 'js-yaml';
|
||||||
|
import { AbstractTaskPlugin, BaseAccess, BaseNotification } from '@certd/pipeline';
|
||||||
|
import { BaseAddon } from '@certd/lib-server';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查对象是否是指定类的原型
|
||||||
|
*/
|
||||||
|
function isPrototypeOf(value, cls) {
|
||||||
|
return cls.prototype.isPrototypeOf(value.prototype);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载单个插件模块
|
||||||
|
*/
|
||||||
|
async function loadSingleModule(filePath) {
|
||||||
|
try {
|
||||||
|
// 转换为 file:// URL(Windows 必需)
|
||||||
|
const moduleUrl = pathToFileURL(filePath).href;
|
||||||
|
const module = await import(moduleUrl);
|
||||||
|
return module.default || module;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`加载模块 ${filePath} 失败:`, err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分析单个插件并生成 YAML 配置
|
||||||
|
*/
|
||||||
|
async function convertSinglePlugin(pluginPath) {
|
||||||
|
console.log(`开始转换插件: ${pluginPath}`);
|
||||||
|
|
||||||
|
// 加载插件模块
|
||||||
|
const module = await loadSingleModule(pluginPath);
|
||||||
|
if (!module) {
|
||||||
|
console.error('加载插件失败,退出');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理模块中的所有导出
|
||||||
|
const entry = Object.entries(module);
|
||||||
|
if (entry.length === 0) {
|
||||||
|
console.error('插件模块没有导出任何内容');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`插件模块导出了 ${entry.length} 个对象: ${entry.map(([name]) => name).join(', ')}`);
|
||||||
|
|
||||||
|
// 处理每个导出的对象
|
||||||
|
for (const [name, value] of entry) {
|
||||||
|
// 检查是否是插件(有 define 属性)
|
||||||
|
if (!value.define) {
|
||||||
|
console.log(`跳过非插件对象: ${name}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`处理插件: ${name}`);
|
||||||
|
|
||||||
|
// 构建插件定义
|
||||||
|
const pluginDefine = {
|
||||||
|
...value.define,
|
||||||
|
};
|
||||||
|
|
||||||
|
let subType = '';
|
||||||
|
|
||||||
|
// 确定插件类型
|
||||||
|
if (pluginDefine.accessType) {
|
||||||
|
pluginDefine.pluginType = 'dnsProvider';
|
||||||
|
} else if (isPrototypeOf(value, AbstractTaskPlugin)) {
|
||||||
|
pluginDefine.pluginType = 'deploy';
|
||||||
|
} else if (isPrototypeOf(value, BaseNotification)) {
|
||||||
|
pluginDefine.pluginType = 'notification';
|
||||||
|
} else if (isPrototypeOf(value, BaseAccess)) {
|
||||||
|
pluginDefine.pluginType = 'access';
|
||||||
|
} else if (isPrototypeOf(value, BaseAddon)) {
|
||||||
|
pluginDefine.pluginType = 'addon';
|
||||||
|
subType = '_' + (pluginDefine.addonType || '');
|
||||||
|
} else {
|
||||||
|
console.log(`[warning] 未知的插件类型:${pluginDefine.name}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginDefine.type = 'builtIn';
|
||||||
|
|
||||||
|
// 计算脚本文件路径
|
||||||
|
const relativePath = path.relative(process.cwd(), pluginPath);
|
||||||
|
const scriptFilePath = relativePath.replace(/\\/g, '/').replace(/\.js$/, '.js');
|
||||||
|
pluginDefine.scriptFilePath = scriptFilePath;
|
||||||
|
|
||||||
|
console.log(`插件类型: ${pluginDefine.pluginType}`);
|
||||||
|
console.log(`脚本路径: ${scriptFilePath}`);
|
||||||
|
|
||||||
|
// 生成 YAML 配置
|
||||||
|
const yamlContent = yaml.dump(pluginDefine);
|
||||||
|
console.log('\n生成的 YAML 配置:');
|
||||||
|
console.log(yamlContent);
|
||||||
|
|
||||||
|
// 可选:保存到文件
|
||||||
|
const outputDir = './metadata';
|
||||||
|
if (!fs.existsSync(outputDir)) {
|
||||||
|
fs.mkdirSync(outputDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const outputFileName = `${pluginDefine.pluginType}${subType}_${pluginDefine.name}.yaml`;
|
||||||
|
const outputPath = path.join(outputDir, outputFileName);
|
||||||
|
|
||||||
|
fs.writeFileSync(outputPath, yamlContent, 'utf8');
|
||||||
|
console.log(`\nYAML 配置已保存到: ${outputPath}`);
|
||||||
|
|
||||||
|
return pluginDefine;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error('未找到有效的插件定义');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主函数
|
||||||
|
*/
|
||||||
|
async function main() {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
if (args.length === 0) {
|
||||||
|
console.error('请指定插件文件路径');
|
||||||
|
console.log('用法: node convert-plugin-to-yaml.js <插件文件路径>');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginPath = args[0];
|
||||||
|
|
||||||
|
if (!fs.existsSync(pluginPath)) {
|
||||||
|
console.error(`插件文件不存在: ${pluginPath}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await convertSinglePlugin(pluginPath);
|
||||||
|
console.log('\n插件转换完成!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('转换过程中出错:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果直接运行此脚本
|
||||||
|
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||||
|
main();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出函数,以便其他模块使用
|
||||||
|
export { convertSinglePlugin, loadSingleModule, isPrototypeOf };
|
||||||
@@ -0,0 +1,662 @@
|
|||||||
|
---
|
||||||
|
name: task-plugin-dev
|
||||||
|
description: 用于开发 Certd 系统中的 Task 插件,继承自 AbstractTaskPlugin 类,被流水线调用 execute 方法将证书部署到对应的应用上。当用户需要创建任务插件、部署证书、自动化任务或修改现有 Task 插件时触发。
|
||||||
|
version: 1.0.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# Task 插件开发技能
|
||||||
|
|
||||||
|
## 角色定义
|
||||||
|
|
||||||
|
你是一名 Certd 插件开发专家,擅长创建和实现 Task 类型的插件,熟悉 TypeScript 编程和 Certd 插件开发规范。
|
||||||
|
|
||||||
|
## 核心指令
|
||||||
|
|
||||||
|
请严格按照以下步骤执行任务:
|
||||||
|
|
||||||
|
1. **导入必要的依赖**
|
||||||
|
|
||||||
|
- 导入 `AbstractTaskPlugin`, `IsTaskPlugin`, `PageSearch`, `pluginGroups`, `RunStrategy`, `TaskInput` 等必要的类型和装饰器
|
||||||
|
- 导入 `CertInfo`, `CertReader` 等证书相关类型
|
||||||
|
- 导入 `createCertDomainGetterInputDefine`, `createRemoteSelectInputDefine` 等工具函数
|
||||||
|
- 导入 `optionsUtils` 等辅助工具
|
||||||
|
- 导入 `CertApplyPluginNames` 等常量
|
||||||
|
|
||||||
|
2. **使用 @IsTaskPlugin 注解注册插件**
|
||||||
|
|
||||||
|
- 配置插件的唯一标识、标题、图标
|
||||||
|
- 设置插件分组
|
||||||
|
- 配置默认策略(如 `SkipWhenSucceed`)
|
||||||
|
- 确保类名与插件名称一致
|
||||||
|
|
||||||
|
3. **定义任务输入参数**
|
||||||
|
|
||||||
|
- 使用 `@TaskInput` 注解定义各种输入参数
|
||||||
|
- 必须包含证书选择参数,用于获取前置任务输出的域名证书
|
||||||
|
- 可以添加授权选择框、文本输入、选择框等参数
|
||||||
|
- 使用 `createCertDomainGetterInputDefine` 获取证书域名列表
|
||||||
|
|
||||||
|
4. **实现动态显隐配置**
|
||||||
|
|
||||||
|
- 使用 `mergeScript` 实现根据其他输入值动态控制输入项的显隐状态
|
||||||
|
- 利用 `ctx.compute` 函数访问表单中的其他字段值
|
||||||
|
|
||||||
|
5. **实现插件方法**
|
||||||
|
|
||||||
|
- **onInstance 方法**:插件实例化时执行的初始化操作
|
||||||
|
- **execute 方法**:插件的核心执行逻辑
|
||||||
|
- 获取授权信息
|
||||||
|
- 读取证书信息
|
||||||
|
- 执行具体的部署逻辑
|
||||||
|
- 处理错误并记录日志
|
||||||
|
- 实现幂等性:确保重复执行不会导致问题
|
||||||
|
- 实现超时处理:设置合理的超时时间
|
||||||
|
- 实现回滚机制:在部署失败时能够回滚到之前的状态
|
||||||
|
- **后端获取选项方法**:用于前端从后端获取选项的方法
|
||||||
|
|
||||||
|
6. **遵循开发最佳实践**
|
||||||
|
- 插件命名:遵循命名规范,大写字母开头,驼峰命名
|
||||||
|
- 类名规范:类名应与插件名称一致
|
||||||
|
- 日志输出:使用 `this.logger` 输出日志
|
||||||
|
- 错误处理:捕获并记录执行过程中的错误
|
||||||
|
- 授权获取:使用 `this.getAccess(accessId)` 获取授权信息
|
||||||
|
|
||||||
|
## 输出规范
|
||||||
|
|
||||||
|
- 必须包含完整的插件实现代码
|
||||||
|
- 代码必须包含详细的注释说明
|
||||||
|
- 提供完整的示例代码,展示插件的使用方法
|
||||||
|
- 包含开发注意事项和最佳实践
|
||||||
|
|
||||||
|
## 示例
|
||||||
|
|
||||||
|
### 示例 1: 基本 Task 插件
|
||||||
|
|
||||||
|
#### 用户输入
|
||||||
|
|
||||||
|
创建一个 Task 插件,用于将证书部署到对应的应用上。
|
||||||
|
|
||||||
|
#### 你的回答
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||||
|
import { CertInfo, CertReader } from '@certd/plugin-cert';
|
||||||
|
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib';
|
||||||
|
import { optionsUtils } from '@certd/basic';
|
||||||
|
import { CertApplyPluginNames } from '@certd/plugin-cert';
|
||||||
|
|
||||||
|
@IsTaskPlugin({
|
||||||
|
//命名规范,插件类型+功能,大写字母开头,驼峰命名
|
||||||
|
name: 'DemoTest',
|
||||||
|
title: 'Demo-测试插件',
|
||||||
|
icon: 'clarity:plugin-line',
|
||||||
|
//插件分组
|
||||||
|
group: pluginGroups.other.key,
|
||||||
|
default: {
|
||||||
|
//默认值配置照抄即可
|
||||||
|
strategy: {
|
||||||
|
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
//类名规范,跟上面插件名称(name)一致
|
||||||
|
export class DemoTest extends AbstractTaskPlugin {
|
||||||
|
//测试参数
|
||||||
|
@TaskInput({
|
||||||
|
title: '属性示例',
|
||||||
|
value: '默认值',
|
||||||
|
component: {
|
||||||
|
//前端组件配置,具体配置见组件文档 https://www.antdv.com/components/input-cn
|
||||||
|
name: 'a-input',
|
||||||
|
vModel: 'value', //双向绑定组件的props名称
|
||||||
|
},
|
||||||
|
helper: '帮助说明,[链接](https://certd.docmirror.cn)',
|
||||||
|
required: false, //是否必填
|
||||||
|
})
|
||||||
|
text!: string;
|
||||||
|
|
||||||
|
//测试参数
|
||||||
|
@TaskInput({
|
||||||
|
title: '选择框',
|
||||||
|
component: {
|
||||||
|
//前端组件配置,具体配置见组件文档 https://www.antdv.com/components/select-cn
|
||||||
|
name: 'a-auto-complete',
|
||||||
|
vModel: 'value',
|
||||||
|
options: [
|
||||||
|
//选项列表
|
||||||
|
{ label: '动态显', value: 'show' },
|
||||||
|
{ label: '动态隐', value: 'hide' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
select!: string;
|
||||||
|
|
||||||
|
@TaskInput({
|
||||||
|
title: '动态显隐',
|
||||||
|
helper: '我会根据选择框的值进行显隐',
|
||||||
|
show: true, //动态计算的值会覆盖它
|
||||||
|
//动态计算脚本, mergeScript返回的对象会合并当前配置
|
||||||
|
mergeScript: `
|
||||||
|
return {
|
||||||
|
show: ctx.compute(({form})=>{
|
||||||
|
return form.select === 'show';
|
||||||
|
})
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
showText!: string;
|
||||||
|
|
||||||
|
//测试参数
|
||||||
|
@TaskInput({
|
||||||
|
title: '多选框',
|
||||||
|
component: {
|
||||||
|
//前端组件配置,具体配置见组件文档 https://www.antdv.com/components/select-cn
|
||||||
|
name: 'a-select',
|
||||||
|
vModel: 'value',
|
||||||
|
mode: 'tags',
|
||||||
|
multiple: true,
|
||||||
|
options: [
|
||||||
|
{ value: '1', label: '选项1' },
|
||||||
|
{ value: '2', label: '选项2' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
multiSelect!: string;
|
||||||
|
|
||||||
|
//测试参数
|
||||||
|
@TaskInput({
|
||||||
|
title: 'switch',
|
||||||
|
component: {
|
||||||
|
//前端组件配置,具体配置见组件文档 https://www.antdv.com/components/switch-cn
|
||||||
|
name: 'a-switch',
|
||||||
|
vModel: 'checked',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
switch!: boolean;
|
||||||
|
|
||||||
|
//证书选择,此项必须要有
|
||||||
|
@TaskInput({
|
||||||
|
title: '域名证书',
|
||||||
|
helper: '请选择前置任务输出的域名证书',
|
||||||
|
component: {
|
||||||
|
name: 'output-selector',
|
||||||
|
from: [...CertApplyPluginNames],
|
||||||
|
},
|
||||||
|
// required: true, // 必填
|
||||||
|
})
|
||||||
|
cert!: CertInfo;
|
||||||
|
|
||||||
|
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
|
||||||
|
//前端可以展示,当前申请的证书域名列表
|
||||||
|
certDomains!: string[];
|
||||||
|
|
||||||
|
//授权选择框
|
||||||
|
@TaskInput({
|
||||||
|
title: 'demo授权',
|
||||||
|
helper: 'demoAccess授权',
|
||||||
|
component: {
|
||||||
|
name: 'access-selector',
|
||||||
|
type: 'demo', //固定授权类型
|
||||||
|
},
|
||||||
|
// rules: [{ required: true, message: '此项必填' }],
|
||||||
|
// required: true, //必填
|
||||||
|
})
|
||||||
|
accessId!: string;
|
||||||
|
|
||||||
|
@TaskInput(
|
||||||
|
createRemoteSelectInputDefine({
|
||||||
|
title: '从后端获取选项',
|
||||||
|
helper: '选择时可以从后端获取选项',
|
||||||
|
action: DemoTest.prototype.onGetSiteList.name,
|
||||||
|
//当以下参数变化时,触发获取选项
|
||||||
|
watches: ['certDomains', 'accessId'],
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
siteName!: string | string[];
|
||||||
|
|
||||||
|
//插件实例化时执行的方法
|
||||||
|
async onInstance() {}
|
||||||
|
|
||||||
|
//插件执行方法
|
||||||
|
async execute(): Promise<void> {
|
||||||
|
const { select, text, cert, accessId } = this;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const access = await this.getAccess(accessId);
|
||||||
|
this.logger.debug('access', access);
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error('获取授权失败', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const certReader = new CertReader(cert);
|
||||||
|
this.logger.debug('certReader', certReader);
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error('读取crt失败', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.info('DemoTestPlugin execute');
|
||||||
|
this.logger.info('text:', text);
|
||||||
|
this.logger.info('select:', select);
|
||||||
|
this.logger.info('switch:', this.switch);
|
||||||
|
this.logger.info('授权id:', accessId);
|
||||||
|
|
||||||
|
// 具体的部署逻辑
|
||||||
|
// const res = await this.http.request({
|
||||||
|
// url: 'https://api.demo.com',
|
||||||
|
// method: 'GET',
|
||||||
|
// });
|
||||||
|
// if (res.code !== 0) {
|
||||||
|
// //检查res是否报错,你需要抛异常,来结束插件执行,否则会判定为执行成功,下次执行时会跳过本任务
|
||||||
|
// throw new Error(res.message);
|
||||||
|
// }
|
||||||
|
// this.logger.info('部署成功:', res);
|
||||||
|
}
|
||||||
|
|
||||||
|
//此方法演示,如何让前端在添加插件时可以从后端获取选项,这里是后端返回选项的方法
|
||||||
|
async onGetSiteList(req: PageSearch) {
|
||||||
|
if (!this.accessId) {
|
||||||
|
throw new Error('请选择Access授权');
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const access = await this.getAccess(this.accessId);
|
||||||
|
|
||||||
|
// const siteRes = await access.GetDomainList(req);
|
||||||
|
//以下是模拟数据
|
||||||
|
const siteRes = [
|
||||||
|
{ id: 1, siteName: 'site1.com' },
|
||||||
|
{ id: 2, siteName: 'site2.com' },
|
||||||
|
{ id: 3, siteName: 'site2.com' },
|
||||||
|
];
|
||||||
|
//转换为前端所需要的格式
|
||||||
|
const options = siteRes.map((item: any) => {
|
||||||
|
return {
|
||||||
|
value: item.siteName,
|
||||||
|
label: item.siteName,
|
||||||
|
domain: item.siteName,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
//将站点域名名称根据证书域名进行匹配分组,分成匹配的和不匹配的两组选项,返回给前端,供用户选择
|
||||||
|
return {
|
||||||
|
list: optionsUtils.buildGroupOptions(options, this.certDomains),
|
||||||
|
total: siteRes.length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例 2: Nginx 部署 Task 插件
|
||||||
|
|
||||||
|
#### 用户输入
|
||||||
|
|
||||||
|
创建一个 Task 插件,用于将证书部署到 Nginx 服务器上。
|
||||||
|
|
||||||
|
#### 你的回答
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { AbstractTaskPlugin, CertInfo, IsTaskPlugin, TaskInput, pluginGroups, RunStrategy } from '@certd/pipeline';
|
||||||
|
import { CertReader } from '@certd/plugin-lib';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nginx 证书部署插件
|
||||||
|
*/
|
||||||
|
@IsTaskPlugin({
|
||||||
|
name: 'NginxDeploy',
|
||||||
|
title: 'Nginx 部署',
|
||||||
|
desc: '将证书部署到 Nginx 服务器上',
|
||||||
|
icon: 'clarity:server-line',
|
||||||
|
group: pluginGroups.deploy.key,
|
||||||
|
default: {
|
||||||
|
strategy: {
|
||||||
|
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export class NginxDeploy extends AbstractTaskPlugin {
|
||||||
|
/**
|
||||||
|
* 服务器授权
|
||||||
|
*/
|
||||||
|
@TaskInput({
|
||||||
|
title: '服务器授权',
|
||||||
|
component: {
|
||||||
|
name: 'access-selector',
|
||||||
|
vModel: 'accessId',
|
||||||
|
accessTypes: ['ssh'],
|
||||||
|
placeholder: '请选择服务器授权',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
accessId = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 域名证书
|
||||||
|
*/
|
||||||
|
@TaskInput({
|
||||||
|
title: '域名证书',
|
||||||
|
component: {
|
||||||
|
name: 'output-selector',
|
||||||
|
from: ['CertApply', 'CertApplyCloudflare'],
|
||||||
|
field: 'cert',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
cert!: CertInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 证书路径
|
||||||
|
*/
|
||||||
|
@TaskInput({
|
||||||
|
title: '证书路径',
|
||||||
|
value: '/etc/nginx/ssl',
|
||||||
|
component: {
|
||||||
|
name: 'a-input',
|
||||||
|
placeholder: '请输入证书存储路径',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
certPath = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nginx 配置文件路径
|
||||||
|
*/
|
||||||
|
@TaskInput({
|
||||||
|
title: 'Nginx 配置文件',
|
||||||
|
value: '/etc/nginx/conf.d',
|
||||||
|
component: {
|
||||||
|
name: 'a-input',
|
||||||
|
placeholder: '请输入 Nginx 配置文件路径',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
nginxConfPath = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务名称
|
||||||
|
*/
|
||||||
|
@TaskInput({
|
||||||
|
title: '服务名称',
|
||||||
|
component: {
|
||||||
|
name: 'a-input',
|
||||||
|
placeholder: '请输入服务名称(用于生成配置文件)',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
serviceName = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行部署
|
||||||
|
*/
|
||||||
|
async execute(): Promise<void> {
|
||||||
|
this.logger.info('开始部署证书到 Nginx');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 获取服务器授权
|
||||||
|
const sshAccess = await this.getAccess(this.accessId);
|
||||||
|
this.logger.info('获取服务器授权成功');
|
||||||
|
|
||||||
|
// 2. 读取证书信息
|
||||||
|
const certReader = new CertReader(this.cert);
|
||||||
|
const cert = certReader.getCert();
|
||||||
|
const key = certReader.getKey();
|
||||||
|
const fullchain = certReader.getFullChain();
|
||||||
|
this.logger.info('读取证书信息成功');
|
||||||
|
|
||||||
|
// 3. 准备部署路径
|
||||||
|
const certFile = `${this.certPath}/${this.serviceName}.pem`;
|
||||||
|
const keyFile = `${this.certPath}/${this.serviceName}.key`;
|
||||||
|
const confFile = `${this.nginxConfPath}/${this.serviceName}.conf`;
|
||||||
|
|
||||||
|
// 4. 创建证书目录
|
||||||
|
await sshAccess.exec(`mkdir -p ${this.certPath}`);
|
||||||
|
this.logger.info('创建证书目录成功');
|
||||||
|
|
||||||
|
// 5. 上传证书文件
|
||||||
|
await sshAccess.uploadContent(cert, certFile);
|
||||||
|
await sshAccess.uploadContent(key, keyFile);
|
||||||
|
await sshAccess.uploadContent(fullchain, `${this.certPath}/${this.serviceName}-fullchain.pem`);
|
||||||
|
this.logger.info('上传证书文件成功');
|
||||||
|
|
||||||
|
// 6. 生成 Nginx 配置
|
||||||
|
const nginxConf = `server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name ${this.cert.domains.join(' ')};
|
||||||
|
|
||||||
|
ssl_certificate ${certFile};
|
||||||
|
ssl_certificate_key ${keyFile};
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:80;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 7. 上传 Nginx 配置
|
||||||
|
await sshAccess.uploadContent(nginxConf, confFile);
|
||||||
|
this.logger.info('上传 Nginx 配置成功');
|
||||||
|
|
||||||
|
// 8. 测试 Nginx 配置
|
||||||
|
const testResult = await sshAccess.exec('nginx -t');
|
||||||
|
if (testResult.includes('test is successful')) {
|
||||||
|
this.logger.info('Nginx 配置测试成功');
|
||||||
|
} else {
|
||||||
|
throw new Error(`Nginx 配置测试失败: ${testResult}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9. 重启 Nginx 服务
|
||||||
|
await sshAccess.exec('systemctl reload nginx');
|
||||||
|
this.logger.info('重启 Nginx 服务成功');
|
||||||
|
|
||||||
|
this.logger.info('证书部署到 Nginx 成功');
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('部署失败:', error);
|
||||||
|
throw new Error(`部署到 Nginx 失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实例化插件
|
||||||
|
new NginxDeploy();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例 3: 阿里云 OSS 部署 Task 插件
|
||||||
|
|
||||||
|
#### 用户输入
|
||||||
|
|
||||||
|
创建一个 Task 插件,用于将证书部署到阿里云 OSS 上。
|
||||||
|
|
||||||
|
#### 你的回答
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { AbstractTaskPlugin, CertInfo, IsTaskPlugin, TaskInput, pluginGroups, RunStrategy } from '@certd/pipeline';
|
||||||
|
import { CertReader } from '@certd/plugin-lib';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阿里云 OSS 证书部署插件
|
||||||
|
*/
|
||||||
|
@IsTaskPlugin({
|
||||||
|
name: 'AliyunOSSDeploy',
|
||||||
|
title: '阿里云 OSS 部署',
|
||||||
|
desc: '将证书部署到阿里云 OSS 存储上',
|
||||||
|
icon: 'clarity:cloud-line',
|
||||||
|
group: pluginGroups.deploy.key,
|
||||||
|
default: {
|
||||||
|
strategy: {
|
||||||
|
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export class AliyunOSSDeploy extends AbstractTaskPlugin {
|
||||||
|
/**
|
||||||
|
* 阿里云授权
|
||||||
|
*/
|
||||||
|
@TaskInput({
|
||||||
|
title: '阿里云授权',
|
||||||
|
component: {
|
||||||
|
name: 'access-selector',
|
||||||
|
vModel: 'accessId',
|
||||||
|
accessTypes: ['aliyun'],
|
||||||
|
placeholder: '请选择阿里云授权',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
accessId = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 域名证书
|
||||||
|
*/
|
||||||
|
@TaskInput({
|
||||||
|
title: '域名证书',
|
||||||
|
component: {
|
||||||
|
name: 'output-selector',
|
||||||
|
from: ['CertApply', 'CertApplyCloudflare'],
|
||||||
|
field: 'cert',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
cert!: CertInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OSS 存储桶
|
||||||
|
*/
|
||||||
|
@TaskInput({
|
||||||
|
title: 'OSS 存储桶',
|
||||||
|
component: {
|
||||||
|
name: 'a-input',
|
||||||
|
placeholder: '请输入 OSS 存储桶名称',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
bucketName = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储路径
|
||||||
|
*/
|
||||||
|
@TaskInput({
|
||||||
|
title: '存储路径',
|
||||||
|
value: 'ssl/',
|
||||||
|
component: {
|
||||||
|
name: 'a-input',
|
||||||
|
placeholder: '请输入证书存储路径',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
storagePath = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行部署
|
||||||
|
*/
|
||||||
|
async execute(): Promise<void> {
|
||||||
|
this.logger.info('开始部署证书到阿里云 OSS');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 获取阿里云授权
|
||||||
|
const aliyunAccess = await this.getAccess(this.accessId);
|
||||||
|
this.logger.info('获取阿里云授权成功');
|
||||||
|
|
||||||
|
// 2. 读取证书信息
|
||||||
|
const certReader = new CertReader(this.cert);
|
||||||
|
const cert = certReader.getCert();
|
||||||
|
const key = certReader.getKey();
|
||||||
|
const fullchain = certReader.getFullChain();
|
||||||
|
this.logger.info('读取证书信息成功');
|
||||||
|
|
||||||
|
// 3. 准备存储路径
|
||||||
|
const basePath = this.storagePath.endsWith('/') ? this.storagePath : `${this.storagePath}/`;
|
||||||
|
const certFileName = `${basePath}${this.cert.domains[0]}.pem`;
|
||||||
|
const keyFileName = `${basePath}${this.cert.domains[0]}.key`;
|
||||||
|
const fullchainFileName = `${basePath}${this.cert.domains[0]}-fullchain.pem`;
|
||||||
|
|
||||||
|
// 4. 上传证书到 OSS
|
||||||
|
await aliyunAccess.uploadToOSS({
|
||||||
|
bucket: this.bucketName,
|
||||||
|
key: certFileName,
|
||||||
|
content: cert,
|
||||||
|
});
|
||||||
|
this.logger.info('上传证书文件成功');
|
||||||
|
|
||||||
|
await aliyunAccess.uploadToOSS({
|
||||||
|
bucket: this.bucketName,
|
||||||
|
key: keyFileName,
|
||||||
|
content: key,
|
||||||
|
});
|
||||||
|
this.logger.info('上传私钥文件成功');
|
||||||
|
|
||||||
|
await aliyunAccess.uploadToOSS({
|
||||||
|
bucket: this.bucketName,
|
||||||
|
key: fullchainFileName,
|
||||||
|
content: fullchain,
|
||||||
|
});
|
||||||
|
this.logger.info('上传完整证书链成功');
|
||||||
|
|
||||||
|
// 5. 设置文件访问权限(可选)
|
||||||
|
await aliyunAccess.setOSSObjectAcl({
|
||||||
|
bucket: this.bucketName,
|
||||||
|
key: certFileName,
|
||||||
|
acl: 'private',
|
||||||
|
});
|
||||||
|
|
||||||
|
await aliyunAccess.setOSSObjectAcl({
|
||||||
|
bucket: this.bucketName,
|
||||||
|
key: keyFileName,
|
||||||
|
acl: 'private',
|
||||||
|
});
|
||||||
|
|
||||||
|
await aliyunAccess.setOSSObjectAcl({
|
||||||
|
bucket: this.bucketName,
|
||||||
|
key: fullchainFileName,
|
||||||
|
acl: 'private',
|
||||||
|
});
|
||||||
|
this.logger.info('设置文件访问权限成功');
|
||||||
|
|
||||||
|
this.logger.info('证书部署到阿里云 OSS 成功');
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('部署失败:', error);
|
||||||
|
throw new Error(`部署到阿里云 OSS 失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实例化插件
|
||||||
|
new AliyunOSSDeploy();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **插件命名**:插件名称应遵循命名规范,大写字母开头,驼峰命名。
|
||||||
|
2. **类名规范**:类名应与插件名称(name)一致。
|
||||||
|
3. **证书选择**:必须包含证书选择参数,用于获取前置任务输出的域名证书。
|
||||||
|
4. **日志输出**:使用 `this.logger` 输出日志,而不是 `console`,参数文本化,不要传对象,否则会输出`[object Object]}`。
|
||||||
|
5. **错误处理**:执行过程中的错误应被捕获并记录。
|
||||||
|
6. **授权获取**:使用 `this.getAccess(accessId)` 获取授权信息。
|
||||||
|
|
||||||
|
## 部署逻辑注意事项
|
||||||
|
|
||||||
|
1. **部署接口逻辑**:
|
||||||
|
|
||||||
|
- 研究应用的部署接口逻辑,一般有两种:
|
||||||
|
a. 用户选择网站ID,给网站部署新证书
|
||||||
|
b. 用户选择证书ID,只需要更新证书即可
|
||||||
|
- 保证多次执行都能针对同一个对象部署证书
|
||||||
|
- 确保出错后重新运行能够回归到正常状态
|
||||||
|
|
||||||
|
2. **前置证书选择**:
|
||||||
|
|
||||||
|
- 前置证书可以是原始的 `certInfo` 类型,也可能是上传到平台之后返回的证书id
|
||||||
|
- 根据接口要求选择合适的证书类型:
|
||||||
|
a. 如果接口需要上传后的证书id,那么部署时要先将证书上传,再部署
|
||||||
|
b. 如果接口需要原始的 `certInfo` 类型,那么直接使用 `certInfo` 部署证书
|
||||||
|
c. 当两者都支持时,判断用户选择的证书类型,再考虑优先上传再部署
|
||||||
|
|
||||||
|
3. **证书清理**:
|
||||||
|
- 如果是先上传再部署的,那么在部署完成后,可能需要考虑清理证书
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
我需要开发一个 Task 插件,用于将申请的证书部署到指定的应用系统中。请指导我如何实现。
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
# Task 插件开发指南
|
||||||
|
|
||||||
|
## 开发步骤
|
||||||
|
|
||||||
|
### 1. 导入必要的依赖
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
|
||||||
|
import { CertInfo, CertReader } from '@certd/plugin-cert';
|
||||||
|
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib';
|
||||||
|
import { optionsUtils } from '@certd/basic';
|
||||||
|
import { CertApplyPluginNames } from '@certd/plugin-cert';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 使用 @IsTaskPlugin 注解注册插件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@IsTaskPlugin({
|
||||||
|
// 命名规范,插件类型+功能,大写字母开头,驼峰命名
|
||||||
|
name: 'DemoTest',
|
||||||
|
title: 'Demo-测试插件', // 插件标题
|
||||||
|
icon: 'clarity:plugin-line', // 插件图标
|
||||||
|
// 插件分组
|
||||||
|
group: pluginGroups.other.key,
|
||||||
|
default: {
|
||||||
|
// 默认值配置照抄即可
|
||||||
|
strategy: {
|
||||||
|
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
// 类名规范,跟上面插件名称(name)一致
|
||||||
|
export class DemoTest extends AbstractTaskPlugin {
|
||||||
|
// 插件实现...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 定义任务输入参数
|
||||||
|
|
||||||
|
使用 `@TaskInput` 注解定义任务输入参数:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 测试参数
|
||||||
|
@TaskInput({
|
||||||
|
title: '属性示例',
|
||||||
|
value: '默认值',
|
||||||
|
component: {
|
||||||
|
//前端组件配置,具体配置见组件文档 https://www.antdv.com/components/input-cn
|
||||||
|
name: 'a-input',
|
||||||
|
vModel: 'value', //双向绑定组件的props名称
|
||||||
|
},
|
||||||
|
helper: '帮助说明,[链接](https://certd.docmirror.cn)',
|
||||||
|
required: false, //是否必填
|
||||||
|
})
|
||||||
|
text!: string;
|
||||||
|
|
||||||
|
//证书选择,此项必须要有
|
||||||
|
@TaskInput({
|
||||||
|
title: '域名证书',
|
||||||
|
helper: '请选择前置任务输出的域名证书',
|
||||||
|
component: {
|
||||||
|
name: 'output-selector',
|
||||||
|
from: [...CertApplyPluginNames],
|
||||||
|
},
|
||||||
|
// required: true, // 必填
|
||||||
|
})
|
||||||
|
cert!: CertInfo;
|
||||||
|
|
||||||
|
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
|
||||||
|
//前端可以展示,当前申请的证书域名列表
|
||||||
|
certDomains!: string[];
|
||||||
|
|
||||||
|
//授权选择框
|
||||||
|
@TaskInput({
|
||||||
|
title: 'demo授权',
|
||||||
|
helper: 'demoAccess授权',
|
||||||
|
component: {
|
||||||
|
name: 'access-selector',
|
||||||
|
type: 'demo', //固定授权类型
|
||||||
|
},
|
||||||
|
// rules: [{ required: true, message: '此项必填' }],
|
||||||
|
// required: true, //必填
|
||||||
|
})
|
||||||
|
accessId!: string;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 实现插件方法
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
//插件实例化时执行的方法
|
||||||
|
async onInstance() {}
|
||||||
|
|
||||||
|
//插件执行方法
|
||||||
|
async execute(): Promise<void> {
|
||||||
|
const { select, text, cert, accessId } = this;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const access = await this.getAccess(accessId);
|
||||||
|
this.logger.debug('access', access);
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error('获取授权失败', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const certReader = new CertReader(cert);
|
||||||
|
this.logger.debug('certReader', certReader);
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error('读取crt失败', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.info('DemoTestPlugin execute');
|
||||||
|
this.logger.info('text:', text);
|
||||||
|
this.logger.info('select:', select);
|
||||||
|
this.logger.info('switch:', this.switch);
|
||||||
|
this.logger.info('授权id:', accessId);
|
||||||
|
|
||||||
|
// 具体的部署逻辑
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **插件命名**:插件名称应遵循命名规范,大写字母开头,驼峰命名。
|
||||||
|
2. **类名规范**:类名应与插件名称(name)一致。
|
||||||
|
3. **证书选择**:必须包含证书选择参数,用于获取前置任务输出的域名证书。
|
||||||
|
4. **日志输出**:使用 `this.logger` 输出日志,而不是 `console`。
|
||||||
|
5. **错误处理**:执行过程中的错误应被捕获并记录。
|
||||||
|
6. **授权获取**:使用 `this.getAccess(accessId)` 获取授权信息。
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
{
|
||||||
|
// 使用 IntelliSense 了解相关属性。
|
||||||
|
// 悬停以查看现有属性的描述。
|
||||||
|
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "client",
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"cwd": "${workspaceFolder}/packages/ui/certd-client",
|
||||||
|
"runtimeExecutable": "pnpm",
|
||||||
|
"runtimeArgs": ["dev"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "server",
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"cwd": "${workspaceFolder}/packages/ui/certd-server",
|
||||||
|
"runtimeExecutable": "pnpm",
|
||||||
|
"runtimeArgs": ["dev"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "server-mysql",
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"cwd": "${workspaceFolder}/packages/ui/certd-server",
|
||||||
|
"runtimeExecutable": "pnpm",
|
||||||
|
"runtimeArgs": ["dev-mysql"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "server-pg",
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"cwd": "${workspaceFolder}/packages/ui/certd-server",
|
||||||
|
"runtimeExecutable": "pnpm",
|
||||||
|
"runtimeArgs": ["dev-pg"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "server-pgpl",
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"cwd": "${workspaceFolder}/packages/ui/certd-server",
|
||||||
|
"runtimeExecutable": "pnpm",
|
||||||
|
"runtimeArgs": ["dev-pgpl"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "server-common",
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"cwd": "${workspaceFolder}/packages/ui/certd-server",
|
||||||
|
"runtimeExecutable": "pnpm",
|
||||||
|
"runtimeArgs": ["dev-commpro"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "server-new",
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"cwd": "${workspaceFolder}/packages/ui/certd-server",
|
||||||
|
"runtimeExecutable": "pnpm",
|
||||||
|
"runtimeArgs": ["dev-new"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "server-local-plus",
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"cwd": "${workspaceFolder}/packages/ui/certd-server",
|
||||||
|
"runtimeExecutable": "npm",
|
||||||
|
"runtimeArgs": ["run", "dev-localplus"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen",
|
||||||
|
"env": {
|
||||||
|
"plus_use_prod": "false",
|
||||||
|
"PLUS_SERVER_BASE_URL": "http://127.0.0.1:11007"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compounds": [
|
||||||
|
{
|
||||||
|
"name": "all",
|
||||||
|
"configurations": ["server", "client"],
|
||||||
|
"stopAll": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"eslint.debug": false,
|
||||||
|
"eslint.format.enable": true,
|
||||||
|
"typescript.tsc.autoDetect": "watch",
|
||||||
|
"git.scanRepositories": ["./packages/pro"],
|
||||||
|
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||||
|
},
|
||||||
|
"editor.tabSize": 2,
|
||||||
|
"explorer.autoReveal": false,
|
||||||
|
"[javascript]": {
|
||||||
|
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||||
|
},
|
||||||
|
"[less]": {
|
||||||
|
"editor.defaultFormatter": "vscode.css-language-features"
|
||||||
|
},
|
||||||
|
"scm.repositories.visible": 9,
|
||||||
|
"scm.repositories.explorer": false,
|
||||||
|
"scm.repositories.selectionMode": "multiple",
|
||||||
|
"scm.repositories.sortOrder": "discovery time",
|
||||||
|
"git.ignoreLimitWarning": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "启动Client",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "npm",
|
||||||
|
"args": ["run", "dev"],
|
||||||
|
"options": {
|
||||||
|
"cwd": "${workspaceFolder}/packages/ui/certd-client"
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
},
|
||||||
|
"presentation": {
|
||||||
|
"echo": true,
|
||||||
|
"reveal": "always",
|
||||||
|
"focus": false,
|
||||||
|
"panel": "shared"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "启动Server",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "npm",
|
||||||
|
"args": ["run", "dev"],
|
||||||
|
"options": {
|
||||||
|
"cwd": "${workspaceFolder}/packages/ui/certd-server"
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
},
|
||||||
|
"presentation": {
|
||||||
|
"echo": true,
|
||||||
|
"reveal": "always",
|
||||||
|
"focus": false,
|
||||||
|
"panel": "shared"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "同时启动Client和Server",
|
||||||
|
"dependsOn": ["启动Client", "启动Server"],
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
},
|
||||||
|
"problemMatcher": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# Certd 开发 Agent 上下文
|
||||||
|
|
||||||
|
这个文件是给在本仓库工作的开发 agent 看的常驻项目说明。进入仓库后先读本文,再按任务读取对应导航或规则文件,避免每次重新全量扫描项目。
|
||||||
|
|
||||||
|
仓库代码导航、目录地图、常用入口和参考文件见 `.codex/repo-map.md`。更细的开发规则拆在 `.codex/agent-rules/` 下;本文只保留最高优先级的规则、架构边界和工作方式。
|
||||||
|
|
||||||
|
## 项目定位
|
||||||
|
|
||||||
|
Certd 是支持私有化部署的 SSL/TLS 证书自动化管理平台,提供 Web 管理台和后端服务,用于证书申请、续期、部署、监控、通知和开放 API 集成。
|
||||||
|
|
||||||
|
核心产品模型是“证书流水线”:
|
||||||
|
|
||||||
|
- 通过 ACME 申请证书
|
||||||
|
- 使用 DNS-01、HTTP-01、CNAME 代理或服务商集成完成域名验证
|
||||||
|
- 将证书转换或导出为 pem、pfx、der、jks、p7b 等格式
|
||||||
|
- 部署证书到主机、Nginx、Kubernetes、CDN、云厂商、面板等目标
|
||||||
|
- 通知用户,并监控站点证书过期时间
|
||||||
|
|
||||||
|
系统会保存证书、云厂商凭据、SSH 信息、API Key 等敏感数据,始终按私有化/本地部署产品处理,避免泄露本地数据和配置。
|
||||||
|
|
||||||
|
## 必读索引
|
||||||
|
|
||||||
|
- `.codex/repo-map.md`:仓库结构、后端/前端入口、流水线与插件地图、验证命令
|
||||||
|
- `.codex/agent-rules/backend.md`:后端、数据库迁移、文件上传、service/事务约定
|
||||||
|
- `.codex/agent-rules/frontend.md`:前端、Fast Crud、弹窗表单、格式化和禁跑命令
|
||||||
|
- `.codex/agent-rules/plugins.md`:流水线、插件归属、ACME/EAB、插件开发技能
|
||||||
|
- `.codex/agent-rules/testing.md`:测试优先策略、单测位置、ESM mock、聚焦验证
|
||||||
|
- `.codex/agent-rules/coding-style.md`:注释、可读性、DRY、单一职责等通用代码风格
|
||||||
|
|
||||||
|
## 仓库边界
|
||||||
|
|
||||||
|
这是一个 pnpm + lerna 的 monorepo。核心定位:
|
||||||
|
|
||||||
|
- `packages/ui/certd-server`:后端服务
|
||||||
|
- `packages/ui/certd-client`:前端 Web 管理台
|
||||||
|
- `packages/core/pipeline`:流水线核心
|
||||||
|
- `packages/core/acme-client`:ACME 协议客户端
|
||||||
|
- `packages/plugins/plugin-lib`:通用插件辅助能力和证书相关共享代码
|
||||||
|
|
||||||
|
`packages/pro/` 是独立 Git 工作区,使用 `packages/pro/.git` 管理。根仓库的 `git status` / `git diff` 默认看不到这里的实际改动;修改商业版代码后,要在 `packages/pro` 目录内单独执行 `git status` / `git diff` 检查。
|
||||||
|
|
||||||
|
## 硬性规则
|
||||||
|
|
||||||
|
- 根包管理器是 pnpm,不要引入 npm/yarn lockfile。
|
||||||
|
- 不要主动运行 `pnpm install`;用户会事先准备好 `node_modules`。如果 `pnpm install` 或测试因缺少依赖、TTY、网络问题失败,停止尝试并告知用户环境问题。
|
||||||
|
- 前端不要运行 `pnpm tsc` / `vue-tsc`;当前依赖组合中 `vue-tsc@1.8.27` 会抛无效内部错误。前端 `test:unit` 只是占位脚本。
|
||||||
|
- 不要把 `packages/ui/certd-server/data/`、`logs/`、生成的 metadata/dist 等运行时或构建产物纳入改动,除非任务明确要求。
|
||||||
|
- 做数据库结构变更时,添加或更新迁移脚本,不要依赖 TypeORM 自动同步。
|
||||||
|
- 做插件相关任务时,先读取对应 `.trae/skills/<skill>/SKILL.md`,再进入具体实现。
|
||||||
|
- 后端 service 拼接可选 `projectId` 查询条件时,不要直接写 `{ userId, projectId }`;应使用 `BaseService.buildUserProjectQuery(userId, projectId)`,只有 `projectId != null` 时才加入查询条件。
|
||||||
|
|
||||||
|
## 工作方式
|
||||||
|
|
||||||
|
- 先读本文;需要代码导航、目录入口、参考文件或验证命令时读 `.codex/repo-map.md`。
|
||||||
|
- 任务涉及后端、前端、插件、测试或代码风格时,先读取 `.codex/agent-rules/` 下对应规则文件,再查看具体代码。
|
||||||
|
- 在 PowerShell 中读取中文、Markdown、locale、文档类文件时,显式使用 `Get-Content -Encoding utf8`;如果仍乱码,再执行 `[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()` 后重试。
|
||||||
|
- 做后端任务时,先定位 `packages/ui/certd-server/src/modules` 下的模块,以及相关 entity/service/controller。
|
||||||
|
- 做前端任务时,先定位 `packages/ui/certd-client/src/views/certd` 下的页面,再找对应 `src/api`。
|
||||||
|
- 做服务商、DNS、部署、通知相关任务时,先看 `packages/ui/certd-server/src/plugins`,再看 `packages/plugins/plugin-lib` 里的共享辅助能力。
|
||||||
|
- 优先沿用现有模块、插件、服务模式,再考虑新增抽象;避免为了形式上的“复用”制造过度设计。
|
||||||
|
- 实现新功能或修复行为缺陷前,优先补对应单元测试并确认红灯,再实现代码并跑聚焦验证。确实不适合先写测试时,在回复中说明原因和替代验证方式。
|
||||||
|
- 后补单元测试时,先按正确行为写预期;如果红灯需要修改既有实现,先向用户确认这是 bug 还是既有需求,避免未经确认改变行为。
|
||||||
|
- 优先对改动包运行聚焦测试或格式化/ESLint;只有跨包影响明显时再考虑更大范围构建。
|
||||||
|
|
||||||
|
## 架构边界
|
||||||
|
|
||||||
|
插件是核心能力,不是边缘功能。新增服务商、DNS 验证、证书部署、通知方式等能力,通常应该放在插件包里,或放在 `packages/ui/certd-server/src/plugins/<plugin-name>/` 下。
|
||||||
|
|
||||||
|
修改证书申请、验证、部署或通知行为时,先判断改动属于 ACME client、pipeline 核心抽象、后端 module/service/entity/controller、具体插件实现,还是前端 view/form/schema。
|
||||||
|
|
||||||
|
如果只是某个服务商或部署目标的问题,不要轻易修改共享 pipeline/core 行为,除非确实是可复用的公共能力。
|
||||||
@@ -1,37 +1,54 @@
|
|||||||
# Certd
|
# Certd
|
||||||
|
|
||||||
Certd 是一个免费全自动申请和自动部署更新SSL证书的管理系统。
|
中文 | [English](./README_en.md)
|
||||||
后缀d取自linux守护进程的命名风格,意为证书守护进程。
|
|
||||||
|
|
||||||
关键字:证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具
|
Certd® 是一个免费的全自动证书管理系统,让你的网站证书永不过期。
|
||||||
|
后缀d取自linux守护进程的命名风格,意为证书守护进程
|
||||||
|
|
||||||
## 一、特性
|
> 首创流水线申请部署证书模式,已被多个项目“借鉴”,被抄也是一种成功。
|
||||||
本项目不仅支持证书申请过程自动化,还可以自动化部署更新证书,让你的证书永不过期。
|
|
||||||
|
|
||||||
* 全自动申请证书(支持所有注册商注册的域名)
|
|
||||||
* 全自动部署更新证书(目前支持部署到主机、阿里云、腾讯云等,目前已支持60+部署插件)
|
|
||||||
* 支持DNS-01、HTTP-01、CNAME代理等多种域名验证方式
|
|
||||||
* 支持通配符域名/泛域名,支持多个域名打到一个证书上,支持pem、pfx、der、jks等多种证书格式
|
|
||||||
* 邮件通知、webhook通知
|
|
||||||
* 私有化部署,数据保存本地,授权信息加密存储,镜像由Github Actions构建,过程公开透明
|
|
||||||
* 支持SQLite,PostgreSQL、MySQL数据库
|
|
||||||
|
|
||||||
>
|
|
||||||
> 流水线数量现已调整为无限制,欢迎大家使用
|
|
||||||
>
|
|
||||||
|
|
||||||
> 关于证书续期:
|
> 关于证书续期:
|
||||||
>* 实际上没有办法不改变证书文件本身情况下直接续期或者续签。
|
>
|
||||||
>* 我们所说的续期,其实就是按照全套流程重新申请一份新证书,然后重新部署上去。
|
> - 实际上没有办法不改变证书文件本身情况下直接续期或者续签。
|
||||||
|
> - 我们所说的续期,其实就是按照全套流程重新申请一份新证书,然后重新部署上去。
|
||||||
|
> - 免费证书过期时间90天,以后可能还会缩短,所以自动化部署必不可少
|
||||||
|
|
||||||
|
> 流水线数量现已调整为无限制,欢迎大家使用
|
||||||
|
|
||||||
|
| 官方开源地址: | |
|
||||||
|
| ------------------------------------------ | ---------------------------------------------------------------- |
|
||||||
|
| [Github](https://github.com/certd/certd) |  |
|
||||||
|
| [Gitee](https://gitee.com/certd/certd) |  |
|
||||||
|
| [AtomGit](https://atomgit.com/certd/certd) |  |
|
||||||
|
|
||||||
|
## 一、特性
|
||||||
|
|
||||||
|
本项目不仅支持证书申请过程自动化,还可以自动化部署更新证书,让你的证书永不过期。
|
||||||
|
|
||||||
|
- **全自动申请证书**: 支持所有注册商注册的域名,支持DNS-01、HTTP-01、CNAME代理等多种域名验证方式
|
||||||
|
- **全自动部署更新证书**: 目前支持部署到主机、阿里云、腾讯云等110+部署插件
|
||||||
|
- **多种证书格式**: 支持pem、pfx、der、jks、p7b
|
||||||
|
- **免费通配符域名/泛域名证书**: 支持多个域名打到一个证书上
|
||||||
|
- **多种通知方式**: 邮件通知、webhook通知、企微、钉钉、飞书、anpush等多种通知方式
|
||||||
|
- **私有化部署**: 数据保存本地,安装简单快捷,镜像由Github Actions构建,过程公开透明
|
||||||
|
- **多重安全保障**: 授权加密,站点隐藏,2FA,密码防爆破等多重安全保障
|
||||||
|
- **多数据库支持**:支持SQLite、PostgreSQL、MySQL、MariaDB
|
||||||
|
- **开放接口支持**: 提供RESTful API接口,方便集成到其他系统
|
||||||
|
- **站点证书监控**: 定时监控网站证书的过期时间
|
||||||
|
- **多用户管理**: 用户可以管理自己的证书流水线
|
||||||
|
- **项目管理**: 企业级项目管理模式
|
||||||
|
- **多语言支持**: 中英双语切换
|
||||||
|
- **无忧升级**: 版本向下兼容
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
## 二、在线体验
|
## 二、在线体验
|
||||||
|
|
||||||
官方Demo地址,自助注册后体验
|
官方Demo地址,自助注册后体验
|
||||||
|
|
||||||
https://certd.handfree.work/
|
https://certd.handfree.work/
|
||||||
|
|
||||||
> 注意数据将不定期清理,不定期停止定时任务,生产使用请自行部署
|
> 注意数据将不定期清理,不定期停止定时任务,生产使用请自行部署
|
||||||
> 包含敏感信息,务必自己本地部署进行生产使用
|
> 包含敏感信息,务必自己本地部署进行生产使用
|
||||||
|
|
||||||

|

|
||||||
@@ -41,203 +58,171 @@ https://certd.handfree.work/
|
|||||||
仅需3步,让你的证书永不过期
|
仅需3步,让你的证书永不过期
|
||||||
|
|
||||||
### 1. 创建证书流水线
|
### 1. 创建证书流水线
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
> 添加成功后,就可以直接运行流水线申请证书了
|
> 添加成功后,就可以直接运行流水线申请证书了
|
||||||
|
|
||||||
### 2. 添加部署任务
|
### 2. 添加部署任务
|
||||||
|
|
||||||
当然我们一般需要把证书部署到应用上,certd支持海量的部署插件,您可以根据自身实际情况进行选择,比如部署到Nginx、阿里云、腾讯云、K8S、CDN、宝塔、1Panel等等
|
当然我们一般需要把证书部署到应用上,certd支持海量的部署插件,您可以根据自身实际情况进行选择,比如部署到Nginx、阿里云、腾讯云、K8S、CDN、宝塔、1Panel等等
|
||||||
|
|
||||||
此处演示部署证书到主机的nginx上
|
此处演示部署证书到主机的nginx上
|
||||||

|

|
||||||
|
|
||||||
如果目前的部署插件都无法满足,您也可以手动下载,然后自行部署
|
如果目前的部署插件都无法满足,您也可以手动下载,然后自行部署
|
||||||

|

|
||||||
|
|
||||||
### 3. 定时运行
|
### 3. 定时运行
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
|
||||||
|
-------> [点我查看详细使用步骤演示](./step.md) <--------
|
||||||
|
↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
|
||||||
|
|
||||||
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
|
更多教程请访问官方文档 [certd.docmirror.cn](https://certd.docmirror.cn/guide/)
|
||||||
-------> [点我查看详细使用步骤演示](./step.md) <--------
|
|
||||||
↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
|
|
||||||
|
|
||||||
更多教程请访问文档网站 [certd.docmirror.cn](https://certd.docmirror.cn/)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 四、私有化部署
|
## 四、私有化部署
|
||||||
|
|
||||||
由于证书、授权信息等属于高度敏感数据,请务必私有化部署,保障数据安全
|
由于证书、授权信息等属于高度敏感数据,请务必私有化部署,保障数据安全
|
||||||
|
|
||||||
您可以根据实际情况从如下方式中选择一种方式进行私有化部署:
|
您可以根据实际情况从如下方式中选择一种方式进行私有化部署:
|
||||||
|
|
||||||
1. [宝塔面板方式部署](https://certd.docmirror.cn/guide/install/docker/)
|
1. 【推荐】[Docker方式部署 ](https://certd.docmirror.cn/guide/install/docker/)
|
||||||
2. [1Panel面板方式部署](https://certd.docmirror.cn/guide/install/1panel/)
|
2. 【推荐】[宝塔面板方式部署 ](https://certd.docmirror.cn/guide/install/docker/)
|
||||||
3. [Docker方式部署](https://certd.docmirror.cn/guide/install/docker/)
|
3. 【推荐】[1Panel面板方式部署](https://certd.docmirror.cn/guide/install/1panel/)
|
||||||
4. [源码方式部署](https://certd.docmirror.cn/guide/install/source/)
|
4. 【推荐】[雨云一键部署](https://app.rainyun.com/apps/rca/store/6646/?ref=NzExMDQ2) : 首充翻倍,每月仅需2.2元
|
||||||
|
[<img src="https://rainyun-apps.cn-nb1.rains3.com/materials/deploy-on-rainyun-cn.svg">](https://app.rainyun.com/apps/rca/store/6646/?ref=NzExMDQ2)
|
||||||
|
|
||||||
|
5. 【推荐】[一键安装脚本](https://certd.docmirror.cn/guide/install/docker/)(自动安装 Docker,Certd):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://gitee.com/certd/certd/raw/v2/docker/run/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
6. 【不推荐】[源码方式部署 ](https://certd.docmirror.cn/guide/install/source/)
|
||||||
|
|
||||||
#### Docker镜像说明:
|
#### Docker镜像说明:
|
||||||
* 国内镜像地址:
|
|
||||||
* `registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest`
|
|
||||||
* `registry.cn-shenzhen.aliyuncs.com/handsfree/certd:armv7`、`[version]-armv7`
|
|
||||||
* DockerHub地址:
|
|
||||||
* `https://hub.docker.com/r/greper/certd`
|
|
||||||
* `greper/certd:latest`
|
|
||||||
* `greper/certd:armv7`、`greper/certd:[version]-armv7`
|
|
||||||
|
|
||||||
* 镜像构建通过`Actions`自动执行,过程公开透明,请放心使用
|
- 国内镜像地址:
|
||||||
* [点我查看镜像构建日志](https://github.com/certd/certd/actions/workflows/build-image.yml)
|
- `registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest`
|
||||||
|
- `registry.cn-shenzhen.aliyuncs.com/handsfree/certd:armv7`、`[version]-armv7`
|
||||||
|
- DockerHub地址:
|
||||||
|
- `https://hub.docker.com/r/greper/certd`
|
||||||
|
- `greper/certd:latest`
|
||||||
|
- `greper/certd:armv7`、`greper/certd:[version]-armv7`
|
||||||
|
- GitHub Packages地址:
|
||||||
|
|
||||||
|
- `ghcr.io/certd/certd:latest`
|
||||||
|
- `ghcr.io/certd/certd:armv7`、`ghcr.io/certd/certd:[version]-armv7`
|
||||||
|
|
||||||
|
- 镜像构建通过`Actions`自动执行,过程公开透明,请放心使用
|
||||||
|
- [点我查看镜像构建日志](https://github.com/certd/certd/actions/workflows/build-image.yml)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
> 注意:
|
||||||
|
>
|
||||||
|
> - 本应用存储的证书、授权信息等属于高度敏感数据,请做好安全防护
|
||||||
|
> - 请务必使用HTTPS协议访问本应用,避免被中间人攻击
|
||||||
|
> - 请务必使用web应用防火墙防护本应用,防止XSS、SQL注入等攻击
|
||||||
|
> - 请务必做好服务器本身的安全防护,防止数据库泄露
|
||||||
|
> - 请务必做好数据备份,避免数据丢失
|
||||||
|
> - [更多安全生产建议点我](https://certd.docmirror.cn/guide/feature/safe/)
|
||||||
|
|
||||||
## 五、 升级
|
## 五、生态
|
||||||
|
|
||||||
### docker-compose方式部署
|
### 1. 客户端工具 SSL-Assistant
|
||||||
#### 1. 如果使用固定版本号
|
|
||||||
1. 修改`docker-compose.yaml`中的镜像版本号
|
|
||||||
2. 运行`docker compose up -d` 即可
|
|
||||||
|
|
||||||
#### 2. 如果需要使用最新版本
|
`SSL Assistant` 是一个运行于主机上的证书部署管理助手客户端。
|
||||||
```shell
|
支持自动扫描主机`Nginx`配置,然后从`Certd`拉取证书并部署。
|
||||||
#重新拉取镜像
|
在不想暴露ssh主机密码情况下,该工具非常好用。
|
||||||
docker pull registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
|
||||||
# 重新启动容器
|
|
||||||
docker compose down
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
> 数据默认存在`/data/certd`目录下,不用担心数据丢失
|
|
||||||
|
|
||||||
### 自动升级(仅限尝鲜建议非生产使用)
|
开源地址: https://github.com/Youngxj/SSL-Assistant
|
||||||
```yaml
|
|
||||||
version: '3.3'
|
|
||||||
services:
|
|
||||||
certd:
|
|
||||||
image: registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
|
||||||
container_name: certd
|
|
||||||
restart: unless-stopped
|
|
||||||
volumes:
|
|
||||||
- /data/certd:/app/data
|
|
||||||
ports:
|
|
||||||
- "7001:7001"
|
|
||||||
- "7002:7002"
|
|
||||||
environment:
|
|
||||||
- certd_system_resetAdminPasswd=false
|
|
||||||
labels:
|
|
||||||
com.centurylinklabs.watchtower.enable: "true"
|
|
||||||
|
|
||||||
certd-updater: # 添加 Watchtower 服务
|
## 六、更多帮助
|
||||||
image: containrrr/watchtower:latest
|
|
||||||
container_name: certd-updater
|
|
||||||
restart: unless-stopped
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
|
||||||
# 配置 自动更新
|
|
||||||
environment:
|
|
||||||
- WATCHTOWER_CLEANUP=true # 自动清理旧版本容器
|
|
||||||
- WATCHTOWER_INCLUDE_STOPPED=false # 不更新已停止的容器
|
|
||||||
- WATCHTOWER_LABEL_ENABLE=true # 根据容器标签进行更新
|
|
||||||
- WATCHTOWER_POLL_INTERVAL=300 # 每 5 分钟检查一次更新
|
|
||||||
|
|
||||||
```
|
请访问官方文档:[https://certd.docmirror.cn/](https://certd.docmirror.cn/guide/)
|
||||||
|
|
||||||
### 其他部署方式升级方法
|
- 升级方法:[升级方法](https://certd.docmirror.cn/guide/install/upgrade/)
|
||||||
请参考 https://certd.docmirror.cn/guide/install/upgrade.html
|
- 常见问题:[忘记密码](https://certd.docmirror.cn/guide/use/forgotpasswd/)
|
||||||
|
- 多数据库:[多数据库配置](https://certd.docmirror.cn/guide/install/database/)
|
||||||
|
- 站点安全:[站点安全特性](https://certd.docmirror.cn/guide/feature/safe/)
|
||||||
|
- 更新日志:[CHANGELOG](./CHANGELOG.md)
|
||||||
|
|
||||||
|
## 七、联系作者
|
||||||
|
|
||||||
|
|
||||||
### 更新日志:
|
|
||||||
|
|
||||||
[CHANGELOG](./CHANGELOG.md)
|
|
||||||
|
|
||||||
|
|
||||||
## 六、一些说明
|
|
||||||
* 本项目ssl证书提供商为letencrypt/Google/ZeroSSL
|
|
||||||
* 申请过程遵循acme协议
|
|
||||||
* 证书续期:
|
|
||||||
* 实际上没有办法不改变证书文件本身情况下直接续期或者续签。
|
|
||||||
* 我们所说的续期,其实就是按照全套流程重新申请一份新证书,然后重新部署上去。
|
|
||||||
* 免费证书过期时间90天,以后可能还会缩短,所以自动化部署必不可少
|
|
||||||
* 设置每天自动运行,当证书过期前35天,会自动重新申请证书并部署
|
|
||||||
|
|
||||||
|
|
||||||
## 七、不同平台的设置说明
|
|
||||||
|
|
||||||
* 已迁移到新的文档网站,请到常见问题章节查看
|
|
||||||
* [最新文档站链接 https://certd.docmirror.cn](https://certd.docmirror.cn/)
|
|
||||||
|
|
||||||
## 八、问题处理
|
|
||||||
### 7.1 忘记管理员密码
|
|
||||||
[重置管理员密码方法](https://certd.docmirror.cn/guide/use/forgotpasswd/)
|
|
||||||
|
|
||||||
## 九、联系作者
|
|
||||||
如有疑问,欢迎加入群聊(请备注certd)
|
如有疑问,欢迎加入群聊(请备注certd)
|
||||||
|
|
||||||
| 加群 | 微信群 | QQ群 |
|
| 加群 | 微信群 | QQ群 |
|
||||||
|---------|-------|-------|
|
| ------ | ----------------------------------------------------------- | ----------------------------------------------------------- |
|
||||||
| 二维码 | <img height="230" src="./docs/guide/contact/images/wx.png"> | <img height="230" src="./docs/guide/contact/images/qq.png"> |
|
| 二维码 | <img height="230" src="./docs/guide/contact/images/wx.png"> | <img height="230" src="./docs/guide/contact/images/qq.png"> |
|
||||||
|
|
||||||
也可以加作者好友
|
也可以加作者好友
|
||||||
|
|
||||||
| 加作者好友 | 微信 QQ |
|
| 加作者好友 | 微信 QQ |
|
||||||
|---------|-------------------------------------------------------------|
|
| ---------- | ----------------------------------------------------------- |
|
||||||
| 二维码 | <img height="230" src="./docs/guide/contact/images/me.png"> |
|
| 二维码 | <img height="230" src="./docs/guide/contact/images/me.png"> |
|
||||||
|
|
||||||
|
## 八、赞助捐赠
|
||||||
|
|
||||||
## 十、捐赠
|
开源为什么要做专业版收费?
|
||||||
************************
|
|
||||||
支持开源,为爱发电,我已入驻爱发电
|
|
||||||
https://afdian.com/a/greper
|
|
||||||
|
|
||||||
发电权益:
|
1. 纯靠为爱发电不可持续(比如:我的[dev-sidecar项目](https://github.com/docmirror/dev-sidecar)即便是拥有20K+star,也差点凉凉,幸亏有另外大佬接手用爱发电)
|
||||||
1. 可加入发电专属群,可以获得作者一对一技术支持
|
2. 没有赞助的项目,作者会比较任性,不会用心倾听用户的心声,不顾用户体验(比如:下意识拒绝需求、频繁破坏性变更升级、全盘推倒重来之类的)
|
||||||
2. 您的需求我们将优先实现,并且将作为专业版功能提供
|
3. 没有赞助的项目,交流群的戾气有时候比较重,容易起冲突
|
||||||
3. 一年期专业版激活码
|
|
||||||
4. 赠送国外免费服务器部署方案(0成本使用Certd,可能需要翻墙,不过现在性能越来越差了)
|
|
||||||
|
|
||||||
|
赞助权益:
|
||||||
|
|
||||||
专业版特权对比
|
1. 可加入专属VIP群,可以获得作者一对一技术支持,必要时可以远程协助
|
||||||
|
2. 您的需求我们将优先实现,并且可能将作为专业版功能提供
|
||||||
|
3. 获得专业版功能
|
||||||
|
|
||||||
| 功能 | 免费版 | 专业版 |
|
[50元专业版优惠券限时领取](https://app.handfree.work/subject/#/app/certd/product)
|
||||||
|---------|--------------------|-----------------------------|
|
|
||||||
| 免费证书申请 | 免费无限制 | 免费无限制 |
|
|
||||||
| 域名数量 | 无限制 | 无限制 |
|
|
||||||
| 证书流水线条数 | 无限制 | 无限制 |
|
|
||||||
| 站点证书监控 | 限制1条 | 无限制 |
|
|
||||||
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署等 | 支持群晖、宝塔、1Panel等,持续开发中 |
|
|
||||||
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、飞书、anpush、server酱等 |
|
|
||||||
|
|
||||||
|
专业版、商业版特权对比
|
||||||
|
|
||||||
************************
|
| 功能 | 免费版 | 专业版 | 商业版 |
|
||||||
|
| ---------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------ | --------------------------------------------------- |
|
||||||
|
| 证书申请 | 无限制 | 无限制 | 无限制 |
|
||||||
|
| 证书域名数量 | 无限制 | 无限制 | 无限制 |
|
||||||
|
| 证书流水线条数 | 无限制 | 无限制 | 无限制 |
|
||||||
|
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署、宝塔、1Panel等大部分插件 | 群晖、威联通、proxmox等 | 同专业版 |
|
||||||
|
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、钉钉、飞书、anpush、server酱等 | 同专业版 |
|
||||||
|
| 站点监控 | 限制1条 | 无限制 | 无限制 |
|
||||||
|
| 批量操作 | 无 | 流水线模版,流水线复制,批量运行,批量设置通知、定时等 | 同专业版 |
|
||||||
|
| VIP群 | 无 | 可加,一对一技术支持,必要时可申请远程协助 | 商业版技术支持 |
|
||||||
|
| 站点个性化 | 无 | 无 | 可自定义站点名称、Logo等,移除Certd元素,首页警告等 |
|
||||||
|
| 套餐功能 | 无 | 无 | 支持配置套餐供用户购买 |
|
||||||
|
| 数据统计 | 无 | 无 | 支持站点各类统计数据 |
|
||||||
|
| 插件管理 | 无 | 无 | 支持公共EAB设置,插件选项配置 |
|
||||||
|
| 是否可商用 | 不允许 | 不允许 | 可对外运营 |
|
||||||
|
|
||||||
## 十一、贡献代码
|
## 九、贡献代码
|
||||||
|
|
||||||
1. 本地开发 [贡献插件](https://certd.docmirror.cn/guide/development/)
|
1. 本地开发请参考 [贡献插件向导](https://certd.docmirror.cn/guide/development/)
|
||||||
2. 作为贡献者,代表您同意您贡献的代码如下许可:
|
2. 作为贡献者,代表您同意您贡献的代码如下许可:
|
||||||
1. 可以调整开源协议以使其更严格或更宽松。
|
1. 可以调整开源协议以使其更严格或更宽松。
|
||||||
2. 可以用于商业用途。
|
2. 可以用于商业用途。
|
||||||
|
|
||||||
|
感谢以下贡献者做出的贡献。
|
||||||
|
|
||||||
|
<a href="https://github.com/certd/certd/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=certd/certd" />
|
||||||
|
</a>
|
||||||
|
|
||||||
## 十二、 开源许可
|
## 十、 开源许可
|
||||||
* 本项目遵循 GNU Affero General Public License(AGPL)开源协议。
|
|
||||||
* 允许个人和公司内部自由使用、复制、修改和分发本项目,未获得商业授权情况下禁止任何形式的商业用途
|
|
||||||
* 未获得商业授权情况下,禁止任何对logo、版权信息及授权许可相关代码的修改。
|
|
||||||
* 如需商业授权,请联系作者。
|
|
||||||
|
|
||||||
## 十三、我的其他项目(求Star)
|
- 本项目遵循 GNU Affero General Public License(AGPL)开源协议。
|
||||||
|
- 允许个人和公司内部自由使用、复制、修改和分发本项目,未获得商业授权情况下禁止任何形式的商业用途
|
||||||
| 项目名称 | stars | 项目描述 |
|
- 未获得商业授权情况下,禁止任何对logo、版权信息及授权许可相关代码的修改。
|
||||||
|---------------------------------------------------------|-------------------------------------------------------------------------------------------------------|-----------------------------------|
|
- 如需商业授权,请联系作者。
|
||||||
| [袖手AI](https://ai.handsfree.work/) | | 袖手GPT,国内可用,无需FQ,每日免费额度 |
|
|
||||||
| [fast-crud](https://gitee.com/fast-crud/fast-crud/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/fast-crud/fast-crud?logo=github"/> | 基于vue3的crud快速开发框架 |
|
|
||||||
| [dev-sidecar](https://github.com/docmirror/dev-sidecar/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/docmirror/dev-sidecar?logo=github"/> | 直连访问github工具,无需FQ,解决github无法访问的问题 |
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 十四、更新日志
|
|
||||||
|
|
||||||
更新日志:[CHANGELOG](./CHANGELOG.md)
|
|
||||||
|
|
||||||
|
## 十一、我的其他项目(求Star)
|
||||||
|
|
||||||
|
| 项目名称 | stars | 项目描述 |
|
||||||
|
| ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
|
||||||
|
| [fast-crud](https://gitee.com/fast-crud/fast-crud/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/fast-crud/fast-crud?logo=github"/> | 基于vue3的crud快速开发框架 |
|
||||||
|
| [dev-sidecar](https://github.com/docmirror/dev-sidecar/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/docmirror/dev-sidecar?logo=github"/> | 直连访问github工具,无需FQ,解决github无法访问的问题 |
|
||||||
|
| [winsvc-manager](https://github.com/greper/winsvc-manager/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/greper/winsvc-manager?logo=github"/> | 可视化包装应用成为一个Windows服务,使其后台运行 |
|
||||||
|
|||||||
@@ -0,0 +1,211 @@
|
|||||||
|
# Certd
|
||||||
|
|
||||||
|
[中文](./README.md) | English
|
||||||
|
|
||||||
|
Certd® is a free, fully automated certificate management system that ensures your website certificates never expire. The suffix 'd' is inspired by the naming convention of Linux daemons, representing a certificate daemon.
|
||||||
|
|
||||||
|
> We pioneered the pipeline-based certificate application and deployment model, which has been "referenced" by multiple projects. Being copied is also a form of success.
|
||||||
|
|
||||||
|
> Regarding certificate renewal:
|
||||||
|
>
|
||||||
|
> - In fact, it's impossible to renew or reissue a certificate without modifying the certificate file itself.
|
||||||
|
> - What we refer to as renewal is essentially applying for a new certificate following the full process and redeploying it.
|
||||||
|
> - Free certificates expire in 90 days, which may be shortened in the future. Therefore, automated deployment is essential.
|
||||||
|
|
||||||
|
> The number of pipelines is now unlimited. Welcome to use it.
|
||||||
|
|
||||||
|
Official Open Source Address:
|
||||||
|
|
||||||
|
[Github](https://github.com/certd/certd) 
|
||||||
|
[Gitee](https://gitee.com/certd/certd) 
|
||||||
|
[AtomGit](https://atomgit.com/certd/certd) 
|
||||||
|
|
||||||
|
## 1. Features
|
||||||
|
|
||||||
|
This project not only supports automated certificate application but also automated certificate deployment and updates, ensuring your certificates never expire.
|
||||||
|
|
||||||
|
- Fully automated certificate application (supports domains registered with all registrars and multiple domain verification methods such as DNS-01, HTTP-01, and CNAME proxy).
|
||||||
|
- Fully automated certificate deployment and updates (currently supports deployment to over 70 plugins, including hosts, Alibaba Cloud, Tencent Cloud, etc.).
|
||||||
|
- Supports wildcard domains/pan-domains, allows multiple domains in a single certificate, and supports various certificate formats such as pem, pfx, der, and jks.
|
||||||
|
- Multiple notification methods, including email, webhook, WeChat Work, DingTalk, Lark, and anpush.
|
||||||
|
- On-premises deployment, local data storage, simple and quick installation. Images are built by Github Actions, with a transparent process.
|
||||||
|
- Multiple security measures, including authorization encryption, site hiding, 2FA, and password brute-force protection.
|
||||||
|
- Supports multiple databases such as SQLite, PostgreSQL, MySQL, and MariaDB.
|
||||||
|
- Open API support.
|
||||||
|
- Site certificate monitoring.
|
||||||
|
- Multi-user management.
|
||||||
|
- Multi-language support (Chinese and English switching).
|
||||||
|
- Downward compatibility across all versions, with one-click worry-free upgrades.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 2. Online Experience
|
||||||
|
|
||||||
|
Visit the official demo site and register to experience it.
|
||||||
|
|
||||||
|
https://certd.handfree.work/
|
||||||
|
|
||||||
|
> Note: Data will be cleaned up irregularly, and scheduled tasks may be stopped. For production use, please deploy it yourself.
|
||||||
|
> The content contains sensitive information. Make sure to deploy it locally for production use.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 3. Usage Tutorial
|
||||||
|
|
||||||
|
Just 3 steps to ensure your certificates never expire.
|
||||||
|
|
||||||
|
### 1. Create a Certificate Pipeline
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> After successful addition, you can directly run the pipeline to apply for a certificate.
|
||||||
|
|
||||||
|
### 2. Add a Deployment Task
|
||||||
|
|
||||||
|
Normally, we need to deploy certificates to applications. Certd supports a wide range of deployment plugins. You can choose based on your needs, such as deploying to Nginx, Alibaba Cloud, Tencent Cloud, K8S, CDN, Baota, 1Panel, etc.
|
||||||
|
|
||||||
|
Here's a demonstration of deploying certificates to a host's Nginx:
|
||||||
|

|
||||||
|
|
||||||
|
If the current deployment plugins don't meet your needs, you can also download them manually and deploy them yourself.
|
||||||
|

|
||||||
|
|
||||||
|
### 3. Run Scheduled Tasks
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
|
||||||
|
-------> [Click here to view detailed usage steps](./step.md) <--------
|
||||||
|
↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
|
||||||
|
|
||||||
|
For more tutorials, please visit the official documentation [certd.docmirror.cn](https://certd.docmirror.cn/guide/).
|
||||||
|
|
||||||
|
## 4. On-Premises Deployment
|
||||||
|
|
||||||
|
Since certificates, authorization information, and other data are highly sensitive, please make sure to deploy them on-premises to ensure data security.
|
||||||
|
|
||||||
|
You can choose one of the following deployment methods based on your needs:
|
||||||
|
|
||||||
|
1. 【Recommended】[Docker Deployment](https://certd.docmirror.cn/guide/install/docker/)
|
||||||
|
2. 【Recommended】[BT Panel Deployment](https://certd.docmirror.cn/guide/install/docker/)
|
||||||
|
3. 【Recommended】[1Panel Deployment](https://certd.docmirror.cn/guide/install/1panel/)
|
||||||
|
4. 【Recommended】[Rainyun One-Click Deployment](https://app.rainyun.com/apps/rca/store/6646/?ref=NzExMDQ2_): Double your first recharge, only $2.2 per month.
|
||||||
|
[<img src="https://rainyun-apps.cn-nb1.rains3.com/materials/deploy-on-rainyun-cn.svg">](https://app.rainyun.com/apps/rca/store/6646/?ref=NzExMDQ2_)
|
||||||
|
5. 【Not Recommended】[Source Code Deployment](https://certd.docmirror.cn/guide/install/source/)
|
||||||
|
|
||||||
|
#### Docker Image Information:
|
||||||
|
|
||||||
|
- Domestic Image Addresses:
|
||||||
|
- `registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest`
|
||||||
|
- `registry.cn-shenzhen.aliyuncs.com/handsfree/certd:armv7`, `[version]-armv7`
|
||||||
|
- DockerHub Addresses:
|
||||||
|
- `https://hub.docker.com/r/greper/certd`
|
||||||
|
- `greper/certd:latest`
|
||||||
|
- `greper/certd:armv7`, `greper/certd:[version]-armv7`
|
||||||
|
- GitHub Packages Addresses:
|
||||||
|
|
||||||
|
- `ghcr.io/certd/certd:latest`
|
||||||
|
- `ghcr.io/certd/certd:armv7`, `ghcr.io/certd/certd:[version]-armv7`
|
||||||
|
|
||||||
|
- Images are built automatically by `Actions`, with a transparent process. Please use them with confidence.
|
||||||
|
- [Click here to view image build logs](https://github.com/certd/certd/actions/workflows/build-image.yml)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> Note:
|
||||||
|
>
|
||||||
|
> - The certificates, authorization information, and other data stored in this application are highly sensitive. Please take appropriate security measures.
|
||||||
|
> - Make sure to use the HTTPS protocol to access this application to avoid man-in-the-middle attacks.
|
||||||
|
> - Make sure to use a web application firewall to protect this application from attacks such as XSS and SQL injection.
|
||||||
|
> - Make sure to secure the server itself to prevent database leakage.
|
||||||
|
> - Make sure to back up your data to avoid data loss.
|
||||||
|
> - [Click here for more production safety suggestions](https://certd.docmirror.cn/guide/feature/safe/)
|
||||||
|
|
||||||
|
## 5. Ecosystem
|
||||||
|
|
||||||
|
### 1. Client Tool: SSL-Assistant
|
||||||
|
|
||||||
|
`SSL Assistant` is a certificate deployment and management assistant client that runs on hosts. It supports automatic scanning of the host's `Nginx` configuration and pulling certificates from `Certd` for deployment. This tool is very useful when you don't want to expose your SSH host password.
|
||||||
|
|
||||||
|
Open-source Address: https://github.com/Youngxj/SSL-Assistant
|
||||||
|
|
||||||
|
## 6. More Help
|
||||||
|
|
||||||
|
Please visit the official documentation: [https://certd.docmirror.cn/](https://certd.docmirror.cn/guide/).
|
||||||
|
|
||||||
|
- Upgrade Method: [Upgrade Guide](https://certd.docmirror.cn/guide/install/upgrade/)
|
||||||
|
- Common Issues: [Forgot Password](https://certd.docmirror.cn/guide/use/forgotpasswd/)
|
||||||
|
- Multi-Database: [Multi-Database Configuration](https://certd.docmirror.cn/guide/install/database/)
|
||||||
|
- Site Security: [Site Security Features](https://certd.docmirror.cn/guide/feature/safe/)
|
||||||
|
- Changelog: [CHANGELOG](./CHANGELOG.md)
|
||||||
|
|
||||||
|
## 7. Contact the Author
|
||||||
|
|
||||||
|
If you have any questions, feel free to join the group chat (please mention 'certd' in your message).
|
||||||
|
|
||||||
|
| Join Group | WeChat Group | QQ Group |
|
||||||
|
| ---------- | ----------------------------------------------------------- | ----------------------------------------------------------- |
|
||||||
|
| QR Code | <img height="230" src="./docs/guide/contact/images/wx.png"> | <img height="230" src="./docs/guide/contact/images/qq.png"> |
|
||||||
|
|
||||||
|
You can also add the author as a friend.
|
||||||
|
|
||||||
|
| Add Author as Friend | WeChat QQ |
|
||||||
|
| -------------------- | ----------------------------------------------------------- |
|
||||||
|
| QR Code | <img height="230" src="./docs/guide/contact/images/me.png"> |
|
||||||
|
|
||||||
|
## 8. Donation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[](https://github.com/sponsors/greper)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Support open-source projects and contribute with love. I've joined Afdian.
|
||||||
|
https://afdian.com/a/greper
|
||||||
|
|
||||||
|
Benefits of Contribution:
|
||||||
|
|
||||||
|
1. Join the exclusive contributor group and get one-on-one technical support from the author.
|
||||||
|
2. Your requests will be prioritized and implemented as professional edition features.
|
||||||
|
3. Receive a one-year professional edition activation code.
|
||||||
|
|
||||||
|
Comparison of Professional Edition Privileges:
|
||||||
|
|
||||||
|
| Feature | Free Edition | Professional Edition |
|
||||||
|
| ------------------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- |
|
||||||
|
| Free Certificate Application | Unlimited for free | Unlimited for free |
|
||||||
|
| Number of Domains | Unlimited | Unlimited |
|
||||||
|
| Number of Certificate Pipelines | Unlimited | Unlimited |
|
||||||
|
| Site Certificate Monitoring | Limited to 1 | Unlimited |
|
||||||
|
| Automatic Deployment Plugins | Most plugins such as Alibaba Cloud CDN, Tencent Cloud, QiNiu CDN, Host Deployment, Baota, 1Panel | Synology |
|
||||||
|
| Notifications | Email, Custom Webhook | Email without configuration, WeChat Work, DingTalk, Lark, anpush, ServerChan, etc. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Contribute Code
|
||||||
|
|
||||||
|
1. For local development, please refer to the [Plugin Contribution Guide](https://certd.docmirror.cn/guide/development/).
|
||||||
|
2. As a contributor, you agree that your contributed code is subject to the following license:
|
||||||
|
1. The open-source license can be adjusted to be more or less restrictive.
|
||||||
|
2. It can be used for commercial purposes.
|
||||||
|
|
||||||
|
Thank you to the following contributors.
|
||||||
|
|
||||||
|
<a href="https://github.com/certd/certd/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=certd/certd" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
## 10. Open-Source License
|
||||||
|
|
||||||
|
- This project follows the GNU Affero General Public License (AGPL).
|
||||||
|
- Individuals and companies are allowed to use, copy, modify, and distribute this project freely for internal use. Any form of commercial use is prohibited without obtaining commercial authorization.
|
||||||
|
- Without commercial authorization, any modification of the logo, copyright information, and license-related code is prohibited.
|
||||||
|
- For commercial authorization, please contact the author.
|
||||||
|
|
||||||
|
## 11. My Other Projects (Please Star)
|
||||||
|
|
||||||
|
| Project Name | Stars | Project Description |
|
||||||
|
| -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
||||||
|
| [fast-crud](https://gitee.com/fast-crud/fast-crud/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/fast-crud/fast-crud?logo=github"/> | A fast CRUD development framework based on Vue3. |
|
||||||
|
| [dev-sidecar](https://github.com/docmirror/dev-sidecar/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/docmirror/dev-sidecar?logo=github"/> | A tool to access GitHub directly without a VPN, solving the problem of inaccessible GitHub. |
|
||||||
@@ -1 +0,0 @@
|
|||||||
1
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
23:51
|
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
version: '3.3' # 兼容旧版docker-compose
|
||||||
|
services:
|
||||||
|
certd:
|
||||||
|
# 镜像 # ↓↓↓↓↓ ---- 镜像版本号,建议改成固定版本号,例如:certd:1.29.0
|
||||||
|
image: registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
||||||
|
# image: ghcr.io/certd/certd:latest # --------- 如果 报镜像not found,可以尝试其他镜像源
|
||||||
|
# image: greper/certd:latest
|
||||||
|
container_name: certd # 容器名
|
||||||
|
restart: unless-stopped # 自动重启
|
||||||
|
volumes:
|
||||||
|
# ↓↓↓↓↓ -------------------------------------------------------- 数据库以及证书存储路径,默认存在宿主机的/data/certd/目录下,【您需要定时备份此目录,以保障数据容灾】
|
||||||
|
- /data/certd:/app/data # 只要修改冒号前面的,冒号后面的/app/data切记切记不要动
|
||||||
|
#- /volume1/docker/certd:/app/data:delegated #群晖使用这个配置
|
||||||
|
# ↓↓↓↓↓ -------------------------------------------------------- 如果走时不准,考虑挂载localtime文件
|
||||||
|
#- /etc/localtime:/etc/localtime
|
||||||
|
#- /etc/timezone:/etc/timezone
|
||||||
|
ports: # 端口映射
|
||||||
|
# ↓↓↓↓ ---------------------------------------------------------- 如果端口有冲突,可以修改第一个7001为其他不冲突的端口号,第二个7001不要动
|
||||||
|
- "7001:7001"
|
||||||
|
# ↓↓↓↓ ---------------------------------------------------------- https端口,可以根据实际情况,是否暴露该端口
|
||||||
|
- "7002:7002"
|
||||||
|
#↓↓↓↓ -------------------------------------------------------------- 如果出现getaddrinfo EAI_AGAIN 或 getaddrinfo ENOTFOUND 错误,可以尝试设置dns
|
||||||
|
# dns:
|
||||||
|
# - 223.5.5.5 # 阿里云公共dns
|
||||||
|
# - 223.6.6.6
|
||||||
|
# # ↓↓↓↓ --------------------------------------------------------- 如果你服务器在腾讯云,可以用这个替换上面阿里云的公共dns
|
||||||
|
# - 119.29.29.29 # 腾讯云公共dns
|
||||||
|
# - 182.254.116.116
|
||||||
|
# # ↓↓↓↓ --------------------------------------------------------- 如果你服务器部署在国外,可以用这个替换上面阿里云的公共dns
|
||||||
|
# - 8.8.8.8 # 谷歌公共dns
|
||||||
|
# - 8.8.4.4
|
||||||
|
# extra_hosts:
|
||||||
|
# # ↓↓↓↓ -------------------------------------------------------- 这里可以配置自定义hosts,外网域名可以指向本地局域网ip地址
|
||||||
|
# - "localdomain.com:192.168.1.3"
|
||||||
|
# # ↓↓↓↓ ------------------------------------------------ 直接使用主机的网络,如果网络问题实在找不到原因,可以尝试打开此参数
|
||||||
|
# network_mode: host
|
||||||
|
labels:
|
||||||
|
com.centurylinklabs.watchtower.enable: "true"
|
||||||
|
# ↓↓↓↓ -------------------------------------------------------------- 启用ipv6网络,还需要把下面networks的注释放开
|
||||||
|
# networks:
|
||||||
|
# - ip6net
|
||||||
|
environment:
|
||||||
|
# ↓↓↓↓ ----------------------------------------------------- 使用上海东八时区
|
||||||
|
- TZ=Asia/Shanghai
|
||||||
|
# 设置环境变量即可自定义certd配置
|
||||||
|
# 配置项见: packages/ui/certd-server/src/config/config.default.ts
|
||||||
|
# 配置规则: certd_ + 配置项, 点号用_代替
|
||||||
|
# #↓↓↓↓ ----------------------------- 如果忘记管理员密码,可以设置为true,docker compose up -d 重建容器之后,管理员密码将改成123456,然后请及时修改回false
|
||||||
|
- certd_system_resetAdminPasswd=false
|
||||||
|
# ↓↓↓ 要使用ipv6,将此配置修改为::
|
||||||
|
- certd_koa_hostname=0.0.0.0
|
||||||
|
|
||||||
|
# 默认使用sqlite文件数据库,如果需要使用其他数据库,请设置以下环境变量
|
||||||
|
# 注意: 选定使用一种数据库之后,不支持更换数据库。
|
||||||
|
# 数据库迁移方法:1、使用新数据库重新部署一套,然后将旧数据同步过去,注意flyway_history表的数据不要同步
|
||||||
|
# #↓↓↓↓ ----------------------------- 使用postgresql数据库,需要提前创建数据库
|
||||||
|
# - certd_flyway_scriptDir=./db/migration-pg # 升级脚本目录
|
||||||
|
# - certd_typeorm_dataSource_default_type=postgres # 数据库类型
|
||||||
|
# - certd_typeorm_dataSource_default_host=localhost # 数据库地址
|
||||||
|
# - certd_typeorm_dataSource_default_port=5433 # 数据库端口
|
||||||
|
# - certd_typeorm_dataSource_default_username=postgres # 用户名
|
||||||
|
# - certd_typeorm_dataSource_default_password=yourpasswd # 密码
|
||||||
|
# - certd_typeorm_dataSource_default_database=certd # 数据库名
|
||||||
|
|
||||||
|
# #↓↓↓↓ ----------------------------- 使用mysql8数据库,需要提前创建数据库 charset=utf8mb4, collation=utf8mb4_bin
|
||||||
|
# - certd_flyway_scriptDir=./db/migration-mysql # 升级脚本目录
|
||||||
|
# - certd_typeorm_dataSource_default_type=mysql # 数据库类型, 或者 mariadb
|
||||||
|
# - certd_typeorm_dataSource_default_host=localhost # 数据库地址
|
||||||
|
# - certd_typeorm_dataSource_default_port=3306 # 数据库端口
|
||||||
|
# - certd_typeorm_dataSource_default_username=root # 用户名
|
||||||
|
# - certd_typeorm_dataSource_default_password=yourpasswd # 密码
|
||||||
|
# - certd_typeorm_dataSource_default_database=certd # 数据库名
|
||||||
|
|
||||||
|
# ↓↓↓↓ --------------------------------------------------------- 自动升级,上面certd的版本号要保持为latest
|
||||||
|
certd-updater: # 添加 Watchtower 服务
|
||||||
|
image: containrrr/watchtower:latest
|
||||||
|
container_name: certd-updater
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
# 配置 自动更新
|
||||||
|
environment:
|
||||||
|
- WATCHTOWER_CLEANUP=true # 自动清理旧版本容器
|
||||||
|
- WATCHTOWER_INCLUDE_STOPPED=false # 不更新已停止的容器
|
||||||
|
- WATCHTOWER_LABEL_ENABLE=true # 根据容器标签进行更新
|
||||||
|
- WATCHTOWER_POLL_INTERVAL=600 # 每 10 分钟检查一次更新
|
||||||
|
|
||||||
|
|
||||||
|
# ↓↓↓↓ -------------------------------------------------------------- 启用ipv6网络,还需要把上面networks的注释放开
|
||||||
|
#networks:
|
||||||
|
# ip6net:
|
||||||
|
# enable_ipv6: true
|
||||||
|
# ipam:
|
||||||
|
# config:
|
||||||
|
# - subnet: 2001:db8::/64
|
||||||
@@ -3,18 +3,23 @@ services:
|
|||||||
certd:
|
certd:
|
||||||
# 镜像 # ↓↓↓↓↓ ---- 镜像版本号,建议改成固定版本号,例如:certd:1.29.0
|
# 镜像 # ↓↓↓↓↓ ---- 镜像版本号,建议改成固定版本号,例如:certd:1.29.0
|
||||||
image: registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
image: registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
||||||
|
# image: ghcr.io/certd/certd:latest # --------- 如果 报镜像not found,可以尝试其他镜像源
|
||||||
|
# image: greper/certd:latest
|
||||||
container_name: certd # 容器名
|
container_name: certd # 容器名
|
||||||
restart: unless-stopped # 自动重启
|
restart: unless-stopped # 自动重启
|
||||||
volumes:
|
volumes:
|
||||||
# ↓↓↓↓↓ -------------------------------------------------------- 数据库以及证书存储路径,默认存在宿主机的/data/certd/目录下,【您需要定时备份此目录,以保障数据容灾】
|
# ↓↓↓↓↓ -------------------------------------------------------- 数据库以及证书存储路径,默认存在宿主机的/data/certd/目录下,【您需要定时备份此目录,以保障数据容灾】
|
||||||
# 只要修改冒号前面的,冒号后面的/app/data不要动
|
- /data/certd:/app/data # 只要修改冒号前面的,冒号后面的/app/data切记切记不要动
|
||||||
- /data/certd:/app/data
|
#- /volume1/docker/certd:/app/data:delegated #群晖使用这个配置
|
||||||
|
# ↓↓↓↓↓ -------------------------------------------------------- 如果走时不准,考虑挂载localtime文件
|
||||||
|
#- /etc/localtime:/etc/localtime
|
||||||
|
#- /etc/timezone:/etc/timezone
|
||||||
ports: # 端口映射
|
ports: # 端口映射
|
||||||
# ↓↓↓↓ ---------------------------------------------------------- 如果端口有冲突,可以修改第一个7001为其他不冲突的端口号,第二个7001不要动
|
# ↓↓↓↓ ---------------------------------------------------------- 如果端口有冲突,可以修改第一个7001为其他不冲突的端口号,第二个7001不要动
|
||||||
- "7001:7001"
|
- "7001:7001"
|
||||||
# ↓↓↓↓ ---------------------------------------------------------- https端口,可以根据实际情况,是否暴露该端口
|
# ↓↓↓↓ ---------------------------------------------------------- https端口,可以根据实际情况,是否暴露该端口
|
||||||
- "7002:7002"
|
- "7002:7002"
|
||||||
#↓↓↓↓ -------------------------------------------------------------- 如果出现getaddrinfo ENOTFOUND错误,可以尝试设置dns
|
#↓↓↓↓ -------------------------------------------------------------- 如果出现getaddrinfo EAI_AGAIN 或 getaddrinfo ENOTFOUND 错误,可以尝试设置dns
|
||||||
# dns:
|
# dns:
|
||||||
# - 223.5.5.5 # 阿里云公共dns
|
# - 223.5.5.5 # 阿里云公共dns
|
||||||
# - 223.6.6.6
|
# - 223.6.6.6
|
||||||
@@ -35,11 +40,15 @@ services:
|
|||||||
# networks:
|
# networks:
|
||||||
# - ip6net
|
# - ip6net
|
||||||
environment:
|
environment:
|
||||||
|
# ↓↓↓↓ ----------------------------------------------------- 使用上海东八时区
|
||||||
|
- TZ=Asia/Shanghai
|
||||||
# 设置环境变量即可自定义certd配置
|
# 设置环境变量即可自定义certd配置
|
||||||
# 配置项见: packages/ui/certd-server/src/config/config.default.ts
|
# 配置项见: packages/ui/certd-server/src/config/config.default.ts
|
||||||
# 配置规则: certd_ + 配置项, 点号用_代替
|
# 配置规则: certd_ + 配置项, 点号用_代替
|
||||||
# #↓↓↓↓ ----------------------------- 如果忘记管理员密码,可以设置为true,重启之后,管理员密码将改成123456,然后请及时修改回false
|
# #↓↓↓↓ ----------------------------- 如果忘记管理员密码,可以设置为true,docker compose up -d 重建容器之后,管理员密码将改成123456,然后请及时修改回false
|
||||||
- certd_system_resetAdminPasswd=false
|
- certd_system_resetAdminPasswd=false
|
||||||
|
# ↓↓↓ 要使用ipv6,将此配置修改为::
|
||||||
|
- certd_koa_hostname=0.0.0.0
|
||||||
|
|
||||||
# 默认使用sqlite文件数据库,如果需要使用其他数据库,请设置以下环境变量
|
# 默认使用sqlite文件数据库,如果需要使用其他数据库,请设置以下环境变量
|
||||||
# 注意: 选定使用一种数据库之后,不支持更换数据库。
|
# 注意: 选定使用一种数据库之后,不支持更换数据库。
|
||||||
@@ -53,7 +62,7 @@ services:
|
|||||||
# - certd_typeorm_dataSource_default_password=yourpasswd # 密码
|
# - certd_typeorm_dataSource_default_password=yourpasswd # 密码
|
||||||
# - certd_typeorm_dataSource_default_database=certd # 数据库名
|
# - certd_typeorm_dataSource_default_database=certd # 数据库名
|
||||||
|
|
||||||
# #↓↓↓↓ ----------------------------- 使用mysql数据库,需要提前创建数据库 charset=utf8mb4, collation=utf8mb4_bin
|
# #↓↓↓↓ ----------------------------- 使用mysql8数据库,需要提前创建数据库 charset=utf8mb4, collation=utf8mb4_bin
|
||||||
# - certd_flyway_scriptDir=./db/migration-mysql # 升级脚本目录
|
# - certd_flyway_scriptDir=./db/migration-mysql # 升级脚本目录
|
||||||
# - certd_typeorm_dataSource_default_type=mysql # 数据库类型, 或者 mariadb
|
# - certd_typeorm_dataSource_default_type=mysql # 数据库类型, 或者 mariadb
|
||||||
# - certd_typeorm_dataSource_default_host=localhost # 数据库地址
|
# - certd_typeorm_dataSource_default_host=localhost # 数据库地址
|
||||||
|
|||||||
@@ -0,0 +1,340 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CERTD_VERSION="${CERTD_VERSION:-latest}"
|
||||||
|
INSTALL_DIR="${INSTALL_DIR:-/opt/certd}"
|
||||||
|
COMPOSE_FILE_URL="https://gitee.com/certd/certd/raw/v2/docker/run/docker-compose.yaml"
|
||||||
|
COMPOSE_FILE="$INSTALL_DIR/docker-compose.yaml"
|
||||||
|
|
||||||
|
DOCKER_MIRROR="https://mirrors.aliyun.com"
|
||||||
|
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
echo -e "${GREEN}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_command() {
|
||||||
|
command -v "$1" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
get_local_ip() {
|
||||||
|
LOCAL_IP=$(ip route get 1.1.1.1 2>/dev/null | grep -oP 'src \K[^ ]+' | head -1)
|
||||||
|
if [ -z "$LOCAL_IP" ]; then
|
||||||
|
LOCAL_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
|
||||||
|
fi
|
||||||
|
if [ -z "$LOCAL_IP" ]; then
|
||||||
|
LOCAL_IP="127.0.0.1"
|
||||||
|
fi
|
||||||
|
echo "$LOCAL_IP"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_public_ip() {
|
||||||
|
PUBLIC_IP=$(curl -s --max-time 5 https://api.ipify.org 2>/dev/null)
|
||||||
|
if [ -z "$PUBLIC_IP" ]; then
|
||||||
|
PUBLIC_IP=$(curl -s --max-time 5 https://checkip.amazonaws.com 2>/dev/null)
|
||||||
|
fi
|
||||||
|
if [ -z "$PUBLIC_IP" ]; then
|
||||||
|
PUBLIC_IP=""
|
||||||
|
fi
|
||||||
|
echo "$PUBLIC_IP"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_access_urls() {
|
||||||
|
LOCAL_IP=$(get_local_ip)
|
||||||
|
PUBLIC_IP=$(get_public_ip)
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
log_info "安装完成!"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
echo "访问地址:"
|
||||||
|
if [ -n "$PUBLIC_IP" ]; then
|
||||||
|
echo -e " ${GREEN}外网访问:${NC} http://$PUBLIC_IP:7001"
|
||||||
|
fi
|
||||||
|
echo -e " ${GREEN}局域网:${NC} http://$LOCAL_IP:7001"
|
||||||
|
echo ""
|
||||||
|
echo "配置文件: $COMPOSE_FILE"
|
||||||
|
echo ""
|
||||||
|
echo "常用命令:"
|
||||||
|
echo " cd $INSTALL_DIR"
|
||||||
|
echo " docker compose logs -f # 查看日志"
|
||||||
|
echo " docker compose restart # 重启服务"
|
||||||
|
echo " docker compose down # 停止服务"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_os() {
|
||||||
|
if [ -f /etc/os-release ]; then
|
||||||
|
. /etc/os-release
|
||||||
|
OS=$ID
|
||||||
|
VER=$VERSION_ID
|
||||||
|
elif [ -f /etc/centos-release ]; then
|
||||||
|
OS="centos"
|
||||||
|
elif [ -f /etc/redhat-release ]; then
|
||||||
|
OS="rhel"
|
||||||
|
else
|
||||||
|
OS="unknown"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_docker() {
|
||||||
|
if check_command docker; then
|
||||||
|
DOCKER_VERSION=$(docker --version 2>/dev/null | awk '{print $3}' | tr -d ',')
|
||||||
|
log_info "Docker 已安装: $DOCKER_VERSION"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log_warn "Docker 未安装"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_docker_compose() {
|
||||||
|
if check_command docker-compose; then
|
||||||
|
COMPOSE_VERSION=$(docker-compose --version 2>/dev/null | awk '{print $3}' | tr -d ',')
|
||||||
|
log_info "Docker Compose 已安装: $COMPOSE_VERSION"
|
||||||
|
return 0
|
||||||
|
elif docker compose version >/dev/null 2>&1; then
|
||||||
|
log_info "Docker Compose (插件版) 已安装"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log_warn "Docker Compose 未安装"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_docker_ubuntu() {
|
||||||
|
log_info "正在安装 Docker (Ubuntu/Debian)..."
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y ca-certificates curl gnupg lsb-release
|
||||||
|
|
||||||
|
mkdir -p /etc/apt/keyrings
|
||||||
|
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/${OS}/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg 2>/dev/null || \
|
||||||
|
curl -fsSL https://download.docker.com/linux/${OS}/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||||
|
|
||||||
|
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.aliyun.com/docker-ce/linux/${OS} $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||||
|
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||||
|
systemctl enable docker
|
||||||
|
systemctl start docker
|
||||||
|
|
||||||
|
log_info "Docker 安装完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_docker_centos() {
|
||||||
|
log_info "正在安装 Docker (CentOS/RHEL)..."
|
||||||
|
yum install -y yum-utils
|
||||||
|
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
|
||||||
|
yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||||
|
systemctl enable docker
|
||||||
|
systemctl start docker
|
||||||
|
|
||||||
|
log_info "Docker 安装完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_dockerrocky() {
|
||||||
|
log_info "正在安装 Docker (Rocky Linux/AlmaLinux)..."
|
||||||
|
dnf install -y yum-utils
|
||||||
|
dnf config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
|
||||||
|
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||||
|
systemctl enable docker
|
||||||
|
systemctl start docker
|
||||||
|
|
||||||
|
log_info "Docker 安装完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_docker_debian() {
|
||||||
|
log_info "正在安装 Docker (Debian)..."
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y ca-certificates curl gnupg2
|
||||||
|
|
||||||
|
mkdir -p /etc/apt/keyrings
|
||||||
|
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/debian/gpg | gpg --armor -o /etc/apt/keyrings/docker.gpg 2>/dev/null || \
|
||||||
|
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --armor -o /etc/apt/keyrings/docker.gpg
|
||||||
|
|
||||||
|
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.aliyun.com/docker-ce/linux/debian $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list
|
||||||
|
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||||
|
systemctl enable docker
|
||||||
|
systemctl start docker
|
||||||
|
|
||||||
|
log_info "Docker 安装完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_docker() {
|
||||||
|
detect_os
|
||||||
|
log_info "检测到操作系统: $OS"
|
||||||
|
|
||||||
|
case $OS in
|
||||||
|
ubuntu)
|
||||||
|
install_docker_ubuntu
|
||||||
|
;;
|
||||||
|
debian)
|
||||||
|
install_docker_debian
|
||||||
|
;;
|
||||||
|
centos)
|
||||||
|
install_docker_centos
|
||||||
|
;;
|
||||||
|
rhel|rocky|almalinux)
|
||||||
|
install_dockerrocky
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_error "不支持的操作系统: $OS"
|
||||||
|
log_info "请手动安装 Docker"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
install_docker_compose_standalone() {
|
||||||
|
log_info "正在安装 Docker Compose (独立版本)..."
|
||||||
|
|
||||||
|
COMPOSE_URLS=(
|
||||||
|
"https://get.daocloud.io/docker/compose/releases/download/v2.12.2/docker-compose-$(uname -s)-$(uname -m)"
|
||||||
|
"https://mirror.sjtu.edu.cn/github/docker/compose/releases/download/v2.12.2/docker-compose-$(uname -s)-$(uname -m)"
|
||||||
|
"https://github.com/docker/compose/releases/download/v2.12.2/docker-compose-$(uname -s)-$(uname -m)"
|
||||||
|
)
|
||||||
|
|
||||||
|
for url in "${COMPOSE_URLS[@]}"; do
|
||||||
|
log_info "尝试从: $url"
|
||||||
|
if curl -L "$url" -o /usr/local/bin/docker-compose 2>/dev/null; then
|
||||||
|
chmod +x /usr/local/bin/docker-compose
|
||||||
|
log_info "Docker Compose 安装完成"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
log_warn "下载失败,尝试下一个源..."
|
||||||
|
done
|
||||||
|
|
||||||
|
log_error "Docker Compose 安装失败"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
install_docker_compose() {
|
||||||
|
if check_command docker && docker compose version >/dev/null 2>&1; then
|
||||||
|
log_info "Docker Compose 插件已可用"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if check_command docker-compose; then
|
||||||
|
log_info "Docker Compose 独立版本已安装"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
install_docker_compose_standalone
|
||||||
|
}
|
||||||
|
|
||||||
|
download_compose_file() {
|
||||||
|
log_info "正在下载 docker-compose.yaml..."
|
||||||
|
mkdir -p "$INSTALL_DIR"
|
||||||
|
|
||||||
|
if curl -fsSL "$COMPOSE_FILE_URL" -o "$COMPOSE_FILE.tmp"; then
|
||||||
|
mv "$COMPOSE_FILE.tmp" "$COMPOSE_FILE"
|
||||||
|
log_info "docker-compose.yaml 已下载到 $COMPOSE_FILE"
|
||||||
|
|
||||||
|
if [ "$CERTD_VERSION" != "latest" ]; then
|
||||||
|
sed -i "s|certd:latest|certd:$CERTD_VERSION|g" "$COMPOSE_FILE"
|
||||||
|
log_info "已修改镜像版本为: $CERTD_VERSION"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_error "下载失败,请检查网络连接"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
start_certd() {
|
||||||
|
log_info "正在启动 Certd 容器..."
|
||||||
|
cd "$INSTALL_DIR"
|
||||||
|
|
||||||
|
if docker compose -f "$COMPOSE_FILE" up -d 2>/dev/null; then
|
||||||
|
log_info "Certd 启动成功!"
|
||||||
|
elif docker-compose -f "$COMPOSE_FILE" up -d; then
|
||||||
|
log_info "Certd 启动成功!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 2
|
||||||
|
docker ps --filter "name=certd" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_usage() {
|
||||||
|
echo "用法: $0 [选项]"
|
||||||
|
echo ""
|
||||||
|
echo "选项:"
|
||||||
|
echo " -v, --version VERSION 指定 Certd 版本 (默认: latest)"
|
||||||
|
echo " -p, --path PATH 指定安装路径 (默认: /opt/certd)"
|
||||||
|
echo " -h, --help 显示帮助信息"
|
||||||
|
echo ""
|
||||||
|
echo "示例:"
|
||||||
|
echo " $0 # 使用默认配置安装"
|
||||||
|
echo " $0 -v 1.29.0 # 安装指定版本"
|
||||||
|
echo " $0 -p /data/certd # 安装到指定目录"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
echo "=========================================="
|
||||||
|
echo " Certd 一键安装脚本"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-v|--version)
|
||||||
|
CERTD_VERSION="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-p|--path)
|
||||||
|
INSTALL_DIR="$2"
|
||||||
|
COMPOSE_FILE="$INSTALL_DIR/docker-compose.yaml"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
show_usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_error "未知选项: $1"
|
||||||
|
show_usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
log_info "Certd 版本: $CERTD_VERSION"
|
||||||
|
log_info "安装路径: $INSTALL_DIR"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
DOCKER_INSTALLED=true
|
||||||
|
COMPOSE_INSTALLED=true
|
||||||
|
|
||||||
|
if ! check_docker; then
|
||||||
|
echo ""
|
||||||
|
log_info "正在安装 Docker..."
|
||||||
|
install_docker
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! check_docker_compose; then
|
||||||
|
echo ""
|
||||||
|
log_info "正在安装 Docker Compose..."
|
||||||
|
install_docker_compose
|
||||||
|
fi
|
||||||
|
|
||||||
|
download_compose_file
|
||||||
|
start_certd
|
||||||
|
|
||||||
|
show_access_urls
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
@@ -1,148 +1,163 @@
|
|||||||
import { defineConfig } from "vitepress";
|
import {defineConfig} from "vitepress";
|
||||||
// Import lightbox plugin
|
// Import lightbox plugin
|
||||||
import lightbox from "vitepress-plugin-lightbox";
|
import lightbox from "vitepress-plugin-lightbox";
|
||||||
|
|
||||||
// https://vitepress.dev/reference/site-config
|
// https://vitepress.dev/reference/site-config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
title: "Certd",
|
title: "Certd",
|
||||||
titleTemplate: "开源SSL证书管理工具,证书自动化申请部署,让你的网站证书永不过期",
|
titleTemplate: "开源SSL证书管理工具,证书自动化申请部署,让你的网站证书永不过期",
|
||||||
description: "Certd帮助文档,Certd是一款开源免费的全自动SSL证书管理工具;证书自动化申请部署流水线;自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔。",
|
description: "Certd帮助文档,Certd是一款开源免费的全自动SSL证书管理工具;证书自动化申请部署流水线;自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔。",
|
||||||
markdown: {
|
markdown: {
|
||||||
config: (md) => {
|
config: (md) => {
|
||||||
// Use lightbox plugin
|
// Use lightbox plugin
|
||||||
md.use(lightbox, {});
|
md.use(lightbox, {});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sitemap: {
|
sitemap: {
|
||||||
hostname: 'https://certd.docmirror.cn'
|
hostname: 'https://certd.docmirror.cn'
|
||||||
},
|
},
|
||||||
head: [
|
head: [
|
||||||
// [
|
// [
|
||||||
// 'meta',
|
// 'meta',
|
||||||
// {
|
// {
|
||||||
// name: 'viewport',
|
// name: 'viewport',
|
||||||
// content:
|
// content:
|
||||||
// 'width=device-width,initial-scale=1,minimfast-cum-scale=1.0,maximum-scale=1.0,user-scalable=no',
|
// 'width=device-width,initial-scale=1,minimfast-cum-scale=1.0,maximum-scale=1.0,user-scalable=no',
|
||||||
// },
|
// },
|
||||||
// ],
|
// ],
|
||||||
["meta", {
|
["meta", {
|
||||||
name: "keywords",
|
name: "keywords",
|
||||||
content: "证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具、Certd、SSL证书自动部署、证书自动化,https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署,SSL平台,证书管理平台,证书流水线"
|
content: "证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具、Certd、SSL证书自动部署、证书自动化,https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署,SSL平台,证书管理平台,证书流水线"
|
||||||
}],
|
}],
|
||||||
// ["meta", { name: "google-site-verification",content: "V5XLTSnXoT15uQotwpxJoQolUo2d5UbSL-TacsyOsC0"}],
|
// ["meta", { name: "google-site-verification",content: "V5XLTSnXoT15uQotwpxJoQolUo2d5UbSL-TacsyOsC0"}],
|
||||||
//<meta name="baidu-site-verification" content="codeva-MiWN8Y07Ua" />
|
//<meta name="baidu-site-verification" content="codeva-MiWN8Y07Ua" />
|
||||||
// ["meta", {name: "baidu-site-verification",content: "codeva-MiWN8Y07Ua"}],
|
// ["meta", {name: "baidu-site-verification",content: "codeva-MiWN8Y07Ua"}],
|
||||||
["link", { rel: "icon", href: "/static/logo/logo.svg" }]
|
["link", {rel: "icon", href: "/static/logo/logo.svg"}]
|
||||||
],
|
],
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
logo: "/static/logo/logo.svg",
|
logo: "/static/logo/logo.svg",
|
||||||
search: {
|
search: {
|
||||||
provider: "local",
|
provider: "local",
|
||||||
options: {
|
options: {
|
||||||
detailedView: true,
|
detailedView: true,
|
||||||
translations: {
|
translations: {
|
||||||
button: {
|
button: {
|
||||||
buttonText: "搜索文档",
|
buttonText: "搜索文档",
|
||||||
buttonAriaLabel: "搜索文档"
|
buttonAriaLabel: "搜索文档"
|
||||||
},
|
},
|
||||||
modal: {
|
modal: {
|
||||||
noResultsText: "无法找到相关结果",
|
noResultsText: "无法找到相关结果",
|
||||||
resetButtonTitle: "清除查询条件",
|
resetButtonTitle: "清除查询条件",
|
||||||
footer: {
|
footer: {
|
||||||
selectText: "选择",
|
selectText: "选择",
|
||||||
closeText: "关闭",
|
closeText: "关闭",
|
||||||
navigateText: "切换"
|
navigateText: "切换"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// https://vitepress.dev/reference/default-theme-config
|
|
||||||
nav: [
|
|
||||||
{ text: "首页", link: "/" },
|
|
||||||
{ text: "指南", link: "/guide/" },
|
|
||||||
{ text: "商业版", link: "/comm/" },
|
|
||||||
{ text: "Demo体验", link: "https://certd.handfree.work" }
|
|
||||||
],
|
|
||||||
sidebar: {
|
|
||||||
"/guide/": [
|
|
||||||
{
|
|
||||||
text: "入门",
|
|
||||||
items: [
|
|
||||||
{ text: "简介", link: "/guide/" },
|
|
||||||
{ text: "快速开始", link: "/guide/start.md" },
|
|
||||||
{
|
|
||||||
text: "私有化部署",
|
|
||||||
items: [
|
|
||||||
{ text: "docker部署", link: "/guide/install/docker/" },
|
|
||||||
{ text: "宝塔面板部署", link: "/guide/install/baota/" },
|
|
||||||
{ text: "1Panel部署", link: "/guide/install/1panel/" },
|
|
||||||
{ text: "群晖部署", link: "/guide/use/synology/" },
|
|
||||||
{ text: "源码部署", link: "/guide/install/source/" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{ text: "演示教程", link: "/guide/tutorial.md" },
|
|
||||||
{ text: "版本升级", link: "/guide/install/upgrade.md" }
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
// https://vitepress.dev/reference/default-theme-config
|
||||||
text: "特性",
|
nav: [
|
||||||
items: [
|
{text: "首页", link: "/"},
|
||||||
{ text: "CNAME代理校验", link: "/guide/feature/cname/index.md" },
|
{text: "指南", link: "/guide/"},
|
||||||
{ text: "插件列表", link: "/guide/plugins.md" },
|
{text: "Demo体验", link: "https://certd.handfree.work"}
|
||||||
{ text: "多数据库支持", link: "/guide/install/database.md" },
|
],
|
||||||
{ text: "开放接口", link: "/guide/open/index.md" }
|
sidebar: {
|
||||||
]
|
"/guide/": [
|
||||||
|
{
|
||||||
|
text: "入门",
|
||||||
|
items: [
|
||||||
|
{text: "简介", link: "/guide/"},
|
||||||
|
{text: "快速开始", link: "/guide/start.md"},
|
||||||
|
{
|
||||||
|
text: "私有化部署",
|
||||||
|
items: [
|
||||||
|
{text: "docker部署", link: "/guide/install/docker/"},
|
||||||
|
{text: "宝塔面板部署", link: "/guide/install/baota/"},
|
||||||
|
{text: "1Panel部署", link: "/guide/install/1panel/"},
|
||||||
|
{text: "群晖部署", link: "/guide/use/synology/"},
|
||||||
|
{text: "源码部署", link: "/guide/install/source/"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{text: "演示教程", link: "/guide/tutorial.md"},
|
||||||
|
{text: "版本升级", link: "/guide/install/upgrade.md"},
|
||||||
|
{text: "赞助专业版", link: "/guide/donate/"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "特性",
|
||||||
|
items: [
|
||||||
|
{text: "CNAME代理校验", link: "/guide/feature/cname/index.md"},
|
||||||
|
{text: "多数据库支持", link: "/guide/install/database.md"},
|
||||||
|
{text: "开放接口", link: "/guide/open/index.md"},
|
||||||
|
{
|
||||||
|
text: "站点安全", link: "/guide/feature/safe/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "插件列表", items: [
|
||||||
|
{text: "DNS提供商", link: "/guide/plugins/dns-provider.md"},
|
||||||
|
{text: "任务插件", link: "/guide/plugins/deploy.md"},
|
||||||
|
{text: "通知插件", link: "/guide/plugins/notification.md"},
|
||||||
|
{text: "授权提供商", link: "/guide/plugins/access.md"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "常见问题",
|
||||||
|
items: [
|
||||||
|
{text: "QA", link: "/guide/qa/use.md"},
|
||||||
|
{text: "忘记密码/无法登录", link: "/guide/use/forgotpasswd/"},
|
||||||
|
{text: "群晖证书部署", link: "/guide/use/synology/"},
|
||||||
|
{text: "腾讯云密钥获取", link: "/guide/use/tencent/"},
|
||||||
|
{text: "连接windows主机", link: "/guide/use/host/windows.md"},
|
||||||
|
{text: "Google EAB获取", link: "/guide/use/google/"},
|
||||||
|
{text: "阿里云相关", link: "/guide/use/aliyun/"},
|
||||||
|
{text: "Azure相关", link: "/guide/use/azure/dns.md"},
|
||||||
|
{text: "数据备份", link: "/guide/use/backup/"},
|
||||||
|
{text: "Certd本身的证书更新", link: "/guide/use/https/index.md"},
|
||||||
|
{text: "js脚本插件使用", link: "/guide/use/custom-script/index.md"},
|
||||||
|
{text: "邮箱配置", link: "/guide/use/email/index.md"},
|
||||||
|
{text: "证书复用", link: "/guide/use/pretask/"},
|
||||||
|
{text: "IPv6支持", link: "/guide/use/setting/ipv6.md"},
|
||||||
|
{text: "ESXi", link: "/guide/use/ESXi/index.md"},
|
||||||
|
{text: "宝塔动态IP白名单", link: "/guide/use/baota/white_list.md"},
|
||||||
|
{text: "子域名托管", link: "/guide/use/cert/subdomain.md"},
|
||||||
|
{text: "流水线有效期", link: "/guide/use/pipeline/valid.md"},
|
||||||
|
{text: "IP证书申请", link: "/guide/use/cert/ip.md"},
|
||||||
|
{text: "企业模式", link: "/guide/use/mode/enterprise.md"},
|
||||||
|
{text: "插件开发", link: "/guide/use/dev/plugin.md"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "商业版配置", link: "/guide/use/comm/", items: [
|
||||||
|
{text: "支付宝配置", link: "/guide/use/comm/payments/alipay.md"},
|
||||||
|
{text: "微信支付配置", link: "/guide/use/comm/payments/wxpay.md"},
|
||||||
|
{text: "彩虹易支付配置", link: "/guide/use/comm/payments/yizhifu.md"},
|
||||||
|
{text: "插件选项映射", link: "/guide/use/comm/plugin/"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "其他",
|
||||||
|
items: [
|
||||||
|
{text: "贡献代码", link: "/guide/development/index.md"},
|
||||||
|
{text: "更新日志", link: "/guide/changelogs/CHANGELOG.md"},
|
||||||
|
{text: "镜像说明", link: "/guide/image.md"},
|
||||||
|
{text: "联系我们", link: "/guide/contact/"},
|
||||||
|
{text: "开源协议", link: "/guide/license/"},
|
||||||
|
{text: "我的其他开源项目", link: "/guide/link/"},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
text: "常见问题",
|
|
||||||
items: [
|
|
||||||
{ text: "群晖证书部署", link: "/guide/use/synology/" },
|
|
||||||
{ text: "腾讯云密钥获取", link: "/guide/use/tencent/" },
|
|
||||||
{ text: "连接windows主机", link: "/guide/use/host/windows.md" },
|
|
||||||
{ text: "Google EAB获取", link: "/guide/use/google/" },
|
|
||||||
{ text: "阿里云相关", link: "/guide/use/aliyun/" },
|
|
||||||
{ text: "忘记密码", link: "/guide/use/forgotpasswd/" },
|
|
||||||
{ text: "数据备份", link: "/guide/use/backup/" },
|
|
||||||
{ text: "Certd本身的证书更新", link: "/guide/use/https/index.md" },
|
|
||||||
{ text: "js脚本插件使用", link: "/guide/use/custom-script/index.md" },
|
|
||||||
{ text: "邮箱配置", link: "/guide/use/email/index.md" },
|
|
||||||
{ text: "IPv6支持", link: "/guide/use/setting/ipv6.md" },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "其他",
|
|
||||||
items: [
|
|
||||||
{ text: "贡献代码", link: "/guide/development/index.md" },
|
|
||||||
{ text: "更新日志", link: "/guide/changelogs/CHANGELOG.md" },
|
|
||||||
{ text: "镜像说明", link: "/guide/image.md" },
|
|
||||||
{ text: "联系我们", link: "/guide/contact/" },
|
|
||||||
{ text: "捐赠", link: "/guide/donate/" },
|
|
||||||
{ text: "开源协议", link: "/guide/license/" },
|
|
||||||
{ text: "我的其他开源项目", link: "/guide/link/" },
|
|
||||||
|
|
||||||
]
|
socialLinks: [
|
||||||
|
{icon: "github", link: "https://github.com/certd/certd"}
|
||||||
|
],
|
||||||
|
footer: {
|
||||||
|
message: "Certd帮助文档 | <a href='https://beian.miit.gov.cn/' target='_blank'>粤ICP备14088435号</a> ",
|
||||||
|
copyright: "Copyright © 2021-present <a href='https://handfree.work/' target='_blank'>handfree.work</a> "
|
||||||
}
|
}
|
||||||
],
|
|
||||||
"/comm/": [
|
|
||||||
{
|
|
||||||
text: "商业版",
|
|
||||||
items: [
|
|
||||||
{ text: "支付宝配置", link: "/comm/payments/alipay.md" },
|
|
||||||
{ text: "微信支付配置", link: "/comm/payments/wxpay.md" },
|
|
||||||
{ text: "彩虹易支付配置", link: "/comm/payments/yizhifu.md" },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
,
|
|
||||||
},
|
|
||||||
|
|
||||||
socialLinks: [
|
|
||||||
{ icon: "github", link: "https://github.com/certd/certd" }
|
|
||||||
],
|
|
||||||
footer: {
|
|
||||||
message: "Certd帮助文档 | <a href='https://beian.miit.gov.cn/' target='_blank'>粤ICP备14088435号</a> ",
|
|
||||||
copyright: "Copyright © 2021-present <a href='https://handfree.work/' target='_blank'>handfree.work</a> "
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
|
||||||
|
# 授权插件Demo
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { AccessInput, BaseAccess, IsAccess } from '@certd/pipeline';
|
||||||
|
import { isDev } from '../../utils/env.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 这个注解将注册一个授权配置
|
||||||
|
* 在certd的后台管理系统中,用户可以选择添加此类型的授权
|
||||||
|
*/
|
||||||
|
@IsAccess({
|
||||||
|
name: 'demo',
|
||||||
|
title: '授权插件示例',
|
||||||
|
icon: 'clarity:plugin-line',
|
||||||
|
desc: '',
|
||||||
|
})
|
||||||
|
export class DemoAccess extends BaseAccess {
|
||||||
|
/**
|
||||||
|
* 授权属性配置
|
||||||
|
*/
|
||||||
|
@AccessInput({
|
||||||
|
title: '密钥Id',
|
||||||
|
component: {
|
||||||
|
placeholder: 'demoKeyId',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
demoKeyId = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 授权属性配置
|
||||||
|
*/
|
||||||
|
@AccessInput({
|
||||||
|
//标题
|
||||||
|
title: '密钥串',
|
||||||
|
component: {
|
||||||
|
//input组件的placeholder
|
||||||
|
placeholder: 'demoKeySecret',
|
||||||
|
},
|
||||||
|
//是否必填
|
||||||
|
required: true,
|
||||||
|
//改属性是否需要加密
|
||||||
|
encrypt: true,
|
||||||
|
})
|
||||||
|
//属性名称
|
||||||
|
demoKeySecret = '';
|
||||||
|
}
|
||||||
|
new DemoAccess();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# 阿里云授权
|
||||||
|
```ts
|
||||||
|
|
||||||
|
import { IsAccess, AccessInput, BaseAccess } from "@certd/pipeline";
|
||||||
|
|
||||||
|
@IsAccess({
|
||||||
|
name: "aliyun",
|
||||||
|
title: "阿里云授权",
|
||||||
|
desc: "",
|
||||||
|
icon: "ant-design:aliyun-outlined",
|
||||||
|
order: 0,
|
||||||
|
})
|
||||||
|
export class AliyunAccess extends BaseAccess {
|
||||||
|
@AccessInput({
|
||||||
|
title: "accessKeyId",
|
||||||
|
component: {
|
||||||
|
placeholder: "accessKeyId",
|
||||||
|
},
|
||||||
|
helper: "登录阿里云控制台->AccessKey管理页面获取。",
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
accessKeyId = "";
|
||||||
|
@AccessInput({
|
||||||
|
title: "accessKeySecret",
|
||||||
|
component: {
|
||||||
|
placeholder: "accessKeySecret",
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
encrypt: true,
|
||||||
|
helper: "注意:证书申请需要dns解析权限;其他阿里云插件,需要对应的权限,比如证书上传需要证书管理权限;嫌麻烦就用主账号的全量权限的accessKey",
|
||||||
|
})
|
||||||
|
accessKeySecret = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
new AliyunAccess();
|
||||||
|
```
|
||||||
@@ -39,13 +39,21 @@ pnpm install
|
|||||||
pnpm init
|
pnpm init
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### lib包编译:
|
||||||
|
将packages下面依赖的包都编译一遍,并监听改动。
|
||||||
|
```shell
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
### 启动 server:
|
### 启动 server:
|
||||||
|
启动server
|
||||||
```shell
|
```shell
|
||||||
cd packages/ui/certd-server
|
cd packages/ui/certd-server
|
||||||
pnpm dev
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### 启动 client:
|
### 启动 client:
|
||||||
|
启动前端
|
||||||
```shell
|
```shell
|
||||||
cd packages/ui/certd-client
|
cd packages/ui/certd-client
|
||||||
pnpm dev
|
pnpm dev
|
||||||
|
|||||||
|
After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 305 KiB |
|
After Width: | Height: | Size: 92 KiB |
@@ -1,28 +1,62 @@
|
|||||||
# 捐赠
|
# 专业版赞助
|
||||||
************************
|
|
||||||
支持开源,为爱发电,我已入驻爱发电
|
|
||||||
https://afdian.com/a/greper
|
|
||||||
|
|
||||||
## 发电权益:
|
## 开源为什么要做专业版收费?
|
||||||
1. 可加入发电专属群,可以获得作者一对一技术支持
|
1. 纯靠为爱发电不可持续,容易烂尾(比如:我的[dev-sidecar项目](https://github.com/docmirror/dev-sidecar)即便是拥有20K+star,也差点凉凉,幸亏有另外大佬接手用爱发电)
|
||||||
|
2. 没有赞助的项目,作者会比较任性,不会用心倾听用户的心声,不顾用户体验(比如:下意识拒绝需求、频繁破坏性变更升级、全盘推倒重来之类的)
|
||||||
|
3. 没有赞助的项目,交流群的戾气有时候比较重,容易起冲突
|
||||||
|
|
||||||
|
## 赞助权益:
|
||||||
|
1. 可加入专属VIP群,可以获得作者一对一技术支持,必要时可以远程协助
|
||||||
2. 您的需求我们将优先实现,并且将作为专业版功能提供
|
2. 您的需求我们将优先实现,并且将作为专业版功能提供
|
||||||
3. 一年期专业版激活码
|
3. 获得专业版功能
|
||||||
|
|
||||||
|
****------------------****
|
||||||
|
> [限时¥50永久专业版优惠券,点我立刻领取](https://app.handfree.work/subject/#/app/certd/product)
|
||||||
|
|
||||||
|
****------------------****
|
||||||
## 专业版特权对比
|
## 专业版特权对比
|
||||||
|
|
||||||
| 功能 | 免费版 | 专业版 |
|
| 功能 | 免费版 | 专业版 | 商业版 |
|
||||||
|---------|------------------------|-----------------------------|
|
|---------|---------------------------------------|--------------------------------|---------------------------------|
|
||||||
| 免费证书申请 | 免费无限制 | 免费无限制 |
|
| 证书申请 | 无限制 | 无限制 | 无限制 |
|
||||||
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署等 | 支持群晖、宝塔、1Panel等,持续开发中 |
|
| 证书域名数量 | 无限制 | 无限制 | 无限制 |
|
||||||
| 证书流水线条数 | 无限制 | 无限制 |
|
| 证书流水线条数 | 无限制 | 无限制 | 无限制 |
|
||||||
| 站点证书监控 | 限制1条 | 无限制 |
|
| 自动部署插件 | 阿里云CDN、腾讯云、七牛CDN、主机部署、宝塔、1Panel等大部分插件 | 群晖、威联通、proxmox等 | 同专业版 |
|
||||||
| 通知 | 邮件通知、自定义webhook | 邮件免配置、企微、飞书、anpush、server酱等 |
|
| 通知 | 邮件通知、自定义webhook | 企微、钉钉、飞书、anpush、server酱等 | 同专业版 |
|
||||||
|
| 站点监控 | 限制1条 | 无限制 | 无限制 |
|
||||||
|
| 批量操作 | 无 | 流水线模版,流水线复制,批量运行,批量设置通知、定时等 | 同专业版 |
|
||||||
|
| VIP群 | 无 | 可加,一对一技术支持,必要时可申请远程协助 | 商业版技术支持 |
|
||||||
|
| 站点个性化 | 无 | 无 | 可自定义站点名称、Logo等,移除Certd元素,首页警告等 |
|
||||||
|
| 套餐功能 | 无 | 无 | 支持配置套餐供用户购买 |
|
||||||
|
| 数据统计 | 无 | 无 | 支持站点各类统计数据 |
|
||||||
|
| 插件管理 | 无 | 无 | 支持公共EAB设置,插件选项配置 |
|
||||||
|
| 是否可商用 | 不允许 | 不允许 | 可对外运营 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 专业版激活方式
|
## 专业版激活方式
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
发电后,在私信中获取激活码
|
|
||||||
************************
|
## 相关问题
|
||||||
|
|
||||||
|
### 1. 购买后VIP状态或时长未更新
|
||||||
|
系统管理-->账号绑定页面,打开一下即可自动更新VIP最新状态(如果未登录袖手账号需要先登录)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
### 2. 开发票
|
||||||
|
联系我们(微信:xiaojunnuo),并提供支付金额
|
||||||
|
|
||||||
|
### 3. VIP是否可以迁移换绑服务器?
|
||||||
|
可以的。
|
||||||
|
* 方式1. 直接将备份数据在新服务器上还原即可(首次访问会提示您是否绑定新url,点击是即可)
|
||||||
|
* 方式2. 如果旧站点数据丢失,您也可以部署一个新站点,然后在系统管理-->账号绑定页面,转移VIP即可
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
@@ -0,0 +1,27 @@
|
|||||||
|
# 站点隐藏
|
||||||
|
|
||||||
|
* 一般来说Certd设置好之后,很少需要访问。
|
||||||
|
* 所以我们`平时`可以把`站点访问关闭`,需要的时候再打开,减少站点被攻击的风险
|
||||||
|
|
||||||
|
## 1、开启站点隐藏
|
||||||
|
`系统管理->系统设置->安全设置->站点隐藏 `
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
:::warning
|
||||||
|
|
||||||
|
注意保存好`解除地址`和`解除密码`
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 2、临时关闭站点隐藏
|
||||||
|
|
||||||
|
访问上面的`解除地址`,输入`解除密码`,`临时解除`站点隐藏
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 3、忘记解除地址和解除密码怎么办
|
||||||
|
登录服务器,在数据库平级的目录下创建`.unhidden`命名的空白文件,即可临时解除站点隐藏
|
||||||
|
临时解除后会自动删除`.unhidden`文件,请尽快设置好新的`解除地址`和`解除密码`,并记住
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,49 @@
|
|||||||
|
# 安全特性
|
||||||
|
|
||||||
|
Certd 存储了证书以及授权等敏感数据,所以需要严格保障安全。
|
||||||
|
我们提供了以下安全特性,以及安全生产建议(请遵照建议进行生产部署以保障数据安全)
|
||||||
|
|
||||||
|
## 一、站点安全特性
|
||||||
|
|
||||||
|
### 1、 授权数据加密存储【默认开启】
|
||||||
|
* 所有的授权敏感字段会加密后存储
|
||||||
|
* 每个用户独立维护授权数据,连管理员都无权查看
|
||||||
|
|
||||||
|

|
||||||
|
星号部分为加密数据
|
||||||
|
|
||||||
|
### 2、 密码防爆破【默认开启】
|
||||||
|
* 登录失败次数过多,账号将被锁定,最高24小时(重启服务可解除锁定)
|
||||||
|
* 用户登录密码加密hash后存储,无法计算出密码明文
|
||||||
|

|
||||||
|
|
||||||
|
### 3、站点隐藏【建议开启】
|
||||||
|
* 一般来说Certd设置好之后,后续很少需要访问修改。
|
||||||
|
* 所以我们平时可以把站点访问关闭,需要的时候再打开,减少站点被攻击的风险
|
||||||
|
* 请前往 `系统管理->系统设置->安全设置->开启站点隐藏`
|
||||||
|

|
||||||
|
|
||||||
|
点击查看 [站点隐藏功能详细使用说明](./hidden/)
|
||||||
|
|
||||||
|
|
||||||
|
### 4、登录双重验证
|
||||||
|
|
||||||
|
支持2FA双重认证
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 5、数据库自动备份【建议开启】
|
||||||
|
* [自动备份设置说明](../../use/backup/)
|
||||||
|
|
||||||
|
|
||||||
|
## 二、安全生产建议
|
||||||
|
|
||||||
|
尽管`Cert`本身实现了很多安全特性,但`外部环境的安全`仍需要您来确保。
|
||||||
|
请`务必`遵循如下建议做好安全防护
|
||||||
|
|
||||||
|
* 请`务必`使用`HTTPS协议`访问本应用,避免被中间人攻击
|
||||||
|
* 请`务必`使用`web应用防火墙`防护本应用,防止XSS、SQL注入等攻击
|
||||||
|
* 请`务必`做好`服务器本身`的安全防护,防止数据库泄露
|
||||||
|
* 请`务必`做好[`数据备份`](../../use/backup/),避免数据丢失
|
||||||
|
* 请`务必`修改管理员账号用户名,且建议将admin注册为普通用户,且设置为禁用。
|
||||||
|
* 建议开启[`站点隐藏`](./hidden/)功能
|
||||||
@@ -9,6 +9,10 @@
|
|||||||
* `greper/certd:latest`
|
* `greper/certd:latest`
|
||||||
* `greper/certd:armv7`、`greper/certd:[version]-armv7`
|
* `greper/certd:armv7`、`greper/certd:[version]-armv7`
|
||||||
|
|
||||||
|
## GitHub Packages地址:
|
||||||
|
* `ghcr.io/certd/certd:latest`
|
||||||
|
* `ghcr.io/certd/certd:armv7`、`ghcr.io/certd/certd:[version]-armv7`
|
||||||
|
*
|
||||||
## 镜像构建公开
|
## 镜像构建公开
|
||||||
镜像构建通过`Actions`自动执行,过程公开透明,请放心使用
|
镜像构建通过`Actions`自动执行,过程公开透明,请放心使用
|
||||||
* [点我查看镜像构建日志](https://github.com/certd/certd/actions/workflows/build-image.yml)
|
* [点我查看镜像构建日志](https://github.com/certd/certd/actions/workflows/build-image.yml)
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 26 KiB |
@@ -6,29 +6,39 @@ Certd 是一款开源、免费、全自动申请和部署更新SSL证书的工
|
|||||||
关键字:证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具
|
关键字:证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具
|
||||||
|
|
||||||
|
|
||||||
## 一、特性
|
| 官方开源地址: | |
|
||||||
|
| ---- | ---- |
|
||||||
|
| [Github](https://github.com/certd/certd)|  |
|
||||||
|
| [Gitee](https://gitee.com/certd/certd) |  |
|
||||||
|
| [AtomGit](https://atomgit.com/certd/certd) | |
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 1、关于证书续期
|
||||||
|
>* 实际上没有办法不改变证书文件本身情况下直接续期或者续签。
|
||||||
|
>* 我们所说的续期,其实就是按照全套流程重新申请一份新证书,然后重新部署上去。
|
||||||
|
>* 免费证书过期时间90天,以后可能还会缩短,所以自动化部署必不可少
|
||||||
|
|
||||||
|
|
||||||
|
## 2、项目特性
|
||||||
本项目不仅支持证书申请过程自动化,还可以自动化部署更新证书,让你的证书永不过期。
|
本项目不仅支持证书申请过程自动化,还可以自动化部署更新证书,让你的证书永不过期。
|
||||||
|
|
||||||
* 全自动申请证书(支持所有注册商注册的域名)
|
* 全自动申请证书(支持所有注册商注册的域名,支持DNS-01、HTTP-01、CNAME代理等多种域名验证方式)
|
||||||
* 全自动部署更新证书(目前支持部署到主机、部署到阿里云、腾讯云等,目前已支持60+部署插件)
|
* 全自动部署更新证书(目前支持部署到主机、阿里云、腾讯云等100+部署插件)
|
||||||
* 支持通配符域名/泛域名,支持多个域名打到一个证书上
|
* 支持通配符域名/泛域名,支持多个域名打到一个证书上,支持pem、pfx、der、jks等多种证书格式
|
||||||
* 邮件通知
|
* 邮件通知、webhook通知、企微、钉钉、飞书、anpush等多种通知方式
|
||||||
* 私有化部署,保障数据安全
|
* 私有化部署,数据保存本地,安装升级非常简单快捷
|
||||||
* 支持SQLite、Postgresql、MySQL数据库
|
* 镜像由Github Actions构建,过程公开透明
|
||||||
|
* 授权加密,站点隐藏,2FA,密码防爆破等多重安全保障
|
||||||
|
* 支持SQLite,PostgreSQL、MySQL多种数据库
|
||||||
|
* 开放接口支持
|
||||||
|
* 站点证书监控
|
||||||
|
* 多用户管理
|
||||||
|
|
||||||
|
|
||||||
## 二、一些说明
|

|
||||||
* 本项目申请证书过程遵循acme协议
|
|
||||||
* 需要验证域名所有权,一般有两种方式
|
|
||||||
* http-01: 在网站根目录下放置一份txt文件
|
|
||||||
* dns-01: 需要给域名添加txt解析记录,通配符域名只能用这种方式(本项目仅支持dns-01)
|
|
||||||
* 证书续期:
|
|
||||||
* 实际上没有办法不改变证书文件本身情况下直接续期或者续签。
|
|
||||||
* 我们所说的续期,其实就是按照全套流程重新申请一份新证书,然后重新部署上去。
|
|
||||||
* 免费证书过期时间90天,以后可能还会缩短,所以自动化部署必不可少
|
|
||||||
* 设置每天自动运行,当证书过期前35天,会自动重新申请证书并部署
|
|
||||||
|
|
||||||
## 三、证书颁发机构对比
|
|
||||||
* Let's Encrypt:申请最简单。
|
|
||||||
* Google: 大厂光环,兼容性好,首次需要翻墙获取EAB。
|
|
||||||
* ZeroSSL: 需要EAB,获取EAB无需翻墙。
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"notice": "永久专业版上线,新用户立减50,升级到最新版点击下方“立即赞助”按钮前往获取",
|
||||||
|
"plus": {
|
||||||
|
"name": "专业版",
|
||||||
|
"price": "89.9",
|
||||||
|
"price3": "199",
|
||||||
|
"tooltip": "开源需要您的赞助支持",
|
||||||
|
"priceText":"¥89.9/年",
|
||||||
|
"discountText":"永久专业版50优惠券立即领取"
|
||||||
|
},
|
||||||
|
"comm": {
|
||||||
|
"name": "商业版",
|
||||||
|
"price": "399",
|
||||||
|
"price3": "899",
|
||||||
|
"tooltip": "3年优惠300",
|
||||||
|
"priceText":"¥399/年",
|
||||||
|
"discountText":"¥899/3年(3年优惠300)"
|
||||||
|
},
|
||||||
|
"app":{
|
||||||
|
"minVersion":"1.36.0",
|
||||||
|
"minVersionTip":"版本过低,为了您的数据安全,请尽快升级"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 21 KiB |
@@ -7,10 +7,41 @@ https://1panel.cn/docs/installation/online_installation/
|
|||||||
|
|
||||||
## 二、部署certd
|
## 二、部署certd
|
||||||
|
|
||||||
|
有两种安装方式
|
||||||
|
|
||||||
|
### 1. 应用商店方式安装【推荐】
|
||||||
|
|
||||||
|
#### 1.1 安装
|
||||||
|
打开`1Panel->应用商店`,更新远程应用,搜索`certd`,点击安装
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 1.2 访问测试:
|
||||||
|
http://ip:7001
|
||||||
|
https://ip:7002
|
||||||
|
默认账号密码
|
||||||
|
admin/123456
|
||||||
|
登录后请及时修改密码
|
||||||
|
|
||||||
|
#### 1.3 备份
|
||||||
|

|
||||||
|
|
||||||
|
#### 1.4 恢复
|
||||||
|
安装新Certd后,点击导入备份按钮,选择上面备份的文件即可
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 2. docker-compose方式安装
|
||||||
|
|
||||||
|
#### 2.1 安装
|
||||||
1. 打开`docker-compose.yaml`,整个内容复制下来
|
1. 打开`docker-compose.yaml`,整个内容复制下来
|
||||||
https://gitee.com/certd/certd/raw/v2/docker/run/docker-compose.yaml
|
https://gitee.com/certd/certd/raw/v2/docker/run/docker-compose.yaml
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
默认使用SQLite数据库,如果需要使用MySQL、PostgreSQL数据库,请参考[多数据库支持](../database.md)
|
||||||
|
:::
|
||||||
|
|
||||||
2. 然后到 `1Panel->容器->编排->新建编排`
|
2. 然后到 `1Panel->容器->编排->新建编排`
|
||||||
输入名称,粘贴`docker-compose.yaml`原文内容
|
输入名称,粘贴`docker-compose.yaml`原文内容
|
||||||
@@ -20,9 +51,12 @@ https://1panel.cn/docs/installation/online_installation/
|
|||||||

|

|
||||||
|
|
||||||
> 默认使用sqlite数据库,数据保存在`/data/certd`目录下,您可以手动备份该目录
|
> 默认使用sqlite数据库,数据保存在`/data/certd`目录下,您可以手动备份该目录
|
||||||
> certd还支持`mysql`和`postgresql`数据库,[点我了解如何切换其他数据库](../database)
|
|
||||||
|
|
||||||
3. 访问测试
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 2.2 访问测试
|
||||||
|
|
||||||
http://ip:7001
|
http://ip:7001
|
||||||
https://ip:7002
|
https://ip:7002
|
||||||
@@ -30,7 +64,7 @@ https://ip:7002
|
|||||||
admin/123456
|
admin/123456
|
||||||
登录后请及时修改密码
|
登录后请及时修改密码
|
||||||
|
|
||||||
## 三、升级
|
#### 2.3 升级
|
||||||
|
|
||||||
1. 找到容器,点击更多->升级
|
1. 找到容器,点击更多->升级
|
||||||

|

|
||||||
@@ -39,11 +73,11 @@ admin/123456
|
|||||||

|

|
||||||
|
|
||||||
|
|
||||||
## 四、数据备份
|
#### 2.4 备份
|
||||||
|
|
||||||
> 默认数据保存在`/data/certd`目录下,可以手动备份
|
> 默认数据保存在`/data/certd`目录下,可以手动备份
|
||||||
> 建议配置一条 [数据库备份流水线](../../use/backup/),自动备份
|
> 建议配置一条 [数据库备份流水线](../../use/backup/),自动备份
|
||||||
|
|
||||||
## 五、备份恢复
|
#### 2.5 恢复
|
||||||
|
|
||||||
将备份的`db.sqlite`及同目录下的其他文件一起覆盖到原来的位置,重启certd即可
|
将备份的`db.sqlite`及同目录下的其他文件一起覆盖到原来的位置,重启certd即可
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
|
||||||
|
## 自动升级配置
|
||||||
|
|
||||||
|
### 1. 方法一:使用watchtower监控自动升级【推荐】
|
||||||
|
|
||||||
|
1. 修改docker-compose.yaml文件增加如下配置
|
||||||
|
或 [下载完整的自动升级docker-compose.yaml配置](https://gitee.com/certd/certd/raw/v2/docker/auto/docker-compose.yaml)
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
certd:
|
||||||
|
# 镜像 # ↓↓↓↓↓ ---- 镜像版本号 这里要保持为latest
|
||||||
|
image: registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
||||||
|
... # 这里是你原来的docker-compose.yaml配置
|
||||||
|
|
||||||
|
# ↓↓↓↓ --------------------------------------------------------- 增加一个标签,表示certd需要自动升级
|
||||||
|
labels:
|
||||||
|
com.centurylinklabs.watchtower.enable: "true"
|
||||||
|
|
||||||
|
# ↓↓↓↓ --------------------------------------------------------- 自动升级watchtower配置,注意:上面certd的版本号要保持为latest
|
||||||
|
certd-updater: # 添加 Watchtower 服务
|
||||||
|
image: containrrr/watchtower:latest
|
||||||
|
container_name: certd-updater
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
# 配置 自动更新
|
||||||
|
environment:
|
||||||
|
- WATCHTOWER_CLEANUP=true # 自动清理旧版本容器
|
||||||
|
- WATCHTOWER_INCLUDE_STOPPED=false # 不更新已停止的容器
|
||||||
|
- WATCHTOWER_LABEL_ENABLE=true # 根据容器标签进行更新
|
||||||
|
- WATCHTOWER_POLL_INTERVAL=600 # 每 10 分钟检查一次更新
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 重启certd容器
|
||||||
|
```shell
|
||||||
|
cd certd
|
||||||
|
docker compose down
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 2. 方法二:使用Certd版本监控功能【不太稳定】
|
||||||
|
|
||||||
|
1. 选择Github-检查Release版本插件
|
||||||
|

|
||||||
|
按如下图填写配置
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
2. 检测到新版本后执行宿主机升级命令:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# 拉取最新镜像
|
||||||
|
docker pull registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
||||||
|
# 升级容器命令, 替换成你自己的certd更新命令
|
||||||
|
export RESTART_CERT='sleep 10; cd ~/deploy/certd/ ; docker compose down; docker compose up -d'
|
||||||
|
# 构造一个脚本10s后在后台执行,避免容器销毁时执行太快,导致流水线任务无法结束
|
||||||
|
nohup sh -c '$RESTART_CERT' >/dev/null 2>&1 & echo '10秒后重启' && exit
|
||||||
|
```
|
||||||
|
After Width: | Height: | Size: 82 KiB |
@@ -10,45 +10,51 @@
|
|||||||
* 登录宝塔面板,在菜单栏中点击 Docker,首次进入会提示安装Docker服务,点击立即安装,按提示完成安装
|
* 登录宝塔面板,在菜单栏中点击 Docker,首次进入会提示安装Docker服务,点击立即安装,按提示完成安装
|
||||||
|
|
||||||
### 2、部署certd
|
### 2、部署certd
|
||||||
|
以下两种方式任选一种:
|
||||||
|
|
||||||
#### 2.1 应用商店一键部署【推荐】
|
#### 2.1 应用商店方式一键部署【推荐】
|
||||||
|
|
||||||
* 在应用商店中找到`certd`(要先点右上角更新应用)
|
* 在宝塔Docker应用商店中找到`certd`(要先点右上角更新应用)
|
||||||
* 点击安装,配置域名等基本信息即可完成安装
|
* 点击安装,配置域名等基本信息即可完成安装
|
||||||
|
|
||||||
> 需要宝塔9.2.0及以上版本才支持
|
> 需要宝塔9.2.0及以上版本才支持
|
||||||
|
|
||||||
#### 2.2 容器编排部署
|
#### 2.2 容器编排方式部署
|
||||||
|
|
||||||
1. 打开`docker-compose.yaml`,整个内容复制下来
|
1. 打开`docker-compose.yaml`,整个内容复制下来
|
||||||
https://gitee.com/certd/certd/raw/v2/docker/run/docker-compose.yaml
|
https://gitee.com/certd/certd/raw/v2/docker/run/docker-compose.yaml
|
||||||
|
|
||||||
|
|
||||||
然后到宝塔里面进到docker->容器编排->添加容器编排
|
然后到宝塔里面进到docker->容器编排->添加容器编排
|
||||||

|

|
||||||
点击确定,等待启动完成
|
点击确定,等待启动完成
|
||||||

|

|
||||||
|
|
||||||
> certd默认使用sqlite数据库,另外支持`mysql`和`postgresql`数据库,[点我了解如何切换其他数据库](../database)
|
::: tip
|
||||||
|
默认安装使用SQLite数据库,如果需要使用MySQL、PostgreSQL数据库,请参考[多数据库支持](../database.md)
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
## 二、访问应用
|
## 二、访问应用
|
||||||
|
|
||||||
http://ip:7001
|
http://ip:7001
|
||||||
https://ip:7002
|
https://ip:7002
|
||||||
默认账号密码
|
默认账号密码
|
||||||
admin/123456
|
admin/123456
|
||||||
登录后请及时修改密码
|
登录后请及时修改密码
|
||||||
|
|
||||||
## 三、如何升级
|
## 三、如何升级
|
||||||
宝塔升级certd非常简单
|
宝塔升级certd非常简单
|
||||||
|
|
||||||
`docker`->`容器编排`->`左侧选择Certd`->`更新镜像`
|
打开容器页面: `docker`->`容器编排`->`左侧选择Certd`->`更新镜像`
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## 四、数据备份
|
## 四、数据备份
|
||||||
|
|
||||||
|
部署方式不同,数据保存位置不同
|
||||||
|
|
||||||
### 4.1 应用商店部署方式
|
### 4.1 应用商店部署方式
|
||||||
点击进入安装路径,数据保存在`./data`目录下,可以手动备份
|
点击进入安装路径,数据保存在`./data`目录下,可以手动备份
|
||||||
|
|
||||||
@@ -62,7 +68,6 @@ admin/123456
|
|||||||
数据默认保存在`/data/certd`目录下,可以手动备份
|
数据默认保存在`/data/certd`目录下,可以手动备份
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### 4.3 自动备份
|
### 4.3 自动备份
|
||||||
|
|
||||||
> 建议配置一条 [数据库备份流水线](../../use/backup/),自动备份
|
> 建议配置一条 [数据库备份流水线](../../use/backup/),自动备份
|
||||||
@@ -70,3 +75,15 @@ admin/123456
|
|||||||
## 五、备份恢复
|
## 五、备份恢复
|
||||||
|
|
||||||
将备份的`db.sqlite`及同目录下的其他文件一起覆盖到原来的位置,重启certd即可
|
将备份的`db.sqlite`及同目录下的其他文件一起覆盖到原来的位置,重启certd即可
|
||||||
|
|
||||||
|
|
||||||
|
## 六、宝塔部署相关问题排查
|
||||||
|
|
||||||
|
### 1. 无法访问Certd
|
||||||
|
1. 确认服务器的安全规则,是否放开了对应端口
|
||||||
|
2. 确认宝塔防火墙是否放开对应端口
|
||||||
|
3. 尝试将Certd容器加入宝塔的`bridge`网络
|
||||||
|

|
||||||
|
|
||||||
|
### 2. 动态IP无法加白名单问题
|
||||||
|
[Nginx代理解决方案](../../use/baota/white_list.md)
|
||||||
@@ -65,9 +65,54 @@ docker-compose up -d
|
|||||||
|
|
||||||
## 二、从旧版的sqlite切换数据库
|
## 二、从旧版的sqlite切换数据库
|
||||||
|
|
||||||
1. 先将`旧certd`升级到最新版 (`建议:备份sqlite数据库` )
|
从旧版`sqlite`迁移到`mysql`或`postgresql`数据库
|
||||||
2. 按照上面全新安装方式部署一套`新的certd` (`注意:新旧版本的certd要一致`)
|
|
||||||
3. 使用数据库工具将数据从sqlite导入到mysql或postgresql (`注意:flyway_history数据表不要导入`)
|
|
||||||
4. 重启新certd
|
|
||||||
5. 确认没有问题之后,删除旧版certd
|
|
||||||
|
|
||||||
|
下面以 `SQLite` 转 `MySQL` 为例进行演示
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 0.前提条件:
|
||||||
|
1. SQLite版Certd站点已经`升级到最新版` (`建议:备份sqlite数据库` )
|
||||||
|
2. `全新安装`MySQL版本Certd(`确保是全新的,因为里面的数据会被清空覆盖`)
|
||||||
|
3. 两套Certd站点版本一致
|
||||||
|
|
||||||
|
#### 1. 安装DBeaver工具
|
||||||
|
|
||||||
|
[https://dbeaver.io/download/](https://dbeaver.io/download/)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 2. 连接到sqlite数据库
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 3. 连接到mysql或postgresql数据库
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
#### 4. 开始同步数据
|
||||||
|
|
||||||
|
选择mysql数据库,选择所有的表(`flyway_history除外`),右键导入数据
|
||||||
|
|
||||||
|
> 切记flyway_history数据表不要导入
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
下一步、下一步,直到数据加载设置,勾选`在加载前截断目标表`(此选项很重要,并且会清空mysql certd数据库中的数据)
|
||||||
|

|
||||||
|
|
||||||
|
#### 5. 导入完成
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 6. 重启MySQL版本Certd
|
||||||
|
|
||||||
|
访问MySQL版本测试,数据已成功迁移
|
||||||
|
|
||||||
|
确认没有问题之后,删除旧版certd
|
||||||
@@ -2,7 +2,23 @@
|
|||||||
|
|
||||||
## 一、安装
|
## 一、安装
|
||||||
|
|
||||||
### 1. 环境准备
|
### 一键脚本安装(推荐)
|
||||||
|
|
||||||
|
如果您的服务器未安装 Docker,该脚本会自动为您安装 Docker 和 Docker Compose,并启动 Certd 容器。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://gitee.com/certd/certd/raw/v2/docker/run/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
> 支持 Ubuntu、Debian、CentOS、Rocky Linux、AlmaLinux 等主流发行版。
|
||||||
|
> docker-compose文件目录:`/opt/certd` ,升级时需要先进入此目录
|
||||||
|
> 运行时数据默认保存路径:`/data/certd` ,可使用参数指定:`-p /data/certd`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 手动安装
|
||||||
|
|
||||||
|
#### 1. 环境准备
|
||||||
|
|
||||||
1.1 准备一台云服务器
|
1.1 准备一台云服务器
|
||||||
|
|
||||||
@@ -19,9 +35,9 @@ https://docs.docker.com/engine/install/
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 随便创建一个目录
|
# 随便创建一个目录
|
||||||
mkdir certd
|
mkdir /opt/certd
|
||||||
# 进入目录
|
# 进入目录
|
||||||
cd certd
|
cd /opt/certd
|
||||||
# 下载docker-compose.yaml文件,或者手动下载放到certd目录下
|
# 下载docker-compose.yaml文件,或者手动下载放到certd目录下
|
||||||
wget https://gitee.com/certd/certd/raw/v2/docker/run/docker-compose.yaml
|
wget https://gitee.com/certd/certd/raw/v2/docker/run/docker-compose.yaml
|
||||||
|
|
||||||
@@ -42,8 +58,9 @@ docker compose up -d
|
|||||||
> 如果提示 没有docker compose命令,请安装docker-compose
|
> 如果提示 没有docker compose命令,请安装docker-compose
|
||||||
> https://docs.docker.com/compose/install/linux/
|
> https://docs.docker.com/compose/install/linux/
|
||||||
|
|
||||||
> certd默认使用sqlite数据库,另外还支持`mysql`和`postgresql`数据库,[点我了解如何切换其他数据库](../database)
|
::: tip
|
||||||
|
默认安装使用SQLite数据库,如果需要使用MySQL、PostgreSQL数据库,请参考[多数据库支持](../database.md)
|
||||||
|
:::
|
||||||
|
|
||||||
### 3. 访问测试
|
### 3. 访问测试
|
||||||
|
|
||||||
@@ -53,7 +70,16 @@ https://your_server_ip:7002
|
|||||||
记得修改密码
|
记得修改密码
|
||||||
|
|
||||||
|
|
||||||
## 二、升级
|
## 二、升级Certd
|
||||||
|
|
||||||
|
::: warning
|
||||||
|
如果您是第一次升级certd版本,切记切记先备份一下数据
|
||||||
|
```
|
||||||
|
# 查看/opt/certd/docker-compose.yaml配置
|
||||||
|
- /data/certd:/app/data # 请务必确保 /app/data 这个路径没有改动,固定写死
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
### 如果使用固定版本号
|
### 如果使用固定版本号
|
||||||
1. 修改`docker-compose.yaml`中的镜像版本号
|
1. 修改`docker-compose.yaml`中的镜像版本号
|
||||||
@@ -61,6 +87,7 @@ https://your_server_ip:7002
|
|||||||
|
|
||||||
### 如果使用`latest`版本
|
### 如果使用`latest`版本
|
||||||
```shell
|
```shell
|
||||||
|
cd /opt/certd
|
||||||
#重新拉取镜像
|
#重新拉取镜像
|
||||||
docker pull registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
docker pull registry.cn-shenzhen.aliyuncs.com/handsfree/certd:latest
|
||||||
# 重新启动容器
|
# 重新启动容器
|
||||||
|
|||||||
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 194 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 21 KiB |
@@ -1,17 +1,21 @@
|
|||||||
# 源码部署
|
# 源码部署
|
||||||
如果没有`git`和`nodejs`基础,则不推荐
|
如果没有开发基础、没有运维基础、没有`git`和`nodejs`基础,强烈不推荐此方式
|
||||||
|
|
||||||
## 一、源码安装
|
## 一、源码安装
|
||||||
|
|
||||||
### 环境要求
|
### 环境要求
|
||||||
- nodejs 20 及以上
|
- nodejs 22 及以上
|
||||||
### 源码启动
|
### 源码启动
|
||||||
```shell
|
```shell
|
||||||
# 克隆代码
|
# 克隆代码
|
||||||
git clone https://github.com/certd/certd --depth=1
|
git clone https://github.com/certd/certd --depth=1
|
||||||
# git checkout v1.x.x # 当v2主干分支代码无法正常启动时,可以尝试此命令,1.x.x换成最新版本号
|
# git checkout v1.x.x # 当v2主干分支代码无法正常启动时,可以尝试此命令,1.x.x换成最新版本号
|
||||||
cd certd
|
cd certd
|
||||||
|
|
||||||
# 启动服务
|
# 启动服务
|
||||||
./start.sh
|
./start.sh
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
>如果是windows,请先安装`git for windows` ,然后右键,选择`open git bash here`打开终端,再执行`./start.sh`命令
|
>如果是windows,请先安装`git for windows` ,然后右键,选择`open git bash here`打开终端,再执行`./start.sh`命令
|
||||||
@@ -20,9 +24,9 @@ cd certd
|
|||||||
|
|
||||||
### 访问测试
|
### 访问测试
|
||||||
|
|
||||||
http://your_server_ip:7001
|
http://your_server_ip:7001
|
||||||
https://your_server_ip:7002
|
https://your_server_ip:7002
|
||||||
默认账号密码:admin/123456
|
默认账号密码:admin/123456
|
||||||
记得修改密码
|
记得修改密码
|
||||||
|
|
||||||
|
|
||||||
@@ -36,19 +40,39 @@ cp -rf ./packages/ui/certd-server/data ../certd-data-backup
|
|||||||
|
|
||||||
git pull
|
git pull
|
||||||
# 如果提示pull失败,可以尝试强制更新
|
# 如果提示pull失败,可以尝试强制更新
|
||||||
# git checkout v2 -f && git pull
|
# git checkout v2 -f && git pull
|
||||||
|
|
||||||
# 先停止旧的服务,7001是certd的默认端口
|
# 先停止旧的服务,7001是certd的默认端口
|
||||||
kill -9 $(lsof -t -i:7001)
|
kill -9 $(lsof -t -i:7001)
|
||||||
# 重新编译启动
|
# 重新编译启动
|
||||||
./start.sh
|
./start.sh
|
||||||
|
|
||||||
```
|
```
|
||||||
|
::: warning
|
||||||
|
升级certd版本前,切记切记先备份一下数据
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
## 三、数据备份
|
## 三、数据备份
|
||||||
> 数据默认保存在 `./packages/ui/certd-server/data` 目录下
|
> 数据默认保存在 `./packages/ui/certd-server/data` 目录下
|
||||||
> 建议配置一条[数据库备份流水线](../../use/backup/) 自动备份
|
> 建议配置一条[数据库备份流水线](../../use/backup/) 自动备份
|
||||||
|
|
||||||
|
|
||||||
## 四、备份恢复
|
## 四、备份恢复
|
||||||
|
|
||||||
将备份的`db.sqlite`及同目录下的其他文件覆盖到原来的位置,重启certd即可
|
将备份的`db.sqlite`及同目录下的其他文件覆盖到原来的位置,重启certd即可
|
||||||
|
|
||||||
|
## 六、常见问题
|
||||||
|
|
||||||
|
### 1. npm install better-sqlite3 时,提示node-gyp需要vscode环境编译
|
||||||
|
|
||||||
|
1. 首先确保node版本为22以上
|
||||||
|
2. 将下面两行加到 ~/.npmrc 里面
|
||||||
|
3. 重新install
|
||||||
|
> better_sqlite3_binary_host=https://registry.npmmirror.com/-/binary/better-sqlite3
|
||||||
|
> better_sqlite3_binary_host_mirror=https://registry.npmmirror.com/-/binary/better-sqlite3
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||