mirror of
https://github.com/tnb-labs/panel.git
synced 2026-08-31 01:12:17 +08:00
Compare commits
1553 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 276ada3ab2 | |||
| fb5b048e5e | |||
| 0e84a9bcbb | |||
| ea42eccbeb | |||
| f17616d3af | |||
| 90c753c6f4 | |||
| 50d0095a62 | |||
| 8fd18406b5 | |||
| 3c48269ee5 | |||
| 974ca958bc | |||
| f78bb65a66 | |||
| 283e4f0a79 | |||
| 7a3308fac0 | |||
| ffd9f1264c | |||
| f56cbaa56a | |||
| b1077d1d7a | |||
| 18ed104a5a | |||
| 6f4179a01b | |||
| 1102c9753c | |||
| cbafb3fd96 | |||
| 4b4b69d93a | |||
| b0753eee91 | |||
| a2e8b01417 | |||
| 50a016e3b9 | |||
| 19c6f92e8c | |||
| 0f2bcb5290 | |||
| ad59657710 | |||
| 857cff0460 | |||
| cd615046ff | |||
| c1b7a9fbd1 | |||
| 92425e99cc | |||
| d64dca4c2b | |||
| bfc9150133 | |||
| bd24e34aa9 | |||
| 7ff1329d50 | |||
| 79f6bcd038 | |||
| ed477ede6b | |||
| 05b7f8e9ca | |||
| 247bc18ef7 | |||
| 8d111bd059 | |||
| a060ba136e | |||
| b01f04e6b5 | |||
| 09c6fbdeb9 | |||
| 760dab260f | |||
| 7c09ca8c48 | |||
| 6f0c11d36d | |||
| 7d8b1ddb55 | |||
| 293ff47e99 | |||
| 450a08e4ac | |||
| 3a50717972 | |||
| 20ca20ef2f | |||
| be7ebf963b | |||
| 361ad4caa5 | |||
| 9030a2e7e9 | |||
| e8c1ad2b43 | |||
| 9519be6303 | |||
| bbf70dd664 | |||
| 59d0f2f4f3 | |||
| 667320e5c9 | |||
| 1cbe7f3d87 | |||
| c70f7d6a79 | |||
| acc661050b | |||
| eef3e6c179 | |||
| 52d58fba3d | |||
| faa5cc98b0 | |||
| 167d557234 | |||
| 626e515f82 | |||
| c43dc54e93 | |||
| 664c24da97 | |||
| 6a2e75e6a8 | |||
| 3443fb1292 | |||
| 4dd06f5812 | |||
| 4896f0f823 | |||
| 13f06322dd | |||
| c3d5cdee5c | |||
| d9463df998 | |||
| 500f91d05e | |||
| 51079b3fc1 | |||
| 5269924ca4 | |||
| 9a47d3c2bb | |||
| 03e45a0907 | |||
| 4c6daf5028 | |||
| d173755f98 | |||
| a3827565c1 | |||
| df1dc40b80 | |||
| 4a19bf4e42 | |||
| d24f02378e | |||
| 05c22835e7 | |||
| 1f8e605f44 | |||
| 56dc03bfc7 | |||
| 11e56631b8 | |||
| 6811eb290f | |||
| 7f2c693735 | |||
| c699c96e6c | |||
| aef7dbd5bf | |||
| 04e0ccb64f | |||
| e69c50f7fd | |||
| 423a00ec5a | |||
| 3b87f48090 | |||
| 340ff9431d | |||
| ff0b1086f9 | |||
| 3b6197c3d0 | |||
| a386dc3a2a | |||
| 0e958cbd78 | |||
| 22cefeec09 | |||
| 9ac9f76c5a | |||
| f4de0f3de9 | |||
| 2f9975dbab | |||
| ba1f5b284d | |||
| d1f5e1d896 | |||
| 0aebdd08e1 | |||
| db1c489266 | |||
| 9cdc161f14 | |||
| f64b06b774 | |||
| c189cd6eb5 | |||
| 403126efaf | |||
| 0440f33cfb | |||
| 7aeb56f6b7 | |||
| 3f46cb66f7 | |||
| 0d62d435d9 | |||
| 5fd50feab4 | |||
| 5018ffa82e | |||
| e2f2864aa6 | |||
| 0c012141ec | |||
| aede27f908 | |||
| c9bf471c9d | |||
| 69fe99e040 | |||
| 45f4a01921 | |||
| df59816e4e | |||
| 8f1b69bd4b | |||
| 21d120451c | |||
| 72ecb6904d | |||
| b058d851fe | |||
| 5c6892c7f5 | |||
| 046105a542 | |||
| 86ff6bb88a | |||
| 06bd235730 | |||
| ddd19f1597 | |||
| e9bbd3bc5c | |||
| 30f94c920c | |||
| 9445d1cb82 | |||
| 13cd9b0b50 | |||
| 7442597a3c | |||
| 0e2e157001 | |||
| 4d2588e449 | |||
| 50bb9af9bc | |||
| 6f7c9269ca | |||
| 84741b594a | |||
| 35968f0567 | |||
| 258a0ffb4a | |||
| b3201e5630 | |||
| d460723a9e | |||
| 86842f8bc1 | |||
| 845cbb1048 | |||
| 0877489ae4 | |||
| d75baefe42 | |||
| 22721c2450 | |||
| 5fb0242601 | |||
| 51ffdcfa90 | |||
| 0d04c872aa | |||
| 53571eaf92 | |||
| 12d890252a | |||
| fbe316ea0f | |||
| 941c1e1bc2 | |||
| 598f8180f0 | |||
| a512d21f21 | |||
| 980b3674b0 | |||
| e205f51197 | |||
| 582fa4d93e | |||
| c43de6d23e | |||
| 28e2c61a4a | |||
| f437740438 | |||
| 918d8c2104 | |||
| 7988804603 | |||
| 0fd6db3249 | |||
| 9f52671ca3 | |||
| b14795a9fb | |||
| fd6aef2af7 | |||
| 08bc9e2390 | |||
| 1c40ad4d81 | |||
| ff68df94c5 | |||
| c3e7642b17 | |||
| 1c9b41da97 | |||
| d2c4ab3bb4 | |||
| 0cdf62af31 | |||
| c7ed032500 | |||
| 3412709f9b | |||
| 7a2d64744c | |||
| c5e6c3a770 | |||
| 8a27b0b8e4 | |||
| e463951362 | |||
| ef2dcfd071 | |||
| 5c54038cde | |||
| e67f32507a | |||
| de20811ca9 | |||
| 84779023bf | |||
| 348c5f513e | |||
| fad7da38b6 | |||
| bf3ce388d1 | |||
| 11657ab9a3 | |||
| 50e073c25e | |||
| ce15dcd4b7 | |||
| ec9a3117f7 | |||
| dd67bc47c4 | |||
| f26c722cab | |||
| 87c936490e | |||
| 3703c3cb5f | |||
| 958b479b8f | |||
| bcecec3756 | |||
| 39fb75a531 | |||
| c0945610f8 | |||
| 743936f29a | |||
| 8f85ac8c28 | |||
| 026969a5ce | |||
| d29e3d4d87 | |||
| 458b805357 | |||
| f745e33d98 | |||
| 762d5994ed | |||
| 63205d7238 | |||
| 2b697e95d6 | |||
| b86a83a6a4 | |||
| af809f83db | |||
| af75adafb3 | |||
| c9a00cc502 | |||
| ff476aeed5 | |||
| c07a60d1c8 | |||
| 908509e06b | |||
| 1f55c2448d | |||
| d885be3fea | |||
| 5304398216 | |||
| d0b3c14421 | |||
| 4dc21b5443 | |||
| a7e53fb02f | |||
| 7a7bd7bd4f | |||
| 2cf52e63d8 | |||
| 7d9bb1b191 | |||
| f1eccd6e7e | |||
| 01b627b3e2 | |||
| 3acc9b81b1 | |||
| c280f8f603 | |||
| 7e46f6dda8 | |||
| 47b312eafb | |||
| 9fc3c70b9b | |||
| b341eddd04 | |||
| a236ca9db8 | |||
| 06f6610d3b | |||
| 02e0aef265 | |||
| cd85032ecb | |||
| f2e41a3364 | |||
| 1012e5a246 | |||
| bb28e5ef6d | |||
| 1e5181c88e | |||
| e9b299f874 | |||
| d9837277ff | |||
| 87420648b3 | |||
| 58f303c751 | |||
| 8388cfe8bb | |||
| 4ad5c40c61 | |||
| 35587c9eca | |||
| 009d618cda | |||
| 4895f2e85b | |||
| 82be478ba5 | |||
| 7a118d1318 | |||
| a5a396f60e | |||
| 794a620113 | |||
| b923628630 | |||
| 31f39555ab | |||
| fe90dd3dc8 | |||
| c3054dcb91 | |||
| ddfcd3e45c | |||
| f9cda73981 | |||
| 3a1149b0f5 | |||
| 2f63523540 | |||
| 9cbef88984 | |||
| 8031e53852 | |||
| b5203b194a | |||
| 3d8623a9f9 | |||
| 35d398a4b7 | |||
| d210a47f84 | |||
| eb164e35a3 | |||
| 787263ecb7 | |||
| 4dd203b040 | |||
| b91038599c | |||
| f068ccb627 | |||
| 8161996a68 | |||
| fc3b508b07 | |||
| 2a62947664 | |||
| 59870c8f09 | |||
| e6d8f50aa3 | |||
| 4e54fb589f | |||
| b85ca6fd70 | |||
| 6ea1295f86 | |||
| 584cd74806 | |||
| 035046449d | |||
| d8ccc9d549 | |||
| 1178d45b1d | |||
| deed5e8d90 | |||
| 7fbb7e7097 | |||
| 1846caf388 | |||
| f4e3296389 | |||
| ee006282df | |||
| 3fa62ba0b1 | |||
| 2a1b5356dc | |||
| a890a20768 | |||
| bfcfbcd861 | |||
| 6c66aadb9b | |||
| 7bef2f34c0 | |||
| 2a77a0399e | |||
| 1d2ba12696 | |||
| abe1b709c9 | |||
| 91cf5c80bf | |||
| 295936d792 | |||
| 88c9150c5c | |||
| dbd581d8a6 | |||
| 1329069246 | |||
| d345f5a235 | |||
| 9aa951e247 | |||
| 01a228f3ad | |||
| e9c0190a49 | |||
| 8e71d28f78 | |||
| 1dca403677 | |||
| 0d428f885b | |||
| 4c59ab79c9 | |||
| 70f41b1678 | |||
| ea240a9350 | |||
| 0deeb281ea | |||
| cdc0ac72ae | |||
| a6ab9b24fa | |||
| 3bec347a8e | |||
| 726f98c164 | |||
| ff860a8c4e | |||
| 7a575267bb | |||
| 5ceec97412 | |||
| 082701b2d8 | |||
| 52df9c8955 | |||
| 97a15b7dd4 | |||
| 70c940569c | |||
| d5017376d3 | |||
| f1657daebb | |||
| 77cb594ee4 | |||
| cdb1296462 | |||
| 0d54c36eb6 | |||
| 3168e6efe3 | |||
| bf04648c64 | |||
| f48c975de3 | |||
| 1d41b2c61c | |||
| b23d32565b | |||
| a36c9344af | |||
| 243f89f619 | |||
| 752f70aa6b | |||
| 01a24374b7 | |||
| 401b85b87e | |||
| 47537e282b | |||
| f2d3911266 | |||
| 9baeaa47e0 | |||
| a5f609e23f | |||
| caa8448104 | |||
| 84f3a18074 | |||
| 61b2a70e84 | |||
| 710d38905f | |||
| a4cb2d2dd1 | |||
| f0cbd9b9b8 | |||
| e8ed4215d0 | |||
| 1b509fc9ad | |||
| 2856068b90 | |||
| 2b1f6b3e0a | |||
| 8a80182d2a | |||
| 0625ccfabe | |||
| 54b3b60efd | |||
| 24312f5993 | |||
| 31a22389d1 | |||
| de5e28ca81 | |||
| 585cc5604a | |||
| 874561a9d1 | |||
| 7d5a0ac1c0 | |||
| 8e31361eaf | |||
| cff71af26b | |||
| 89ace2360c | |||
| 3afc00be2f | |||
| 8aaf6f1377 | |||
| f0e9543bf1 | |||
| 017604ec11 | |||
| ab6e0903f5 | |||
| eba8392451 | |||
| 978c9b2fc3 | |||
| 2d525e9680 | |||
| 2099d2ca57 | |||
| 5e3ec29076 | |||
| ce84087702 | |||
| 855ed52a8b | |||
| 984820ee12 | |||
| d850292622 | |||
| b3c546dc13 | |||
| 7940b50a72 | |||
| 45616b4ab3 | |||
| ffc67b945d | |||
| 554a7dfbd7 | |||
| 647f9a530d | |||
| 245a120572 | |||
| f0ecfbe883 | |||
| 718a4f566d | |||
| 4a7795b4b7 | |||
| 7a5eb3210c | |||
| 0bc2585824 | |||
| edadc9b045 | |||
| 9f52de654a | |||
| b5dc070c9c | |||
| 7b2d6a980b | |||
| 3d107fe548 | |||
| ccf0ee3a22 | |||
| 9d200f3ce7 | |||
| 15364635fe | |||
| ffa53e93fe | |||
| 0b1d2a570b | |||
| e6fcd48e32 | |||
| 61b74b31d9 | |||
| b65a4249cb | |||
| da25dc2950 | |||
| f5709e735f | |||
| b2632535dd | |||
| 46b07f7903 | |||
| 5ed874f407 | |||
| 7b2b740dd9 | |||
| 80fde60526 | |||
| d6718dac13 | |||
| b86fc187d7 | |||
| 9530a06da5 | |||
| 0ecbe3da0e | |||
| 5e7093c8ec | |||
| 9733c6bbbd | |||
| db80cca9dc | |||
| 4b7eb49415 | |||
| 74a2fbb7e0 | |||
| 74b98202ec | |||
| 132d8e1201 | |||
| 1b365daa55 | |||
| 0a5861b617 | |||
| 50b1d39c5c | |||
| 96a25f657c | |||
| 008c2eb716 | |||
| c1622050fd | |||
| 338ea9dbba | |||
| 89f103dac6 | |||
| 1545246eb0 | |||
| 14cb1c2576 | |||
| e60caaee35 | |||
| 40cc33e61e | |||
| 93c1ecc6a3 | |||
| 4794872a9b | |||
| 3f3d83de16 | |||
| 87494e0a92 | |||
| 616ae77ef0 | |||
| a328c29870 | |||
| a277a5d214 | |||
| 4bb03d29f7 | |||
| bcb2040008 | |||
| ba4ca99ee7 | |||
| 7ad3e6f598 | |||
| f206a03e04 | |||
| 51a0c91d60 | |||
| 68130dd440 | |||
| 5b1b716cdd | |||
| cc210be717 | |||
| 2ef8040c3e | |||
| 3f75b68c1d | |||
| ea709f22ab | |||
| c8d223d9ac | |||
| a77d14fa76 | |||
| 696853181f | |||
| ce5074b254 | |||
| 4694db2242 | |||
| 899040ed25 | |||
| 2a0cb99141 | |||
| 9d3d4c49c1 | |||
| 3c19d8ed9a | |||
| 2c85070921 | |||
| aa181ababc | |||
| 500cc1845a | |||
| 91c3fc3951 | |||
| f70dd51e29 | |||
| 9319226fd8 | |||
| 62bd8927e3 | |||
| 2ed9d0b6fa | |||
| e1f0a7d04f | |||
| 9bd08fcb37 | |||
| b46aaf4313 | |||
| 8c5f441561 | |||
| bf2ae3c4fd | |||
| 9fe1776cea | |||
| 03766381df | |||
| d583aa68fa | |||
| b382d598f6 | |||
| b1a75b954c | |||
| 05fc80c1df | |||
| d520aab8ef | |||
| e331b308cf | |||
| 28b354b2f8 | |||
| 4ff1c865ed | |||
| b8fa605752 | |||
| 16cb85b07f | |||
| e70b5451f6 | |||
| d8e0957f79 | |||
| d90097994e | |||
| ffd5a775dc | |||
| aaf3fe30eb | |||
| 0ebd1c0699 | |||
| c9e2870ffe | |||
| 2b8890305c | |||
| b17b7ccfee | |||
| 748c82af1d | |||
| 8598f042a2 | |||
| d75f6b61d2 | |||
| 7d10e52708 | |||
| 5fdce07190 | |||
| a64a1ba5c8 | |||
| 6c2b0c2490 | |||
| 8b69dcde0e | |||
| c2508e0170 | |||
| af3c10f6c6 | |||
| 35771fe945 | |||
| 027eb73ab4 | |||
| bd453daed9 | |||
| b4bd840781 | |||
| e305640817 | |||
| ba7f7e1c53 | |||
| ed2582876d | |||
| 3641f30443 | |||
| ffc4ef1790 | |||
| 58f98f8c16 | |||
| 6f8219d1a7 | |||
| 79828f57c9 | |||
| 445a38cbc9 | |||
| ad85dfee69 | |||
| a2f3b25728 | |||
| 27c9baf5b8 | |||
| d2c80305f5 | |||
| 2152064cf7 | |||
| 042a6798fe | |||
| b11fa7e613 | |||
| 747041527f | |||
| bae16c0903 | |||
| 4bff5f403f | |||
| 0fc595c723 | |||
| f5147eb3e6 | |||
| 8d5476ebf5 | |||
| ad3e375d33 | |||
| c60bba465d | |||
| 378993f7c5 | |||
| 29807dedc4 | |||
| c710503d31 | |||
| 9a64885f86 | |||
| a8c0afe911 | |||
| 7fb5b14d42 | |||
| 2998181c23 | |||
| 049a36f1fb | |||
| 189f1c678b | |||
| e2a691a40c | |||
| f2f235fa4f | |||
| f33494ccb1 | |||
| 138a8b1130 | |||
| 08f528551d | |||
| 880a0db5f9 | |||
| 11303fff63 | |||
| c74a6208a1 | |||
| 4c4c8647cd | |||
| b061200831 | |||
| 7abc4c20f6 | |||
| a0a5795cee | |||
| c24ba188c2 | |||
| 98b5271a23 | |||
| 6b3b65d1f8 | |||
| c1494cd732 | |||
| 8bb4383b7e | |||
| edf72a8606 | |||
| 73eed059cf | |||
| 4334408c53 | |||
| 24e1dafc53 | |||
| 609e1b2037 | |||
| 426e52a576 | |||
| 47497e1098 | |||
| dcfe067569 | |||
| b5593f6152 | |||
| 2173dca7f9 | |||
| 15a5b01a49 | |||
| 916f5bf0b2 | |||
| f8f35e4996 | |||
| ebb6cf8235 | |||
| d2713b47f3 | |||
| 0d90f0f7c1 | |||
| 355c50c18c | |||
| 190d717a67 | |||
| c8195aec01 | |||
| b5ae62bc70 | |||
| 269bd050bb | |||
| 8e056344c1 | |||
| b8d88f0b2d | |||
| 1b79df7d8b | |||
| 5d4fc515fb | |||
| 1d64f015cf | |||
| 663bba3550 | |||
| b38d5393b2 | |||
| 0e18152df8 | |||
| 2321de1ef7 | |||
| 32efbc12dd | |||
| b4891f24a4 | |||
| 49b39740e1 | |||
| 1d1ada4f72 | |||
| 490d789513 | |||
| a3c9cfc8b5 | |||
| 631bac6ea5 | |||
| 716de528d7 | |||
| a964d5440f | |||
| e18cb2e686 | |||
| d7cfc00c7b | |||
| 43060c531c | |||
| d635725db5 | |||
| 56606a3248 | |||
| fa96b25f54 | |||
| f46383011c | |||
| e2ccc74737 | |||
| d4e1756f91 | |||
| a993249537 | |||
| 85e3946969 | |||
| 1361e72d3f | |||
| 8f2e01554a | |||
| f794705a5f | |||
| 308fe166a7 | |||
| b2c8dac7fd | |||
| c9f5e06637 | |||
| 609aa5d792 | |||
| 34bae77bf9 | |||
| 2f2ac9da87 | |||
| 4708196ba2 | |||
| 2fefba51e1 | |||
| 8d50b7c082 | |||
| b36e0695ba | |||
| 9c52e41fc9 | |||
| 91bd1d6af7 | |||
| db03346853 | |||
| cd1c7f6cd1 | |||
| 36ebc24307 | |||
| 7a1cdb2a4f | |||
| 487ca0734d | |||
| e43a6a83b3 | |||
| 85617c9fed | |||
| 64ddb08584 | |||
| ba6d695eba | |||
| e19b4aa8e2 | |||
| f506b0425f | |||
| 9629c6bc51 | |||
| 426de0e18d | |||
| b18a4a8282 | |||
| 26b84b3a91 | |||
| 63278ee05f | |||
| bbc928a150 | |||
| 78ca557131 | |||
| 763f68ae33 | |||
| 8ab004e5d9 | |||
| f9d7de06f2 | |||
| 88b4d290b4 | |||
| 44e127d5ba | |||
| 7601cc2601 | |||
| e112ddf522 | |||
| 2601ca5383 | |||
| 87e5d9d784 | |||
| f94a1dd6fe | |||
| e8292f3c52 | |||
| 1724229661 | |||
| b12abf9e64 | |||
| f6c81389f2 | |||
| 58d0418839 | |||
| 65387b23ac | |||
| 7986235fb4 | |||
| 2a6be12970 | |||
| d0b2af5cce | |||
| 9c8567a815 | |||
| 008f1e7e08 | |||
| ecfccab6af | |||
| 54efe01644 | |||
| 2d967b2b36 | |||
| d71a07eaa4 | |||
| 1bc926f0f4 | |||
| 7d09418b3f | |||
| 3079b2f5b7 | |||
| 5131172293 | |||
| e53afe5b3d | |||
| 5ee35cbd30 | |||
| 6dd40e6bdb | |||
| 6af8864907 | |||
| 7b0e54d8b9 | |||
| 37d048c5dc | |||
| 0582113172 | |||
| 7c482bf615 | |||
| c05e995046 | |||
| d25fc9804e | |||
| f6878982ad | |||
| 821736d06d | |||
| 38edda3650 | |||
| e5d16fdf9b | |||
| 0f942611e9 | |||
| 6916c02752 | |||
| 5632a5aa1d | |||
| dcaa603d28 | |||
| f763b746b2 | |||
| 67e4a9eeee | |||
| 0976cd6f41 | |||
| a37ad7d519 | |||
| 261e92a659 | |||
| 978ae70e44 | |||
| df9b2ff009 | |||
| 28d83cccd7 | |||
| a3664973e3 | |||
| 34cc143492 | |||
| 25edcf6f27 | |||
| e30e6a4740 | |||
| a46e47f19a | |||
| ff4ebf022a | |||
| 4f3d37ce53 | |||
| 40fba11a95 | |||
| 446db0ca69 | |||
| a684f93e94 | |||
| f26083cf38 | |||
| ab20aa5e8f | |||
| 901f857dd6 | |||
| 36b2cae106 | |||
| a26855d583 | |||
| 531af95edf | |||
| ba302d25ad | |||
| efeadbe308 | |||
| e3822d29ad | |||
| 1ab41763c0 | |||
| 1a2cf67525 | |||
| ad77920f63 | |||
| f3836b71fd | |||
| 60ef754091 | |||
| 4025430cde | |||
| 1d881961aa | |||
| 52882b46b9 | |||
| db06196d30 | |||
| 32c04eb10d | |||
| e3761c3a31 | |||
| 8786009022 | |||
| d86e8cf3c2 | |||
| 0db38dd4df | |||
| b752eeb632 | |||
| 31d1507baf | |||
| 84a3d2563a | |||
| d856813f58 | |||
| 4955f2fc06 | |||
| 587846d4f9 | |||
| 38e260e36d | |||
| 4daa98ef5c | |||
| de639d700f | |||
| 0b2b4c643c | |||
| 027ffe3e70 | |||
| c9c4d436ea | |||
| a69d78042d | |||
| 1e9399ae37 | |||
| 6bc2c6fc90 | |||
| e4a7afbdb9 | |||
| 74bf295acd | |||
| 0c7e64a3f5 | |||
| 1331c10b65 | |||
| 3e2d0897f3 | |||
| e07867334f | |||
| 998e1d4877 | |||
| 282ea57a67 | |||
| aca5db5405 | |||
| 589b1ee0b4 | |||
| 48829ba9ce | |||
| 81da14d615 | |||
| 5378072c2c | |||
| af8e6ce5ae | |||
| 990a193582 | |||
| a4f7c4887b | |||
| a516ff0b75 | |||
| e20cafd6d1 | |||
| f6a50b5c62 | |||
| bd770a02a4 | |||
| 163ccfe084 | |||
| 81592a1870 | |||
| 837e691da7 | |||
| df05ac14eb | |||
| 9927bb1b97 | |||
| 117d395cf1 | |||
| 4538bb5cd9 | |||
| 97cf93d20a | |||
| 81e96e950a | |||
| f360b2d5a1 | |||
| 66441fcb0a | |||
| 3a49428654 | |||
| c87f52f696 | |||
| bc573dca1a | |||
| ecd23f4733 | |||
| 19ef9f4a14 | |||
| aacb01e731 | |||
| ec3c508b88 | |||
| e0cd0e390c | |||
| b690779caa | |||
| 3a1997d1f8 | |||
| dd5c201d92 | |||
| e23847148e | |||
| 19ca32f2b4 | |||
| 2c018c9faa | |||
| d527a5170b | |||
| 2e6c8324f1 | |||
| 2892b4f35b | |||
| 84c4bb71be | |||
| 0e74344280 | |||
| 8d5d0ac90d | |||
| 96454df9a5 | |||
| 4674cdd34c | |||
| 1beb2b26b7 | |||
| 726e937779 | |||
| c82c68da9d | |||
| e1274c28f7 | |||
| afa40be261 | |||
| ccd73ebd0d | |||
| 1917f8e8d4 | |||
| 072d847190 | |||
| 29d32a099d | |||
| 0854c3b053 | |||
| 9967b469f1 | |||
| a0a7656a30 | |||
| 32a68b9a0b | |||
| 6926753f93 | |||
| b2158eb8da | |||
| f29b5357e7 | |||
| 822d550760 | |||
| aa420de64e | |||
| e398654bb8 | |||
| 2cd9596a58 | |||
| 138f681260 | |||
| 61bb7c86ec | |||
| 14d4b53369 | |||
| 3d585df346 | |||
| fa9d1feffd | |||
| 81f75f0852 | |||
| f63f8bccaf | |||
| e35a09c1ef | |||
| 4b98079778 | |||
| 4ea4f0c455 | |||
| 30e54f59eb | |||
| abfa10323c | |||
| 7b66868b80 | |||
| ebe9cf53c8 | |||
| 4ec683ba69 | |||
| 6cc9cdc15c | |||
| 8907adf967 | |||
| 1fd42eb55f | |||
| 55979ed385 | |||
| eaa79126db | |||
| 6d4b0628d0 | |||
| e1af869ee4 | |||
| 3a73192fac | |||
| fdc1dace8f | |||
| 21167c392f | |||
| 5584cc18cb | |||
| 596c8c8385 | |||
| c4704614de | |||
| c57d12d53a | |||
| 28d0bb4707 | |||
| 29b8dda4ac | |||
| b74adcf174 | |||
| 268ce78cda | |||
| 831b73f91a | |||
| 31d1bb0ebe | |||
| 307099db4e | |||
| 8daeedf2fa | |||
| 23570d143b | |||
| 5ee8a36d75 | |||
| 507e90168d | |||
| 62780777b7 | |||
| 796b48ca8b | |||
| 7d80e874f1 | |||
| 93e0ca6a57 | |||
| 73f5a05811 | |||
| db6f362cce | |||
| 4d7018876c | |||
| bba7e89e53 | |||
| c3eeffc780 | |||
| c547b00a5f | |||
| e5f837799f | |||
| 25194d8c50 | |||
| e1f6b291bb | |||
| df9425b202 | |||
| c50a33f323 | |||
| b264381b84 | |||
| d5ee789a61 | |||
| 5fa4219a31 | |||
| 430640e93c | |||
| 135c5c844f | |||
| 05721dbb47 | |||
| 0235679821 | |||
| 11e206b41a | |||
| 4985eb2e1f | |||
| 2181653ff7 | |||
| 26f7edaa24 | |||
| 75078c88d9 | |||
| 329aecb444 | |||
| 1559c9f8d3 | |||
| 26add61117 | |||
| 763c625b83 | |||
| 1375de2caa | |||
| c4b84632b4 | |||
| c20bfdc3ea | |||
| cc55aef306 | |||
| ed5c74c753 | |||
| 5a9ccddabd | |||
| e6b82ab9ae | |||
| 8868a125e2 | |||
| 04474acf5f | |||
| 6a17173b3b | |||
| 711fb33b48 | |||
| 3ed8fb4f95 | |||
| 65944a72ec | |||
| 24d18df86b | |||
| b330763746 | |||
| 2e914ea41a | |||
| 85a5d22861 | |||
| 9f31a2061d | |||
| d1b54c2690 | |||
| 029f86d9d5 | |||
| 9de30f30b8 | |||
| 8582bfc36b | |||
| 4a2af3434c | |||
| 7f697ca734 | |||
| 7f08ee92ff | |||
| 2f09043915 | |||
| 50aab262a8 | |||
| 99b033f070 | |||
| 6277a17d53 | |||
| 8bb6bd1882 | |||
| 3161c61001 | |||
| e68a882e75 | |||
| 24066f05fc | |||
| 28325f4af6 | |||
| 0c57e9d628 | |||
| 8fb7ddcf7a | |||
| 76185a97f3 | |||
| 567ef561c7 | |||
| b70b9df0b7 | |||
| 0e4fbea166 | |||
| 6cf4e0c0cc | |||
| 68da337cc0 | |||
| c492639897 | |||
| 5e10b2267e | |||
| 68bbbc76e2 | |||
| 7cd1c764d2 | |||
| a053a4212a | |||
| 2d0974a5d5 | |||
| d90396c8ea | |||
| 877d9c57c8 | |||
| 275760c18a | |||
| 9dd6063611 | |||
| 0acbb05d3f | |||
| d860e9361a | |||
| 97d76f62ae | |||
| 910a2d92cc | |||
| 76ef54fe0e | |||
| bf41b98339 | |||
| f3cec5e476 | |||
| bb5b66958c | |||
| 04fb0a1de8 | |||
| f7752387da | |||
| a7420eef34 | |||
| 28847bcc1f | |||
| cb24946aea | |||
| 371424114a | |||
| 60086b9839 | |||
| 6d55088a0d | |||
| c4234a11f2 | |||
| 7644e1a5f6 | |||
| f8a54b2860 | |||
| 8c1813b9e5 | |||
| 7a9a803451 | |||
| 681d0b5324 | |||
| a33490b316 | |||
| fb95ff2861 | |||
| 326c2585f3 | |||
| 61cc26cdb0 | |||
| 2ecd3a020b | |||
| c5b23feeac | |||
| 4f39f62208 | |||
| 146401e9f9 | |||
| d544ca6a90 | |||
| 43248c1d88 | |||
| 2b5bb04bbd | |||
| 30a5427778 | |||
| c547200c05 | |||
| 7f223812dc | |||
| 0e86c77d6d | |||
| fb24407cd1 | |||
| ec7791b73d | |||
| 7987cec9b9 | |||
| 7be3e799be | |||
| 5667bb236f | |||
| 5a391a459e | |||
| 60b0761de2 | |||
| 952546f845 | |||
| b7c055c43f | |||
| ba4df0c76f | |||
| dfae3f1d8d | |||
| 310d83a2aa | |||
| 77eb802076 | |||
| 913a6776dc | |||
| f245ff06c5 | |||
| 79901878e6 | |||
| c597e6b99c | |||
| 3c98e6ff37 | |||
| 447b0332e3 | |||
| 1459b287fa | |||
| 252b67ce36 | |||
| 8119eca0c6 | |||
| f0b96417bf | |||
| b8018284c6 | |||
| 737986f218 | |||
| 200815bf10 | |||
| 8cff4e3575 | |||
| 64c39bd462 | |||
| 4a5e629ab1 | |||
| 368519f3fa | |||
| 8fa7f4a22b | |||
| a8e4360c29 | |||
| 719a9a580d | |||
| fb32151b76 | |||
| c50d892e52 | |||
| b31ccace85 | |||
| e57da7e41a | |||
| 38141c3f4e | |||
| 69d39e6c67 | |||
| 4d8a330644 | |||
| 26c417f18d | |||
| f6b7fcb4af | |||
| 212e989782 | |||
| d0ece55dd1 | |||
| 03c0b191fe | |||
| adacee1851 | |||
| 2725c4902d | |||
| 77d7539880 | |||
| 560b80038e | |||
| c9d4c79bf8 | |||
| 905f13f0d3 | |||
| e7128c65d9 | |||
| c15e3cc3ae | |||
| d9f1145d93 | |||
| 0aafe8baa3 | |||
| 7b0e9db8dc | |||
| 07881311b3 | |||
| 7d9b84c746 | |||
| 548165ab4a | |||
| a997d548f7 | |||
| aab5cf7e94 | |||
| 382bf4daaa | |||
| 3e048a8683 | |||
| 7fa7bb4bd0 | |||
| d41ddb0ad0 | |||
| cdbadcbb65 | |||
| b14d63c54c | |||
| 3d47855cc1 | |||
| 819db43aaa | |||
| de859afbe7 | |||
| 066c840efe | |||
| bc4fcfe6ed | |||
| d43090b84f | |||
| 919c37d1f9 | |||
| 319b689d79 | |||
| 9a6badd46c | |||
| ee94b01c2d | |||
| 7b471967a2 | |||
| 6e039a7fad | |||
| 2f56606789 | |||
| a3210c0a92 | |||
| 9876821b9a | |||
| ee7f846151 | |||
| 6b1478594a | |||
| 6448e7c5c6 | |||
| 68c088ca03 | |||
| c5ec454b26 | |||
| 12f9820ec5 | |||
| d5d941c62b | |||
| e5f26d2f23 | |||
| 433f3f99f6 | |||
| b866f01985 | |||
| 07ba6624bf | |||
| 855c8cfb51 | |||
| 70175d309e | |||
| 04d286ca03 | |||
| 1a821b361f | |||
| ec101ba9f7 | |||
| 830fa5aa86 | |||
| aed6bb7856 | |||
| 7df9b92c58 | |||
| 9faa2a74df | |||
| 94497f226f | |||
| cff90ef444 | |||
| f8ae9136c0 | |||
| 7716866849 | |||
| c690353f3e | |||
| 373827f8e5 | |||
| 587436781d | |||
| 069353d617 | |||
| 7f87030cc8 | |||
| 9064e41775 | |||
| 04c8eb5cad | |||
| 867c14349e | |||
| a59fe6fe16 | |||
| 71f518c6f9 | |||
| a7192efda0 | |||
| f14b606fda | |||
| 82e310542f | |||
| 16f31120f5 | |||
| 05ffab5d54 | |||
| f39cceda18 | |||
| ec8bf8fdad | |||
| c0f82356aa | |||
| 14c46eccca | |||
| 354e8a1270 | |||
| 924e69070a | |||
| 137624815c | |||
| 1adba2da49 | |||
| 00c02e4b16 | |||
| 813dcaedaf | |||
| 0ef8bddf98 | |||
| 6276009025 | |||
| 2dc7096a2c | |||
| b84655512c | |||
| 271a2cad23 | |||
| a7dd9d411b | |||
| 103529a9b4 | |||
| ccef25314d | |||
| 9f24e1e8a6 | |||
| 7399e1632e | |||
| 90715bcfaf | |||
| a64176e363 | |||
| 910104d400 | |||
| 9e87be88c7 | |||
| fae5d136d4 | |||
| 13f090e2b7 | |||
| 5896ba3c87 | |||
| 65030866e5 | |||
| 5fd00acd48 | |||
| 462d6c0789 | |||
| c6b3b9e0c4 | |||
| 63b6349a74 | |||
| 781c5cf11e | |||
| 8697c893c4 | |||
| 896e242827 | |||
| b891d21c85 | |||
| 399b6f3537 | |||
| cf8237b9c7 | |||
| daccf651b6 | |||
| 4386334c31 | |||
| 0a445a4247 | |||
| 76cb8c46ba | |||
| d27a91570c | |||
| 554183caab | |||
| 275bba850a | |||
| 86c5239569 | |||
| 681f1e0805 | |||
| b767044199 | |||
| 67265ef969 | |||
| 8410b48ac0 | |||
| e6567e5e95 | |||
| 715b42dfbb | |||
| 69ddca44bf | |||
| 71d9151fa0 | |||
| 3fd768a99c | |||
| c02eab6bfc | |||
| 148e5946cf | |||
| 3590191ef9 | |||
| b91c1a02f5 | |||
| d50ef7d62c | |||
| 6ee4d67c2e | |||
| 3e325b13b4 | |||
| d6fb57f9ea | |||
| bffdf2821c | |||
| c82d7bd48a | |||
| e4eb97e9dd | |||
| cbb168b3d5 | |||
| 60e817d9ea | |||
| e514d45486 | |||
| 995d5524ea | |||
| b1d5276813 | |||
| 97ee651948 | |||
| 5ddc25acc7 | |||
| 96ae14eefb | |||
| 820728861c | |||
| b476e9aec3 | |||
| 5252854963 | |||
| fe6e9efd09 | |||
| c0be351b95 | |||
| 573fc88c7a | |||
| 9032450be8 | |||
| bf0cdbe493 | |||
| 446d2410dc | |||
| 17febfa9e6 | |||
| 86eaef0ae3 | |||
| 874ecbc886 | |||
| 0c4b4b0a31 | |||
| 5d8e773827 | |||
| c9e1adc1e9 | |||
| 27ce52ea13 | |||
| dea9702c36 | |||
| 794fa29581 | |||
| 30686bf5a1 | |||
| ae9f3c4d15 | |||
| 13fc5d2a5b | |||
| 7bc2173edb | |||
| 5317e19d6e | |||
| 0910b7c068 | |||
| c800f6642b | |||
| 1b80c85ebf | |||
| 714a33313e | |||
| a70b18b6f4 | |||
| 1a3d2cdd78 | |||
| 45081b5535 | |||
| 600287c246 | |||
| a2607b4846 | |||
| 13bb84f4f4 | |||
| e702f7813a | |||
| ec8156b851 | |||
| 9da24a3238 | |||
| e390102a7c | |||
| 2271084c02 | |||
| dbb4a3514b | |||
| 3eeefcdc9c | |||
| c0be6addbc | |||
| c344ade9fc | |||
| 6c2a3a26ab | |||
| 073f42a3bc | |||
| 0e3007827e | |||
| 8aa23cdd48 | |||
| 9765f7157d | |||
| ee3aabe3c8 | |||
| d39b3f66d6 | |||
| cc26427417 | |||
| 592af262da | |||
| cf64103e36 | |||
| 044b3664ed | |||
| 99eb46c2a9 | |||
| 2cae2fc807 | |||
| bfc0ff5c28 | |||
| 504210cee1 | |||
| 1d6d388d37 | |||
| 727ff7a52d | |||
| 8638dcb9c8 | |||
| d641c78000 | |||
| 49131d1d00 | |||
| ccc43cd175 | |||
| 0131f464cd | |||
| 752bc0a64e | |||
| fac981e1ee | |||
| c6ce1a403d | |||
| 79f7b4a510 | |||
| 815418627f | |||
| d82e59b4eb | |||
| a6a42f0f11 | |||
| 4d6b62ac18 | |||
| 0547157c69 | |||
| 493da1552d | |||
| 0402427b71 | |||
| 623dcbce1c | |||
| 98d9af89ec | |||
| b4546ec0ba | |||
| f140eb3f5f | |||
| 432b5466e8 | |||
| 402562ba93 | |||
| 77bcf6e086 | |||
| d9fee73e87 | |||
| e512187b60 | |||
| 31a0617383 | |||
| 98d80cd359 | |||
| b0cae832c5 | |||
| 00b9a859f8 | |||
| 8b5a7ece43 | |||
| 391b2a9c68 | |||
| cf6deb665c | |||
| 096890bd2f | |||
| 4dba4e5216 | |||
| 2b63c50434 | |||
| 0a2d9262c9 | |||
| 4693cc8fe3 | |||
| b6bad9ea64 | |||
| a3b0bf10e9 | |||
| a627816694 | |||
| a4ea9298a4 | |||
| 233bb6e23b | |||
| bdd20107c7 | |||
| e296008112 | |||
| 3dd01e6c1f | |||
| 5b633e6528 | |||
| 2da260bba0 | |||
| acd8403e4b | |||
| 90e66175ef | |||
| 4f1a7cc1bf | |||
| 053d7d3ca5 | |||
| dae0e8274a | |||
| 2b3d1cd220 | |||
| 5f0a4ca89e | |||
| 67d350ebe2 | |||
| 66c30f3e33 | |||
| 82d763fdee | |||
| 7159c4cccf | |||
| 60357222df | |||
| f6c43cf5a0 | |||
| f1341c5364 | |||
| b8cb8d70d3 | |||
| fa7fca1f11 | |||
| 59cfa096b4 | |||
| 706c04a21f | |||
| 255284caad | |||
| 85b34fc017 | |||
| d42deb54eb | |||
| 002104b0eb | |||
| 4dc943438e | |||
| 81a320cd93 | |||
| df32b3b5de | |||
| db0679cd92 | |||
| 20bbbc6202 | |||
| ce652f45e6 | |||
| c0f7183bc8 | |||
| 60d8ccd1a0 | |||
| e9561af93a | |||
| 7e0ad2c799 | |||
| fe9f1c6e95 | |||
| 193ec9a6ae | |||
| 72c3bed203 | |||
| 2ae259e53a | |||
| 3cd25e19de | |||
| 9bdffedb7e | |||
| c298a7182e | |||
| d613a06ad2 | |||
| cc2cdaa76b | |||
| 2c38a51515 | |||
| d2e89353c5 | |||
| 998bc02f8c | |||
| 42e3e20eb8 | |||
| 225795db74 | |||
| bd35c42eb0 | |||
| d2587e77d4 | |||
| b0018ee71b | |||
| 985f4a6e6b | |||
| cf910c111f | |||
| d7e0cb02cd | |||
| c9c6c8b23a | |||
| 3d0cb594ae | |||
| b614c9097b | |||
| 78b94fe89c | |||
| 90140f4229 | |||
| d36c3b0dae | |||
| 88b64d6fc8 | |||
| ce4725ed09 | |||
| a2efa9a70b | |||
| 5126e2030a | |||
| b7b5744f17 | |||
| 0f08c10f02 | |||
| 6b4154f4a6 | |||
| 2d45e84b66 | |||
| 3f90402164 | |||
| 53fdcfb715 | |||
| 2ff01579d6 | |||
| 774d13980c | |||
| ae76a0171d | |||
| 137665b77d | |||
| 3b7bf35bf3 | |||
| 0b0c4da3eb | |||
| 7dfcfb98d5 | |||
| 00b9be6bc9 | |||
| 771a9d1f28 | |||
| 3021f466f0 | |||
| 486a9fb58b | |||
| dbc3050f2b | |||
| dc584a9382 | |||
| e1f6afe08f | |||
| a7c95d929f | |||
| 9ff5596aca | |||
| 4369eedfe5 | |||
| 5af46a5039 | |||
| 4e854515c8 | |||
| 552487b9d3 | |||
| 8cadfe3e53 | |||
| 15c0f28252 | |||
| 8e4b73c695 | |||
| cb8f2a0ee1 | |||
| e6b3858937 | |||
| 1bdcf0473e | |||
| edb5e858e4 | |||
| cfedad8a1b | |||
| 08a5c66e62 | |||
| 1d59fad7ad | |||
| e0145ee9e6 | |||
| 4703d8c3ca | |||
| 8b95095e55 | |||
| 709c345201 | |||
| 288968006a | |||
| 7f619f786d | |||
| c9c3de1348 | |||
| 841b1d7d1b | |||
| 6f9d5370e0 | |||
| c8f12b3a89 | |||
| daa3407505 | |||
| f5d2d2204f | |||
| 04ef3ce442 | |||
| aff68bd1c1 | |||
| fca7065dca | |||
| 29330ec457 | |||
| cc15852944 | |||
| ecc44b2e92 | |||
| 2b1e21ba4b | |||
| 7a22469fb1 | |||
| 31c063ba68 | |||
| dad868b64c | |||
| 16de31c838 | |||
| 3c1062a006 | |||
| 7017aff841 | |||
| 334c4bc4c1 | |||
| e07a69ab67 | |||
| f4e31c4902 | |||
| b2d382c2af | |||
| 5d0ad333c9 | |||
| ccf05b8c42 | |||
| 684c1b7140 | |||
| 15e12d083d | |||
| f6937bb4cb | |||
| 8683a85640 | |||
| 7ee6dd4d0e | |||
| 1c7c6074a1 | |||
| af865b62a5 | |||
| 69b430d7cb | |||
| 0956385284 | |||
| 46cf1fa7da | |||
| ac9b42ca4d | |||
| 264335429a | |||
| 323e2bf95a | |||
| 6b413d2962 | |||
| 83e0e33207 | |||
| 827df91241 | |||
| f78f414c77 | |||
| dfb73d1730 | |||
| 92c1d136c2 | |||
| 45428f3e0b | |||
| 139679ed0f | |||
| eed834bd5f | |||
| 422c92cca5 | |||
| 28d699c886 | |||
| f4533c9662 | |||
| ad71a8075b | |||
| 7d39015d97 | |||
| c3498aa5c5 | |||
| 2c3bb5bf91 | |||
| 3c2b6e6488 | |||
| dff0a61182 | |||
| d081f1e312 | |||
| 46809c8d5b | |||
| f1246ee779 | |||
| 03168a5652 | |||
| 0e43d25430 | |||
| 46a9446007 | |||
| 3761223230 | |||
| 836b34e59d | |||
| 3dc1106012 | |||
| 3eb8bf793f | |||
| cd823c4506 | |||
| 36d45ee66f | |||
| 068d36de3d | |||
| e3607b9bc7 | |||
| 76a2ff8e43 | |||
| b3e3da6833 | |||
| fa6c574743 | |||
| e7625a77a8 | |||
| e1b3c80771 | |||
| 3e9228cb65 | |||
| e8b5bb266f | |||
| a6d27f261b | |||
| 1a85571fca | |||
| 9b4f308367 | |||
| aabf49d041 | |||
| 0d22b53b36 | |||
| 8d9c25c195 | |||
| bfd4fe41d3 | |||
| 8400705053 | |||
| b3e72acad8 | |||
| 33884c4c67 | |||
| 45a939571d | |||
| ac4f2a8531 | |||
| 7c09b017c9 | |||
| 02c207d0b8 | |||
| 6d23f59d6a | |||
| 4c55552dec | |||
| 5ba24d11f6 | |||
| bcf557c47e | |||
| cac55a4f79 | |||
| 0d26edf6f0 | |||
| 7a39ef706c | |||
| f5c37c0402 | |||
| 16d571e4ea | |||
| b4b75aa9c5 | |||
| 6ed7784a89 | |||
| d2c7c1109d | |||
| 079a7f7c07 | |||
| 923c6a478c | |||
| 9e0f65cf52 | |||
| eab8184b6b | |||
| 3972a17bd1 | |||
| 34b7b8a10a | |||
| a4da49d8ae | |||
| f8a989fbfc | |||
| 4497b8fa82 | |||
| 0bb7ccd121 | |||
| b90227017c | |||
| 75d941060f | |||
| b37be8aae9 | |||
| f5a471768b | |||
| f6ad6b23cb | |||
| f5b9d3e912 | |||
| 9619c3f55b | |||
| 8c08ebb041 | |||
| 0567319671 | |||
| 760c3be9c7 | |||
| 1084968e99 | |||
| 188c2ffce0 | |||
| f6296e7704 | |||
| bd149f6524 | |||
| 989beb3642 | |||
| 5d4d94a5d8 | |||
| 41eca93f12 | |||
| 946def76c5 | |||
| 24768a4078 | |||
| 7751117dc6 | |||
| f9f286335d | |||
| 1d50da79bb | |||
| 7e78b6eaa3 | |||
| a098c988a7 | |||
| 0b04f32aed | |||
| e14c1db060 | |||
| e109e16610 | |||
| cd3f2b5951 | |||
| fe28e84bf7 | |||
| fe4e3850c9 | |||
| 2a070e14c0 | |||
| 3984f85d57 | |||
| dcd4eb1e49 | |||
| bc62fbadbb | |||
| d17ef0bfda | |||
| 622d08f6f4 | |||
| 2a2fbbf287 | |||
| 77fa6ec58f | |||
| fae4894cea | |||
| b4195459c5 | |||
| 5a40cd3910 | |||
| c6971004f5 | |||
| e6ff46ccac |
@@ -9,7 +9,7 @@ tmp_dir = "storage/temp"
|
||||
# Array of commands to run before each build
|
||||
pre_cmd = []
|
||||
# Just plain old shell command. You could use `make` as well.
|
||||
cmd = "go build -o storage/temp/main.exe ./cmd/web"
|
||||
cmd = "go build -trimpath -buildvcs=false -o storage/temp/main.exe ./cmd/web"
|
||||
# Array of commands to run after ^C
|
||||
post_cmd = []
|
||||
# Binary file yields from `cmd`.
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
# 贡献指南
|
||||
|
||||
感谢你考虑为 AcePanel 做出贡献!这份文档将帮助你了解如何参与到项目中来。
|
||||
|
||||
## 目录
|
||||
|
||||
- [我能做什么贡献?](#我能做什么贡献)
|
||||
- [开发环境设置](#开发环境设置)
|
||||
- [开发流程](#开发流程)
|
||||
- [代码规范](#代码规范)
|
||||
- [提交信息规范](#提交信息规范)
|
||||
- [Pull Request 流程](#pull-request-流程)
|
||||
|
||||
## 我能做什么贡献?
|
||||
|
||||
你可以通过以下方式为 AcePanel 做出贡献:
|
||||
|
||||
- 🐛 报告 Bug
|
||||
- 💡 提出新功能建议
|
||||
- 📝 改进文档
|
||||
- 🔧 修复 Bug
|
||||
- ✨ 实现新功能
|
||||
- 🧪 编写测试
|
||||
- 🌍 翻译面板及文档
|
||||
|
||||
## 开发环境设置
|
||||
|
||||
### 前置要求
|
||||
|
||||
- Go 1.25 或更高版本
|
||||
- Node.js 22+ 和 pnpm
|
||||
- Git
|
||||
- 本地开发环境没有特殊要求,但必须在 Linux 上运行和测试
|
||||
- 在开发及测试前端项目时,需修改面板配置文件打开 debug 模式或者关闭安全入口
|
||||
|
||||
### 克隆仓库
|
||||
|
||||
```bash
|
||||
git clone https://github.com/acepanel/panel.git
|
||||
cd panel
|
||||
```
|
||||
|
||||
### 后端设置
|
||||
|
||||
1. 安装 Go 依赖:
|
||||
|
||||
```bash
|
||||
go mod download
|
||||
```
|
||||
|
||||
2. 复制配置文件:
|
||||
|
||||
```bash
|
||||
cp config.example.yml config.yml # 按需修改配置
|
||||
```
|
||||
|
||||
3. 构建项目:
|
||||
|
||||
```bash
|
||||
go build -o ace ./cmd/ace # 主程序
|
||||
go build -o cli ./cmd/cli # CLI 工具
|
||||
```
|
||||
|
||||
### 前端设置
|
||||
|
||||
1. 进入前端目录:
|
||||
|
||||
```bash
|
||||
cd web
|
||||
```
|
||||
|
||||
2. 安装依赖:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
3. 配置开发环境:
|
||||
|
||||
```bash
|
||||
cp .env.production .env # 按需修改
|
||||
cp settings/proxy-config.example.ts settings/proxy-config.ts # 配置 Linux 测试服务器信息
|
||||
pnpm run gen-auto-import # 生成自动导入文件,开发中无需导入 vue alova 等常用包
|
||||
pnpm run gettext:compile # 预编译翻译文件,否则开发中没有翻译
|
||||
```
|
||||
|
||||
4. 启动开发服务器:
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## 开发流程
|
||||
|
||||
### 项目架构
|
||||
|
||||
AcePanel 采用类 DDD 分层架构,依赖关系为:route → service → biz ← data
|
||||
|
||||
主要目录结构:
|
||||
|
||||
- `cmd/` - 程序入口(ace 主程序、cli 工具)
|
||||
- `internal/route/` - HTTP 路由定义
|
||||
- `internal/service/` - 服务层(处理 HTTP 请求/响应)
|
||||
- `internal/biz/` - 业务逻辑层(定义业务接口和领域模型)
|
||||
- `internal/data/` - 数据访问层(实现 biz 接口)
|
||||
- `pkg/` - 工具函数和通用包
|
||||
- `web/` - Vue 3 前端项目
|
||||
|
||||
### 开发新功能的标准流程
|
||||
|
||||
1. **在 `internal/route/` 中添加路由**
|
||||
- 按需注入需要的服务
|
||||
|
||||
2. **在 `internal/service/` 中实现服务方法**
|
||||
- 处理请求验证和响应格式化
|
||||
- 使用 `Success()` 返回成功响应
|
||||
- 使用 `Error()` 返回错误响应
|
||||
- 使用 `ErrorSystem()` 返回系统严重错误
|
||||
|
||||
3. **在 `internal/biz/` 中定义业务接口**
|
||||
- 定义 Repository 接口
|
||||
- 定义领域模型结构体
|
||||
- 保持接口简洁明确
|
||||
|
||||
4. **在 `internal/data/` 中实现 biz 接口**
|
||||
- 创建 repo 结构体
|
||||
- 实现构造函数
|
||||
- 实现所有接口方法
|
||||
|
||||
5. **使用 Wire 进行依赖注入**
|
||||
- 在对应的 `包名.go` 文件中添加 Provider
|
||||
- 运行 `go generate ./...` 生成依赖注入代码
|
||||
|
||||
## 代码规范
|
||||
|
||||
**所有代码注释必须使用简体中文**
|
||||
|
||||
面板基于 Gettext 搭建了自动化国际化流程,所有对用户可见的文本均需支持国际化,原文使用英文。
|
||||
|
||||
开发中 Go 代码中注入 `*gotext.Locale`,前端导入 `useGettext` 进行翻译。
|
||||
|
||||
### Go 代码规范
|
||||
|
||||
- 遵循 Go 官方代码风格
|
||||
- 使用 `gofmt` 格式化代码和 `golangci-lint` 检查代码质量
|
||||
- 函数和方法注释必须以函数名开头,复杂逻辑应添加注释说明
|
||||
|
||||
### 前端代码规范
|
||||
|
||||
- 使用 Vue 3 Composition API
|
||||
- 遵循项目已有的组件结构和编码风格
|
||||
- 使用 TypeScript 进行类型检查
|
||||
- 运行 `pnpm lint` 检查代码质量
|
||||
|
||||
## 提交信息规范
|
||||
|
||||
我们使用语义化的提交信息,格式为:
|
||||
|
||||
```
|
||||
<类型>(<范围>): <简短描述>
|
||||
|
||||
<详细描述>(可选)
|
||||
|
||||
<关联的 Issue>(可选)
|
||||
```
|
||||
|
||||
### 类型
|
||||
|
||||
- `feat`: 新功能
|
||||
- `fix`: Bug 修复
|
||||
- `docs`: 文档更新
|
||||
- `style`: 代码格式调整(不影响功能)
|
||||
- `refactor`: 代码重构
|
||||
- `perf`: 性能优化
|
||||
- `test`: 测试相关
|
||||
- `chore`: 构建/工具链相关
|
||||
|
||||
### 示例
|
||||
|
||||
```
|
||||
feat(website): 添加网站备份功能
|
||||
|
||||
实现了网站配置和数据的自动备份功能,支持:
|
||||
- 按计划自动备份
|
||||
- 手动立即备份
|
||||
- 备份文件压缩存储
|
||||
|
||||
Closes #123
|
||||
```
|
||||
|
||||
```
|
||||
fix(apache): 修复代理配置解析错误
|
||||
|
||||
修复了在解析包含特殊字符的代理配置时的崩溃问题
|
||||
```
|
||||
|
||||
## Pull Request 流程
|
||||
|
||||
### 1. Fork 项目
|
||||
|
||||
点击 GitHub 页面右上角的 "Fork" 按钮。
|
||||
|
||||
### 2. 创建开发分支
|
||||
|
||||
```bash
|
||||
git checkout -b your-develop-name
|
||||
```
|
||||
|
||||
### 3. 进行开发
|
||||
|
||||
- 遵循代码规范
|
||||
- 中文编写必要的代码注释
|
||||
- 添加必要的测试,目前主要针对 `pkg` 目录下的公共包
|
||||
- 若进行大范围的重构/修改,请提前与维护者沟通
|
||||
|
||||
### 4. 提交更改
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "feat(scope): 描述你的更改"
|
||||
```
|
||||
|
||||
### 5. 推送到你的 Fork
|
||||
|
||||
```bash
|
||||
git push origin your-develop-name
|
||||
```
|
||||
|
||||
### 6. 创建 Pull Request
|
||||
|
||||
1. 访问你 Fork 的仓库
|
||||
2. 点击 "New Pull Request"
|
||||
3. 选择你的分支
|
||||
4. 填写 PR 检查单并点击 "Create Pull Request"
|
||||
|
||||
### 7. 等待审查
|
||||
|
||||
当 Pull Request 开发完毕后,请为其添加 `🚀 Review Ready` 标签,维护者将及时进行评审并提供反馈。请及时响应评论并根据需要进行修改。
|
||||
|
||||
## 许可证
|
||||
|
||||
通过向本项目贡献代码,你同意你的贡献将在与项目相同的许可证下发布。
|
||||
|
||||
---
|
||||
|
||||
再次感谢你的贡献!🎉
|
||||
+1
-1
@@ -1 +1 @@
|
||||
custom: [ 'https://afdian.com/a/tnblabs' ]
|
||||
custom: [ 'https://afdian.com/a/tnborg' ]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: ☢️ 报告问题 (Bug Report)
|
||||
description: 创建一个报告以帮助我们改进 (Create a report to help us improve)
|
||||
type: ☢️ Bug
|
||||
type: Bug
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: ✨ 功能请求 (Feature Request)
|
||||
description: 为这个项目提出一个想法 (Suggest an idea for this project)
|
||||
type: ✨ Feature
|
||||
type: Feature
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 42 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 210 KiB After Width: | Height: | Size: 407 KiB |
@@ -0,0 +1,206 @@
|
||||
## 项目概述
|
||||
|
||||
AcePanel 是基于 Go 语言开发的新一代 Linux 服务器运维管理面板。项目采用前后端分离架构:
|
||||
- 后端:Go 1.25 + go-chi 路由 + GORM + Wire 依赖注入
|
||||
- 前端:Vue 3 + Vite + Naive UI + pnpm
|
||||
|
||||
## 语言和编码规范
|
||||
|
||||
**所有代码注释、文档和回复必须使用简体中文。**
|
||||
|
||||
## 构建和测试
|
||||
|
||||
### 后端构建
|
||||
|
||||
构建主程序:
|
||||
```bash
|
||||
go build -o ace ./cmd/ace
|
||||
```
|
||||
|
||||
构建 CLI 工具:
|
||||
```bash
|
||||
go build -o cli ./cmd/cli
|
||||
```
|
||||
|
||||
### 前端开发
|
||||
|
||||
进入前端目录:
|
||||
```bash
|
||||
cd web
|
||||
```
|
||||
|
||||
安装依赖:
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
开发模式(带热重载):
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
构建生产版本:
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## 代码架构
|
||||
|
||||
项目采用类 DDD 分层架构,依赖关系为:route -> service -> biz <- data
|
||||
|
||||
### 核心目录结构
|
||||
|
||||
- **`cmd/`**: 程序入口
|
||||
- `ace/`: 面板主程序
|
||||
- `cli/`: 命令行工具
|
||||
|
||||
- **`internal/app/`**: 应用入口和配置
|
||||
|
||||
- **`internal/route/`**: HTTP 路由定义
|
||||
- 定义路由规则
|
||||
- 注入所需的 service 依赖
|
||||
|
||||
- **`internal/service/`**: 服务层(类似 DDD 的 application 层)
|
||||
- 处理 HTTP 请求/响应
|
||||
- DTO 到 DO 的转换
|
||||
- 协调多个 biz 接口完成业务流程
|
||||
- **不应处理复杂业务逻辑**
|
||||
|
||||
- **`internal/biz/`**: 业务逻辑层(类似 DDD 的 domain 层)
|
||||
- 定义业务接口(Repository 模式)
|
||||
- 定义领域模型和数据结构
|
||||
- 使用依赖倒置原则:biz 定义接口,data 实现接口
|
||||
|
||||
- **`internal/data/`**: 数据访问层(类似 DDD 的 repository 层)
|
||||
- 实现 biz 中定义的业务接口
|
||||
- 封装数据库、缓存等操作
|
||||
- 处理数据持久化逻辑
|
||||
|
||||
- **`internal/http/`**: HTTP 相关
|
||||
- `middleware/`: 自定义中间件
|
||||
- `request/`: 请求结构体定义
|
||||
- `rule/`: 自定义验证规则
|
||||
|
||||
- **`internal/apps/`**: 面板子应用实现
|
||||
|
||||
- **`internal/bootstrap/`**: 各模块启动引导
|
||||
|
||||
- **`internal/migration/`**: 数据库迁移
|
||||
|
||||
- **`internal/job/`**: 后台任务
|
||||
|
||||
- **`internal/queuejob/`**: 任务队列
|
||||
|
||||
- **`pkg/`**: 工具函数和通用包
|
||||
- 包含各种独立的工具模块
|
||||
- 可被项目任何部分引用
|
||||
|
||||
- **`web/`**: Vue 3 前端项目
|
||||
|
||||
## 开发新功能的标准流程
|
||||
|
||||
1. **在 `internal/route/` 中添加路由**
|
||||
- 参考已有路由文件(如 `http.go`)
|
||||
- 注入需要的 service 依赖
|
||||
- 定义路由规则和 handler 映射
|
||||
|
||||
2. **在 `internal/service/` 中实现服务方法**
|
||||
- **先阅读已有的类似服务**以了解代码风格
|
||||
- 处理请求验证和响应格式化
|
||||
- 使用 `Success()` 返回成功响应
|
||||
- 使用 `Error()` 返回错误响应
|
||||
- 使用 `ErrorSystem()` 返回系统严重错误
|
||||
- 调用 biz 层接口完成业务逻辑
|
||||
|
||||
3. **在 `internal/biz/` 中定义业务接口**
|
||||
- **先阅读已有的类似接口定义**
|
||||
- 定义 Repository 接口(如 `WebsiteRepo`)
|
||||
- 定义领域模型结构体(如 `Website`)
|
||||
- 保持接口简洁明确
|
||||
|
||||
4. **在 `internal/data/` 中实现 biz 接口**
|
||||
- **先阅读已有的类似实现**
|
||||
- 创建 repo 结构体(如 `websiteRepo`)
|
||||
- 实现构造函数(如 `NewWebsiteRepo`)
|
||||
- 实现所有接口方法
|
||||
- 处理数据库操作和缓存逻辑
|
||||
|
||||
5. **使用 Wire 进行依赖注入**
|
||||
- 在对应的 wire.go 文件中添加 provider
|
||||
- 运行 `go generate` 生成依赖注入代码
|
||||
|
||||
## 技术栈特定注意事项
|
||||
|
||||
### Go 语言规范
|
||||
|
||||
- 使用 Go 1.25 稳定版本
|
||||
- 遵循 Go 标准库和习惯用法
|
||||
- 日志使用标准库的 `slog` 包
|
||||
- 使用 `github.com/samber/lo` 进行函数式编程辅助
|
||||
|
||||
### 当前框架
|
||||
|
||||
- 路由:`github.com/go-chi/chi/v5`
|
||||
- ORM:`gorm.io/gorm`
|
||||
- 依赖注入:`github.com/google/wire`
|
||||
- 验证:`github.com/gookit/validate`
|
||||
|
||||
### 助手函数(service 层)
|
||||
|
||||
在 service 层使用以下助手函数:
|
||||
- `Success(w, data)`: 返回成功响应
|
||||
- `Error(w, statusCode, format, args...)`: 返回错误响应
|
||||
- `ErrorSystem(w, format, args...)`: 返回系统严重错误(500)
|
||||
- `Bind[T](r)`: 绑定请求参数到泛型类型 T
|
||||
- `Paginate[T](...)`: 构建分页响应
|
||||
|
||||
### 数据库
|
||||
|
||||
- 使用 SQLite(`github.com/ncruces/go-sqlite3`)
|
||||
- 使用 GORM 进行数据库迁移和操作
|
||||
|
||||
### 安全性
|
||||
|
||||
- 不需要实现命令注入过滤,文件名过滤等,因为这是服务器面板,所有登录的用户都被视为管理员
|
||||
|
||||
## 代码风格
|
||||
|
||||
- 所有代码注释必须使用简体中文
|
||||
- 遵循 Go 官方代码风格
|
||||
- 使用 `gofmt` 格式化代码
|
||||
- 复杂逻辑添加注释说明
|
||||
- 导出的函数和类型必须有注释
|
||||
|
||||
## Wire 依赖注入
|
||||
|
||||
项目使用 Wire 进行依赖注入。当添加新的依赖时:
|
||||
|
||||
1. 在 `cmd/ace/wire.go` 或 `cmd/cli/wire.go` 中添加 provider
|
||||
2. 运行生成命令:
|
||||
```bash
|
||||
go generate ./...
|
||||
```
|
||||
|
||||
## 前端开发注意事项
|
||||
|
||||
- 使用 Vue 3 Composition API
|
||||
- UI 框架:Naive UI
|
||||
- 状态管理:Pinia
|
||||
- HTTP 请求:Alova.js,使用 useRequest 等助手函数进行数据获取,无需处理 onError 错误。
|
||||
- 图标:@iconify/vue
|
||||
- 终端:xterm.js
|
||||
- 遵循项目已有的组件结构和编码风格
|
||||
|
||||
## 配置文件
|
||||
|
||||
开发时需要准备配置文件:
|
||||
```bash
|
||||
cp config.example.yml config.yml
|
||||
```
|
||||
|
||||
前端开发配置:
|
||||
```bash
|
||||
cd web
|
||||
cp .env.production .env
|
||||
cp settings/proxy-config.example.ts settings/proxy-config.ts
|
||||
```
|
||||
+24
-26
@@ -4,22 +4,23 @@ on:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
frontend:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: latest
|
||||
run_install: true
|
||||
package_json_file: web/package.json
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: web/pnpm-lock.yaml
|
||||
- name: Build frontend
|
||||
@@ -27,36 +28,37 @@ jobs:
|
||||
run: |
|
||||
cp .env.production .env
|
||||
cp settings/proxy-config.example.ts settings/proxy-config.ts
|
||||
pnpm run gettext:compile
|
||||
pnpm build
|
||||
- name: Upload frontend
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: frontend
|
||||
path: web/dist/
|
||||
backend:
|
||||
needs: frontend
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
goarch: [ amd64, arm64 ]
|
||||
fail-fast: true
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
cache: true
|
||||
go-version: 'stable'
|
||||
- name: Install dependencies
|
||||
run: go mod tidy
|
||||
- name: Download frontend
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: frontend
|
||||
path: internal/embed/frontend
|
||||
path: pkg/embed/frontend
|
||||
- name: Set build info
|
||||
run: |
|
||||
echo "VERSION=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo '0.0.0')" >> $GITHUB_ENV
|
||||
@@ -73,23 +75,19 @@ jobs:
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
run: |
|
||||
LDFLAGS="-s -w --extldflags '-static'"
|
||||
LDFLAGS="${LDFLAGS} -X 'github.com/tnb-labs/panel/internal/app.Version=${VERSION}'"
|
||||
LDFLAGS="${LDFLAGS} -X 'github.com/tnb-labs/panel/internal/app.BuildTime=${BUILD_TIME}'"
|
||||
LDFLAGS="${LDFLAGS} -X 'github.com/tnb-labs/panel/internal/app.CommitHash=${COMMIT_HASH}'"
|
||||
LDFLAGS="${LDFLAGS} -X 'github.com/tnb-labs/panel/internal/app.GoVersion=${GO_VERSION}'"
|
||||
LDFLAGS="${LDFLAGS} -X 'github.com/tnb-labs/panel/internal/app.BuildID=${BUILD_ID}'"
|
||||
LDFLAGS="${LDFLAGS} -X 'github.com/tnb-labs/panel/internal/app.BuildUser=${BUILD_USER}'"
|
||||
LDFLAGS="${LDFLAGS} -X 'github.com/tnb-labs/panel/internal/app.BuildHost=${BUILD_HOST}'"
|
||||
go build -ldflags "${LDFLAGS}" -o web-${{ matrix.goarch }} ./cmd/web
|
||||
go build -ldflags "${LDFLAGS}" -o cli-${{ matrix.goarch }} ./cmd/cli
|
||||
- name: Compress ${{ matrix.goarch }}
|
||||
run: |
|
||||
upx --best --lzma web-${{ matrix.goarch }}
|
||||
upx --best --lzma cli-${{ matrix.goarch }}
|
||||
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/internal/app.Version=${VERSION}'"
|
||||
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/internal/app.BuildTime=${BUILD_TIME}'"
|
||||
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/internal/app.CommitHash=${COMMIT_HASH}'"
|
||||
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/internal/app.GoVersion=${GO_VERSION}'"
|
||||
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/internal/app.BuildID=${BUILD_ID}'"
|
||||
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/internal/app.BuildUser=${BUILD_USER}'"
|
||||
LDFLAGS="${LDFLAGS} -X 'github.com/acepanel/panel/internal/app.BuildHost=${BUILD_HOST}'"
|
||||
go build -trimpath -buildvcs=false -ldflags "${LDFLAGS}" -o ace-${{ matrix.goarch }} ./cmd/ace
|
||||
go build -trimpath -buildvcs=false -ldflags "${LDFLAGS}" -o cli-${{ matrix.goarch }} ./cmd/cli
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: backend-${{ matrix.goarch }}
|
||||
path: |
|
||||
web-${{ matrix.goarch }}
|
||||
ace-${{ matrix.goarch }}
|
||||
cli-${{ matrix.goarch }}
|
||||
|
||||
@@ -7,27 +7,26 @@ permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
cache: true
|
||||
go-version: 'stable'
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: latest
|
||||
run_install: true
|
||||
package_json_file: web/package.json
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: web/pnpm-lock.yaml
|
||||
- name: Build frontend
|
||||
@@ -35,13 +34,12 @@ jobs:
|
||||
run: |
|
||||
cp .env.production .env
|
||||
cp settings/proxy-config.example.ts settings/proxy-config.ts
|
||||
pnpm run gettext:compile
|
||||
pnpm build
|
||||
- name: Set environment variables
|
||||
run: |
|
||||
echo "GOVERSION=$(go version | cut -d' ' -f3)" >> $GITHUB_ENV
|
||||
echo "HOSTNAME=$(hostname)" >> $GITHUB_ENV
|
||||
- name: Clear node_modules
|
||||
run: rm -rf node_modules
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
|
||||
@@ -11,6 +11,7 @@ permissions:
|
||||
jobs:
|
||||
issue-lock:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Set GH_REPO
|
||||
run: echo "GH_REPO=${{ github.repository }}" >> $GITHUB_ENV
|
||||
@@ -1,27 +0,0 @@
|
||||
name: Issue Auto Reply
|
||||
on:
|
||||
issues:
|
||||
types: [ labeled ]
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
issue-reply:
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: ☢️ Bug
|
||||
if: github.event.label.name == '☢️ Bug'
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
actions: 'create-comment'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
body: |
|
||||
Hi @${{ github.event.issue.user.login }} 👋
|
||||
|
||||
感谢提交 Bug,请确保您的描述清晰,日志完整,最好能提供复现步骤,以便我们更快定位问题并解决。
|
||||
Thanks for submitting a bug, please make sure your description is clear, log is complete, and it is best to provide a reproduction step so that we can locate and solve the problem faster.
|
||||
|
||||

|
||||
@@ -0,0 +1,59 @@
|
||||
name: L10n
|
||||
on:
|
||||
workflow_dispatch:
|
||||
concurrency:
|
||||
group: l10n
|
||||
cancel-in-progress: true
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
jobs:
|
||||
l10n:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
run_install: true
|
||||
package_json_file: web/package.json
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: web/pnpm-lock.yaml
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
cache: true
|
||||
go-version: 'stable'
|
||||
- name: Install gettext
|
||||
run: |
|
||||
sudo apt-get install -y gettext
|
||||
- name: Install xgotext
|
||||
run: |
|
||||
go install github.com/leonelquinteros/gotext/cli/xgotext@latest
|
||||
- name: Generate pot files
|
||||
run: |
|
||||
~/go/bin/xgotext -default backend -pkg-tree ./cmd/ace -out ./pkg/embed/locales
|
||||
cd web && pnpm run gettext:extract
|
||||
- uses: stefanzweifel/git-auto-commit-action@v7
|
||||
name: Commit changes
|
||||
with:
|
||||
commit_message: "chore(l10n): update pot files"
|
||||
- name: Sync with Crowdin
|
||||
uses: crowdin/github-action@v2
|
||||
with:
|
||||
config: crowdin.yml
|
||||
upload_sources: true
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
export_only_approved: true
|
||||
create_pull_request: true
|
||||
pull_request_title: 'l10n: sync translations with Crowdin'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
+11
-12
@@ -9,28 +9,28 @@ permissions:
|
||||
jobs:
|
||||
golangci:
|
||||
name: golanci-lint
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
cache: true
|
||||
go-version: 'stable'
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
skip-cache: true
|
||||
version: latest
|
||||
args: --timeout=30m ./...
|
||||
govulncheck:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
cache: true
|
||||
go-version: 'stable'
|
||||
@@ -39,20 +39,19 @@ jobs:
|
||||
- name: Run Govulncheck
|
||||
run: govulncheck ./...
|
||||
frontend:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: latest
|
||||
run_install: true
|
||||
package_json_file: web/package.json
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: web/pnpm-lock.yaml
|
||||
- name: Run pnpm lint
|
||||
|
||||
@@ -3,20 +3,27 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
mockery:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
cache: true
|
||||
go-version: 'stable'
|
||||
- name: Install Mockery
|
||||
run: |
|
||||
go install github.com/vektra/mockery/v2@latest
|
||||
- name: Generate Mocks
|
||||
run: |
|
||||
~/go/bin/mockery
|
||||
- uses: stefanzweifel/git-auto-commit-action@v5
|
||||
git pull
|
||||
- uses: stefanzweifel/git-auto-commit-action@v7
|
||||
name: Commit changes
|
||||
with:
|
||||
commit_message: "chore: update mocks"
|
||||
|
||||
@@ -4,19 +4,24 @@ on:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
unit:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
cache: true
|
||||
go-version: 'stable'
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get install -y curl jq
|
||||
# https://github.com/golang/go/issues/75031
|
||||
- name: Set toolchain version
|
||||
run: go env -w GOTOOLCHAIN=go1.25.1+auto
|
||||
- name: Set up environment
|
||||
run: |
|
||||
cp config.example.yml config.yml
|
||||
@@ -25,5 +30,6 @@ jobs:
|
||||
- name: Upload coverage report to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
file: ./coverage.out
|
||||
token: ${{ secrets.CODECOV }}
|
||||
disable_search: true
|
||||
files: coverage.out
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -11,6 +11,8 @@ _cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
# 编译文件
|
||||
ace
|
||||
cli
|
||||
*.com
|
||||
*.class
|
||||
*.dll
|
||||
@@ -63,3 +65,6 @@ out/
|
||||
*.sublime*
|
||||
__debug_bin
|
||||
.project
|
||||
|
||||
# 傻逼 node_modules
|
||||
node_modules/
|
||||
|
||||
+26
-34
@@ -2,9 +2,9 @@ version: 2
|
||||
project_name: panel
|
||||
|
||||
builds:
|
||||
- id: web
|
||||
main: ./cmd/web
|
||||
binary: web
|
||||
- id: ace
|
||||
main: ./cmd/ace
|
||||
binary: ace
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
@@ -12,15 +12,18 @@ builds:
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
flags:
|
||||
- -trimpath
|
||||
- -buildvcs=false
|
||||
ldflags:
|
||||
- -s -w --extldflags "-static"
|
||||
- -X 'github.com/tnb-labs/panel/internal/app.Version={{ .Version }}'
|
||||
- -X 'github.com/tnb-labs/panel/internal/app.BuildTime={{ .Now.Format "2006-01-02 15:04:05 MST" }}'
|
||||
- -X 'github.com/tnb-labs/panel/internal/app.CommitHash={{ .ShortCommit }}'
|
||||
- -X 'github.com/tnb-labs/panel/internal/app.GoVersion={{ .Env.GOVERSION }}'
|
||||
- -X 'github.com/tnb-labs/panel/internal/app.BuildID={{ .Env.GITHUB_RUN_ID }}'
|
||||
- -X 'github.com/tnb-labs/panel/internal/app.BuildUser={{ .Env.USER }}'
|
||||
- -X 'github.com/tnb-labs/panel/internal/app.BuildHost={{ .Env.HOSTNAME }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.Version={{ .Version }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.BuildTime={{ .Now.Format "2006-01-02 15:04:05 MST" }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.CommitHash={{ .ShortCommit }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.GoVersion={{ .Env.GOVERSION }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.BuildID={{ .Env.GITHUB_RUN_ID }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.BuildUser={{ .Env.USER }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.BuildHost={{ .Env.HOSTNAME }}'
|
||||
- id: cli
|
||||
main: ./cmd/cli
|
||||
binary: cli
|
||||
@@ -31,36 +34,25 @@ builds:
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
flags:
|
||||
- -trimpath
|
||||
- -buildvcs=false
|
||||
ldflags:
|
||||
- -s -w --extldflags "-static"
|
||||
- -X 'github.com/tnb-labs/panel/internal/app.Version={{ .Version }}'
|
||||
- -X 'github.com/tnb-labs/panel/internal/app.BuildTime={{ .Now.Format "2006-01-02 15:04:05 MST" }}'
|
||||
- -X 'github.com/tnb-labs/panel/internal/app.CommitHash={{ .ShortCommit }}'
|
||||
- -X 'github.com/tnb-labs/panel/internal/app.GoVersion={{ .Env.GOVERSION }}'
|
||||
- -X 'github.com/tnb-labs/panel/internal/app.BuildID={{ .Env.GITHUB_RUN_ID }}'
|
||||
- -X 'github.com/tnb-labs/panel/internal/app.BuildUser={{ .Env.USER }}'
|
||||
- -X 'github.com/tnb-labs/panel/internal/app.BuildHost={{ .Env.HOSTNAME }}'
|
||||
|
||||
upx:
|
||||
- enabled: true
|
||||
# Filter by build ID.
|
||||
ids:
|
||||
- web
|
||||
- cli
|
||||
# Compress argument.
|
||||
# Valid options are from '1' (faster) to '9' (better), and 'best'.
|
||||
compress: best
|
||||
# Whether to try LZMA (slower).
|
||||
lzma: true
|
||||
# Whether to try all methods and filters (slow).
|
||||
brute: false
|
||||
- -X 'github.com/acepanel/panel/internal/app.Version={{ .Version }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.BuildTime={{ .Now.Format "2006-01-02 15:04:05 MST" }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.CommitHash={{ .ShortCommit }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.GoVersion={{ .Env.GOVERSION }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.BuildID={{ .Env.GITHUB_RUN_ID }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.BuildUser={{ .Env.USER }}'
|
||||
- -X 'github.com/acepanel/panel/internal/app.BuildHost={{ .Env.HOSTNAME }}'
|
||||
|
||||
archives:
|
||||
- id: panel
|
||||
builds:
|
||||
- web
|
||||
ids:
|
||||
- ace
|
||||
- cli
|
||||
formats: ["zip", "tar.gz", "tar.xz"]
|
||||
formats: ["zip"]
|
||||
wrap_in_directory: false
|
||||
strip_binary_directory: true
|
||||
files:
|
||||
|
||||
+1
-1
@@ -6,6 +6,6 @@ outpkg: "{{.PackageName}}"
|
||||
filename: "{{.InterfaceName}}.go"
|
||||
all: True
|
||||
packages:
|
||||
github.com/tnb-labs/panel/internal/biz:
|
||||
github.com/acepanel/panel/internal/biz:
|
||||
config:
|
||||
recursive: True
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## 项目概述
|
||||
|
||||
AcePanel 是基于 Go 语言开发的新一代 Linux 服务器运维管理面板。项目采用前后端分离架构:
|
||||
- 后端:Go 1.25 + go-chi 路由 + GORM + Wire 依赖注入
|
||||
- 前端:Vue 3 + Vite + Naive UI + pnpm
|
||||
|
||||
## 语言和编码规范
|
||||
|
||||
**所有代码注释、文档和回复必须使用简体中文。**
|
||||
|
||||
## 构建和测试
|
||||
|
||||
### 后端构建
|
||||
|
||||
构建主程序:
|
||||
```bash
|
||||
go build -o ace ./cmd/ace
|
||||
```
|
||||
|
||||
构建 CLI 工具:
|
||||
```bash
|
||||
go build -o cli ./cmd/cli
|
||||
```
|
||||
|
||||
### 前端开发
|
||||
|
||||
进入前端目录:
|
||||
```bash
|
||||
cd web
|
||||
```
|
||||
|
||||
安装依赖:
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
开发模式(带热重载):
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
构建生产版本:
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## 代码架构
|
||||
|
||||
项目采用类 DDD 分层架构,依赖关系为:route -> service -> biz <- data
|
||||
|
||||
### 核心目录结构
|
||||
|
||||
- **`cmd/`**: 程序入口
|
||||
- `ace/`: 面板主程序
|
||||
- `cli/`: 命令行工具
|
||||
|
||||
- **`internal/app/`**: 应用入口和配置
|
||||
|
||||
- **`internal/route/`**: HTTP 路由定义
|
||||
- 定义路由规则
|
||||
- 注入所需的 service 依赖
|
||||
|
||||
- **`internal/service/`**: 服务层(类似 DDD 的 application 层)
|
||||
- 处理 HTTP 请求/响应
|
||||
- DTO 到 DO 的转换
|
||||
- 协调多个 biz 接口完成业务流程
|
||||
- **不应处理复杂业务逻辑**
|
||||
|
||||
- **`internal/biz/`**: 业务逻辑层(类似 DDD 的 domain 层)
|
||||
- 定义业务接口(Repository 模式)
|
||||
- 定义领域模型和数据结构
|
||||
- 使用依赖倒置原则:biz 定义接口,data 实现接口
|
||||
|
||||
- **`internal/data/`**: 数据访问层(类似 DDD 的 repository 层)
|
||||
- 实现 biz 中定义的业务接口
|
||||
- 封装数据库、缓存等操作
|
||||
- 处理数据持久化逻辑
|
||||
|
||||
- **`internal/http/`**: HTTP 相关
|
||||
- `middleware/`: 自定义中间件
|
||||
- `request/`: 请求结构体定义
|
||||
- `rule/`: 自定义验证规则
|
||||
|
||||
- **`internal/apps/`**: 面板子应用实现
|
||||
|
||||
- **`internal/bootstrap/`**: 各模块启动引导
|
||||
|
||||
- **`internal/migration/`**: 数据库迁移
|
||||
|
||||
- **`internal/job/`**: 后台任务
|
||||
|
||||
- **`internal/queuejob/`**: 任务队列
|
||||
|
||||
- **`pkg/`**: 工具函数和通用包
|
||||
- 包含各种独立的工具模块
|
||||
- 可被项目任何部分引用
|
||||
|
||||
- **`web/`**: Vue 3 前端项目
|
||||
|
||||
## 开发新功能的标准流程
|
||||
|
||||
1. **在 `internal/route/` 中添加路由**
|
||||
- 参考已有路由文件(如 `http.go`)
|
||||
- 注入需要的 service 依赖
|
||||
- 定义路由规则和 handler 映射
|
||||
|
||||
2. **在 `internal/service/` 中实现服务方法**
|
||||
- **先阅读已有的类似服务**以了解代码风格
|
||||
- 处理请求验证和响应格式化
|
||||
- 使用 `Success()` 返回成功响应
|
||||
- 使用 `Error()` 返回错误响应
|
||||
- 使用 `ErrorSystem()` 返回系统严重错误
|
||||
- 调用 biz 层接口完成业务逻辑
|
||||
|
||||
3. **在 `internal/biz/` 中定义业务接口**
|
||||
- **先阅读已有的类似接口定义**
|
||||
- 定义 Repository 接口(如 `WebsiteRepo`)
|
||||
- 定义领域模型结构体(如 `Website`)
|
||||
- 保持接口简洁明确
|
||||
|
||||
4. **在 `internal/data/` 中实现 biz 接口**
|
||||
- **先阅读已有的类似实现**
|
||||
- 创建 repo 结构体(如 `websiteRepo`)
|
||||
- 实现构造函数(如 `NewWebsiteRepo`)
|
||||
- 实现所有接口方法
|
||||
- 处理数据库操作和缓存逻辑
|
||||
|
||||
5. **使用 Wire 进行依赖注入**
|
||||
- 在对应的 wire.go 文件中添加 provider
|
||||
- 运行 `go generate` 生成依赖注入代码
|
||||
|
||||
## 技术栈特定注意事项
|
||||
|
||||
### Go 语言规范
|
||||
|
||||
- 使用 Go 1.25 稳定版本
|
||||
- 遵循 Go 标准库和习惯用法
|
||||
- 日志使用标准库的 `slog` 包
|
||||
- 使用 `github.com/samber/lo` 进行函数式编程辅助
|
||||
|
||||
### 当前框架
|
||||
|
||||
- 路由:`github.com/go-chi/chi/v5`
|
||||
- ORM:`gorm.io/gorm`
|
||||
- 依赖注入:`github.com/google/wire`
|
||||
- 验证:`github.com/gookit/validate`
|
||||
|
||||
### 助手函数(service 层)
|
||||
|
||||
在 service 层使用以下助手函数:
|
||||
- `Success(w, data)`: 返回成功响应
|
||||
- `Error(w, statusCode, format, args...)`: 返回错误响应
|
||||
- `ErrorSystem(w, format, args...)`: 返回系统严重错误(500)
|
||||
- `Bind[T](r)`: 绑定请求参数到泛型类型 T
|
||||
- `Paginate[T](...)`: 构建分页响应
|
||||
|
||||
### 数据库
|
||||
|
||||
- 使用 SQLite(`github.com/ncruces/go-sqlite3`)
|
||||
- 使用 GORM 进行数据库迁移和操作
|
||||
|
||||
### 安全性
|
||||
|
||||
- 不需要实现命令注入过滤,文件名过滤等,因为这是服务器面板,所有登录的用户都被视为管理员
|
||||
|
||||
## 代码风格
|
||||
|
||||
- 所有代码注释必须使用简体中文
|
||||
- 遵循 Go 官方代码风格
|
||||
- 使用 `gofmt` 格式化代码
|
||||
- 复杂逻辑添加注释说明
|
||||
- 导出的函数和类型必须有注释
|
||||
|
||||
## Wire 依赖注入
|
||||
|
||||
项目使用 Wire 进行依赖注入。当添加新的依赖时:
|
||||
|
||||
1. 在 `cmd/ace/wire.go` 或 `cmd/cli/wire.go` 中添加 provider
|
||||
2. 运行生成命令:
|
||||
```bash
|
||||
go generate ./...
|
||||
```
|
||||
|
||||
## 前端开发注意事项
|
||||
|
||||
- 使用 Vue 3 Composition API
|
||||
- UI 框架:Naive UI
|
||||
- 状态管理:Pinia
|
||||
- HTTP 请求:Alova.js,使用 useRequest 等助手函数进行数据获取,无需处理 onError 错误。
|
||||
- 图标:@iconify/vue
|
||||
- 终端:xterm.js
|
||||
- 遵循项目已有的组件结构和编码风格
|
||||
|
||||
## 配置文件
|
||||
|
||||
开发时需要准备配置文件:
|
||||
```bash
|
||||
cp config.example.yml config.yml
|
||||
```
|
||||
|
||||
前端开发配置:
|
||||
```bash
|
||||
cd web
|
||||
cp .env.production .env
|
||||
cp settings/proxy-config.example.ts settings/proxy-config.ts
|
||||
```
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
## 行为准则
|
||||
|
||||
耗子面板遵守业界通用的行为准则。任何违反行为准则的行为都可以报告给我们:
|
||||
AcePanel 遵守业界通用的行为准则。任何违反行为准则的行为都可以报告给我们:
|
||||
|
||||
- 参与者将容忍反对意见。
|
||||
- 参与者必须确保他们的语言和行为没有人身攻击和贬低个人言论。
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
The Rat Panel complies with the industry's common code of conduct. Any breach of the Code of Conduct can be reported to us:
|
||||
The AcePanel complies with the industry's common code of conduct. Any breach of the Code of Conduct can be reported to us:
|
||||
|
||||
- Participants will be tolerant of opposing views.
|
||||
- Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
||||
|
||||
@@ -1,661 +1,38 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
Copyright (c) 2022-2025, AcePanel contributors
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
免责声明
|
||||
|
||||
本软件是按照现有技术和条件所能达到的现状提供的,用户出于自愿而使用本软件,树新峰(耗子科技)不作任何形式的保证,包括但不限于:
|
||||
|
||||
1. 树新峰不对于因使用或无法使用本软件而造成的任何直接、间接、偶然、附带、特殊及后续的「损害」承担责任,即使已被告知此类「损害」的可能性;
|
||||
2. 树新峰无组织本软件的交流社区的义务与责任,不承担因技术交流导致某一方故障而产生的「损害」;
|
||||
3. 树新峰不对本软件的无故障、适用性、可用性、准确性、质量满意度等做任何形式的保证;
|
||||
4. 树新峰不保证本软件可以满足您的要求,也不保证其内容、服务或功能无任何错误或不会中断,不保证任何缺陷将得到更正;
|
||||
5. 树新峰对本软件提供的任何信息或建议均不构成任何担保;
|
||||
6. 用户因使用本软件违反国家法律法规的,树新峰不承担任何责任;
|
||||
7. 本条款所指的「损害」包括但不限于经济损失、利润损失、收入损失、业务中断、数据丢失等。
|
||||
|
||||
@@ -2,22 +2,26 @@
|
||||
[简体中文] | [<a href="README_EN.md">English</a>]
|
||||
</p>
|
||||
|
||||
<h1 align="center" style="font-size: 40px">耗子面板</h1>
|
||||
<p align="center"><a href="https://acepanel.net"><img src=".github/assets/logo-full.png" alt="AcePanel" width="300" /></a></p>
|
||||
|
||||
<p align="center">新一代全能服务器运维管理面板<br>简单轻量,高效运维</p>
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://go.dev/)
|
||||
[](https://github.com/tnb-labs/panel/releases)
|
||||
[](https://github.com/tnb-labs/panel/actions)
|
||||
[](https://goreportcard.com/report/github.com/tnb-labs/panel)
|
||||
[](https://github.com/tnb-labs/panel)
|
||||
[](https://www.gnu.org/licenses/agpl-3.0.html)
|
||||
官网:[acepanel.net](https://acepanel.net) | QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | 微信群:[复制此链接](https://work.weixin.qq.com/gm/d8ebf618553398d454e3378695c858b6)
|
||||
|
||||
</div>
|
||||
|
||||
新一代全能服务器运维管理面板。简单轻量,高效运维。
|
||||
<div align="center">
|
||||
|
||||
官网:[panel.haozi.net](https://panel.haozi.net) | QQ群:[12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | 微信群:[复制此链接](https://work.weixin.qq.com/gm/d8ebf618553398d454e3378695c858b6)
|
||||
[](https://go.dev/)
|
||||
[](https://github.com/acepanel/panel/releases)
|
||||
[](https://github.com/acepanel/panel/actions)
|
||||
[](https://goreportcard.com/report/github.com/acepanel/panel)
|
||||
[](https://github.com/acepanel/panel)
|
||||
[](https://opensource.org/license/bsd-3-clause)
|
||||
|
||||
</div>
|
||||
|
||||
## 优势
|
||||
|
||||
@@ -26,58 +30,62 @@
|
||||
3. **追随时代:** 整体设计走在时代前沿,对新系统兼容性好,在同类产品中处于领先地位
|
||||
4. **高效运维:** 功能完善,自定义能力强,既可快速部署小型网站,也可基于定制化需求部署复杂应用
|
||||
5. **离线运行:** 支持离线模式,甚至可以在部署完成后停止面板进程,不会对已有服务造成任何影响
|
||||
6. **安全稳定:** 面板采用业界多种技术保障本体安全性,已在我们的多个生存环境中长期稳定运行
|
||||
6. **安全稳定:** 面板采用业界多种技术保障本体安全性,已在我们的多个生产环境中长期稳定运行
|
||||
7. **全面开源:** 少有的全开源面板,您可以在遵守开源协议的前提下对面板自由修改、二次开发
|
||||
8. **全面开源:** 承诺面板本体未来不会引入任何收费/授权功能,永久免费使用
|
||||
8. **永久免费:** 承诺面板本体未来不会引入任何收费/授权功能,永久免费使用
|
||||
|
||||
## 快速安装
|
||||
|
||||
支持 `amd64` | `arm64` 架构下的干净的主流系统,具体支持的系统请参考[安装文档](https://acepanel.github.io/quickstart/install)。
|
||||
|
||||
```shell
|
||||
bash <(curl -sSLm 10 https://dl.acepanel.net/helper.sh)
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> 耗子面板 2.x -> AcePanel 3.0 迁移工具将在近期发布,敬请期待!
|
||||
|
||||
## UI 截图
|
||||
|
||||

|
||||
|
||||
## 快速安装
|
||||
|
||||
[https://ratpanel.github.io/quickstart/install.html](https://ratpanel.github.io/quickstart/install.html)
|
||||
|
||||
## 合作伙伴
|
||||
|
||||
如果耗子面板对您有帮助,欢迎赞助我们,同时感谢以下支持者/赞助商的支持:
|
||||
|
||||
 
|
||||
|
||||
<a href="https://www.weixiaoduo.com/">
|
||||
<img height="80" src=".github/assets/wxd.png" alt="微晓朵">
|
||||
</a>
|
||||
<a href="https://www.dkdun.cn/aff/MQZZNVHQ">
|
||||
<img height="80" src=".github/assets/dk.png" alt="林枫云">
|
||||
</a>
|
||||
<a href="https://su.sctes.com/register?code=8st689ujpmm2p">
|
||||
<img height="80" src=".github/assets/sctes.png" alt="无畏云加速">
|
||||
</a>
|
||||
<a href="https://su.sctes.com/register?code=8st689ujpmm2p">
|
||||
<img height="80" src=".github/assets/wafpro.png" alt="WAFPRO">
|
||||
</a>
|
||||
<a href="https://scdn.ddunyun.com/">
|
||||
<img height="80" src=".github/assets/ddunyun.png" alt="盾云SCDN">
|
||||
</a>
|
||||
<a href="https://hongtoutong.com">
|
||||
<img height="80" src=".github/assets/htt.png" alt="红透通">
|
||||
</a>
|
||||
<a href="https://1ms.run">
|
||||
<img height="80" src=".github/assets/1ms.svg" alt="毫秒镜像提供经过审核的 Docker 镜像加速服务">
|
||||
</a>
|
||||
如果 AcePanel 对您有帮助,欢迎[赞助我们](https://github.com/acepanel/panel/issues/90),同时感谢以下支持者/赞助商的支持:
|
||||
|
||||
<p align="center">
|
||||
<a target="_blank" href="https://afdian.com/a/tnblabs">
|
||||
<img alt="sponsors" src="https://github.com/tnb-labs/sponsor/blob/main/sponsors.svg?raw=true"/>
|
||||
<a href="https://www.weixiaoduo.com/">
|
||||
<img height="60" src=".github/assets/wxd.png" alt="微晓朵">
|
||||
</a>
|
||||
<a href="https://www.dkdun.cn/aff/MQZZNVHQ">
|
||||
<img height="60" src=".github/assets/dk.png" alt="林枫云">
|
||||
</a>
|
||||
<a href="https://cloud.panguidc.com/aff/DMRRFVJX">
|
||||
<img height="60" src=".github/assets/pangu.png" alt="盘古云">
|
||||
</a>
|
||||
<a href="https://waf.pro/">
|
||||
<img height="60" src=".github/assets/wafpro.png" alt="WAFPRO">
|
||||
</a>
|
||||
<a href="https://scdn.ddunyun.com/">
|
||||
<img height="60" src=".github/assets/ddunyun.png" alt="盾云SCDN">
|
||||
</a>
|
||||
<a href="https://1ms.run/">
|
||||
<img height="60" src=".github/assets/1ms.svg" alt="毫秒镜像">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a target="_blank" href="https://afdian.com/a/tnborg">
|
||||
<img alt="sponsors" src="https://github.com/tnborg/sponsor/blob/main/sponsors.svg?raw=true"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## Star 历史
|
||||
|
||||
<a href="https://star-history.com/#tnb-labs/panel&Date">
|
||||
<a href="https://star-history.com/#acepanel/panel&Date">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=tnb-labs/panel&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=tnb-labs/panel&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=tnb-labs/panel&type=Date" />
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=acepanel/panel&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=acepanel/panel&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=acepanel/panel&type=Date" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
+49
-44
@@ -2,22 +2,26 @@
|
||||
[<a href="README.md">简体中文</a>] | [English]
|
||||
</p>
|
||||
|
||||
<h1 align="center" style="font-size: 40px">Rat Panel</h1>
|
||||
<p align="center"><a href="https://acepanel.net"><img src=".github/assets/logo-full.png" alt="AcePanel" width="300" /></a></p>
|
||||
|
||||
<p align="center">New generation of all-in-one server operation and maintenance management panel<br>Simple and lightweight, efficient operation and maintenance</p>
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://go.dev/)
|
||||
[](https://github.com/tnb-labs/panel/releases)
|
||||
[](https://github.com/tnb-labs/panel/actions)
|
||||
[](https://goreportcard.com/report/github.com/tnb-labs/panel)
|
||||
[](https://github.com/tnb-labs/panel)
|
||||
[](https://www.gnu.org/licenses/agpl-3.0.html)
|
||||
Website: [acepanel.net](https://acepanel.net) | QQ group: [12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | WeChat group: [Copy this link](https://work.weixin.qq.com/gm/d8ebf618553398d454e3378695c858b6)
|
||||
|
||||
</div>
|
||||
|
||||
A new generation of all-in-one server operation and maintenance management panel. Simple and lightweight, efficient operation and maintenance.
|
||||
<div align="center">
|
||||
|
||||
Website: [panel.haozi.net](https://panel.haozi.net) | QQ group: [12370907](https://jq.qq.com/?_wv=1027&k=I1oJKSTH) | WeChat group: [Copy this link](https://work.weixin.qq.com/gm/d8ebf618553398d454e3378695c858b6)
|
||||
[](https://go.dev/)
|
||||
[](https://github.com/acepanel/panel/releases)
|
||||
[](https://github.com/acepanel/panel/actions)
|
||||
[](https://goreportcard.com/report/github.com/acepanel/panel)
|
||||
[](https://github.com/acepanel/panel)
|
||||
[](https://opensource.org/license/bsd-3-clause)
|
||||
|
||||
</div>
|
||||
|
||||
## Advantages
|
||||
|
||||
@@ -30,54 +34,55 @@ Website: [panel.haozi.net](https://panel.haozi.net) | QQ group: [12370907](https
|
||||
7. **Fully Open Source:** Few fully open source panels, you can freely modify and develop the panel on the premise of complying with the open source agreement
|
||||
8. **Permanently free:** Commit to the panel body will not introduce any charging/authorization functions in the future, and will be permanently free to use
|
||||
|
||||
## Quick Install
|
||||
|
||||
Supported clean mainstream systems under `amd64` | `arm64` architecture, please refer to the [installation documentation](https://acepanel.github.io/en/quickstart/install) for specific supported systems.
|
||||
|
||||
```shell
|
||||
bash <(curl -sSLm 10 https://dl.acepanel.net/helper.sh)
|
||||
```
|
||||
|
||||
## UI Screenshots
|
||||
|
||||

|
||||
|
||||
## Quick Install
|
||||
|
||||
[https://ratpanel.github.io/quickstart/install.html](https://ratpanel.github.io/quickstart/install.html)
|
||||
|
||||
## Partners
|
||||
|
||||
If the Rat Panel is helpful to you, welcome to sponsor us, also thanks to the following supporters/sponsors:
|
||||
|
||||
 
|
||||
|
||||
<a href="https://www.weixiaoduo.com/">
|
||||
<img height="80" src=".github/assets/wxd.png" alt="微晓朵">
|
||||
</a>
|
||||
<a href="https://www.dkdun.cn/aff/MQZZNVHQ">
|
||||
<img height="80" src=".github/assets/dk.png" alt="林枫云">
|
||||
</a>
|
||||
<a href="https://su.sctes.com/register?code=8st689ujpmm2p">
|
||||
<img height="80" src=".github/assets/sctes.png" alt="无畏云加速">
|
||||
</a>
|
||||
<a href="https://su.sctes.com/register?code=8st689ujpmm2p">
|
||||
<img height="80" src=".github/assets/wafpro.png" alt="WAFPRO">
|
||||
</a>
|
||||
<a href="https://scdn.ddunyun.com/">
|
||||
<img height="80" src=".github/assets/ddunyun.png" alt="盾云SCDN">
|
||||
</a>
|
||||
<a href="https://hongtoutong.com">
|
||||
<img height="80" src=".github/assets/htt.png" alt="红透通">
|
||||
</a>
|
||||
<a href="https://1ms.run">
|
||||
<img height="80" src=".github/assets/1ms.svg" alt="毫秒镜像提供经过审核的 Docker 镜像加速服务">
|
||||
</a>
|
||||
If the AcePanel is helpful to you, welcome to [sponsor us](https://github.com/acepanel/panel/issues/90), also thanks to the following supporters/sponsors:
|
||||
|
||||
<p align="center">
|
||||
<a target="_blank" href="https://afdian.com/a/tnblabs">
|
||||
<img alt="sponsors" src="https://github.com/tnb-labs/sponsor/blob/main/sponsors.svg?raw=true"/>
|
||||
<a href="https://www.weixiaoduo.com/">
|
||||
<img height="60" src=".github/assets/wxd.png" alt="微晓朵">
|
||||
</a>
|
||||
<a href="https://www.dkdun.cn/aff/MQZZNVHQ">
|
||||
<img height="60" src=".github/assets/dk.png" alt="林枫云">
|
||||
</a>
|
||||
<a href="https://cloud.panguidc.com/aff/DMRRFVJX">
|
||||
<img height="60" src=".github/assets/pangu.png" alt="盘古云">
|
||||
</a>
|
||||
<a href="https://waf.pro/">
|
||||
<img height="60" src=".github/assets/wafpro.png" alt="WAFPRO">
|
||||
</a>
|
||||
<a href="https://scdn.ddunyun.com/">
|
||||
<img height="60" src=".github/assets/ddunyun.png" alt="盾云SCDN">
|
||||
</a>
|
||||
<a href="https://1ms.run/">
|
||||
<img height="60" src=".github/assets/1ms.svg" alt="毫秒镜像">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a target="_blank" href="https://afdian.com/a/tnborg">
|
||||
<img alt="sponsors" src="https://github.com/tnborg/sponsor/blob/main/sponsors.svg?raw=true"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## Star History
|
||||
|
||||
<a href="https://star-history.com/#tnb-labs/panel&Date">
|
||||
<a href="https://star-history.com/#acepanel/panel&Date">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=tnb-labs/panel&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=tnb-labs/panel&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=tnb-labs/panel&type=Date" />
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=acepanel/panel&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=acepanel/panel&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=acepanel/panel&type=Date" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
+19
-14
@@ -1,25 +1,30 @@
|
||||
## 安全说明
|
||||
## Supported Versions
|
||||
|
||||
**我们不对面板的安全性做任何保证**,但安全性是我们最关心的问题之一,我们已在多个不同应用的生产环境广泛应用耗子面板,至今无任何安全事故。
|
||||
|
||||
如果您在面板中发现任何安全问题,请勿提交 Issue,可通过以下方式直接联系我们:
|
||||
|
||||
- 邮箱:[admin@haozi.net](mailto:admin@haozi.net)
|
||||
- QQ:826896000
|
||||
|
||||
提前感谢您的支持与帮助!
|
||||
|
||||
致某些安全初学者:通过面板 `session` / `access_token` 执行的任何操作(包括且不限于:获取 root 权限、读取/写入系统敏感文件、执行任意 shell 命令等)均不被认为是安全问题,请不要刷此类报告浪费彼此的时间,这类低水平的报告对你的简历也没有任何帮助。
|
||||
| Version | Supported |
|
||||
|---------|--------------------|
|
||||
| v2.x | :white_check_mark: |
|
||||
| v1.x | :x: |
|
||||
|
||||
## Security Policy
|
||||
|
||||
**We do not make any guarantees about the security of the panel**, but security is one of our top concerns. We have widely used the Rat panel in the production environment of multiple different applications, and there has been no security incidents to date.
|
||||
|
||||
If you find any security issues while using the panel, please do not submit an Issue. You can contact us directly through the following methods:
|
||||
|
||||
- (Recommend) [GitHub Security Advisories](https://github.com/acepanel/panel/security/advisories/new)
|
||||
- Email: [admin@haozi.net](mailto:admin@haozi.net)
|
||||
- Telegram: @devhaozi
|
||||
|
||||
Thank you in advance for your support and help!
|
||||
|
||||
To some security beginners: Any operation performed through the panel `session` / `access_token` (including but not limited to: obtaining root permissions, reading/writing sensitive system files, executing arbitrary shell commands, etc.) is not considered a security issue. Please do not waste each other's time by submitting such reports. Such low-level reports are of no help to your resume.
|
||||
To some security beginners: Any operation performed through an already logged-in panel's `session` / `access_token` (including but not limited to: obtaining root permissions, reading/writing sensitive system files, executing arbitrary shell commands, etc.) is not considered a security issue. Please do not waste each other's time by submitting such reports.
|
||||
|
||||
## 安全说明
|
||||
|
||||
如果您在面板中发现任何安全问题,请勿提交 Issue,可通过以下方式直接联系我们:
|
||||
|
||||
- (推荐)[GitHub 安全公告](https://github.com/acepanel/panel/security/advisories/new)
|
||||
- 邮箱:[admin@haozi.net](mailto:admin@haozi.net)
|
||||
- QQ:826896000
|
||||
|
||||
提前感谢您的支持与帮助!
|
||||
|
||||
致某些安全初学者:通过已登录面板的 `session` / `access_token` 执行的任何操作(包括且不限于:获取 root 权限、读取/写入系统敏感文件、执行任意 shell 命令等)均不被认为是安全问题,请不要刷此类报告浪费彼此的时间。
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# cmd
|
||||
|
||||
cmd 目录存放应用的入口文件。
|
||||
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime/debug"
|
||||
_ "time/tzdata"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if os.Geteuid() != 0 {
|
||||
panic("panel must run as root")
|
||||
}
|
||||
|
||||
debug.SetGCPercent(10)
|
||||
debug.SetMemoryLimit(128 << 20)
|
||||
|
||||
web, err := initWeb()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err = web.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
//go:build wireinject
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/apps"
|
||||
"github.com/acepanel/panel/internal/bootstrap"
|
||||
"github.com/acepanel/panel/internal/data"
|
||||
"github.com/acepanel/panel/internal/http/middleware"
|
||||
"github.com/acepanel/panel/internal/job"
|
||||
"github.com/acepanel/panel/internal/route"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
)
|
||||
|
||||
// initWeb init application.
|
||||
func initWeb() (*app.Web, error) {
|
||||
panic(wire.Build(bootstrap.ProviderSet, middleware.ProviderSet, route.ProviderSet, service.ProviderSet, data.ProviderSet, apps.ProviderSet, job.ProviderSet, app.NewWeb))
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
// Code generated by Wire. DO NOT EDIT.
|
||||
|
||||
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
|
||||
//go:build !wireinject
|
||||
// +build !wireinject
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/apps/apache"
|
||||
"github.com/acepanel/panel/internal/apps/codeserver"
|
||||
"github.com/acepanel/panel/internal/apps/docker"
|
||||
"github.com/acepanel/panel/internal/apps/fail2ban"
|
||||
"github.com/acepanel/panel/internal/apps/frp"
|
||||
"github.com/acepanel/panel/internal/apps/gitea"
|
||||
"github.com/acepanel/panel/internal/apps/mariadb"
|
||||
"github.com/acepanel/panel/internal/apps/memcached"
|
||||
"github.com/acepanel/panel/internal/apps/minio"
|
||||
"github.com/acepanel/panel/internal/apps/mysql"
|
||||
"github.com/acepanel/panel/internal/apps/nginx"
|
||||
"github.com/acepanel/panel/internal/apps/openresty"
|
||||
"github.com/acepanel/panel/internal/apps/percona"
|
||||
"github.com/acepanel/panel/internal/apps/phpmyadmin"
|
||||
"github.com/acepanel/panel/internal/apps/podman"
|
||||
"github.com/acepanel/panel/internal/apps/postgresql"
|
||||
"github.com/acepanel/panel/internal/apps/pureftpd"
|
||||
"github.com/acepanel/panel/internal/apps/redis"
|
||||
"github.com/acepanel/panel/internal/apps/rsync"
|
||||
"github.com/acepanel/panel/internal/apps/s3fs"
|
||||
"github.com/acepanel/panel/internal/apps/supervisor"
|
||||
"github.com/acepanel/panel/internal/bootstrap"
|
||||
"github.com/acepanel/panel/internal/data"
|
||||
"github.com/acepanel/panel/internal/http/middleware"
|
||||
"github.com/acepanel/panel/internal/job"
|
||||
"github.com/acepanel/panel/internal/route"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
)
|
||||
|
||||
import (
|
||||
_ "time/tzdata"
|
||||
)
|
||||
|
||||
// Injectors from wire.go:
|
||||
|
||||
// initWeb init application.
|
||||
func initWeb() (*app.Web, error) {
|
||||
config, err := bootstrap.NewConf()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
locale, err := bootstrap.NewT(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db, err := bootstrap.NewDB(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
manager, err := bootstrap.NewSession(config, db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger := bootstrap.NewLog(config)
|
||||
cacheRepo := data.NewCacheRepo(db)
|
||||
queue := bootstrap.NewQueue()
|
||||
taskRepo := data.NewTaskRepo(locale, db, logger, queue)
|
||||
appRepo := data.NewAppRepo(locale, config, db, logger, cacheRepo, taskRepo)
|
||||
userTokenRepo := data.NewUserTokenRepo(locale, config, db)
|
||||
middlewares := middleware.NewMiddlewares(config, manager, appRepo, userTokenRepo)
|
||||
userRepo := data.NewUserRepo(locale, db, logger)
|
||||
userService := service.NewUserService(locale, config, manager, userRepo)
|
||||
userTokenService := service.NewUserTokenService(locale, userTokenRepo)
|
||||
databaseServerRepo := data.NewDatabaseServerRepo(locale, db, logger)
|
||||
databaseUserRepo := data.NewDatabaseUserRepo(locale, db, logger, databaseServerRepo)
|
||||
databaseRepo := data.NewDatabaseRepo(locale, db, logger, databaseServerRepo, databaseUserRepo)
|
||||
settingRepo := data.NewSettingRepo(locale, db, logger, config, taskRepo)
|
||||
certRepo := data.NewCertRepo(locale, db, logger, settingRepo)
|
||||
certAccountRepo := data.NewCertAccountRepo(locale, db, userRepo, logger)
|
||||
websiteRepo := data.NewWebsiteRepo(locale, db, logger, cacheRepo, databaseRepo, databaseServerRepo, databaseUserRepo, certRepo, certAccountRepo, settingRepo)
|
||||
projectRepo := data.NewProjectRepo(locale, db, logger)
|
||||
environmentRepo := data.NewEnvironmentRepo(locale, config, cacheRepo, taskRepo)
|
||||
cronRepo := data.NewCronRepo(locale, db, logger)
|
||||
backupRepo := data.NewBackupRepo(locale, config, db, logger, settingRepo, websiteRepo)
|
||||
homeService := service.NewHomeService(locale, config, taskRepo, websiteRepo, projectRepo, appRepo, environmentRepo, settingRepo, cronRepo, backupRepo)
|
||||
taskService := service.NewTaskService(taskRepo)
|
||||
websiteService := service.NewWebsiteService(websiteRepo, settingRepo)
|
||||
projectService := service.NewProjectService(projectRepo, settingRepo)
|
||||
databaseService := service.NewDatabaseService(databaseRepo)
|
||||
databaseServerService := service.NewDatabaseServerService(databaseServerRepo)
|
||||
databaseUserService := service.NewDatabaseUserService(databaseUserRepo)
|
||||
backupService := service.NewBackupService(locale, backupRepo)
|
||||
backupAccountRepo := data.NewBackupAccountRepo(locale, db, logger, settingRepo)
|
||||
backupStorageService := service.NewBackupStorageService(locale, backupAccountRepo)
|
||||
certService := service.NewCertService(locale, certRepo)
|
||||
certDNSRepo := data.NewCertDNSRepo(db, logger)
|
||||
certDNSService := service.NewCertDNSService(certDNSRepo)
|
||||
certAccountService := service.NewCertAccountService(certAccountRepo)
|
||||
appService := service.NewAppService(locale, appRepo, cacheRepo, settingRepo)
|
||||
environmentService := service.NewEnvironmentService(locale, environmentRepo, taskRepo)
|
||||
environmentGoService := service.NewEnvironmentGoService(locale, environmentRepo)
|
||||
environmentJavaService := service.NewEnvironmentJavaService(locale, environmentRepo)
|
||||
environmentNodejsService := service.NewEnvironmentNodejsService(locale, environmentRepo)
|
||||
environmentPHPService := service.NewEnvironmentPHPService(locale, config, environmentRepo, taskRepo)
|
||||
environmentPythonService := service.NewEnvironmentPythonService(locale, environmentRepo)
|
||||
cronService := service.NewCronService(cronRepo)
|
||||
processService := service.NewProcessService()
|
||||
safeRepo := data.NewSafeRepo(logger)
|
||||
safeService := service.NewSafeService(safeRepo)
|
||||
firewallService := service.NewFirewallService()
|
||||
sshRepo := data.NewSSHRepo(locale, db, logger)
|
||||
sshService := service.NewSSHService(sshRepo)
|
||||
containerRepo := data.NewContainerRepo()
|
||||
containerService := service.NewContainerService(containerRepo)
|
||||
containerComposeRepo := data.NewContainerComposeRepo()
|
||||
containerComposeService := service.NewContainerComposeService(containerComposeRepo)
|
||||
containerNetworkRepo := data.NewContainerNetworkRepo()
|
||||
containerNetworkService := service.NewContainerNetworkService(containerNetworkRepo)
|
||||
containerImageRepo := data.NewContainerImageRepo()
|
||||
containerImageService := service.NewContainerImageService(containerImageRepo)
|
||||
containerVolumeRepo := data.NewContainerVolumeRepo()
|
||||
containerVolumeService := service.NewContainerVolumeService(containerVolumeRepo)
|
||||
fileService := service.NewFileService(locale, taskRepo)
|
||||
logRepo := data.NewLogRepo(db)
|
||||
logService := service.NewLogService(logRepo)
|
||||
monitorRepo := data.NewMonitorRepo(db, settingRepo)
|
||||
monitorService := service.NewMonitorService(settingRepo, monitorRepo)
|
||||
settingService := service.NewSettingService(locale, db, settingRepo, certRepo, certAccountRepo)
|
||||
systemctlService := service.NewSystemctlService(locale)
|
||||
toolboxSystemService := service.NewToolboxSystemService(locale)
|
||||
toolboxBenchmarkService := service.NewToolboxBenchmarkService(locale)
|
||||
toolboxSSHService := service.NewToolboxSSHService(locale)
|
||||
toolboxDiskService := service.NewToolboxDiskService(locale)
|
||||
toolboxLogService := service.NewToolboxLogService(locale, db, containerImageRepo, settingRepo)
|
||||
webHookRepo := data.NewWebHookRepo(locale, db, logger)
|
||||
webHookService := service.NewWebHookService(webHookRepo)
|
||||
templateRepo := data.NewTemplateRepo(locale, cacheRepo)
|
||||
templateService := service.NewTemplateService(locale, templateRepo, settingRepo)
|
||||
apacheApp := apache.NewApp(locale)
|
||||
codeserverApp := codeserver.NewApp()
|
||||
dockerApp := docker.NewApp()
|
||||
fail2banApp := fail2ban.NewApp(locale, websiteRepo)
|
||||
frpApp := frp.NewApp()
|
||||
giteaApp := gitea.NewApp()
|
||||
mariadbApp := mariadb.NewApp(locale, settingRepo, databaseServerRepo)
|
||||
memcachedApp := memcached.NewApp(locale)
|
||||
minioApp := minio.NewApp()
|
||||
mysqlApp := mysql.NewApp(locale, settingRepo, databaseServerRepo)
|
||||
nginxApp := nginx.NewApp(locale)
|
||||
openrestyApp := openresty.NewApp(locale)
|
||||
perconaApp := percona.NewApp(locale, settingRepo, databaseServerRepo)
|
||||
phpmyadminApp := phpmyadmin.NewApp(locale)
|
||||
podmanApp := podman.NewApp()
|
||||
postgresqlApp := postgresql.NewApp(locale, settingRepo, databaseServerRepo)
|
||||
pureftpdApp := pureftpd.NewApp(locale)
|
||||
redisApp := redis.NewApp(locale)
|
||||
rsyncApp := rsync.NewApp(locale)
|
||||
s3fsApp := s3fs.NewApp(locale)
|
||||
supervisorApp := supervisor.NewApp(locale)
|
||||
loader := bootstrap.NewLoader(apacheApp, codeserverApp, dockerApp, fail2banApp, frpApp, giteaApp, mariadbApp, memcachedApp, minioApp, mysqlApp, nginxApp, openrestyApp, perconaApp, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp)
|
||||
http := route.NewHttp(config, userService, userTokenService, homeService, taskService, websiteService, projectService, databaseService, databaseServerService, databaseUserService, backupService, backupStorageService, certService, certDNSService, certAccountService, appService, environmentService, environmentGoService, environmentJavaService, environmentNodejsService, environmentPHPService, environmentPythonService, cronService, processService, safeService, firewallService, sshService, containerService, containerComposeService, containerNetworkService, containerImageService, containerVolumeService, fileService, logService, monitorService, settingService, systemctlService, toolboxSystemService, toolboxBenchmarkService, toolboxSSHService, toolboxDiskService, toolboxLogService, webHookService, templateService, loader)
|
||||
wsService := service.NewWsService(locale, config, logger, sshRepo)
|
||||
ws := route.NewWs(wsService)
|
||||
mux, err := bootstrap.NewRouter(locale, middlewares, http, ws)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
server, err := bootstrap.NewHttp(config, mux)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gormigrate := bootstrap.NewMigrate(db)
|
||||
jobs := job.NewJobs(config, db, logger, settingRepo, certRepo, certAccountRepo, backupRepo, cacheRepo, taskRepo)
|
||||
cron, err := bootstrap.NewCron(config, logger, jobs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
validation := bootstrap.NewValidator(config, db)
|
||||
web := app.NewWeb(config, mux, server, gormigrate, cron, queue, validation)
|
||||
return web, nil
|
||||
}
|
||||
@@ -1,19 +1,3 @@
|
||||
/*
|
||||
Copyright (C) 2022 - now Rat Technology Co., Ltd.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
+6
-6
@@ -5,12 +5,12 @@ package main
|
||||
import (
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/apps"
|
||||
"github.com/tnb-labs/panel/internal/bootstrap"
|
||||
"github.com/tnb-labs/panel/internal/data"
|
||||
"github.com/tnb-labs/panel/internal/route"
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/apps"
|
||||
"github.com/acepanel/panel/internal/bootstrap"
|
||||
"github.com/acepanel/panel/internal/data"
|
||||
"github.com/acepanel/panel/internal/route"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
)
|
||||
|
||||
// initCli init command line.
|
||||
|
||||
+65
-65
@@ -7,34 +7,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/apps/benchmark"
|
||||
"github.com/tnb-labs/panel/internal/apps/docker"
|
||||
"github.com/tnb-labs/panel/internal/apps/fail2ban"
|
||||
"github.com/tnb-labs/panel/internal/apps/frp"
|
||||
"github.com/tnb-labs/panel/internal/apps/gitea"
|
||||
"github.com/tnb-labs/panel/internal/apps/memcached"
|
||||
"github.com/tnb-labs/panel/internal/apps/mysql"
|
||||
"github.com/tnb-labs/panel/internal/apps/nginx"
|
||||
"github.com/tnb-labs/panel/internal/apps/php74"
|
||||
"github.com/tnb-labs/panel/internal/apps/php80"
|
||||
"github.com/tnb-labs/panel/internal/apps/php81"
|
||||
"github.com/tnb-labs/panel/internal/apps/php82"
|
||||
"github.com/tnb-labs/panel/internal/apps/php83"
|
||||
"github.com/tnb-labs/panel/internal/apps/php84"
|
||||
"github.com/tnb-labs/panel/internal/apps/phpmyadmin"
|
||||
"github.com/tnb-labs/panel/internal/apps/podman"
|
||||
"github.com/tnb-labs/panel/internal/apps/postgresql"
|
||||
"github.com/tnb-labs/panel/internal/apps/pureftpd"
|
||||
"github.com/tnb-labs/panel/internal/apps/redis"
|
||||
"github.com/tnb-labs/panel/internal/apps/rsync"
|
||||
"github.com/tnb-labs/panel/internal/apps/s3fs"
|
||||
"github.com/tnb-labs/panel/internal/apps/supervisor"
|
||||
"github.com/tnb-labs/panel/internal/apps/toolbox"
|
||||
"github.com/tnb-labs/panel/internal/bootstrap"
|
||||
"github.com/tnb-labs/panel/internal/data"
|
||||
"github.com/tnb-labs/panel/internal/route"
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/apps/apache"
|
||||
"github.com/acepanel/panel/internal/apps/codeserver"
|
||||
"github.com/acepanel/panel/internal/apps/docker"
|
||||
"github.com/acepanel/panel/internal/apps/fail2ban"
|
||||
"github.com/acepanel/panel/internal/apps/frp"
|
||||
"github.com/acepanel/panel/internal/apps/gitea"
|
||||
"github.com/acepanel/panel/internal/apps/mariadb"
|
||||
"github.com/acepanel/panel/internal/apps/memcached"
|
||||
"github.com/acepanel/panel/internal/apps/minio"
|
||||
"github.com/acepanel/panel/internal/apps/mysql"
|
||||
"github.com/acepanel/panel/internal/apps/nginx"
|
||||
"github.com/acepanel/panel/internal/apps/openresty"
|
||||
"github.com/acepanel/panel/internal/apps/percona"
|
||||
"github.com/acepanel/panel/internal/apps/phpmyadmin"
|
||||
"github.com/acepanel/panel/internal/apps/podman"
|
||||
"github.com/acepanel/panel/internal/apps/postgresql"
|
||||
"github.com/acepanel/panel/internal/apps/pureftpd"
|
||||
"github.com/acepanel/panel/internal/apps/redis"
|
||||
"github.com/acepanel/panel/internal/apps/rsync"
|
||||
"github.com/acepanel/panel/internal/apps/s3fs"
|
||||
"github.com/acepanel/panel/internal/apps/supervisor"
|
||||
"github.com/acepanel/panel/internal/bootstrap"
|
||||
"github.com/acepanel/panel/internal/data"
|
||||
"github.com/acepanel/panel/internal/route"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
)
|
||||
|
||||
import (
|
||||
@@ -45,56 +43,58 @@ import (
|
||||
|
||||
// initCli init command line.
|
||||
func initCli() (*app.Cli, error) {
|
||||
koanf, err := bootstrap.NewConf()
|
||||
config, err := bootstrap.NewConf()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger := bootstrap.NewLog(koanf)
|
||||
db, err := bootstrap.NewDB(koanf, logger)
|
||||
locale, err := bootstrap.NewT(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db, err := bootstrap.NewDB(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger := bootstrap.NewLog(config)
|
||||
cacheRepo := data.NewCacheRepo(db)
|
||||
queue := bootstrap.NewQueue()
|
||||
taskRepo := data.NewTaskRepo(db, logger, queue)
|
||||
appRepo := data.NewAppRepo(db, cacheRepo, taskRepo)
|
||||
userRepo := data.NewUserRepo(db)
|
||||
settingRepo := data.NewSettingRepo(db, koanf, taskRepo)
|
||||
databaseServerRepo := data.NewDatabaseServerRepo(db, logger)
|
||||
databaseUserRepo := data.NewDatabaseUserRepo(db, databaseServerRepo)
|
||||
databaseRepo := data.NewDatabaseRepo(db, databaseServerRepo, databaseUserRepo)
|
||||
certRepo := data.NewCertRepo(db)
|
||||
certAccountRepo := data.NewCertAccountRepo(db, userRepo)
|
||||
websiteRepo := data.NewWebsiteRepo(db, cacheRepo, databaseRepo, databaseServerRepo, databaseUserRepo, certRepo, certAccountRepo)
|
||||
backupRepo := data.NewBackupRepo(db, settingRepo, websiteRepo)
|
||||
cliService := service.NewCliService(koanf, db, appRepo, cacheRepo, userRepo, settingRepo, backupRepo, websiteRepo, databaseServerRepo)
|
||||
cli := route.NewCli(cliService)
|
||||
command := bootstrap.NewCli(cli)
|
||||
taskRepo := data.NewTaskRepo(locale, db, logger, queue)
|
||||
appRepo := data.NewAppRepo(locale, config, db, logger, cacheRepo, taskRepo)
|
||||
userRepo := data.NewUserRepo(locale, db, logger)
|
||||
settingRepo := data.NewSettingRepo(locale, db, logger, config, taskRepo)
|
||||
databaseServerRepo := data.NewDatabaseServerRepo(locale, db, logger)
|
||||
databaseUserRepo := data.NewDatabaseUserRepo(locale, db, logger, databaseServerRepo)
|
||||
databaseRepo := data.NewDatabaseRepo(locale, db, logger, databaseServerRepo, databaseUserRepo)
|
||||
certRepo := data.NewCertRepo(locale, db, logger, settingRepo)
|
||||
certAccountRepo := data.NewCertAccountRepo(locale, db, userRepo, logger)
|
||||
websiteRepo := data.NewWebsiteRepo(locale, db, logger, cacheRepo, databaseRepo, databaseServerRepo, databaseUserRepo, certRepo, certAccountRepo, settingRepo)
|
||||
backupRepo := data.NewBackupRepo(locale, config, db, logger, settingRepo, websiteRepo)
|
||||
cliService := service.NewCliService(locale, config, db, appRepo, cacheRepo, userRepo, settingRepo, backupRepo, websiteRepo, databaseServerRepo, certRepo, certAccountRepo)
|
||||
cli := route.NewCli(locale, cliService)
|
||||
command := bootstrap.NewCli(locale, cli)
|
||||
gormigrate := bootstrap.NewMigrate(db)
|
||||
benchmarkApp := benchmark.NewApp()
|
||||
apacheApp := apache.NewApp(locale)
|
||||
codeserverApp := codeserver.NewApp()
|
||||
dockerApp := docker.NewApp()
|
||||
fail2banApp := fail2ban.NewApp(websiteRepo)
|
||||
fail2banApp := fail2ban.NewApp(locale, websiteRepo)
|
||||
frpApp := frp.NewApp()
|
||||
giteaApp := gitea.NewApp()
|
||||
memcachedApp := memcached.NewApp()
|
||||
mysqlApp := mysql.NewApp(settingRepo)
|
||||
nginxApp := nginx.NewApp()
|
||||
php74App := php74.NewApp(taskRepo)
|
||||
php80App := php80.NewApp(taskRepo)
|
||||
php81App := php81.NewApp(taskRepo)
|
||||
php82App := php82.NewApp(taskRepo)
|
||||
php83App := php83.NewApp(taskRepo)
|
||||
php84App := php84.NewApp(taskRepo)
|
||||
phpmyadminApp := phpmyadmin.NewApp()
|
||||
mariadbApp := mariadb.NewApp(locale, settingRepo, databaseServerRepo)
|
||||
memcachedApp := memcached.NewApp(locale)
|
||||
minioApp := minio.NewApp()
|
||||
mysqlApp := mysql.NewApp(locale, settingRepo, databaseServerRepo)
|
||||
nginxApp := nginx.NewApp(locale)
|
||||
openrestyApp := openresty.NewApp(locale)
|
||||
perconaApp := percona.NewApp(locale, settingRepo, databaseServerRepo)
|
||||
phpmyadminApp := phpmyadmin.NewApp(locale)
|
||||
podmanApp := podman.NewApp()
|
||||
postgresqlApp := postgresql.NewApp()
|
||||
pureftpdApp := pureftpd.NewApp()
|
||||
redisApp := redis.NewApp()
|
||||
rsyncApp := rsync.NewApp()
|
||||
s3fsApp := s3fs.NewApp(settingRepo)
|
||||
supervisorApp := supervisor.NewApp()
|
||||
toolboxApp := toolbox.NewApp()
|
||||
loader := bootstrap.NewLoader(benchmarkApp, dockerApp, fail2banApp, frpApp, giteaApp, memcachedApp, mysqlApp, nginxApp, php74App, php80App, php81App, php82App, php83App, php84App, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp, toolboxApp)
|
||||
postgresqlApp := postgresql.NewApp(locale, settingRepo, databaseServerRepo)
|
||||
pureftpdApp := pureftpd.NewApp(locale)
|
||||
redisApp := redis.NewApp(locale)
|
||||
rsyncApp := rsync.NewApp(locale)
|
||||
s3fsApp := s3fs.NewApp(locale)
|
||||
supervisorApp := supervisor.NewApp(locale)
|
||||
loader := bootstrap.NewLoader(apacheApp, codeserverApp, dockerApp, fail2banApp, frpApp, giteaApp, mariadbApp, memcachedApp, minioApp, mysqlApp, nginxApp, openrestyApp, perconaApp, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp)
|
||||
appCli := app.NewCli(command, gormigrate, loader)
|
||||
return appCli, nil
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
Copyright (C) 2022 - now Rat Technology Co., Ltd.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime/debug"
|
||||
_ "time/tzdata"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if os.Geteuid() != 0 {
|
||||
panic("panel must run as root")
|
||||
}
|
||||
|
||||
debug.SetGCPercent(10)
|
||||
debug.SetMemoryLimit(128 << 20)
|
||||
|
||||
web, err := initWeb()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err = web.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
//go:build wireinject
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/apps"
|
||||
"github.com/tnb-labs/panel/internal/bootstrap"
|
||||
"github.com/tnb-labs/panel/internal/data"
|
||||
"github.com/tnb-labs/panel/internal/http/middleware"
|
||||
"github.com/tnb-labs/panel/internal/job"
|
||||
"github.com/tnb-labs/panel/internal/route"
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
)
|
||||
|
||||
// initWeb init application.
|
||||
func initWeb() (*app.Web, error) {
|
||||
panic(wire.Build(bootstrap.ProviderSet, middleware.ProviderSet, route.ProviderSet, service.ProviderSet, data.ProviderSet, apps.ProviderSet, job.ProviderSet, app.NewWeb))
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
// Code generated by Wire. DO NOT EDIT.
|
||||
|
||||
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
|
||||
//go:build !wireinject
|
||||
// +build !wireinject
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/apps/benchmark"
|
||||
"github.com/tnb-labs/panel/internal/apps/docker"
|
||||
"github.com/tnb-labs/panel/internal/apps/fail2ban"
|
||||
"github.com/tnb-labs/panel/internal/apps/frp"
|
||||
"github.com/tnb-labs/panel/internal/apps/gitea"
|
||||
"github.com/tnb-labs/panel/internal/apps/memcached"
|
||||
"github.com/tnb-labs/panel/internal/apps/mysql"
|
||||
"github.com/tnb-labs/panel/internal/apps/nginx"
|
||||
"github.com/tnb-labs/panel/internal/apps/php74"
|
||||
"github.com/tnb-labs/panel/internal/apps/php80"
|
||||
"github.com/tnb-labs/panel/internal/apps/php81"
|
||||
"github.com/tnb-labs/panel/internal/apps/php82"
|
||||
"github.com/tnb-labs/panel/internal/apps/php83"
|
||||
"github.com/tnb-labs/panel/internal/apps/php84"
|
||||
"github.com/tnb-labs/panel/internal/apps/phpmyadmin"
|
||||
"github.com/tnb-labs/panel/internal/apps/podman"
|
||||
"github.com/tnb-labs/panel/internal/apps/postgresql"
|
||||
"github.com/tnb-labs/panel/internal/apps/pureftpd"
|
||||
"github.com/tnb-labs/panel/internal/apps/redis"
|
||||
"github.com/tnb-labs/panel/internal/apps/rsync"
|
||||
"github.com/tnb-labs/panel/internal/apps/s3fs"
|
||||
"github.com/tnb-labs/panel/internal/apps/supervisor"
|
||||
"github.com/tnb-labs/panel/internal/apps/toolbox"
|
||||
"github.com/tnb-labs/panel/internal/bootstrap"
|
||||
"github.com/tnb-labs/panel/internal/data"
|
||||
"github.com/tnb-labs/panel/internal/http/middleware"
|
||||
"github.com/tnb-labs/panel/internal/job"
|
||||
"github.com/tnb-labs/panel/internal/route"
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
)
|
||||
|
||||
import (
|
||||
_ "time/tzdata"
|
||||
)
|
||||
|
||||
// Injectors from wire.go:
|
||||
|
||||
// initWeb init application.
|
||||
func initWeb() (*app.Web, error) {
|
||||
koanf, err := bootstrap.NewConf()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger := bootstrap.NewLog(koanf)
|
||||
db, err := bootstrap.NewDB(koanf, logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
manager, err := bootstrap.NewSession(koanf, db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cacheRepo := data.NewCacheRepo(db)
|
||||
queue := bootstrap.NewQueue()
|
||||
taskRepo := data.NewTaskRepo(db, logger, queue)
|
||||
appRepo := data.NewAppRepo(db, cacheRepo, taskRepo)
|
||||
middlewares := middleware.NewMiddlewares(koanf, logger, manager, appRepo)
|
||||
userRepo := data.NewUserRepo(db)
|
||||
userService := service.NewUserService(koanf, manager, userRepo)
|
||||
databaseServerRepo := data.NewDatabaseServerRepo(db, logger)
|
||||
databaseUserRepo := data.NewDatabaseUserRepo(db, databaseServerRepo)
|
||||
databaseRepo := data.NewDatabaseRepo(db, databaseServerRepo, databaseUserRepo)
|
||||
certRepo := data.NewCertRepo(db)
|
||||
certAccountRepo := data.NewCertAccountRepo(db, userRepo)
|
||||
websiteRepo := data.NewWebsiteRepo(db, cacheRepo, databaseRepo, databaseServerRepo, databaseUserRepo, certRepo, certAccountRepo)
|
||||
settingRepo := data.NewSettingRepo(db, koanf, taskRepo)
|
||||
cronRepo := data.NewCronRepo(db)
|
||||
backupRepo := data.NewBackupRepo(db, settingRepo, websiteRepo)
|
||||
dashboardService := service.NewDashboardService(koanf, taskRepo, websiteRepo, appRepo, settingRepo, cronRepo, backupRepo)
|
||||
taskService := service.NewTaskService(taskRepo)
|
||||
websiteService := service.NewWebsiteService(websiteRepo, settingRepo)
|
||||
databaseService := service.NewDatabaseService(databaseRepo)
|
||||
databaseServerService := service.NewDatabaseServerService(databaseServerRepo)
|
||||
databaseUserService := service.NewDatabaseUserService(databaseUserRepo)
|
||||
backupService := service.NewBackupService(backupRepo)
|
||||
certService := service.NewCertService(certRepo)
|
||||
certDNSRepo := data.NewCertDNSRepo(db)
|
||||
certDNSService := service.NewCertDNSService(certDNSRepo)
|
||||
certAccountService := service.NewCertAccountService(certAccountRepo)
|
||||
appService := service.NewAppService(appRepo, cacheRepo, settingRepo)
|
||||
cronService := service.NewCronService(cronRepo)
|
||||
processService := service.NewProcessService()
|
||||
safeRepo := data.NewSafeRepo()
|
||||
safeService := service.NewSafeService(safeRepo)
|
||||
firewallService := service.NewFirewallService()
|
||||
sshRepo := data.NewSSHRepo(db)
|
||||
sshService := service.NewSSHService(sshRepo)
|
||||
containerRepo := data.NewContainerRepo()
|
||||
containerService := service.NewContainerService(containerRepo)
|
||||
containerNetworkRepo := data.NewContainerNetworkRepo()
|
||||
containerNetworkService := service.NewContainerNetworkService(containerNetworkRepo)
|
||||
containerImageRepo := data.NewContainerImageRepo()
|
||||
containerImageService := service.NewContainerImageService(containerImageRepo)
|
||||
containerVolumeRepo := data.NewContainerVolumeRepo()
|
||||
containerVolumeService := service.NewContainerVolumeService(containerVolumeRepo)
|
||||
fileService := service.NewFileService(taskRepo)
|
||||
monitorRepo := data.NewMonitorRepo(db, settingRepo)
|
||||
monitorService := service.NewMonitorService(settingRepo, monitorRepo)
|
||||
settingService := service.NewSettingService(settingRepo)
|
||||
systemctlService := service.NewSystemctlService()
|
||||
benchmarkApp := benchmark.NewApp()
|
||||
dockerApp := docker.NewApp()
|
||||
fail2banApp := fail2ban.NewApp(websiteRepo)
|
||||
frpApp := frp.NewApp()
|
||||
giteaApp := gitea.NewApp()
|
||||
memcachedApp := memcached.NewApp()
|
||||
mysqlApp := mysql.NewApp(settingRepo)
|
||||
nginxApp := nginx.NewApp()
|
||||
php74App := php74.NewApp(taskRepo)
|
||||
php80App := php80.NewApp(taskRepo)
|
||||
php81App := php81.NewApp(taskRepo)
|
||||
php82App := php82.NewApp(taskRepo)
|
||||
php83App := php83.NewApp(taskRepo)
|
||||
php84App := php84.NewApp(taskRepo)
|
||||
phpmyadminApp := phpmyadmin.NewApp()
|
||||
podmanApp := podman.NewApp()
|
||||
postgresqlApp := postgresql.NewApp()
|
||||
pureftpdApp := pureftpd.NewApp()
|
||||
redisApp := redis.NewApp()
|
||||
rsyncApp := rsync.NewApp()
|
||||
s3fsApp := s3fs.NewApp(settingRepo)
|
||||
supervisorApp := supervisor.NewApp()
|
||||
toolboxApp := toolbox.NewApp()
|
||||
loader := bootstrap.NewLoader(benchmarkApp, dockerApp, fail2banApp, frpApp, giteaApp, memcachedApp, mysqlApp, nginxApp, php74App, php80App, php81App, php82App, php83App, php84App, phpmyadminApp, podmanApp, postgresqlApp, pureftpdApp, redisApp, rsyncApp, s3fsApp, supervisorApp, toolboxApp)
|
||||
http := route.NewHttp(userService, dashboardService, taskService, websiteService, databaseService, databaseServerService, databaseUserService, backupService, certService, certDNSService, certAccountService, appService, cronService, processService, safeService, firewallService, sshService, containerService, containerNetworkService, containerImageService, containerVolumeService, fileService, monitorService, settingService, systemctlService, loader)
|
||||
wsService := service.NewWsService(koanf, sshRepo)
|
||||
ws := route.NewWs(wsService)
|
||||
mux, err := bootstrap.NewRouter(middlewares, http, ws)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
server, err := bootstrap.NewHttp(koanf, mux)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gormigrate := bootstrap.NewMigrate(db)
|
||||
jobs := job.NewJobs(db, logger, settingRepo, certRepo, backupRepo, cacheRepo, taskRepo)
|
||||
cron, err := bootstrap.NewCron(koanf, logger, jobs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
validation := bootstrap.NewValidator(db)
|
||||
web := app.NewWeb(koanf, mux, server, gormigrate, cron, queue, validation)
|
||||
return web, nil
|
||||
}
|
||||
+12
-1
@@ -3,11 +3,22 @@ app:
|
||||
key: a-long-string-with-32-characters
|
||||
locale: zh_CN
|
||||
timezone: Asia/Shanghai
|
||||
root: /www
|
||||
root: /opt/ace
|
||||
api_endpoint: api.acepanel.net
|
||||
download_endpoint: dl.acepanel.net
|
||||
http:
|
||||
debug: false
|
||||
port: 8888
|
||||
entrance: /
|
||||
entrance_error: "418"
|
||||
tls: true
|
||||
acme: true
|
||||
login_captcha: true
|
||||
ip_header: ""
|
||||
bind_domain: [ ]
|
||||
bind_ip: [ ]
|
||||
bind_ua: [ ]
|
||||
database:
|
||||
debug: false
|
||||
session:
|
||||
lifetime: 120
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
project_id_env: CROWDIN_PROJECT_ID
|
||||
api_token_env: CROWDIN_PERSONAL_TOKEN
|
||||
files:
|
||||
- source: /pkg/embed/locales/*.pot
|
||||
translation: /pkg/embed/locales/%locale_with_underscore%/%file_name%.po
|
||||
- source: /web/src/locales/*.pot
|
||||
translation: /web/src/locales/%locale_with_underscore%.po
|
||||
@@ -1,92 +1,115 @@
|
||||
module github.com/tnb-labs/panel
|
||||
module github.com/acepanel/panel
|
||||
|
||||
go 1.24
|
||||
go 1.25.6
|
||||
|
||||
require (
|
||||
github.com/bddjr/hlfhr v1.3.8
|
||||
github.com/beevik/ntp v1.4.3
|
||||
github.com/DeRuina/timberjack v1.3.9
|
||||
github.com/bddjr/hlfhr v1.4.0
|
||||
github.com/beevik/ntp v1.5.0
|
||||
github.com/coder/websocket v1.8.14
|
||||
github.com/containerd/errdefs v1.0.0
|
||||
github.com/coreos/go-systemd/v22 v22.6.0
|
||||
github.com/creack/pty v1.1.24
|
||||
github.com/expr-lang/expr v1.16.9
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-chi/chi/v5 v5.2.1
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.3
|
||||
github.com/go-rat/chix v1.1.4
|
||||
github.com/go-rat/gormstore v1.0.6
|
||||
github.com/go-rat/sessions v1.0.14
|
||||
github.com/go-rat/utils v1.0.9
|
||||
github.com/go-resty/resty/v2 v2.16.5
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/golang-cz/httplog v0.0.0-20241002114323-98e09d6f537a
|
||||
github.com/gomodule/redigo v1.9.2
|
||||
github.com/google/wire v0.6.0
|
||||
github.com/gookit/validate v1.5.4
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/hashicorp/go-version v1.7.0
|
||||
github.com/knadh/koanf/parsers/yaml v0.1.0
|
||||
github.com/knadh/koanf/providers/file v1.1.2
|
||||
github.com/knadh/koanf/v2 v2.1.2
|
||||
github.com/dchest/captcha v1.1.0
|
||||
github.com/expr-lang/expr v1.17.7
|
||||
github.com/go-chi/chi/v5 v5.2.4
|
||||
github.com/go-chi/httplog/v3 v3.3.0
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.5
|
||||
github.com/go-resty/resty/v2 v2.17.1
|
||||
github.com/go-sql-driver/mysql v1.9.3
|
||||
github.com/gomodule/redigo v1.9.3
|
||||
github.com/google/wire v0.7.0
|
||||
github.com/gookit/color v1.6.0
|
||||
github.com/gookit/validate v1.5.6
|
||||
github.com/hashicorp/go-version v1.8.0
|
||||
github.com/klauspost/compress v1.18.3
|
||||
github.com/leonelquinteros/gotext v1.7.2
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/libdns/alidns v1.0.3
|
||||
github.com/libdns/cloudflare v0.1.1
|
||||
github.com/libdns/huaweicloud v0.3.1
|
||||
github.com/libdns/libdns v0.2.2
|
||||
github.com/libdns/tencentcloud v1.2.0
|
||||
github.com/mholt/acmez/v3 v3.0.1
|
||||
github.com/libdns/alidns v1.0.6-beta.3
|
||||
github.com/libdns/cloudflare v0.2.2
|
||||
github.com/libdns/cloudns v1.1.0
|
||||
github.com/libdns/gcore v0.0.0-20250427050847-9964da923833
|
||||
github.com/libdns/huaweicloud v1.0.0
|
||||
github.com/libdns/libdns v1.1.1
|
||||
github.com/libdns/namesilo v1.0.0
|
||||
github.com/libdns/porkbun v1.1.0
|
||||
github.com/libdns/tencentcloud v1.4.3
|
||||
github.com/libdns/westcn v1.0.2
|
||||
github.com/libtnb/chix v1.3.2
|
||||
github.com/libtnb/gormstore v1.1.1
|
||||
github.com/libtnb/sessions v1.2.2
|
||||
github.com/libtnb/utils v1.2.1
|
||||
github.com/mholt/acmez/v3 v3.1.4
|
||||
github.com/moby/moby/api v1.53.0-rc.2
|
||||
github.com/moby/moby/client v0.2.1
|
||||
github.com/ncruces/go-sqlite3 v0.30.5
|
||||
github.com/ncruces/go-sqlite3/gormlite v0.30.2
|
||||
github.com/orandin/slog-gorm v1.4.0
|
||||
github.com/pkg/sftp v1.13.10
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/rhnvrm/simples3 v0.11.1
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/samber/lo v1.49.1
|
||||
github.com/sethvargo/go-limiter v1.0.0
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/spf13/cast v1.7.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250120210832-12a9c7ae0c8a
|
||||
github.com/urfave/cli/v3 v3.0.0-beta1
|
||||
golang.org/x/crypto v0.33.0
|
||||
golang.org/x/net v0.35.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/gorm v1.25.12
|
||||
github.com/samber/lo v1.52.0
|
||||
github.com/sethvargo/go-limiter v1.1.0
|
||||
github.com/shirou/gopsutil/v4 v4.25.12
|
||||
github.com/spf13/cast v1.10.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/studio-b12/gowebdav v0.12.0
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250620092546-c3e307e36701
|
||||
github.com/urfave/cli/v3 v3.6.2
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.4
|
||||
golang.org/x/crypto v0.47.0
|
||||
golang.org/x/net v0.49.0
|
||||
gorm.io/gorm v1.31.1
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||
github.com/G-Core/gcore-dns-sdk-go v0.3.3 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/boombuler/barcode v1.1.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/ebitengine/purego v0.9.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-rat/securecookie v1.1.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/gofiber/schema v1.2.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gookit/filter v1.2.2 // indirect
|
||||
github.com/gookit/goutil v0.6.18 // indirect
|
||||
github.com/gofiber/schema v1.6.0 // indirect
|
||||
github.com/gookit/filter v1.2.3 // indirect
|
||||
github.com/gookit/goutil v0.7.3 // indirect
|
||||
github.com/imega/luaformatter v0.0.0-20211025140405-86b0a68d6bef // indirect
|
||||
github.com/jaevor/go-nanoid v1.4.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/knadh/koanf/maps v0.1.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1096 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.9.0 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/libtnb/securecookie v1.2.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
github.com/tetratelabs/wazero v1.11.0 // indirect
|
||||
github.com/timtadh/data-structures v0.6.2 // indirect
|
||||
github.com/timtadh/lexmachine v0.2.3 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
modernc.org/libc v1.61.13 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.8.2 // indirect
|
||||
modernc.org/sqlite v1.34.5 // indirect
|
||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/libdns/tencentcloud => github.com/devhaozi/tencentcloud v0.0.0-20241129095955-7d4e898ce504
|
||||
github.com/mholt/acmez/v3 => github.com/tnb-labs/acmez/v3 v3.0.0-20250101070450-67fb7b3d86d1
|
||||
github.com/mholt/acmez/v3 => github.com/libtnb/acmez/v3 v3.0.0-20260103184942-a835890fc93e
|
||||
github.com/moby/moby/client => github.com/libtnb/moby/client v0.0.0-20260119133723-7d7dd88cf643
|
||||
github.com/rhnvrm/simples3 => github.com/devhaozi/simples3 v0.0.0-20260124160558-447c94ecedff
|
||||
github.com/stretchr/testify => github.com/libtnb/testify v0.0.0-20260103194301-c7a63ea79696
|
||||
)
|
||||
|
||||
tool github.com/google/wire
|
||||
|
||||
@@ -1,264 +1,511 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/bddjr/hlfhr v1.3.8 h1:QQ6KYgtnBbvYvCWuu/tOnBZamKAPtJzesj2qbjgyn7o=
|
||||
github.com/bddjr/hlfhr v1.3.8/go.mod h1:oyIv4Q9JpCgZFdtH3KyTNWp7YYRWl4zl8k4ozrMAB4g=
|
||||
github.com/beevik/ntp v1.4.3 h1:PlbTvE5NNy4QHmA4Mg57n7mcFTmr1W1j3gcK7L1lqho=
|
||||
github.com/beevik/ntp v1.4.3/go.mod h1:Unr8Zg+2dRn7d8bHFuehIMSvvUYssHMxW3Q5Nx4RW5Q=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DeRuina/timberjack v1.3.9 h1:6UXZ1I7ExPGTX/1UNYawR58LlOJUHKBPiYC7WQ91eBo=
|
||||
github.com/DeRuina/timberjack v1.3.9/go.mod h1:RLoeQrwrCGIEF8gO5nV5b/gMD0QIy7bzQhBUgpp1EqE=
|
||||
github.com/G-Core/gcore-dns-sdk-go v0.3.3 h1:McILJSbJ5nOcT0MI0aBYhEuufCF329YbqKwFIN0RjCI=
|
||||
github.com/G-Core/gcore-dns-sdk-go v0.3.3/go.mod h1:35t795gOfzfVanhzkFyUXEzaBuMXwETmJldPpP28MN4=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/bddjr/hlfhr v1.4.0 h1:EVryUs0mLzQ455bAIKhASqwxFV9IYrGOFjuz0HE6oYE=
|
||||
github.com/bddjr/hlfhr v1.4.0/go.mod h1:oyIv4Q9JpCgZFdtH3KyTNWp7YYRWl4zl8k4ozrMAB4g=
|
||||
github.com/beevik/ntp v1.5.0 h1:y+uj/JjNwlY2JahivxYvtmv4ehfi3h74fAuABB9ZSM4=
|
||||
github.com/beevik/ntp v1.5.0/go.mod h1:mJEhBrwT76w9D+IfOEGvuzyuudiW9E52U2BaTrMOYow=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
|
||||
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo=
|
||||
github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/devhaozi/tencentcloud v0.0.0-20241129095955-7d4e898ce504 h1:Wk7RQyamGDfAz+5hZIT/GIm6ay5cORztgYVXOJTobKY=
|
||||
github.com/devhaozi/tencentcloud v0.0.0-20241129095955-7d4e898ce504/go.mod h1:o0+WCxQ7LGLtyjnjYU4HbGW9uVjN44SdUDhxdUYLGPw=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI=
|
||||
github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
|
||||
github.com/dchest/captcha v1.1.0 h1:2kt47EoYUUkaISobUdTbqwx55xvKOJxyScVfw25xzhQ=
|
||||
github.com/dchest/captcha v1.1.0/go.mod h1:7zoElIawLp7GUMLcj54K9kbw+jEyvz2K0FDdRRYhvWo=
|
||||
github.com/devhaozi/simples3 v0.0.0-20260124160558-447c94ecedff h1:R8fAbRKWR+NVWIkjdK45CXS2cZzXQn8QuoM0Dij2Jso=
|
||||
github.com/devhaozi/simples3 v0.0.0-20260124160558-447c94ecedff/go.mod h1:c2xW30bukipkBlWNnXG1wDjq3gykQ6ww2AB/9NHMLMY=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/expr-lang/expr v1.17.7 h1:Q0xY/e/2aCIp8g9s/LGvMDCC5PxYlvHgDZRQ4y16JX8=
|
||||
github.com/expr-lang/expr v1.17.7/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
||||
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.3 h1:ei3Vq/rpPI/jCJY9mRHJAKg5vU+EhZyWhBAkaAomQuw=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.3/go.mod h1:VJ9FIOBAur+NmQ8c4tDVwOuiJcgupTG105FexPFrXzA=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
||||
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-chi/chi/v5 v5.2.4 h1:WtFKPHwlywe8Srng8j2BhOD9312j9cGUxG1SP4V2cR4=
|
||||
github.com/go-chi/chi/v5 v5.2.4/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||
github.com/go-chi/httplog/v3 v3.3.0 h1:Gr6Y7nSzbpyCyRwKPOVKjDH3BH6TH5uvRNDsTZWDpvU=
|
||||
github.com/go-chi/httplog/v3 v3.3.0/go.mod h1:N/J1l5l1fozUrqIVuT8Z/HzNeSy8TF2EFyokPLe6y2w=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.5 h1:1OyorA5LtdQw12cyJDEHuTrEV3GiXiIhS4/QTTa/SM8=
|
||||
github.com/go-gormigrate/gormigrate/v2 v2.1.5/go.mod h1:mj9ekk/7CPF3VjopaFvWKN2v7fN3D9d3eEOAXRhi/+M=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-rat/chix v1.1.4 h1:YVDwF4vrlkm5hmlDGWKoiWPgP98pJooBvhsto8UWq+o=
|
||||
github.com/go-rat/chix v1.1.4/go.mod h1:eKUTG7GXqMS278PH0QZi8yMWZddvMfxqWu7mverM6kQ=
|
||||
github.com/go-rat/gormstore v1.0.6 h1:YxmaY6n3JoTk6o9RXoZ1+cQ6HQL+ayf/6d8D76ydCAc=
|
||||
github.com/go-rat/gormstore v1.0.6/go.mod h1:xG/n+Du8baWf7ptiafiHLWBZWVXrYIPC42CsoEbfr4M=
|
||||
github.com/go-rat/securecookie v1.1.0 h1:bvwE9n6X3a4eazOIevEvZghkvZ3OpY4BX1cnH/1/x0s=
|
||||
github.com/go-rat/securecookie v1.1.0/go.mod h1:Z870TcL7ktAjRuhPeQXYuQcA6/DdzKwwqSwDQuPmufI=
|
||||
github.com/go-rat/sessions v1.0.14 h1:YSzlGkKzsZEUIJj2s8ZEH4DcOqScm6htIVEOvH2lvd0=
|
||||
github.com/go-rat/sessions v1.0.14/go.mod h1:jqpYqyMKZQl239GQ5W5LFRZUUQMGoNX9w7+zTQA7Soc=
|
||||
github.com/go-rat/utils v1.0.9 h1:16zq6CpeULJgoW+TaJZ+gS4EPl0tQYAcuAdx3/GNCNs=
|
||||
github.com/go-rat/utils v1.0.9/go.mod h1:LhzbrdsTHIo6Rjlz27Nh8edkugmoxbZjN87iV+ch+5M=
|
||||
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
||||
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/gofiber/schema v1.2.0 h1:j+ZRrNnUa/0ZuWrn/6kAtAufEr4jCJ+JuTURAMxNSZg=
|
||||
github.com/gofiber/schema v1.2.0/go.mod h1:YYwj01w3hVfaNjhtJzaqetymL56VW642YS3qZPhuE6c=
|
||||
github.com/golang-cz/httplog v0.0.0-20241002114323-98e09d6f537a h1:BAyyIK6rc6Tq9vT2KVlPJXD8EVv/LpaFwn4fgfLwtHg=
|
||||
github.com/golang-cz/httplog v0.0.0-20241002114323-98e09d6f537a/go.mod h1:bgk4Ij/0OQ89UeoFFAQrSNhbbr4rKJ0fwWfo7wc+TCc=
|
||||
github.com/gomodule/redigo v1.9.2 h1:HrutZBLhSIU8abiSfW8pj8mPhOyMYjZT/wcA4/L9L9s=
|
||||
github.com/gomodule/redigo v1.9.2/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
|
||||
github.com/go-resty/resty/v2 v2.17.1 h1:x3aMpHK1YM9e4va/TMDRlusDDoZiQ+ViDu/WpA6xTM4=
|
||||
github.com/go-resty/resty/v2 v2.17.1/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofiber/schema v1.6.0 h1:rAgVDFwhndtC+hgV7Vu5ItQCn7eC2mBA4Eu1/ZTiEYY=
|
||||
github.com/gofiber/schema v1.6.0/go.mod h1:WNZWpQx8LlPSK7ZaX0OqOh+nQo/eW2OevsXs1VZfs/s=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/gomodule/redigo v1.9.3 h1:dNPSXeXv6HCq2jdyWfjgmhBdqnR6PRO3m/G05nvpPC8=
|
||||
github.com/gomodule/redigo v1.9.3/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI=
|
||||
github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA=
|
||||
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
||||
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
|
||||
github.com/gookit/filter v1.2.2 h1:LSBQLk4M4fpfhaOG0hJ/GJ+qu+2+lLddgxlGfzcd8VQ=
|
||||
github.com/gookit/filter v1.2.2/go.mod h1:y4xpiqM/fV9g9yT/Vg5OWUq4q9siPj0UlH9O57xYuGs=
|
||||
github.com/gookit/goutil v0.6.18 h1:MUVj0G16flubWT8zYVicIuisUiHdgirPAkmnfD2kKgw=
|
||||
github.com/gookit/goutil v0.6.18/go.mod h1:AY/5sAwKe7Xck+mEbuxj0n/bc3qwrGNe3Oeulln7zBA=
|
||||
github.com/gookit/validate v1.5.4 h1:nwBo6vULnVUeNFCOde6RKFRbOCKJXVMnWR0ghedacLg=
|
||||
github.com/gookit/validate v1.5.4/go.mod h1:p9sRPfpvYB4vXICBpEPzv8FoAky+XhUOhWQghgmmat4=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
||||
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4=
|
||||
github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0=
|
||||
github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E=
|
||||
github.com/gookit/color v1.6.0 h1:JjJXBTk1ETNyqyilJhkTXJYYigHG24TM9Xa2M1xAhRA=
|
||||
github.com/gookit/color v1.6.0/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs=
|
||||
github.com/gookit/filter v1.2.3 h1:Zo7cBOtsVzAoa/jtf+Ury6zlsbJXqInFdUpbbnB2vMM=
|
||||
github.com/gookit/filter v1.2.3/go.mod h1:nFLJcOV8dRgS1iiX23gUQgmHUhpuS40qCvAGgIvA1pM=
|
||||
github.com/gookit/goutil v0.7.3 h1:nXDd/AB17nEjqVCNDGioDhVL/gVqdlqRMfFergKDjHE=
|
||||
github.com/gookit/goutil v0.7.3/go.mod h1:vJS9HXctYTCLtCsZot5L5xF+O1oR17cDYO9R0HxBmnU=
|
||||
github.com/gookit/validate v1.5.6 h1:D6vbSZzreuKYpeeXm5FDDEJy3K5E4lcWsQE4saSMZbU=
|
||||
github.com/gookit/validate v1.5.6/go.mod h1:WYEHndRNepIIkM+6CtgEX9MQ9ToIQRhXxmz5oLHF/fc=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
|
||||
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/imega/luaformatter v0.0.0-20211025140405-86b0a68d6bef h1:RC993DdTIHNItsyLj79fgZNLzrf9tBN0GR6W5ZPms6s=
|
||||
github.com/imega/luaformatter v0.0.0-20211025140405-86b0a68d6bef/go.mod h1:i2XCfvmO94HrEOQWllihhtPrkvNfuB2R2p/o6+OVnRU=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jaevor/go-nanoid v1.4.0 h1:mPz0oi3CrQyEtRxeRq927HHtZCJAAtZ7zdy7vOkrvWs=
|
||||
github.com/jaevor/go-nanoid v1.4.0/go.mod h1:GIpPtsvl3eSBsjjIEFQdzzgpi50+Bo1Luk+aYlbJzlc=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
|
||||
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
||||
github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w=
|
||||
github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY=
|
||||
github.com/knadh/koanf/providers/file v1.1.2 h1:aCC36YGOgV5lTtAFz2qkgtWdeQsgfxUkxDOe+2nQY3w=
|
||||
github.com/knadh/koanf/providers/file v1.1.2/go.mod h1:/faSBcv2mxPVjFrXck95qeoyoZ5myJ6uxN8OOVNJJCI=
|
||||
github.com/knadh/koanf/v2 v2.1.2 h1:I2rtLRqXRy1p01m/utEtpZSSA6dcJbgGVuE27kW2PzQ=
|
||||
github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
|
||||
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leonelquinteros/gotext v1.7.2 h1:bDPndU8nt+/kRo1m4l/1OXiiy2v7Z7dfPQ9+YP7G1Mc=
|
||||
github.com/leonelquinteros/gotext v1.7.2/go.mod h1:9/haCkm5P7Jay1sxKDGJ5WIg4zkz8oZKw4ekNpALob8=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ=
|
||||
github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE=
|
||||
github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054=
|
||||
github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU=
|
||||
github.com/libdns/huaweicloud v0.3.1 h1:wro0zpG86JKL3QVEaV/xfHW/59rmqwMYhcbl16w9EYg=
|
||||
github.com/libdns/huaweicloud v0.3.1/go.mod h1:6s5ZIwLUr2qKsQz2SRNo7uh8X5uJohxoBR95rtSpNcI=
|
||||
github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
|
||||
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/libdns/alidns v1.0.6-beta.3 h1:KAmb7FQ1tRzKsaAUGa7ZpGKAMRANwg7+1c7tUbSELq8=
|
||||
github.com/libdns/alidns v1.0.6-beta.3/go.mod h1:RECwyQ88e9VqQVtSrvX76o1ux3gQUKGzMgxICi+u7Ec=
|
||||
github.com/libdns/cloudflare v0.2.2 h1:XWHv+C1dDcApqazlh08Q6pjytYLgR2a+Y3xrXFu0vsI=
|
||||
github.com/libdns/cloudflare v0.2.2/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60=
|
||||
github.com/libdns/cloudns v1.1.0 h1:W+1MadtxKySn3b5RITFTsXgTIvr5VoO5x97cewjlDcs=
|
||||
github.com/libdns/cloudns v1.1.0/go.mod h1:/22V6tYYDALDpM4pw/RGGJ+X2F1Luibty9kKpKvkqBM=
|
||||
github.com/libdns/gcore v0.0.0-20250427050847-9964da923833 h1:/gsawtsq03cI7qgK65v16tYHIh40omL9YEzHWqnc63I=
|
||||
github.com/libdns/gcore v0.0.0-20250427050847-9964da923833/go.mod h1:jZJEV7pCTOJFlaUhHty+YwR05dzoSmInXK/vT3wOeVg=
|
||||
github.com/libdns/huaweicloud v1.0.0 h1:BQUIkOAjF++ouiANRIE3jdMlCfeiAAr6tGqb+8hM1jo=
|
||||
github.com/libdns/huaweicloud v1.0.0/go.mod h1:W+XywkW+C93fM50Ayklf6KuoCHZTqbrmFUEtpQnVvcU=
|
||||
github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
|
||||
github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/libdns/namesilo v1.0.0 h1:Shwbj9YnSp4NR617sBOSfDkFozEnWInWCxNwrDvPkCg=
|
||||
github.com/libdns/namesilo v1.0.0/go.mod h1:qdojVsogA6eZDjDPdpIUIfv4ymiX+FuqcVV1eNmHDss=
|
||||
github.com/libdns/porkbun v1.1.0 h1:X763NqXjW26VEl7GvBtF/3CGeuGt9JqoQ35mwIlx40E=
|
||||
github.com/libdns/porkbun v1.1.0/go.mod h1:JL6NfXkkSlLr24AI5Fv0t3/Oa6PXOSOerVsOmr8+URs=
|
||||
github.com/libdns/tencentcloud v1.4.3 h1:xJHYLL1TdPeOtUr6Bu6dHTd1TU6/VFm7BFc2EAzAlvc=
|
||||
github.com/libdns/tencentcloud v1.4.3/go.mod h1:Be9gY3tDa12DuAPU79RV9NZIcjY6qg5s7zKPsP26yAM=
|
||||
github.com/libdns/westcn v1.0.2 h1:PA2M3tME5/0T3klPMzSHvGk1EWnGNNkJiKxaJjFalnM=
|
||||
github.com/libdns/westcn v1.0.2/go.mod h1:iKpk8jjOU+793Yp8nHoihPTCR6N1KWtm4/r8BB9mVnk=
|
||||
github.com/libtnb/acmez/v3 v3.0.0-20260103184942-a835890fc93e h1:HPHmtFcR5VpTJTpDqG4+l16uJfSlpB/X0r3qq22znrk=
|
||||
github.com/libtnb/acmez/v3 v3.0.0-20260103184942-a835890fc93e/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/libtnb/chix v1.3.2 h1:LkA/+OaHeMyTiQoWEcCQKAMSKDZ0LxBXSENQO5d+OKk=
|
||||
github.com/libtnb/chix v1.3.2/go.mod h1:ItssStBa/ov7v4qujyYzORqIf7Z6DO7Q4carOM7pyAQ=
|
||||
github.com/libtnb/gormstore v1.1.1 h1:FG/3P4PuWM6/vB4weVJ31meiSaoeXns1NQlP66quKeg=
|
||||
github.com/libtnb/gormstore v1.1.1/go.mod h1:8A5QzeZxi1MpSmjUVsHTDAL6KnU84feIXMutFLPawwA=
|
||||
github.com/libtnb/moby/client v0.0.0-20260119133723-7d7dd88cf643 h1:B7uVTSsIh/9b+gpVOHWcg+8xmYf1g3E9v4OQYcPCkt8=
|
||||
github.com/libtnb/moby/client v0.0.0-20260119133723-7d7dd88cf643/go.mod h1:y8zxhZOdysHyNZgn9osm6SBDNmissvoai5HLhAdSIFo=
|
||||
github.com/libtnb/securecookie v1.2.0 h1:2uc0PBDm0foeSTrcZ9QTX1IEjf6kFEwfgEYSIXQSKrA=
|
||||
github.com/libtnb/securecookie v1.2.0/go.mod h1:ja+wNGnQzYqcqXQnJWu6icsaWi5JEBwNEMJ2ReTVDxA=
|
||||
github.com/libtnb/sessions v1.2.2 h1:VTTzzeBDJEkJbaPaIU9C4bRj2oAqD0rgQ7UHFkkaNT4=
|
||||
github.com/libtnb/sessions v1.2.2/go.mod h1:qw+FWtBtrPDYCf6MfX0Lk5EhTArpvT72z5Ei4RUMTRg=
|
||||
github.com/libtnb/testify v0.0.0-20260103194301-c7a63ea79696 h1:GN0Y3DG27mMruX536k0jtCSLegjJb/gypzj5gZL6tRI=
|
||||
github.com/libtnb/testify v0.0.0-20260103194301-c7a63ea79696/go.mod h1:HeQeTfKU6tj2Lx1z79UacwYeDioo6M4ZD7BDDI6+rrg=
|
||||
github.com/libtnb/utils v1.2.1 h1:LJmReRREnpqfHyy9PZtNgBh3ZaIGct81b8ZaAsolMkM=
|
||||
github.com/libtnb/utils v1.2.1/go.mod h1:o6LEDeC42PXI21uLWdWJWTVYvR9BtAZfzzTGJVQoQiU=
|
||||
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
|
||||
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/moby/api v1.53.0-rc.2 h1:Ajc2MnWJRC2AAqhIGNaqvwaA2mMYKyuso5PjxPP2KM4=
|
||||
github.com/moby/moby/api v1.53.0-rc.2/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/ncruces/go-sqlite3 v0.30.5 h1:6usmTQ6khriL8oWilkAZSJM/AIpAlVL2zFrlcpDldCE=
|
||||
github.com/ncruces/go-sqlite3 v0.30.5/go.mod h1:0I0JFflTKzfs3Ogfv8erP7CCoV/Z8uxigVDNOR0AQ5E=
|
||||
github.com/ncruces/go-sqlite3/gormlite v0.30.2 h1:FZ8mic14xTatssTkHCrelh9nPeFdXuzgMoNGkfuFbBU=
|
||||
github.com/ncruces/go-sqlite3/gormlite v0.30.2/go.mod h1:W9WLBbqrrOIh2dqFZkeC/xKALG2LDIHY91jowahOdtI=
|
||||
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/orandin/slog-gorm v1.4.0 h1:FgA8hJufF9/jeNSYoEXmHPPBwET2gwlF3B85JdpsTUU=
|
||||
github.com/orandin/slog-gorm v1.4.0/go.mod h1:MoZ51+b7xE9lwGNPYEhxcUtRNrYzjdcKvA8QXQQGEPA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pkg/sftp v1.13.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU=
|
||||
github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||
github.com/sethvargo/go-limiter v1.0.0 h1:JqW13eWEMn0VFv86OKn8wiYJY/m250WoXdrjRV0kLe4=
|
||||
github.com/sethvargo/go-limiter v1.0.0/go.mod h1:01b6tW25Ap+MeLYBuD4aHunMrJoNO5PVUFdS9rac3II=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1096 h1:DMokC7T0UF8wMfT1kD+mX3M+hc2C06gmFvQ9gsfRPmI=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1096/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
|
||||
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
|
||||
github.com/tnb-labs/acmez/v3 v3.0.0-20250101070450-67fb7b3d86d1 h1:7IhZYEiBWy3KfKi9wZkv4Um8QYYF2fYzQ+xNrOx/tHI=
|
||||
github.com/tnb-labs/acmez/v3 v3.0.0-20250101070450-67fb7b3d86d1/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250120210832-12a9c7ae0c8a h1:bFMBmB409YhHd+B4yfM7JPYTCgxnQjqzIfFOiN4Tpsc=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250120210832-12a9c7ae0c8a/go.mod h1:hdMWBc1+TyB6G5ZZBBgPWQ8cjRZ6zpYdhal0uu6E9QM=
|
||||
github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg=
|
||||
github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
||||
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sethvargo/go-limiter v1.1.0 h1:eLeZVQ2zqJOiEs03GguqmBVG6/T6lsZB+6PP1t7J6fA=
|
||||
github.com/sethvargo/go-limiter v1.1.0/go.mod h1:01b6tW25Ap+MeLYBuD4aHunMrJoNO5PVUFdS9rac3II=
|
||||
github.com/shirou/gopsutil/v4 v4.25.12 h1:e7PvW/0RmJ8p8vPGJH4jvNkOyLmbkXgXW4m6ZPic6CY=
|
||||
github.com/shirou/gopsutil/v4 v4.25.12/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||
github.com/studio-b12/gowebdav v0.12.0 h1:kFRtQECt8jmVAvA6RHBz3geXUGJHUZA6/IKpOVUs5kM=
|
||||
github.com/studio-b12/gowebdav v0.12.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
|
||||
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
|
||||
github.com/timtadh/data-structures v0.5.3/go.mod h1:9R4XODhJ8JdWFEI8P/HJKqxuJctfBQw6fDibMQny2oU=
|
||||
github.com/timtadh/data-structures v0.6.1/go.mod h1:uYUnI1cQi/5yMCc7s23I+x8Mn8BCMf4WgK+7/4QSEk4=
|
||||
github.com/timtadh/data-structures v0.6.2 h1:zybDnU5NLjJ7WKMDJpvVwczQuf1wSLBgdRHZ9O4AqJ0=
|
||||
github.com/timtadh/data-structures v0.6.2/go.mod h1:uYUnI1cQi/5yMCc7s23I+x8Mn8BCMf4WgK+7/4QSEk4=
|
||||
github.com/timtadh/getopt v1.0.0/go.mod h1:L3EL6YN2G0eIAhYBo9b7SB9d/kEQmdnwthIlMJfj210=
|
||||
github.com/timtadh/lexmachine v0.2.2/go.mod h1:GBJvD5OAfRn/gnp92zb9KTgHLB7akKyxmVivoYCcjQI=
|
||||
github.com/timtadh/lexmachine v0.2.3 h1:ZqlfHnfMcAygtbNM5Gv7jQf8hmM8LfVzDjfCrq235NQ=
|
||||
github.com/timtadh/lexmachine v0.2.3/go.mod h1:oK1NW+93fQSIF6s+J6sXBFWsCPCFbNmrwKV1i0aqvW0=
|
||||
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
|
||||
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
|
||||
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
|
||||
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250620092546-c3e307e36701 h1:JgeHIJzRSEdcuLXufZrni5+a4yDnBhQG+DdKhqCFhq0=
|
||||
github.com/tufanbarisyildirim/gonginx v0.0.0-20250620092546-c3e307e36701/go.mod h1:ALbEe81QPWOZjDKCKNWodG2iqCMtregG8+ebQgjx2+4=
|
||||
github.com/urfave/cli/v3 v3.6.2 h1:lQuqiPrZ1cIz8hz+HcrG0TNZFxU70dPZ3Yl+pSrH9A8=
|
||||
github.com/urfave/cli/v3 v3.6.2/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U=
|
||||
go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs=
|
||||
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
|
||||
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
|
||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
|
||||
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0=
|
||||
modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo=
|
||||
modernc.org/ccgo/v4 v4.23.16/go.mod h1:nNma8goMTY7aQZQNTyN9AIoJfxav4nvTnvKThAeMDdo=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw=
|
||||
modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8=
|
||||
modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=
|
||||
modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g=
|
||||
modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
|
||||
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
|
||||
+3
-3
@@ -2,13 +2,13 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"github.com/gookit/color"
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"github.com/tnb-labs/panel/pkg/apploader"
|
||||
"github.com/acepanel/panel/pkg/apploader"
|
||||
)
|
||||
|
||||
type Cli struct {
|
||||
@@ -30,7 +30,7 @@ func (r *Cli) Run() error {
|
||||
_ = r.migrator.Migrate()
|
||||
|
||||
if err := r.cmd.Run(context.TODO(), os.Args); err != nil {
|
||||
fmt.Printf("|-%v\n", err)
|
||||
color.Errorf("|-%v\n", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
+7
-7
@@ -11,14 +11,14 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"github.com/gookit/validate"
|
||||
"github.com/knadh/koanf/v2"
|
||||
"github.com/robfig/cron/v3"
|
||||
|
||||
"github.com/tnb-labs/panel/pkg/queue"
|
||||
"github.com/acepanel/panel/pkg/config"
|
||||
"github.com/acepanel/panel/pkg/queue"
|
||||
)
|
||||
|
||||
type Web struct {
|
||||
conf *koanf.Koanf
|
||||
conf *config.Config
|
||||
router *chi.Mux
|
||||
server *hlfhr.Server
|
||||
migrator *gormigrate.Gormigrate
|
||||
@@ -26,7 +26,7 @@ type Web struct {
|
||||
queue *queue.Queue
|
||||
}
|
||||
|
||||
func NewWeb(conf *koanf.Koanf, router *chi.Mux, server *hlfhr.Server, migrator *gormigrate.Gormigrate, cron *cron.Cron, queue *queue.Queue, _ *validate.Validation) *Web {
|
||||
func NewWeb(conf *config.Config, router *chi.Mux, server *hlfhr.Server, migrator *gormigrate.Gormigrate, cron *cron.Cron, queue *queue.Queue, _ *validate.Validation) *Web {
|
||||
return &Web{
|
||||
conf: conf,
|
||||
router: router,
|
||||
@@ -52,15 +52,15 @@ func (r *Web) Run() error {
|
||||
r.queue.Run(context.TODO())
|
||||
|
||||
// run http server
|
||||
if r.conf.Bool("http.tls") {
|
||||
if r.conf.HTTP.TLS {
|
||||
cert := filepath.Join(Root, "panel/storage/cert.pem")
|
||||
key := filepath.Join(Root, "panel/storage/cert.key")
|
||||
fmt.Println("[HTTP] listening and serving on port", r.conf.MustInt("http.port"), "with tls")
|
||||
fmt.Println("[HTTP] listening and serving on port", r.conf.HTTP.Port, "with tls")
|
||||
if err := r.server.ListenAndServeTLS(cert, key); !errors.Is(err, http.ErrServerClosed) {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
fmt.Println("[HTTP] listening and serving on port", r.conf.MustInt("http.port"))
|
||||
fmt.Println("[HTTP] listening and serving on port", r.conf.HTTP.Port)
|
||||
if err := r.server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
package apache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
"github.com/acepanel/panel/pkg/io"
|
||||
"github.com/acepanel/panel/pkg/shell"
|
||||
"github.com/acepanel/panel/pkg/systemctl"
|
||||
"github.com/acepanel/panel/pkg/tools"
|
||||
"github.com/acepanel/panel/pkg/types"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
t *gotext.Locale
|
||||
}
|
||||
|
||||
func NewApp(t *gotext.Locale) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
r.Get("/load", s.Load)
|
||||
r.Get("/config", s.GetConfig)
|
||||
r.Post("/config", s.SaveConfig)
|
||||
r.Get("/error_log", s.ErrorLog)
|
||||
r.Post("/clear_error_log", s.ClearErrorLog)
|
||||
}
|
||||
|
||||
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
config, err := io.Read(fmt.Sprintf("%s/server/apache/conf/httpd.conf", app.Root))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, config)
|
||||
}
|
||||
|
||||
func (s *App) SaveConfig(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[UpdateConfig](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write(fmt.Sprintf("%s/server/apache/conf/httpd.conf", app.Root), req.Config, 0600); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = systemctl.Reload("apache"); err != nil {
|
||||
_, err = shell.Execf("%s/server/apache/bin/apachectl configtest", app.Root)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload apache: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *App) ErrorLog(w http.ResponseWriter, r *http.Request) {
|
||||
service.Success(w, fmt.Sprintf("%s/%s", app.Root, "server/apache/logs/error_log"))
|
||||
}
|
||||
|
||||
func (s *App) ClearErrorLog(w http.ResponseWriter, r *http.Request) {
|
||||
if _, err := shell.Execf("cat /dev/null > %s/%s", app.Root, "server/apache/logs/error_log"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
status, err := shell.Execf("curl -s http://127.0.0.1/server_status?auto 2>/dev/null || true")
|
||||
if err != nil {
|
||||
service.Success(w, []types.NV{})
|
||||
return
|
||||
}
|
||||
|
||||
var data []types.NV
|
||||
|
||||
workers, err := shell.Execf("ps aux | grep httpd | grep -v grep | wc -l")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get apache workers: %v", err))
|
||||
return
|
||||
}
|
||||
data = append(data, types.NV{
|
||||
Name: s.t.Get("Workers"),
|
||||
Value: workers,
|
||||
})
|
||||
|
||||
out, err := shell.Execf("ps aux | grep httpd | grep -v grep | awk '{memsum+=$6};END {print memsum}'")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get apache workers: %v", err))
|
||||
return
|
||||
}
|
||||
mem := tools.FormatBytes(cast.ToFloat64(out))
|
||||
data = append(data, types.NV{
|
||||
Name: s.t.Get("Memory"),
|
||||
Value: mem,
|
||||
})
|
||||
|
||||
// Parse server-status output
|
||||
if match := regexp.MustCompile(`Total Accesses:\s*(\d+)`).FindStringSubmatch(status); len(match) == 2 {
|
||||
data = append(data, types.NV{
|
||||
Name: s.t.Get("Total Accesses"),
|
||||
Value: match[1],
|
||||
})
|
||||
}
|
||||
|
||||
if match := regexp.MustCompile(`Total kBytes:\s*(\d+)`).FindStringSubmatch(status); len(match) == 2 {
|
||||
data = append(data, types.NV{
|
||||
Name: s.t.Get("Total Traffic"),
|
||||
Value: tools.FormatBytes(cast.ToFloat64(match[1]) * 1024),
|
||||
})
|
||||
}
|
||||
|
||||
if match := regexp.MustCompile(`BusyWorkers:\s*(\d+)`).FindStringSubmatch(status); len(match) == 2 {
|
||||
data = append(data, types.NV{
|
||||
Name: s.t.Get("Busy Workers"),
|
||||
Value: match[1],
|
||||
})
|
||||
}
|
||||
|
||||
if match := regexp.MustCompile(`IdleWorkers:\s*(\d+)`).FindStringSubmatch(status); len(match) == 2 {
|
||||
data = append(data, types.NV{
|
||||
Name: s.t.Get("Idle Workers"),
|
||||
Value: match[1],
|
||||
})
|
||||
}
|
||||
|
||||
if match := regexp.MustCompile(`ReqPerSec:\s*([\d.]+)`).FindStringSubmatch(status); len(match) == 2 {
|
||||
data = append(data, types.NV{
|
||||
Name: s.t.Get("Requests/sec"),
|
||||
Value: match[1],
|
||||
})
|
||||
}
|
||||
|
||||
if match := regexp.MustCompile(`BytesPerSec:\s*([\d.]+)`).FindStringSubmatch(status); len(match) == 2 {
|
||||
data = append(data, types.NV{
|
||||
Name: s.t.Get("Bytes/sec"),
|
||||
Value: tools.FormatBytes(cast.ToFloat64(match[1])),
|
||||
})
|
||||
}
|
||||
|
||||
service.Success(w, data)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package apache
|
||||
|
||||
type UpdateConfig struct {
|
||||
Config string `form:"config" json:"config" validate:"required"`
|
||||
}
|
||||
+27
-31
@@ -3,46 +3,43 @@ package apps
|
||||
import (
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/apps/benchmark"
|
||||
"github.com/tnb-labs/panel/internal/apps/docker"
|
||||
"github.com/tnb-labs/panel/internal/apps/fail2ban"
|
||||
"github.com/tnb-labs/panel/internal/apps/frp"
|
||||
"github.com/tnb-labs/panel/internal/apps/gitea"
|
||||
"github.com/tnb-labs/panel/internal/apps/memcached"
|
||||
"github.com/tnb-labs/panel/internal/apps/mysql"
|
||||
"github.com/tnb-labs/panel/internal/apps/nginx"
|
||||
"github.com/tnb-labs/panel/internal/apps/php74"
|
||||
"github.com/tnb-labs/panel/internal/apps/php80"
|
||||
"github.com/tnb-labs/panel/internal/apps/php81"
|
||||
"github.com/tnb-labs/panel/internal/apps/php82"
|
||||
"github.com/tnb-labs/panel/internal/apps/php83"
|
||||
"github.com/tnb-labs/panel/internal/apps/php84"
|
||||
"github.com/tnb-labs/panel/internal/apps/phpmyadmin"
|
||||
"github.com/tnb-labs/panel/internal/apps/podman"
|
||||
"github.com/tnb-labs/panel/internal/apps/postgresql"
|
||||
"github.com/tnb-labs/panel/internal/apps/pureftpd"
|
||||
"github.com/tnb-labs/panel/internal/apps/redis"
|
||||
"github.com/tnb-labs/panel/internal/apps/rsync"
|
||||
"github.com/tnb-labs/panel/internal/apps/s3fs"
|
||||
"github.com/tnb-labs/panel/internal/apps/supervisor"
|
||||
"github.com/tnb-labs/panel/internal/apps/toolbox"
|
||||
"github.com/acepanel/panel/internal/apps/apache"
|
||||
"github.com/acepanel/panel/internal/apps/codeserver"
|
||||
"github.com/acepanel/panel/internal/apps/docker"
|
||||
"github.com/acepanel/panel/internal/apps/fail2ban"
|
||||
"github.com/acepanel/panel/internal/apps/frp"
|
||||
"github.com/acepanel/panel/internal/apps/gitea"
|
||||
"github.com/acepanel/panel/internal/apps/mariadb"
|
||||
"github.com/acepanel/panel/internal/apps/memcached"
|
||||
"github.com/acepanel/panel/internal/apps/minio"
|
||||
"github.com/acepanel/panel/internal/apps/mysql"
|
||||
"github.com/acepanel/panel/internal/apps/nginx"
|
||||
"github.com/acepanel/panel/internal/apps/openresty"
|
||||
"github.com/acepanel/panel/internal/apps/percona"
|
||||
"github.com/acepanel/panel/internal/apps/phpmyadmin"
|
||||
"github.com/acepanel/panel/internal/apps/podman"
|
||||
"github.com/acepanel/panel/internal/apps/postgresql"
|
||||
"github.com/acepanel/panel/internal/apps/pureftpd"
|
||||
"github.com/acepanel/panel/internal/apps/redis"
|
||||
"github.com/acepanel/panel/internal/apps/rsync"
|
||||
"github.com/acepanel/panel/internal/apps/s3fs"
|
||||
"github.com/acepanel/panel/internal/apps/supervisor"
|
||||
)
|
||||
|
||||
var ProviderSet = wire.NewSet(
|
||||
benchmark.NewApp,
|
||||
apache.NewApp,
|
||||
codeserver.NewApp,
|
||||
docker.NewApp,
|
||||
fail2ban.NewApp,
|
||||
frp.NewApp,
|
||||
gitea.NewApp,
|
||||
mariadb.NewApp,
|
||||
memcached.NewApp,
|
||||
minio.NewApp,
|
||||
mysql.NewApp,
|
||||
nginx.NewApp,
|
||||
php74.NewApp,
|
||||
php80.NewApp,
|
||||
php81.NewApp,
|
||||
php82.NewApp,
|
||||
php83.NewApp,
|
||||
php84.NewApp,
|
||||
openresty.NewApp,
|
||||
percona.NewApp,
|
||||
phpmyadmin.NewApp,
|
||||
podman.NewApp,
|
||||
postgresql.NewApp,
|
||||
@@ -51,5 +48,4 @@ var ProviderSet = wire.NewSet(
|
||||
rsync.NewApp,
|
||||
s3fs.NewApp,
|
||||
supervisor.NewApp,
|
||||
toolbox.NewApp,
|
||||
)
|
||||
|
||||
@@ -1,691 +0,0 @@
|
||||
package benchmark
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
cryptorand "crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"io"
|
||||
"math"
|
||||
"math/big"
|
||||
"math/rand/v2"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
r.Post("/test", s.Test)
|
||||
}
|
||||
|
||||
// Test 运行测试
|
||||
func (s *App) Test(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[Test](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
switch req.Name {
|
||||
case "image":
|
||||
result := s.imageProcessing(req.Multi)
|
||||
service.Success(w, result)
|
||||
case "machine":
|
||||
result := s.machineLearning(req.Multi)
|
||||
service.Success(w, result)
|
||||
case "compile":
|
||||
result := s.compileSimulationSingle(req.Multi)
|
||||
service.Success(w, result)
|
||||
case "encryption":
|
||||
result := s.encryptionTest(req.Multi)
|
||||
service.Success(w, result)
|
||||
case "compression":
|
||||
result := s.compressionTest(req.Multi)
|
||||
service.Success(w, result)
|
||||
case "physics":
|
||||
result := s.physicsSimulation(req.Multi)
|
||||
service.Success(w, result)
|
||||
case "json":
|
||||
result := s.jsonProcessing(req.Multi)
|
||||
service.Success(w, result)
|
||||
case "disk":
|
||||
result := s.diskTestTask()
|
||||
service.Success(w, result)
|
||||
case "memory":
|
||||
result := s.memoryTestTask()
|
||||
service.Success(w, result)
|
||||
default:
|
||||
service.Error(w, http.StatusUnprocessableEntity, "未知测试类型")
|
||||
}
|
||||
}
|
||||
|
||||
// calculateCpuScore 计算CPU成绩
|
||||
func (s *App) calculateCpuScore(duration time.Duration) int {
|
||||
score := int((10 / duration.Seconds()) * float64(3000))
|
||||
|
||||
if score < 0 {
|
||||
score = 0
|
||||
}
|
||||
return score
|
||||
}
|
||||
|
||||
// calculateScore 计算内存/硬盘成绩
|
||||
func (s *App) calculateScore(duration time.Duration) int {
|
||||
score := int((20 / duration.Seconds()) * float64(30000))
|
||||
|
||||
if score < 0 {
|
||||
score = 0
|
||||
}
|
||||
return score
|
||||
}
|
||||
|
||||
// 图像处理
|
||||
|
||||
func (s *App) imageProcessing(multi bool) int {
|
||||
n := 1
|
||||
if multi {
|
||||
n = runtime.NumCPU()
|
||||
}
|
||||
start := time.Now()
|
||||
if err := s.imageProcessingTask(n); err != nil {
|
||||
return 0
|
||||
}
|
||||
duration := time.Since(start)
|
||||
return s.calculateCpuScore(duration)
|
||||
}
|
||||
|
||||
func (s *App) imageProcessingTask(numThreads int) error {
|
||||
img := image.NewRGBA(image.Rect(0, 0, 4000, 4000))
|
||||
for x := 0; x < 4000; x++ {
|
||||
for y := 0; y < 4000; y++ {
|
||||
img.Set(x, y, color.RGBA{R: uint8(x % 256), G: uint8(y % 256), A: 255})
|
||||
}
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
dx := img.Bounds().Dx()
|
||||
dy := img.Bounds().Dy()
|
||||
chunkSize := dy / numThreads
|
||||
|
||||
for i := 0; i < numThreads; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
startY := i * chunkSize
|
||||
endY := startY + chunkSize
|
||||
if i == numThreads-1 {
|
||||
endY = dy
|
||||
}
|
||||
for x := 1; x < dx-1; x++ {
|
||||
for y := startY + 1; y < endY-1; y++ {
|
||||
// 卷积操作(模糊)
|
||||
rTotal, gTotal, bTotal := 0, 0, 0
|
||||
for k := -1; k <= 1; k++ {
|
||||
for l := -1; l <= 1; l++ {
|
||||
r, g, b, _ := img.At(x+k, y+l).RGBA()
|
||||
rTotal += int(r)
|
||||
gTotal += int(g)
|
||||
bTotal += int(b)
|
||||
}
|
||||
}
|
||||
rAvg := uint8(rTotal / 9 / 256)
|
||||
gAvg := uint8(gTotal / 9 / 256)
|
||||
bAvg := uint8(bTotal / 9 / 256)
|
||||
img.Set(x, y, color.RGBA{R: rAvg, G: gAvg, B: bAvg, A: 255})
|
||||
}
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
// 机器学习(矩阵乘法)
|
||||
|
||||
func (s *App) machineLearning(multi bool) int {
|
||||
n := 1
|
||||
if multi {
|
||||
n = runtime.NumCPU()
|
||||
}
|
||||
start := time.Now()
|
||||
s.machineLearningTask(n)
|
||||
duration := time.Since(start)
|
||||
return s.calculateCpuScore(duration)
|
||||
}
|
||||
|
||||
func (s *App) machineLearningTask(numThreads int) {
|
||||
size := 900
|
||||
a := make([][]float64, size)
|
||||
b := make([][]float64, size)
|
||||
for i := 0; i < size; i++ {
|
||||
a[i] = make([]float64, size)
|
||||
b[i] = make([]float64, size)
|
||||
for j := 0; j < size; j++ {
|
||||
a[i][j] = rand.Float64()
|
||||
b[i][j] = rand.Float64()
|
||||
}
|
||||
}
|
||||
|
||||
c := make([][]float64, size)
|
||||
for i := 0; i < size; i++ {
|
||||
c[i] = make([]float64, size)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
chunkSize := size / numThreads
|
||||
|
||||
for k := 0; k < numThreads; k++ {
|
||||
wg.Add(1)
|
||||
go func(k int) {
|
||||
defer wg.Done()
|
||||
start := k * chunkSize
|
||||
end := start + chunkSize
|
||||
if k == numThreads-1 {
|
||||
end = size
|
||||
}
|
||||
for i := start; i < end; i++ {
|
||||
for j := 0; j < size; j++ {
|
||||
sum := 0.0
|
||||
for l := 0; l < size; l++ {
|
||||
sum += a[i][l] * b[l][j]
|
||||
}
|
||||
c[i][j] = sum
|
||||
}
|
||||
}
|
||||
}(k)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// 数学问题(计算斐波那契数)
|
||||
|
||||
func (s *App) compileSimulationSingle(multi bool) int {
|
||||
n := 1
|
||||
if multi {
|
||||
n = runtime.NumCPU()
|
||||
}
|
||||
start := time.Now()
|
||||
totalCalculations := 1000
|
||||
fibNumber := 20000
|
||||
|
||||
calculationsPerThread := totalCalculations / n
|
||||
remainder := totalCalculations % n
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
tasks := calculationsPerThread
|
||||
if i < remainder {
|
||||
tasks++ // 处理无法均分的剩余任务
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(tasks int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < tasks; j++ {
|
||||
s.fib(fibNumber)
|
||||
}
|
||||
}(tasks)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
duration := time.Since(start)
|
||||
return s.calculateCpuScore(duration)
|
||||
}
|
||||
|
||||
// 斐波那契函数
|
||||
func (s *App) fib(n int) *big.Int {
|
||||
if n < 2 {
|
||||
return big.NewInt(int64(n))
|
||||
}
|
||||
a := big.NewInt(0)
|
||||
b := big.NewInt(1)
|
||||
temp := big.NewInt(0)
|
||||
for i := 2; i <= n; i++ {
|
||||
temp.Add(a, b)
|
||||
a.Set(b)
|
||||
b.Set(temp)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// AES加密
|
||||
|
||||
func (s *App) encryptionTest(multi bool) int {
|
||||
n := 1
|
||||
if multi {
|
||||
n = runtime.NumCPU()
|
||||
}
|
||||
start := time.Now()
|
||||
if err := s.encryptionTestTask(n); err != nil {
|
||||
return 0
|
||||
}
|
||||
duration := time.Since(start)
|
||||
return s.calculateCpuScore(duration)
|
||||
}
|
||||
|
||||
func (s *App) encryptionTestTask(numThreads int) error {
|
||||
key := []byte("abcdefghijklmnopqrstuvwxyz123456")
|
||||
dataSize := 1024 * 1024 * 512 // 512 MB
|
||||
plaintext := []byte(strings.Repeat("A", dataSize))
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
aesGCM, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chunkSize := dataSize / numThreads
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < numThreads; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
start := i * chunkSize
|
||||
end := start + chunkSize
|
||||
if i == numThreads-1 {
|
||||
end = dataSize
|
||||
}
|
||||
|
||||
nonce := make([]byte, aesGCM.NonceSize())
|
||||
if _, err = cryptorand.Read(nonce); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
aesGCM.Seal(nil, nonce, plaintext[start:end], nil)
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
// 压缩/解压缩
|
||||
|
||||
func (s *App) compressionTest(multi bool) int {
|
||||
n := 1
|
||||
if multi {
|
||||
n = runtime.NumCPU()
|
||||
}
|
||||
start := time.Now()
|
||||
s.compressionTestTask(n)
|
||||
duration := time.Since(start)
|
||||
return s.calculateCpuScore(duration)
|
||||
}
|
||||
|
||||
func (s *App) compressionTestTask(numThreads int) {
|
||||
data := []byte(strings.Repeat("耗子面板", 50000000))
|
||||
chunkSize := len(data) / numThreads
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
compressedChunks := make([]bytes.Buffer, numThreads)
|
||||
|
||||
// 压缩
|
||||
for i := 0; i < numThreads; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
start := i * chunkSize
|
||||
end := start + chunkSize
|
||||
if i == numThreads-1 {
|
||||
end = len(data)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
w := gzip.NewWriter(&buf)
|
||||
_, _ = w.Write(data[start:end])
|
||||
_ = w.Close()
|
||||
compressedChunks[i] = buf
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// 解压缩
|
||||
for i := 0; i < numThreads; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
r, err := gzip.NewReader(&compressedChunks[i])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = io.Copy(io.Discard, r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = r.Close()
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// 物理仿真(N体问题)
|
||||
|
||||
func (s *App) physicsSimulation(multi bool) int {
|
||||
n := 1
|
||||
if multi {
|
||||
n = runtime.NumCPU()
|
||||
}
|
||||
start := time.Now()
|
||||
s.physicsSimulationTask(n)
|
||||
duration := time.Since(start)
|
||||
return s.calculateCpuScore(duration)
|
||||
}
|
||||
|
||||
func (s *App) physicsSimulationTask(numThreads int) {
|
||||
const (
|
||||
numBodies = 4000
|
||||
steps = 30
|
||||
)
|
||||
|
||||
type Body struct {
|
||||
x, y, z, vx, vy, vz float64
|
||||
}
|
||||
|
||||
bodies := make([]Body, numBodies)
|
||||
for i := 0; i < numBodies; i++ {
|
||||
bodies[i] = Body{
|
||||
x: rand.Float64(),
|
||||
y: rand.Float64(),
|
||||
z: rand.Float64(),
|
||||
vx: rand.Float64(),
|
||||
vy: rand.Float64(),
|
||||
vz: rand.Float64(),
|
||||
}
|
||||
}
|
||||
|
||||
chunkSize := numBodies / numThreads
|
||||
|
||||
for step := 0; step < steps; step++ {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// 更新速度
|
||||
for k := 0; k < numThreads; k++ {
|
||||
wg.Add(1)
|
||||
go func(k int) {
|
||||
defer wg.Done()
|
||||
start := k * chunkSize
|
||||
end := start + chunkSize
|
||||
if k == numThreads-1 {
|
||||
end = numBodies
|
||||
}
|
||||
for i := start; i < end; i++ {
|
||||
bi := &bodies[i]
|
||||
for j := 0; j < numBodies; j++ {
|
||||
if i == j {
|
||||
continue
|
||||
}
|
||||
bj := &bodies[j]
|
||||
dx := bj.x - bi.x
|
||||
dy := bj.y - bi.y
|
||||
dz := bj.z - bi.z
|
||||
dist := math.Sqrt(dx*dx + dy*dy + dz*dz)
|
||||
if dist == 0 {
|
||||
continue
|
||||
}
|
||||
force := 1 / (dist * dist)
|
||||
bi.vx += force * dx / dist
|
||||
bi.vy += force * dy / dist
|
||||
bi.vz += force * dz / dist
|
||||
}
|
||||
}
|
||||
}(k)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// 更新位置
|
||||
for k := 0; k < numThreads; k++ {
|
||||
wg.Add(1)
|
||||
go func(k int) {
|
||||
defer wg.Done()
|
||||
start := k * chunkSize
|
||||
end := start + chunkSize
|
||||
if k == numThreads-1 {
|
||||
end = numBodies
|
||||
}
|
||||
for i := start; i < end; i++ {
|
||||
bi := &bodies[i]
|
||||
bi.x += bi.vx
|
||||
bi.y += bi.vy
|
||||
bi.z += bi.vz
|
||||
}
|
||||
}(k)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
// JSON解析
|
||||
|
||||
func (s *App) jsonProcessing(multi bool) int {
|
||||
n := 1
|
||||
if multi {
|
||||
n = runtime.NumCPU()
|
||||
}
|
||||
start := time.Now()
|
||||
s.jsonProcessingTask(n)
|
||||
duration := time.Since(start)
|
||||
return s.calculateCpuScore(duration)
|
||||
}
|
||||
|
||||
func (s *App) jsonProcessingTask(numThreads int) {
|
||||
numElements := 1000000
|
||||
elementsPerThread := numElements / numThreads
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < numThreads; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
start := i * elementsPerThread
|
||||
end := start + elementsPerThread
|
||||
if i == numThreads-1 {
|
||||
end = numElements
|
||||
}
|
||||
|
||||
elements := make([]map[string]any, 0, end-start)
|
||||
for j := start; j < end; j++ {
|
||||
elements = append(elements, map[string]any{
|
||||
"id": j,
|
||||
"value": fmt.Sprintf("Value%d", j),
|
||||
})
|
||||
}
|
||||
encoded, err := json.Marshal(elements)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var parsed []map[string]any
|
||||
err = json.Unmarshal(encoded, &parsed)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// 内存性能
|
||||
|
||||
func (s *App) memoryTestTask() map[string]any {
|
||||
results := make(map[string]any)
|
||||
dataSize := 500 * 1024 * 1024 // 500 MB
|
||||
data := make([]byte, dataSize)
|
||||
_, _ = cryptorand.Read(data)
|
||||
|
||||
start := time.Now()
|
||||
// 内存读写速度
|
||||
results["bandwidth"] = s.memoryBandwidthTest(data)
|
||||
// 内存访问延迟
|
||||
data = data[:100*1024*1024] // 100 MB
|
||||
results["latency"] = s.memoryLatencyTest(data)
|
||||
duration := time.Since(start)
|
||||
results["score"] = s.calculateScore(duration)
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func (s *App) memoryBandwidthTest(data []byte) string {
|
||||
dataSize := len(data)
|
||||
|
||||
startTime := time.Now()
|
||||
|
||||
for i := 0; i < dataSize; i++ {
|
||||
data[i] ^= 0xFF
|
||||
}
|
||||
|
||||
duration := time.Since(startTime).Seconds()
|
||||
if duration == 0 {
|
||||
return "N/A"
|
||||
}
|
||||
speed := float64(dataSize) / duration / (1024 * 1024)
|
||||
return fmt.Sprintf("%.2f MB/s", speed)
|
||||
}
|
||||
|
||||
func (s *App) memoryLatencyTest(data []byte) string {
|
||||
dataSize := len(data)
|
||||
indices := rand.Perm(dataSize)
|
||||
|
||||
startTime := time.Now()
|
||||
sum := byte(0)
|
||||
for _, idx := range indices {
|
||||
sum ^= data[idx]
|
||||
}
|
||||
duration := time.Since(startTime).Seconds()
|
||||
if duration == 0 {
|
||||
return "N/A"
|
||||
}
|
||||
avgLatency := duration * 1e9 / float64(dataSize)
|
||||
return fmt.Sprintf("%.2f ns", avgLatency)
|
||||
}
|
||||
|
||||
// 硬盘IO
|
||||
|
||||
func (s *App) diskTestTask() map[string]any {
|
||||
results := make(map[string]any)
|
||||
blockSizes := []int64{4 * 1024, 64 * 1024, 512 * 1024, 1 * 1024 * 1024} // 4K, 64K, 512K, 1M
|
||||
fileSize := int64(100 * 1024 * 1024) // 100MB 文件
|
||||
|
||||
start := time.Now()
|
||||
for _, blockSize := range blockSizes {
|
||||
result := s.diskIOTest(blockSize, fileSize)
|
||||
results[fmt.Sprintf("%d", blockSize/1024)] = result
|
||||
}
|
||||
duration := time.Since(start)
|
||||
results["score"] = s.calculateScore(duration)
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func (s *App) diskIOTest(blockSize int64, fileSize int64) map[string]any {
|
||||
result := make(map[string]any)
|
||||
tempFile := fmt.Sprintf("tempfile_%d", blockSize)
|
||||
defer os.Remove(tempFile)
|
||||
|
||||
// 写测试
|
||||
writeSpeed, writeIOPS := s.diskWriteTest(tempFile, blockSize, fileSize)
|
||||
// 读测试
|
||||
readSpeed, readIOPS := s.diskReadTest(tempFile, blockSize, fileSize)
|
||||
|
||||
result["write_speed"] = fmt.Sprintf("%.2f MB/s", writeSpeed)
|
||||
result["write_iops"] = fmt.Sprintf("%.2f IOPS", writeIOPS)
|
||||
result["read_speed"] = fmt.Sprintf("%.2f MB/s", readSpeed)
|
||||
result["read_iops"] = fmt.Sprintf("%.2f IOPS", readIOPS)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *App) diskWriteTest(fileName string, blockSize int64, fileSize int64) (float64, float64) {
|
||||
totalBlocks := fileSize / blockSize
|
||||
|
||||
data := make([]byte, blockSize)
|
||||
_, _ = cryptorand.Read(data)
|
||||
|
||||
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_SYNC, 0644)
|
||||
if err != nil {
|
||||
return 0, 0
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
start := time.Now()
|
||||
|
||||
for i := int64(0); i < totalBlocks; i++ {
|
||||
// 生成随机偏移
|
||||
offset := rand.Int64N(fileSize - blockSize + 1)
|
||||
_, err := file.WriteAt(data, offset)
|
||||
if err != nil {
|
||||
return 0, 0
|
||||
}
|
||||
}
|
||||
|
||||
_ = file.Sync()
|
||||
|
||||
duration := time.Since(start).Seconds()
|
||||
if duration == 0 {
|
||||
duration = 1
|
||||
}
|
||||
speed := float64(totalBlocks*blockSize) / duration / (1024 * 1024)
|
||||
iops := float64(totalBlocks) / duration
|
||||
return speed, iops
|
||||
}
|
||||
|
||||
func (s *App) diskReadTest(fileName string, blockSize int64, fileSize int64) (float64, float64) {
|
||||
totalBlocks := fileSize / blockSize
|
||||
|
||||
data := make([]byte, blockSize)
|
||||
|
||||
file, err := os.OpenFile(fileName, os.O_RDONLY|os.O_SYNC, 0644)
|
||||
if err != nil {
|
||||
return 0, 0
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
start := time.Now()
|
||||
|
||||
for i := int64(0); i < totalBlocks; i++ {
|
||||
// 生成随机偏移
|
||||
offset := rand.Int64N(fileSize - blockSize + 1)
|
||||
_, err := file.ReadAt(data, offset)
|
||||
if err != nil && err != io.EOF {
|
||||
return 0, 0
|
||||
}
|
||||
}
|
||||
|
||||
duration := time.Since(start).Seconds()
|
||||
if duration == 0 {
|
||||
duration = 1
|
||||
}
|
||||
speed := float64(totalBlocks*blockSize) / duration / (1024 * 1024)
|
||||
iops := float64(totalBlocks) / duration
|
||||
return speed, iops
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package benchmark
|
||||
|
||||
type Test struct {
|
||||
Name string `json:"name" validate:"required|in:image,machine,compile,encryption,compression,physics,json,memory,disk"`
|
||||
Multi bool `json:"multi"`
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package codeserver
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
"github.com/acepanel/panel/pkg/io"
|
||||
"github.com/acepanel/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
type App struct{}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
r.Get("/config", s.GetConfig)
|
||||
r.Post("/config", s.UpdateConfig)
|
||||
}
|
||||
|
||||
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
config, _ := io.Read("/root/.config/code-server/config.yaml")
|
||||
service.Success(w, config)
|
||||
}
|
||||
|
||||
func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[UpdateConfig](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write("/root/.config/code-server/config.yaml", req.Config, 0600); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = systemctl.Restart("code-server"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package codeserver
|
||||
|
||||
type UpdateConfig struct {
|
||||
Config string `form:"config" json:"config" validate:"required"`
|
||||
}
|
||||
+218
-3
@@ -1,13 +1,16 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
"github.com/tnb-labs/panel/pkg/systemctl"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
"github.com/acepanel/panel/pkg/io"
|
||||
"github.com/acepanel/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
type App struct{}
|
||||
@@ -19,6 +22,8 @@ func NewApp() *App {
|
||||
func (s *App) Route(r chi.Router) {
|
||||
r.Get("/config", s.GetConfig)
|
||||
r.Post("/config", s.UpdateConfig)
|
||||
r.Get("/settings", s.GetSettings)
|
||||
r.Post("/settings", s.UpdateSettings)
|
||||
}
|
||||
|
||||
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -50,3 +55,213 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
// GetSettings 获取 Docker 设置
|
||||
func (s *App) GetSettings(w http.ResponseWriter, r *http.Request) {
|
||||
configPath := "/etc/docker/daemon.json"
|
||||
|
||||
// 读取配置文件
|
||||
content, err := io.Read(configPath)
|
||||
if err != nil {
|
||||
// 如果文件不存在,返回默认设置
|
||||
if os.IsNotExist(err) {
|
||||
service.Success(w, Settings{})
|
||||
return
|
||||
}
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 解析 JSON
|
||||
var daemonConfig DaemonConfig
|
||||
if err = json.Unmarshal([]byte(content), &daemonConfig); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 转换为 Settings 结构
|
||||
settings := Settings{
|
||||
RegistryMirrors: daemonConfig.RegistryMirrors,
|
||||
InsecureRegistries: daemonConfig.InsecureRegistries,
|
||||
LiveRestore: daemonConfig.LiveRestore,
|
||||
LogDriver: daemonConfig.LogDriver,
|
||||
Hosts: daemonConfig.Hosts,
|
||||
DataRoot: daemonConfig.DataRoot,
|
||||
StorageDriver: daemonConfig.StorageDriver,
|
||||
DNS: daemonConfig.DNS,
|
||||
FirewallBackend: daemonConfig.FirewallBackend,
|
||||
Iptables: daemonConfig.Iptables,
|
||||
Ip6tables: daemonConfig.Ip6tables,
|
||||
IpForward: daemonConfig.IpForward,
|
||||
IPv6: daemonConfig.IPv6,
|
||||
Bip: daemonConfig.Bip,
|
||||
}
|
||||
|
||||
// 解析 log-opts
|
||||
if daemonConfig.LogOpts != nil {
|
||||
settings.LogOpts = LogOpts{
|
||||
MaxSize: daemonConfig.LogOpts["max-size"],
|
||||
MaxFile: daemonConfig.LogOpts["max-file"],
|
||||
}
|
||||
}
|
||||
|
||||
// 从 exec-opts 中提取 cgroup-driver
|
||||
for _, opt := range daemonConfig.ExecOpts {
|
||||
if strings.HasPrefix(opt, "native.cgroupdriver=") {
|
||||
settings.CgroupDriver = strings.TrimPrefix(opt, "native.cgroupdriver=")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
service.Success(w, settings)
|
||||
}
|
||||
|
||||
// UpdateSettings 更新 Docker 设置
|
||||
func (s *App) UpdateSettings(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[UpdateSettings](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
configPath := "/etc/docker/daemon.json"
|
||||
settings := req.Settings
|
||||
|
||||
// 读取现有配置(保留其他字段)
|
||||
var existingConfig map[string]any
|
||||
content, err := io.Read(configPath)
|
||||
if err == nil && content != "" {
|
||||
if err = json.Unmarshal([]byte(content), &existingConfig); err != nil {
|
||||
existingConfig = make(map[string]any)
|
||||
}
|
||||
} else {
|
||||
existingConfig = make(map[string]any)
|
||||
}
|
||||
|
||||
// 更新设置字段
|
||||
if len(settings.RegistryMirrors) > 0 {
|
||||
existingConfig["registry-mirrors"] = settings.RegistryMirrors
|
||||
} else {
|
||||
delete(existingConfig, "registry-mirrors")
|
||||
}
|
||||
|
||||
if len(settings.InsecureRegistries) > 0 {
|
||||
existingConfig["insecure-registries"] = settings.InsecureRegistries
|
||||
} else {
|
||||
delete(existingConfig, "insecure-registries")
|
||||
}
|
||||
|
||||
if settings.LiveRestore {
|
||||
existingConfig["live-restore"] = true
|
||||
} else {
|
||||
delete(existingConfig, "live-restore")
|
||||
}
|
||||
|
||||
if settings.LogDriver != "" {
|
||||
existingConfig["log-driver"] = settings.LogDriver
|
||||
} else {
|
||||
delete(existingConfig, "log-driver")
|
||||
}
|
||||
|
||||
// 日志配置
|
||||
if settings.LogOpts.MaxSize != "" || settings.LogOpts.MaxFile != "" {
|
||||
logOpts := make(map[string]string)
|
||||
if settings.LogOpts.MaxSize != "" {
|
||||
logOpts["max-size"] = settings.LogOpts.MaxSize
|
||||
}
|
||||
if settings.LogOpts.MaxFile != "" {
|
||||
logOpts["max-file"] = settings.LogOpts.MaxFile
|
||||
}
|
||||
existingConfig["log-opts"] = logOpts
|
||||
} else {
|
||||
delete(existingConfig, "log-opts")
|
||||
}
|
||||
|
||||
// cgroup-driver
|
||||
if settings.CgroupDriver != "" {
|
||||
existingConfig["exec-opts"] = []string{"native.cgroupdriver=" + settings.CgroupDriver}
|
||||
} else {
|
||||
delete(existingConfig, "exec-opts")
|
||||
}
|
||||
|
||||
if len(settings.Hosts) > 0 {
|
||||
existingConfig["hosts"] = settings.Hosts
|
||||
} else {
|
||||
delete(existingConfig, "hosts")
|
||||
}
|
||||
|
||||
if settings.DataRoot != "" {
|
||||
existingConfig["data-root"] = settings.DataRoot
|
||||
} else {
|
||||
delete(existingConfig, "data-root")
|
||||
}
|
||||
|
||||
if settings.StorageDriver != "" {
|
||||
existingConfig["storage-driver"] = settings.StorageDriver
|
||||
} else {
|
||||
delete(existingConfig, "storage-driver")
|
||||
}
|
||||
|
||||
if len(settings.DNS) > 0 {
|
||||
existingConfig["dns"] = settings.DNS
|
||||
} else {
|
||||
delete(existingConfig, "dns")
|
||||
}
|
||||
|
||||
// 防火墙后端
|
||||
if settings.FirewallBackend != "" {
|
||||
existingConfig["firewall-backend"] = settings.FirewallBackend
|
||||
} else {
|
||||
delete(existingConfig, "firewall-backend")
|
||||
}
|
||||
|
||||
if settings.Iptables != nil {
|
||||
existingConfig["iptables"] = *settings.Iptables
|
||||
} else {
|
||||
delete(existingConfig, "iptables")
|
||||
}
|
||||
|
||||
if settings.Ip6tables != nil {
|
||||
existingConfig["ip6tables"] = *settings.Ip6tables
|
||||
} else {
|
||||
delete(existingConfig, "ip6tables")
|
||||
}
|
||||
|
||||
if settings.IpForward != nil {
|
||||
existingConfig["ip-forward"] = *settings.IpForward
|
||||
} else {
|
||||
delete(existingConfig, "ip-forward")
|
||||
}
|
||||
|
||||
if settings.IPv6 != nil {
|
||||
existingConfig["ipv6"] = *settings.IPv6
|
||||
} else {
|
||||
delete(existingConfig, "ipv6")
|
||||
}
|
||||
|
||||
if settings.Bip != "" {
|
||||
existingConfig["bip"] = settings.Bip
|
||||
} else {
|
||||
delete(existingConfig, "bip")
|
||||
}
|
||||
|
||||
// 序列化并写入文件
|
||||
newContent, err := json.MarshalIndent(existingConfig, "", " ")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write(configPath, string(newContent), 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 重启 Docker 服务
|
||||
if err = systemctl.Restart("docker"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
@@ -3,3 +3,34 @@ package docker
|
||||
type UpdateConfig struct {
|
||||
Config string `form:"config" json:"config" validate:"required"`
|
||||
}
|
||||
|
||||
// LogOpts 日志配置选项
|
||||
type LogOpts struct {
|
||||
MaxSize string `json:"max-size,omitempty"` // 日志文件最大大小,如 "10m"
|
||||
MaxFile string `json:"max-file,omitempty"` // 保存的日志文件份数,如 "3"
|
||||
}
|
||||
|
||||
// Settings Docker daemon 设置
|
||||
type Settings struct {
|
||||
RegistryMirrors []string `json:"registry-mirrors,omitempty"` // 注册表镜像
|
||||
InsecureRegistries []string `json:"insecure-registries,omitempty"` // 非安全镜像仓库
|
||||
LiveRestore bool `json:"live-restore,omitempty"` // Live restore
|
||||
LogDriver string `json:"log-driver,omitempty"` // 日志驱动
|
||||
LogOpts LogOpts `json:"log-opts,omitempty"` // 日志配置选项
|
||||
CgroupDriver string `json:"cgroup-driver,omitempty"` // cgroup 驱动(从 exec-opts 中提取)
|
||||
Hosts []string `json:"hosts,omitempty"` // Socket 路径
|
||||
DataRoot string `json:"data-root,omitempty"` // 数据目录
|
||||
StorageDriver string `json:"storage-driver,omitempty"` // 存储驱动
|
||||
DNS []string `json:"dns,omitempty"` // DNS 配置
|
||||
FirewallBackend string `json:"firewall-backend,omitempty"` // 防火墙后端 (iptables/nftables)
|
||||
Iptables *bool `json:"iptables,omitempty"` // iptables 规则
|
||||
Ip6tables *bool `json:"ip6tables,omitempty"` // ip6tables 规则
|
||||
IpForward *bool `json:"ip-forward,omitempty"` // IP 转发
|
||||
IPv6 *bool `json:"ipv6,omitempty"` // IPv6 支持
|
||||
Bip string `json:"bip,omitempty"` // 默认 bridge 网络 IP 段
|
||||
}
|
||||
|
||||
// UpdateSettings 更新设置请求
|
||||
type UpdateSettings struct {
|
||||
Settings Settings `json:"settings" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package docker
|
||||
|
||||
// DaemonConfig Docker daemon.json 完整配置结构
|
||||
type DaemonConfig struct {
|
||||
RegistryMirrors []string `json:"registry-mirrors,omitempty"`
|
||||
InsecureRegistries []string `json:"insecure-registries,omitempty"`
|
||||
LiveRestore bool `json:"live-restore,omitempty"`
|
||||
LogDriver string `json:"log-driver,omitempty"`
|
||||
LogOpts map[string]string `json:"log-opts,omitempty"`
|
||||
ExecOpts []string `json:"exec-opts,omitempty"`
|
||||
Hosts []string `json:"hosts,omitempty"`
|
||||
DataRoot string `json:"data-root,omitempty"`
|
||||
StorageDriver string `json:"storage-driver,omitempty"`
|
||||
DNS []string `json:"dns,omitempty"`
|
||||
FirewallBackend string `json:"firewall-backend,omitempty"`
|
||||
Iptables *bool `json:"iptables,omitempty"`
|
||||
Ip6tables *bool `json:"ip6tables,omitempty"`
|
||||
IpForward *bool `json:"ip-forward,omitempty"`
|
||||
IPv6 *bool `json:"ipv6,omitempty"`
|
||||
Bip string `json:"bip,omitempty"`
|
||||
// 其他原有配置字段保留
|
||||
Extra map[string]any `json:"-"`
|
||||
}
|
||||
@@ -7,23 +7,26 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/go-rat/utils/str"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/libtnb/chix"
|
||||
"github.com/libtnb/utils/str"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
"github.com/tnb-labs/panel/pkg/shell"
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/biz"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
"github.com/acepanel/panel/pkg/io"
|
||||
"github.com/acepanel/panel/pkg/shell"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
t *gotext.Locale
|
||||
websiteRepo biz.WebsiteRepo
|
||||
}
|
||||
|
||||
func NewApp(website biz.WebsiteRepo) *App {
|
||||
func NewApp(t *gotext.Locale, website biz.WebsiteRepo) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
websiteRepo: website,
|
||||
}
|
||||
}
|
||||
@@ -34,8 +37,8 @@ func (s *App) Route(r chi.Router) {
|
||||
r.Delete("/jails", s.Delete)
|
||||
r.Get("/jails/{name}", s.BanList)
|
||||
r.Post("/unban", s.Unban)
|
||||
r.Post("/whiteList", s.SetWhiteList)
|
||||
r.Get("/whiteList", s.GetWhiteList)
|
||||
r.Post("/white_list", s.SetWhiteList)
|
||||
r.Get("/white_list", s.GetWhiteList)
|
||||
}
|
||||
|
||||
// List 所有规则
|
||||
@@ -47,12 +50,8 @@ func (s *App) List(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
jailList := regexp.MustCompile(`\[(.*?)]`).FindAllStringSubmatch(raw, -1)
|
||||
if len(jailList) == 0 {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "Fail2ban 规则为空")
|
||||
return
|
||||
}
|
||||
|
||||
var jails []Jail
|
||||
jails := make([]Jail, 0)
|
||||
for i, jail := range jailList {
|
||||
if i == 0 {
|
||||
continue
|
||||
@@ -107,7 +106,7 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if (strings.Contains(raw, "["+jailName+"]") && jailType == "service") || (strings.Contains(raw, "["+jailWebsiteName+"]"+"-cc") && jailType == "website" && jailWebsiteMode == "cc") || (strings.Contains(raw, "["+jailWebsiteName+"]"+"-path") && jailType == "website" && jailWebsiteMode == "path") {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "规则已存在")
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("rule already exists"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -115,7 +114,7 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
|
||||
case "website":
|
||||
website, err := s.websiteRepo.GetByName(jailWebsiteName)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "获取网站配置失败:%v", err)
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
var ports string
|
||||
@@ -135,12 +134,12 @@ port = ` + ports + `
|
||||
maxretry = ` + jailMaxRetry + `
|
||||
findtime = ` + jailFindTime + `
|
||||
bantime = ` + jailBanTime + `
|
||||
logpath = ` + app.Root + `/wwwlogs/` + website.Name + `.log
|
||||
logpath = ` + app.Root + `/sites/` + website.Name + `/log/access.log
|
||||
# ` + jailWebsiteName + `-` + jailWebsiteMode + `-END
|
||||
`
|
||||
raw += rule
|
||||
if err = io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -159,7 +158,7 @@ ignoreregex =
|
||||
`
|
||||
}
|
||||
if err = io.Write("/etc/fail2ban/filter.d/haozi-"+jailWebsiteName+"-"+jailWebsiteMode+".conf", filter, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -178,11 +177,11 @@ ignoreregex =
|
||||
filter = "pure-ftpd"
|
||||
port, err = shell.Execf(`cat %s/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`, app.Root)
|
||||
default:
|
||||
service.Error(w, http.StatusUnprocessableEntity, "未知服务")
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("unknown service"))
|
||||
return
|
||||
}
|
||||
if len(port) == 0 || err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "获取服务端口失败,请检查是否安装")
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("get service port failed, please check if it is installed"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -199,13 +198,13 @@ bantime = ` + jailBanTime + `
|
||||
`
|
||||
raw += rule
|
||||
if err := io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = shell.Execf("fail2ban-client reload"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "重载配置失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -226,20 +225,20 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if !strings.Contains(raw, "["+req.Name+"]") {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "规则不存在")
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("rule not found"))
|
||||
return
|
||||
}
|
||||
|
||||
rule := str.Cut(raw, "# "+req.Name+"-START", "# "+req.Name+"-END")
|
||||
raw = strings.Replace(raw, "\n# "+req.Name+"-START"+rule+"# "+req.Name+"-END", "", -1)
|
||||
raw = strings.ReplaceAll(raw, "\n# "+req.Name+"-START"+rule+"# "+req.Name+"-END", "")
|
||||
raw = strings.TrimSpace(raw)
|
||||
if err := io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := shell.Execf("fail2ban-client reload"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "重载配置失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -256,17 +255,17 @@ func (s *App) BanList(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
currentlyBan, err := shell.Execf(`fail2ban-client status %s | grep "Currently banned" | awk '{print $4}'`, req.Name)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取封禁列表失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get current banned list"))
|
||||
return
|
||||
}
|
||||
totalBan, err := shell.Execf(`fail2ban-client status %s | grep "Total banned" | awk '{print $4}'`, req.Name)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取封禁列表失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get total banned list"))
|
||||
return
|
||||
}
|
||||
bannedIp, err := shell.Execf(`fail2ban-client status %s | grep "Banned IP list" | awk -F ":" '{print $2}'`, req.Name)
|
||||
bannedIp, err := shell.Execf(`fail2ban-client status %s | grep "Banned IP list" | sed 's/.*Banned IP list:[[:space:]]*//'`, req.Name)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取封禁列表失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get banned ip list"))
|
||||
return
|
||||
}
|
||||
bannedIpList := strings.Split(bannedIp, " ")
|
||||
@@ -300,7 +299,7 @@ func (s *App) Unban(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if _, err = shell.Execf("fail2ban-client set %s unbanip %s", req.Name, req.IP); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "解封失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -325,17 +324,17 @@ func (s *App) SetWhiteList(w http.ResponseWriter, r *http.Request) {
|
||||
if reg.MatchString(raw) {
|
||||
raw = reg.ReplaceAllString(raw, "ignoreip = "+req.IP+"\n")
|
||||
} else {
|
||||
service.Error(w, http.StatusInternalServerError, "解析Fail2ban规则失败,Fail2ban可能已损坏")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to parse the ignoreip of fail2ban"))
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write("/etc/fail2ban/jail.local", raw, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入Fail2ban规则失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = shell.Execf("fail2ban-client reload"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "重载配置失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
service.Success(w, nil)
|
||||
@@ -353,7 +352,7 @@ func (s *App) GetWhiteList(w http.ResponseWriter, r *http.Request) {
|
||||
ignoreIp := reg.FindStringSubmatch(raw)[1]
|
||||
service.Success(w, ignoreIp)
|
||||
} else {
|
||||
service.Error(w, http.StatusInternalServerError, "解析Fail2ban规则失败,Fail2ban可能已损坏")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to parse the ignoreip of fail2ban"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
"github.com/tnb-labs/panel/pkg/systemctl"
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
"github.com/acepanel/panel/pkg/io"
|
||||
"github.com/acepanel/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
type App struct{}
|
||||
@@ -21,6 +21,8 @@ func NewApp() *App {
|
||||
func (s *App) Route(r chi.Router) {
|
||||
r.Get("/config", s.GetConfig)
|
||||
r.Post("/config", s.UpdateConfig)
|
||||
r.Get("/user", s.GetUser)
|
||||
r.Post("/user", s.UpdateUser)
|
||||
}
|
||||
|
||||
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -58,3 +60,82 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *App) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[Name](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
servicePath := fmt.Sprintf("/etc/systemd/system/%s.service", req.Name)
|
||||
content, err := io.Read(servicePath)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
userInfo := UserInfo{}
|
||||
|
||||
// 解析 User 和 Group
|
||||
if matches := userCaptureRegex.FindStringSubmatch(content); len(matches) > 1 {
|
||||
userInfo.User = matches[1]
|
||||
}
|
||||
if matches := groupCaptureRegex.FindStringSubmatch(content); len(matches) > 1 {
|
||||
userInfo.Group = matches[1]
|
||||
}
|
||||
|
||||
service.Success(w, userInfo)
|
||||
}
|
||||
|
||||
func (s *App) UpdateUser(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[UpdateUser](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
servicePath := fmt.Sprintf("/etc/systemd/system/%s.service", req.Name)
|
||||
content, err := io.Read(servicePath)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查 User 和 Group 是否存在
|
||||
hasUser := userRegex.MatchString(content)
|
||||
hasGroup := groupRegex.MatchString(content)
|
||||
|
||||
// 替换或添加 User 和 Group 配置
|
||||
if hasUser && hasGroup {
|
||||
// 两者都存在,分别替换
|
||||
content = userRegex.ReplaceAllString(content, fmt.Sprintf("User=%s", req.User))
|
||||
content = groupRegex.ReplaceAllString(content, fmt.Sprintf("Group=%s", req.Group))
|
||||
} else if hasUser && !hasGroup {
|
||||
// 只有 User,替换 User 并添加 Group
|
||||
content = userRegex.ReplaceAllString(content, fmt.Sprintf("User=%s\nGroup=%s", req.User, req.Group))
|
||||
} else if !hasUser && hasGroup {
|
||||
// 只有 Group,添加 User 并替换 Group
|
||||
content = serviceRegex.ReplaceAllString(content, fmt.Sprintf("[Service]\nUser=%s", req.User))
|
||||
content = groupRegex.ReplaceAllString(content, fmt.Sprintf("Group=%s", req.Group))
|
||||
} else {
|
||||
// 两者都不存在,在 [Service] 后添加两者
|
||||
content = serviceRegex.ReplaceAllString(content, fmt.Sprintf("[Service]\nUser=%s\nGroup=%s", req.User, req.Group))
|
||||
}
|
||||
|
||||
if err = io.Write(servicePath, content, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = systemctl.DaemonReload(); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
if err = systemctl.Restart(req.Name); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
package frp
|
||||
|
||||
type Name struct {
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
Name string `form:"name" json:"name" validate:"required|in:frps,frpc"`
|
||||
}
|
||||
|
||||
type UpdateConfig struct {
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
Name string `form:"name" json:"name" validate:"required|in:frps,frpc"`
|
||||
Config string `form:"config" json:"config" validate:"required"`
|
||||
}
|
||||
|
||||
type UpdateUser struct {
|
||||
Name string `form:"name" json:"name" validate:"required|in:frps,frpc"`
|
||||
User string `form:"user" json:"user" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
|
||||
Group string `form:"group" json:"group" validate:"required|regex:^[a-zA-Z0-9_-]+$"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package frp
|
||||
|
||||
import "regexp"
|
||||
|
||||
var (
|
||||
userCaptureRegex = regexp.MustCompile(`(?m)^User=(.*)$`)
|
||||
groupCaptureRegex = regexp.MustCompile(`(?m)^Group=(.*)$`)
|
||||
userRegex = regexp.MustCompile(`(?m)^User=.*$`)
|
||||
groupRegex = regexp.MustCompile(`(?m)^Group=.*$`)
|
||||
serviceRegex = regexp.MustCompile(`(?m)^\[Service\]$`)
|
||||
)
|
||||
|
||||
type UserInfo struct {
|
||||
User string `json:"user"`
|
||||
Group string `json:"group"`
|
||||
}
|
||||
@@ -6,10 +6,10 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
"github.com/tnb-labs/panel/pkg/systemctl"
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
"github.com/acepanel/panel/pkg/io"
|
||||
"github.com/acepanel/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
type App struct{}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package mariadb
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/acepanel/panel/internal/apps/mysql"
|
||||
"github.com/acepanel/panel/internal/biz"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
mysql *mysql.App
|
||||
}
|
||||
|
||||
func NewApp(t *gotext.Locale, setting biz.SettingRepo, databaseServer biz.DatabaseServerRepo) *App {
|
||||
return &App{
|
||||
mysql: mysql.NewApp(t, setting, databaseServer),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.mysql.Route(r)
|
||||
}
|
||||
@@ -7,17 +7,22 @@ import (
|
||||
"regexp"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
"github.com/tnb-labs/panel/pkg/systemctl"
|
||||
"github.com/tnb-labs/panel/pkg/types"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
"github.com/acepanel/panel/pkg/io"
|
||||
"github.com/acepanel/panel/pkg/systemctl"
|
||||
"github.com/acepanel/panel/pkg/types"
|
||||
)
|
||||
|
||||
type App struct{}
|
||||
type App struct {
|
||||
t *gotext.Locale
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
func NewApp(t *gotext.Locale) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
@@ -29,7 +34,7 @@ func (s *App) Route(r chi.Router) {
|
||||
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
status, err := systemctl.Status("memcached")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to get Memcached status: %v", err)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get Memcached status: %v", err))
|
||||
return
|
||||
}
|
||||
if !status {
|
||||
@@ -42,11 +47,13 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
service.Success(w, []types.NV{})
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
defer func(conn net.Conn) {
|
||||
_ = conn.Close()
|
||||
}(conn)
|
||||
|
||||
_, err = conn.Write([]byte("stats\nquit\n"))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to write to Memcached: %v", err)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to write to Memcached: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -67,7 +74,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err = scanner.Err(); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to read from Memcached: %v", err)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to read from Memcached: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package minio
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
"github.com/acepanel/panel/pkg/io"
|
||||
"github.com/acepanel/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
type App struct{}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
r.Get("/env", s.GetEnv)
|
||||
r.Post("/env", s.UpdateEnv)
|
||||
}
|
||||
|
||||
func (s *App) GetEnv(w http.ResponseWriter, r *http.Request) {
|
||||
env, _ := io.Read("/etc/default/minio")
|
||||
service.Success(w, env)
|
||||
}
|
||||
|
||||
func (s *App) UpdateEnv(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[UpdateEnv](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write("/etc/default/minio", req.Env, 0600); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = systemctl.Restart("minio"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package minio
|
||||
|
||||
type UpdateEnv struct {
|
||||
Env string `form:"env" json:"env" validate:"required"`
|
||||
}
|
||||
+100
-63
@@ -6,27 +6,32 @@ import (
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"github.com/acepanel/panel/pkg/types"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/tnb-labs/panel/pkg/db"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
"github.com/tnb-labs/panel/pkg/shell"
|
||||
"github.com/tnb-labs/panel/pkg/systemctl"
|
||||
"github.com/tnb-labs/panel/pkg/tools"
|
||||
"github.com/tnb-labs/panel/pkg/types"
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/biz"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
"github.com/acepanel/panel/pkg/db"
|
||||
"github.com/acepanel/panel/pkg/io"
|
||||
"github.com/acepanel/panel/pkg/shell"
|
||||
"github.com/acepanel/panel/pkg/systemctl"
|
||||
"github.com/acepanel/panel/pkg/tools"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
settingRepo biz.SettingRepo
|
||||
t *gotext.Locale
|
||||
settingRepo biz.SettingRepo
|
||||
databaseServerRepo biz.DatabaseServerRepo
|
||||
}
|
||||
|
||||
func NewApp(setting biz.SettingRepo) *App {
|
||||
func NewApp(t *gotext.Locale, setting biz.SettingRepo, databaseServer biz.DatabaseServerRepo) *App {
|
||||
return &App{
|
||||
settingRepo: setting,
|
||||
t: t,
|
||||
settingRepo: setting,
|
||||
databaseServerRepo: databaseServer,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,18 +39,18 @@ func (s *App) Route(r chi.Router) {
|
||||
r.Get("/load", s.Load)
|
||||
r.Get("/config", s.GetConfig)
|
||||
r.Post("/config", s.UpdateConfig)
|
||||
r.Post("/clearErrorLog", s.ClearErrorLog)
|
||||
r.Get("/slowLog", s.SlowLog)
|
||||
r.Post("/clearSlowLog", s.ClearSlowLog)
|
||||
r.Get("/rootPassword", s.GetRootPassword)
|
||||
r.Post("/rootPassword", s.SetRootPassword)
|
||||
r.Post("/clear_log", s.ClearLog)
|
||||
r.Get("/slow_log", s.SlowLog)
|
||||
r.Post("/clear_slow_log", s.ClearSlowLog)
|
||||
r.Get("/root_password", s.GetRootPassword)
|
||||
r.Post("/root_password", s.SetRootPassword)
|
||||
}
|
||||
|
||||
// GetConfig 获取配置
|
||||
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
config, err := io.Read(app.Root + "/server/mysql/conf/my.cnf")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取配置失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -61,12 +66,12 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err = io.Write(app.Root+"/server/mysql/conf/my.cnf", req.Config, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入配置失败:%v", err)
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = systemctl.Restart("mysqld"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "重启失败:%v", err)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to restart MySQL: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -75,57 +80,55 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Load 获取负载
|
||||
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
rootPassword, err := s.settingRepo.Get(biz.SettingKeyMySQLRootPassword)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取root密码失败")
|
||||
return
|
||||
|
||||
}
|
||||
if len(rootPassword) == 0 {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "root密码为空")
|
||||
return
|
||||
}
|
||||
|
||||
status, _ := systemctl.Status("mysqld")
|
||||
if !status {
|
||||
service.Success(w, []types.NV{})
|
||||
return
|
||||
}
|
||||
|
||||
rootPassword, err := s.settingRepo.Get(biz.SettingKeyMySQLRootPassword)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to load MySQL root password: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err = os.Setenv("MYSQL_PWD", rootPassword); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "设置环境变量失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to set MYSQL_PWD env: %v", err))
|
||||
return
|
||||
}
|
||||
raw, err := shell.Execf(`mysqladmin -u root extended-status`)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取负载失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get MySQL status: %v", err))
|
||||
return
|
||||
}
|
||||
if err = os.Unsetenv("MYSQL_PWD"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to unset MYSQL_PWD env: %v", err))
|
||||
return
|
||||
}
|
||||
_ = os.Unsetenv("MYSQL_PWD")
|
||||
|
||||
var load []map[string]string
|
||||
expressions := []struct {
|
||||
regex string
|
||||
name string
|
||||
}{
|
||||
{`Uptime\s+\|\s+(\d+)\s+\|`, "运行时间"},
|
||||
{`Queries\s+\|\s+(\d+)\s+\|`, "总查询次数"},
|
||||
{`Connections\s+\|\s+(\d+)\s+\|`, "总连接次数"},
|
||||
{`Com_commit\s+\|\s+(\d+)\s+\|`, "每秒事务"},
|
||||
{`Com_rollback\s+\|\s+(\d+)\s+\|`, "每秒回滚"},
|
||||
{`Bytes_sent\s+\|\s+(\d+)\s+\|`, "发送"},
|
||||
{`Bytes_received\s+\|\s+(\d+)\s+\|`, "接收"},
|
||||
{`Threads_connected\s+\|\s+(\d+)\s+\|`, "活动连接数"},
|
||||
{`Max_used_connections\s+\|\s+(\d+)\s+\|`, "峰值连接数"},
|
||||
{`Key_read_requests\s+\|\s+(\d+)\s+\|`, "索引命中率"},
|
||||
{`Innodb_buffer_pool_reads\s+\|\s+(\d+)\s+\|`, "Innodb索引命中率"},
|
||||
{`Created_tmp_disk_tables\s+\|\s+(\d+)\s+\|`, "创建临时表到硬盘"},
|
||||
{`Open_tables\s+\|\s+(\d+)\s+\|`, "已打开的表"},
|
||||
{`Select_full_join\s+\|\s+(\d+)\s+\|`, "没有使用索引的量"},
|
||||
{`Select_full_range_join\s+\|\s+(\d+)\s+\|`, "没有索引的JOIN量"},
|
||||
{`Select_range_check\s+\|\s+(\d+)\s+\|`, "没有索引的子查询量"},
|
||||
{`Sort_merge_passes\s+\|\s+(\d+)\s+\|`, "排序后的合并次数"},
|
||||
{`Table_locks_waited\s+\|\s+(\d+)\s+\|`, "锁表次数"},
|
||||
{`Uptime\s+\|\s+(\d+)\s+\|`, s.t.Get("Uptime")},
|
||||
{`Queries\s+\|\s+(\d+)\s+\|`, s.t.Get("Total Queries")},
|
||||
{`Connections\s+\|\s+(\d+)\s+\|`, s.t.Get("Total Connections")},
|
||||
{`Com_commit\s+\|\s+(\d+)\s+\|`, s.t.Get("Transactions per Second")},
|
||||
{`Com_rollback\s+\|\s+(\d+)\s+\|`, s.t.Get("Rollbacks per Second")},
|
||||
{`Bytes_sent\s+\|\s+(\d+)\s+\|`, s.t.Get("Bytes Sent")},
|
||||
{`Bytes_received\s+\|\s+(\d+)\s+\|`, s.t.Get("Bytes Received")},
|
||||
{`Threads_connected\s+\|\s+(\d+)\s+\|`, s.t.Get("Active Connections")},
|
||||
{`Max_used_connections\s+\|\s+(\d+)\s+\|`, s.t.Get("Peak Connections")},
|
||||
{`Key_read_requests\s+\|\s+(\d+)\s+\|`, s.t.Get("Index Hit Rate")},
|
||||
{`Innodb_buffer_pool_reads\s+\|\s+(\d+)\s+\|`, s.t.Get("Innodb Index Hit Rate")},
|
||||
{`Created_tmp_disk_tables\s+\|\s+(\d+)\s+\|`, s.t.Get("Temporary Tables Created on Disk")},
|
||||
{`Open_tables\s+\|\s+(\d+)\s+\|`, s.t.Get("Open Tables")},
|
||||
{`Select_full_join\s+\|\s+(\d+)\s+\|`, s.t.Get("Full Joins without Index")},
|
||||
{`Select_full_range_join\s+\|\s+(\d+)\s+\|`, s.t.Get("Full Range Joins without Index")},
|
||||
{`Select_range_check\s+\|\s+(\d+)\s+\|`, s.t.Get("Subqueries without Index")},
|
||||
{`Sort_merge_passes\s+\|\s+(\d+)\s+\|`, s.t.Get("Sort Merge Passes")},
|
||||
{`Table_locks_waited\s+\|\s+(\d+)\s+\|`, s.t.Get("Table Locks Waited")},
|
||||
}
|
||||
|
||||
for _, expression := range expressions {
|
||||
@@ -133,7 +136,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
matches := re.FindStringSubmatch(raw)
|
||||
if len(matches) > 1 {
|
||||
d := map[string]string{"name": expression.name, "value": matches[1]}
|
||||
if expression.name == "发送" || expression.name == "接收" {
|
||||
if expression.name == s.t.Get("Bytes Sent") || expression.name == s.t.Get("Bytes Received") {
|
||||
d["value"] = tools.FormatBytes(cast.ToFloat64(matches[1]))
|
||||
}
|
||||
|
||||
@@ -150,12 +153,47 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
bufferPoolReadRequests := cast.ToFloat64(load[12]["value"])
|
||||
load[10]["value"] = fmt.Sprintf("%.2f%%", bufferPoolReadRequests/(bufferPoolReads+bufferPoolReadRequests)*100)
|
||||
|
||||
// 查询缓存命中率
|
||||
// MySQL 8.0+ 删除了查询缓存功能
|
||||
qcacheHitsRe := regexp.MustCompile(`Qcache_hits\s+\|\s+(\d+)\s+\|`)
|
||||
qcacheHitsMatches := qcacheHitsRe.FindStringSubmatch(raw)
|
||||
qcacheInsertsRe := regexp.MustCompile(`Qcache_inserts\s+\|\s+(\d+)\s+\|`)
|
||||
qcacheInsertsMatches := qcacheInsertsRe.FindStringSubmatch(raw)
|
||||
qcacheNotCachedRe := regexp.MustCompile(`Qcache_not_cached\s+\|\s+(\d+)\s+\|`)
|
||||
qcacheNotCachedMatches := qcacheNotCachedRe.FindStringSubmatch(raw)
|
||||
if len(qcacheHitsMatches) > 1 && len(qcacheInsertsMatches) > 1 && len(qcacheNotCachedMatches) > 1 {
|
||||
qcacheHits := cast.ToFloat64(qcacheHitsMatches[1])
|
||||
qcacheInserts := cast.ToFloat64(qcacheInsertsMatches[1])
|
||||
qcacheNotCached := cast.ToFloat64(qcacheNotCachedMatches[1])
|
||||
var qcacheHitRate float64
|
||||
denominator := qcacheHits + qcacheInserts + qcacheNotCached
|
||||
if denominator > 0 {
|
||||
qcacheHitRate = qcacheHits / denominator * 100
|
||||
}
|
||||
load = append(load, map[string]string{
|
||||
"name": s.t.Get("Query Cache Hits"),
|
||||
"value": qcacheHitsMatches[1],
|
||||
})
|
||||
load = append(load, map[string]string{
|
||||
"name": s.t.Get("Query Cache Inserts"),
|
||||
"value": qcacheInsertsMatches[1],
|
||||
})
|
||||
load = append(load, map[string]string{
|
||||
"name": s.t.Get("Query Cache Not Cached"),
|
||||
"value": qcacheNotCachedMatches[1],
|
||||
})
|
||||
load = append(load, map[string]string{
|
||||
"name": s.t.Get("Query Cache Hit Rate"),
|
||||
"value": fmt.Sprintf("%.2f%%", qcacheHitRate),
|
||||
})
|
||||
}
|
||||
|
||||
service.Success(w, load)
|
||||
}
|
||||
|
||||
// ClearErrorLog 清空错误日志
|
||||
func (s *App) ClearErrorLog(w http.ResponseWriter, r *http.Request) {
|
||||
if err := systemctl.LogsClear("mysqld"); err != nil {
|
||||
// ClearLog 清空日志
|
||||
func (s *App) ClearLog(w http.ResponseWriter, r *http.Request) {
|
||||
if err := systemctl.LogClear("mysqld"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
@@ -170,7 +208,7 @@ func (s *App) SlowLog(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// ClearSlowLog 清空慢查询日志
|
||||
func (s *App) ClearSlowLog(w http.ResponseWriter, r *http.Request) {
|
||||
if _, err := shell.Execf("echo '' > %s/server/mysql/mysql-slow.log", app.Root); err != nil {
|
||||
if _, err := shell.Execf("cat /dev/null > %s/server/mysql/mysql-slow.log", app.Root); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
@@ -182,11 +220,7 @@ func (s *App) ClearSlowLog(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *App) GetRootPassword(w http.ResponseWriter, r *http.Request) {
|
||||
rootPassword, err := s.settingRepo.Get(biz.SettingKeyMySQLRootPassword)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取root密码失败")
|
||||
return
|
||||
}
|
||||
if len(rootPassword) == 0 {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "root密码为空")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to load MySQL root password: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -216,11 +250,14 @@ func (s *App) SetRootPassword(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = s.settingRepo.Set(biz.SettingKeyMySQLRootPassword, req.Password); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "设置保存失败:%v", err)
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
_ = s.databaseServerRepo.UpdatePassword("local_mysql", req.Password)
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
|
||||
+42
-29
@@ -8,37 +8,49 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
"github.com/tnb-labs/panel/pkg/shell"
|
||||
"github.com/tnb-labs/panel/pkg/systemctl"
|
||||
"github.com/tnb-labs/panel/pkg/tools"
|
||||
"github.com/tnb-labs/panel/pkg/types"
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
"github.com/acepanel/panel/pkg/io"
|
||||
"github.com/acepanel/panel/pkg/shell"
|
||||
"github.com/acepanel/panel/pkg/systemctl"
|
||||
"github.com/acepanel/panel/pkg/tools"
|
||||
"github.com/acepanel/panel/pkg/types"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
// Dependent services
|
||||
t *gotext.Locale
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
func NewApp(t *gotext.Locale) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
r.Get("/load", s.Load)
|
||||
r.Get("/config", s.GetConfig)
|
||||
r.Post("/config", s.SaveConfig)
|
||||
r.Get("/errorLog", s.ErrorLog)
|
||||
r.Post("/clearErrorLog", s.ClearErrorLog)
|
||||
r.Get("/error_log", s.ErrorLog)
|
||||
r.Post("/clear_error_log", s.ClearErrorLog)
|
||||
|
||||
r.Get("/stream/servers", s.ListStreamServers)
|
||||
r.Post("/stream/servers", s.CreateStreamServer)
|
||||
r.Put("/stream/servers/{name}", s.UpdateStreamServer)
|
||||
r.Delete("/stream/servers/{name}", s.DeleteStreamServer)
|
||||
r.Get("/stream/upstreams", s.ListStreamUpstreams)
|
||||
r.Post("/stream/upstreams", s.CreateStreamUpstream)
|
||||
r.Put("/stream/upstreams/{name}", s.UpdateStreamUpstream)
|
||||
r.Delete("/stream/upstreams/{name}", s.DeleteStreamUpstream)
|
||||
}
|
||||
|
||||
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
config, err := io.Read(fmt.Sprintf("%s/server/nginx/conf/nginx.conf", app.Root))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取配置失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -52,14 +64,14 @@ func (s *App) SaveConfig(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write(fmt.Sprintf("%s/server/nginx/conf/nginx.conf", app.Root), req.Config, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "保存配置失败")
|
||||
if err = io.Write(fmt.Sprintf("%s/server/nginx/conf/nginx.conf", app.Root), req.Config, 0600); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = systemctl.Reload("nginx"); err != nil {
|
||||
_, err = shell.Execf("nginx -t")
|
||||
service.Error(w, http.StatusInternalServerError, "重载服务失败: %v", err)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload nginx: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -67,11 +79,11 @@ func (s *App) SaveConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (s *App) ErrorLog(w http.ResponseWriter, r *http.Request) {
|
||||
service.Success(w, fmt.Sprintf("%s/%s", app.Root, "wwwlogs/nginx-error.log"))
|
||||
service.Success(w, fmt.Sprintf("%s/%s", app.Root, "server/nginx/nginx-error.log"))
|
||||
}
|
||||
|
||||
func (s *App) ClearErrorLog(w http.ResponseWriter, r *http.Request) {
|
||||
if _, err := shell.Execf("echo '' > %s/%s", app.Root, "wwwlogs/nginx-error.log"); err != nil {
|
||||
if _, err := shell.Execf("cat /dev/null > %s/%s", app.Root, "server/nginx/nginx-error.log"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
@@ -84,6 +96,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
resp, err := client.R().Get("http://127.0.0.1/nginx_status")
|
||||
if err != nil || !resp.IsSuccess() {
|
||||
service.Success(w, []types.NV{})
|
||||
return
|
||||
}
|
||||
|
||||
raw := resp.String()
|
||||
@@ -91,29 +104,29 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
workers, err := shell.Execf("ps aux | grep nginx | grep 'worker process' | wc -l")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取负载失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get nginx workers: %v", err))
|
||||
return
|
||||
}
|
||||
data = append(data, types.NV{
|
||||
Name: "工作进程",
|
||||
Name: s.t.Get("Workers"),
|
||||
Value: workers,
|
||||
})
|
||||
|
||||
out, err := shell.Execf("ps aux | grep nginx | grep 'worker process' | awk '{memsum+=$6};END {print memsum}'")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取负载失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get nginx workers: %v", err))
|
||||
return
|
||||
}
|
||||
mem := tools.FormatBytes(cast.ToFloat64(out))
|
||||
data = append(data, types.NV{
|
||||
Name: "内存占用",
|
||||
Name: s.t.Get("Memory"),
|
||||
Value: mem,
|
||||
})
|
||||
|
||||
match := regexp.MustCompile(`Active connections:\s+(\d+)`).FindStringSubmatch(raw)
|
||||
if len(match) == 2 {
|
||||
data = append(data, types.NV{
|
||||
Name: "活跃连接数",
|
||||
Name: s.t.Get("Active connections"),
|
||||
Value: match[1],
|
||||
})
|
||||
}
|
||||
@@ -121,15 +134,15 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
match = regexp.MustCompile(`server accepts handled requests\s+(\d+)\s+(\d+)\s+(\d+)`).FindStringSubmatch(raw)
|
||||
if len(match) == 4 {
|
||||
data = append(data, types.NV{
|
||||
Name: "总连接次数",
|
||||
Name: s.t.Get("Total connections"),
|
||||
Value: match[1],
|
||||
})
|
||||
data = append(data, types.NV{
|
||||
Name: "总握手次数",
|
||||
Name: s.t.Get("Total handshakes"),
|
||||
Value: match[2],
|
||||
})
|
||||
data = append(data, types.NV{
|
||||
Name: "总请求次数",
|
||||
Name: s.t.Get("Total requests"),
|
||||
Value: match[3],
|
||||
})
|
||||
}
|
||||
@@ -137,15 +150,15 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
match = regexp.MustCompile(`Reading:\s+(\d+)\s+Writing:\s+(\d+)\s+Waiting:\s+(\d+)`).FindStringSubmatch(raw)
|
||||
if len(match) == 4 {
|
||||
data = append(data, types.NV{
|
||||
Name: "请求数",
|
||||
Name: s.t.Get("Reading"),
|
||||
Value: match[1],
|
||||
})
|
||||
data = append(data, types.NV{
|
||||
Name: "响应数",
|
||||
Name: s.t.Get("Writing"),
|
||||
Value: match[2],
|
||||
})
|
||||
data = append(data, types.NV{
|
||||
Name: "驻留进程",
|
||||
Name: s.t.Get("Waiting"),
|
||||
Value: match[3],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
package nginx
|
||||
|
||||
import "time"
|
||||
|
||||
type UpdateConfig struct {
|
||||
Config string `form:"config" json:"config" validate:"required"`
|
||||
}
|
||||
|
||||
type StreamServer struct {
|
||||
Name string `form:"name" json:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"` // 配置名称,用于文件命名
|
||||
Listen string `form:"listen" json:"listen" validate:"required"` // 监听地址,如: "12345", "0.0.0.0:12345", "[::]:12345"
|
||||
UDP bool `form:"udp" json:"udp"` // 是否 UDP 协议
|
||||
ProxyPass string `form:"proxy_pass" json:"proxy_pass" validate:"required"` // 代理地址,如: "127.0.0.1:3306", "upstream_name"
|
||||
ProxyProtocol bool `form:"proxy_protocol" json:"proxy_protocol"` // 是否启用 PROXY 协议
|
||||
ProxyTimeout time.Duration `form:"proxy_timeout" json:"proxy_timeout"` // 代理超时时间
|
||||
ProxyConnectTimeout time.Duration `form:"proxy_connect_timeout" json:"proxy_connect_timeout"` // 代理连接超时时间
|
||||
SSL bool `form:"ssl" json:"ssl"` // 是否启用 SSL
|
||||
SSLCertificate string `form:"ssl_certificate" json:"ssl_certificate" validate:"requiredIf:SSL,true"` // SSL 证书路径
|
||||
SSLCertificateKey string `form:"ssl_certificate_key" json:"ssl_certificate_key" validate:"requiredIf:SSL,true"` // SSL 私钥路径
|
||||
}
|
||||
|
||||
type StreamUpstream struct {
|
||||
Name string `form:"name" json:"name" validate:"required|regex:^[a-zA-Z0-9_-]+$"` // 上游名称
|
||||
Servers map[string]string `form:"servers" json:"servers" validate:"required"` // 上游服务器及配置,如: map["127.0.0.1:3306"] = "weight=5"
|
||||
Algo string `form:"algo" json:"algo"` // 负载均衡算法,如: "least_conn", "hash $remote_addr"
|
||||
Resolver []string `form:"resolver" json:"resolver"` // DNS 解析器,如: ["8.8.8.8", "ipv6=off"]
|
||||
ResolverTimeout time.Duration `form:"resolver_timeout" json:"resolver_timeout"` // DNS 解析超时时间
|
||||
}
|
||||
|
||||
@@ -0,0 +1,642 @@
|
||||
package nginx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
"github.com/acepanel/panel/pkg/systemctl"
|
||||
webserverNginx "github.com/acepanel/panel/pkg/webserver/nginx"
|
||||
)
|
||||
|
||||
// ListStreamServers 获取 Stream Server 列表
|
||||
func (s *App) ListStreamServers(w http.ResponseWriter, r *http.Request) {
|
||||
servers, err := s.parseStreamServers()
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to list stream servers: %v", err))
|
||||
return
|
||||
}
|
||||
service.Success(w, servers)
|
||||
}
|
||||
|
||||
// CreateStreamServer 创建 Stream Server
|
||||
func (s *App) CreateStreamServer(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[StreamServer](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
configPath := filepath.Join(s.streamDir(), fmt.Sprintf("%s.conf", req.Name))
|
||||
if _, statErr := os.Stat(configPath); statErr == nil {
|
||||
service.Error(w, http.StatusConflict, s.t.Get("stream server config already exists: %s", req.Name))
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.saveStreamServerConfig(configPath, req); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to write stream server config: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err = systemctl.Reload("nginx"); err != nil {
|
||||
_ = os.Remove(configPath)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload nginx: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
// UpdateStreamServer 更新 Stream Server
|
||||
func (s *App) UpdateStreamServer(w http.ResponseWriter, r *http.Request) {
|
||||
name := chi.URLParam(r, "name")
|
||||
if name == "" {
|
||||
service.Error(w, http.StatusBadRequest, s.t.Get("name is required"))
|
||||
return
|
||||
}
|
||||
|
||||
req, err := service.Bind[StreamServer](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
configPath := filepath.Join(s.streamDir(), fmt.Sprintf("%s.conf", name))
|
||||
if _, statErr := os.Stat(configPath); os.IsNotExist(statErr) {
|
||||
service.Error(w, http.StatusNotFound, s.t.Get("stream server not found: %s", name))
|
||||
return
|
||||
}
|
||||
|
||||
newConfigPath := configPath
|
||||
if req.Name != name {
|
||||
newConfigPath = filepath.Join(s.streamDir(), fmt.Sprintf("%s.conf", req.Name))
|
||||
if _, statErr := os.Stat(newConfigPath); statErr == nil {
|
||||
service.Error(w, http.StatusConflict, s.t.Get("stream server config already exists: %s", req.Name))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = s.saveStreamServerConfig(newConfigPath, req); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to write stream server config: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if newConfigPath != configPath {
|
||||
_ = os.Remove(configPath)
|
||||
}
|
||||
|
||||
if err = systemctl.Reload("nginx"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload nginx: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
// DeleteStreamServer 删除 Stream Server
|
||||
func (s *App) DeleteStreamServer(w http.ResponseWriter, r *http.Request) {
|
||||
name := chi.URLParam(r, "name")
|
||||
if name == "" {
|
||||
service.Error(w, http.StatusBadRequest, s.t.Get("name is required"))
|
||||
return
|
||||
}
|
||||
|
||||
configPath := filepath.Join(s.streamDir(), fmt.Sprintf("%s.conf", name))
|
||||
if _, statErr := os.Stat(configPath); os.IsNotExist(statErr) {
|
||||
service.Error(w, http.StatusNotFound, s.t.Get("stream server not found: %s", name))
|
||||
return
|
||||
}
|
||||
|
||||
if err := os.Remove(configPath); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to delete stream server config: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err := systemctl.Reload("nginx"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload nginx: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
// ListStreamUpstreams 获取 Stream Upstream 列表
|
||||
func (s *App) ListStreamUpstreams(w http.ResponseWriter, r *http.Request) {
|
||||
upstreams, err := s.parseStreamUpstreams()
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to list stream upstreams: %v", err))
|
||||
return
|
||||
}
|
||||
service.Success(w, upstreams)
|
||||
}
|
||||
|
||||
// CreateStreamUpstream 创建 Stream Upstream
|
||||
func (s *App) CreateStreamUpstream(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[StreamUpstream](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
configPath := filepath.Join(s.streamDir(), fmt.Sprintf("upstream_%s.conf", req.Name))
|
||||
if _, statErr := os.Stat(configPath); statErr == nil {
|
||||
service.Error(w, http.StatusConflict, s.t.Get("stream upstream config already exists: %s", req.Name))
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.saveStreamUpstreamConfig(configPath, req); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to write stream upstream config: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err = systemctl.Reload("nginx"); err != nil {
|
||||
_ = os.Remove(configPath)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload nginx: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
// UpdateStreamUpstream 更新 Stream Upstream
|
||||
func (s *App) UpdateStreamUpstream(w http.ResponseWriter, r *http.Request) {
|
||||
name := chi.URLParam(r, "name")
|
||||
if name == "" {
|
||||
service.Error(w, http.StatusBadRequest, s.t.Get("name is required"))
|
||||
return
|
||||
}
|
||||
|
||||
req, err := service.Bind[StreamUpstream](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
configPath := filepath.Join(s.streamDir(), fmt.Sprintf("upstream_%s.conf", name))
|
||||
if _, statErr := os.Stat(configPath); os.IsNotExist(statErr) {
|
||||
service.Error(w, http.StatusNotFound, s.t.Get("stream upstream not found: %s", name))
|
||||
return
|
||||
}
|
||||
|
||||
newConfigPath := configPath
|
||||
if req.Name != name {
|
||||
newConfigPath = filepath.Join(s.streamDir(), fmt.Sprintf("upstream_%s.conf", req.Name))
|
||||
if _, statErr := os.Stat(newConfigPath); statErr == nil {
|
||||
service.Error(w, http.StatusConflict, s.t.Get("stream upstream config already exists: %s", req.Name))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = s.saveStreamUpstreamConfig(newConfigPath, req); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to write stream upstream config: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if newConfigPath != configPath {
|
||||
_ = os.Remove(configPath)
|
||||
}
|
||||
|
||||
if err = systemctl.Reload("nginx"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload nginx: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
// DeleteStreamUpstream 删除 Stream Upstream
|
||||
func (s *App) DeleteStreamUpstream(w http.ResponseWriter, r *http.Request) {
|
||||
name := chi.URLParam(r, "name")
|
||||
if name == "" {
|
||||
service.Error(w, http.StatusBadRequest, s.t.Get("name is required"))
|
||||
return
|
||||
}
|
||||
|
||||
configPath := filepath.Join(s.streamDir(), fmt.Sprintf("upstream_%s.conf", name))
|
||||
if _, statErr := os.Stat(configPath); os.IsNotExist(statErr) {
|
||||
service.Error(w, http.StatusNotFound, s.t.Get("stream upstream not found: %s", name))
|
||||
return
|
||||
}
|
||||
|
||||
if err := os.Remove(configPath); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to delete stream upstream config: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err := systemctl.Reload("nginx"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload nginx: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
// parseStreamServers 解析所有 Stream Server 配置
|
||||
func (s *App) parseStreamServers() ([]StreamServer, error) {
|
||||
entries, err := os.ReadDir(s.streamDir())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
servers := make([]StreamServer, 0)
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
fileName := entry.Name()
|
||||
// 跳过 upstream 配置文件
|
||||
if strings.HasPrefix(fileName, "upstream_") {
|
||||
continue
|
||||
}
|
||||
if !strings.HasSuffix(fileName, ".conf") {
|
||||
continue
|
||||
}
|
||||
|
||||
name := strings.TrimSuffix(fileName, ".conf")
|
||||
configPath := filepath.Join(s.streamDir(), fileName)
|
||||
server, err := s.parseStreamServerFile(configPath, name)
|
||||
if err != nil {
|
||||
continue // 跳过解析失败的文件
|
||||
}
|
||||
if server != nil {
|
||||
servers = append(servers, *server)
|
||||
}
|
||||
}
|
||||
|
||||
// 按名称排序
|
||||
sort.Slice(servers, func(i, j int) bool {
|
||||
return servers[i].Name < servers[j].Name
|
||||
})
|
||||
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
// parseStreamServerFile 解析单个 Stream Server 配置文件
|
||||
func (s *App) parseStreamServerFile(filePath string, name string) (*StreamServer, error) {
|
||||
p, err := webserverNginx.NewParserFromFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
server := &StreamServer{
|
||||
Name: name,
|
||||
}
|
||||
|
||||
// 解析 listen 指令
|
||||
listenDirs, err := p.Find("server.listen")
|
||||
if err == nil && len(listenDirs) > 0 {
|
||||
params := listenDirs[0].GetParameters()
|
||||
if len(params) > 0 {
|
||||
server.Listen = params[0].Value
|
||||
for i := 1; i < len(params); i++ {
|
||||
switch params[i].Value {
|
||||
case "udp":
|
||||
server.UDP = true
|
||||
case "ssl":
|
||||
server.SSL = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 解析 proxy_pass 指令
|
||||
proxyPassDir, err := p.FindOne("server.proxy_pass")
|
||||
if err == nil {
|
||||
params := proxyPassDir.GetParameters()
|
||||
if len(params) > 0 {
|
||||
server.ProxyPass = params[0].Value
|
||||
}
|
||||
}
|
||||
// 解析 proxy_protocol 指令
|
||||
proxyProtocolDir, err := p.FindOne("server.proxy_protocol")
|
||||
if err == nil {
|
||||
params := proxyProtocolDir.GetParameters()
|
||||
if len(params) > 0 && params[0].Value == "on" {
|
||||
server.ProxyProtocol = true
|
||||
}
|
||||
}
|
||||
// 解析 proxy_timeout 指令
|
||||
proxyTimeoutDir, err := p.FindOne("server.proxy_timeout")
|
||||
if err == nil {
|
||||
params := proxyTimeoutDir.GetParameters()
|
||||
if len(params) > 0 {
|
||||
server.ProxyTimeout = parseNginxDuration(params[0].Value)
|
||||
}
|
||||
}
|
||||
// 解析 proxy_connect_timeout 指令
|
||||
proxyConnectTimeoutDir, err := p.FindOne("server.proxy_connect_timeout")
|
||||
if err == nil {
|
||||
params := proxyConnectTimeoutDir.GetParameters()
|
||||
if len(params) > 0 {
|
||||
server.ProxyConnectTimeout = parseNginxDuration(params[0].Value)
|
||||
}
|
||||
}
|
||||
// 解析 ssl_certificate 指令
|
||||
sslCertDir, err := p.FindOne("server.ssl_certificate")
|
||||
if err == nil {
|
||||
params := sslCertDir.GetParameters()
|
||||
if len(params) > 0 {
|
||||
server.SSLCertificate = params[0].Value
|
||||
}
|
||||
}
|
||||
// 解析 ssl_certificate_key 指令
|
||||
sslKeyDir, err := p.FindOne("server.ssl_certificate_key")
|
||||
if err == nil {
|
||||
params := sslKeyDir.GetParameters()
|
||||
if len(params) > 0 {
|
||||
server.SSLCertificateKey = params[0].Value
|
||||
}
|
||||
}
|
||||
|
||||
return server, nil
|
||||
}
|
||||
|
||||
// parseStreamUpstreams 解析所有 Stream Upstream 配置
|
||||
func (s *App) parseStreamUpstreams() ([]StreamUpstream, error) {
|
||||
entries, err := os.ReadDir(s.streamDir())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
upstreams := make([]StreamUpstream, 0)
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
fileName := entry.Name()
|
||||
// 只处理 upstream 配置文件
|
||||
if !strings.HasPrefix(fileName, "upstream_") {
|
||||
continue
|
||||
}
|
||||
if !strings.HasSuffix(fileName, ".conf") {
|
||||
continue
|
||||
}
|
||||
|
||||
name := strings.TrimPrefix(fileName, "upstream_")
|
||||
name = strings.TrimSuffix(name, ".conf")
|
||||
configPath := filepath.Join(s.streamDir(), fileName)
|
||||
upstream, err := s.parseStreamUpstreamFile(configPath, name)
|
||||
if err != nil {
|
||||
continue // 跳过解析失败的文件
|
||||
}
|
||||
if upstream != nil {
|
||||
upstreams = append(upstreams, *upstream)
|
||||
}
|
||||
}
|
||||
|
||||
// 按名称排序
|
||||
sort.Slice(upstreams, func(i, j int) bool {
|
||||
return upstreams[i].Name < upstreams[j].Name
|
||||
})
|
||||
|
||||
return upstreams, nil
|
||||
}
|
||||
|
||||
// parseStreamUpstreamFile 解析单个 Stream Upstream 配置文件
|
||||
func (s *App) parseStreamUpstreamFile(filePath string, expectedName string) (*StreamUpstream, error) {
|
||||
p, err := webserverNginx.NewParserFromFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := p.Config()
|
||||
if cfg == nil || cfg.Block == nil {
|
||||
return nil, fmt.Errorf("invalid config")
|
||||
}
|
||||
|
||||
// 查找 upstream 块
|
||||
upstreamDirectives := cfg.Block.FindDirectives("upstream")
|
||||
if len(upstreamDirectives) == 0 {
|
||||
return nil, fmt.Errorf("no upstream block found")
|
||||
}
|
||||
|
||||
upstreamDir := upstreamDirectives[0]
|
||||
params := upstreamDir.GetParameters()
|
||||
if len(params) == 0 {
|
||||
return nil, fmt.Errorf("upstream name not found")
|
||||
}
|
||||
|
||||
name := params[0].Value
|
||||
if expectedName != "" && name != expectedName {
|
||||
return nil, fmt.Errorf("upstream name mismatch")
|
||||
}
|
||||
|
||||
upstream := &StreamUpstream{
|
||||
Name: name,
|
||||
Servers: make(map[string]string),
|
||||
Resolver: []string{},
|
||||
}
|
||||
|
||||
upstreamBlock := upstreamDir.GetBlock()
|
||||
if upstreamBlock == nil {
|
||||
return nil, fmt.Errorf("upstream block is empty")
|
||||
}
|
||||
|
||||
// 解析 upstream 块中的指令
|
||||
for _, dir := range upstreamBlock.GetDirectives() {
|
||||
switch dir.GetName() {
|
||||
case "server":
|
||||
dirParams := dir.GetParameters()
|
||||
if len(dirParams) > 0 {
|
||||
addr := dirParams[0].Value
|
||||
var options []string
|
||||
for i := 1; i < len(dirParams); i++ {
|
||||
options = append(options, dirParams[i].Value)
|
||||
}
|
||||
upstream.Servers[addr] = strings.Join(options, " ")
|
||||
}
|
||||
case "least_conn", "ip_hash", "random":
|
||||
upstream.Algo = dir.GetName()
|
||||
case "hash":
|
||||
dirParams := dir.GetParameters()
|
||||
if len(dirParams) > 0 {
|
||||
upstream.Algo = "hash " + dirParams[0].Value
|
||||
// 检查是否有 consistent 参数
|
||||
if len(dirParams) > 1 && dirParams[1].Value == "consistent" {
|
||||
upstream.Algo += " consistent"
|
||||
}
|
||||
}
|
||||
case "least_time":
|
||||
dirParams := dir.GetParameters()
|
||||
if len(dirParams) > 0 {
|
||||
upstream.Algo = "least_time " + dirParams[0].Value
|
||||
}
|
||||
case "resolver":
|
||||
dirParams := dir.GetParameters()
|
||||
for _, param := range dirParams {
|
||||
upstream.Resolver = append(upstream.Resolver, param.Value)
|
||||
}
|
||||
case "resolver_timeout":
|
||||
dirParams := dir.GetParameters()
|
||||
if len(dirParams) > 0 {
|
||||
upstream.ResolverTimeout = parseNginxDuration(dirParams[0].Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return upstream, nil
|
||||
}
|
||||
|
||||
// saveStreamServerConfig 生成并保存 Stream Server 配置
|
||||
func (s *App) saveStreamServerConfig(filePath string, server *StreamServer) error {
|
||||
p, err := webserverNginx.NewParserFromString("server {}")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.SetConfigPath(filePath)
|
||||
|
||||
// listen 指令
|
||||
listenParams := []string{server.Listen}
|
||||
if server.UDP {
|
||||
listenParams = append(listenParams, "udp")
|
||||
}
|
||||
if server.SSL {
|
||||
listenParams = append(listenParams, "ssl")
|
||||
}
|
||||
if err = p.SetOne("server.listen", listenParams); err != nil {
|
||||
return err
|
||||
}
|
||||
// proxy_pass 指令
|
||||
if err = p.SetOne("server.proxy_pass", []string{server.ProxyPass}); err != nil {
|
||||
return err
|
||||
}
|
||||
// proxy_protocol 指令
|
||||
if server.ProxyProtocol {
|
||||
if err = p.SetOne("server.proxy_protocol", []string{"on"}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// proxy_timeout 指令
|
||||
if server.ProxyTimeout > 0 {
|
||||
if err = p.SetOne("server.proxy_timeout", []string{formatNginxDuration(server.ProxyTimeout)}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// proxy_connect_timeout 指令
|
||||
if server.ProxyConnectTimeout > 0 {
|
||||
if err = p.SetOne("server.proxy_connect_timeout", []string{formatNginxDuration(server.ProxyConnectTimeout)}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// SSL 配置
|
||||
if server.SSL {
|
||||
if server.SSLCertificate != "" {
|
||||
if err = p.SetOne("server.ssl_certificate", []string{server.SSLCertificate}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if server.SSLCertificateKey != "" {
|
||||
if err = p.SetOne("server.ssl_certificate_key", []string{server.SSLCertificateKey}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return os.WriteFile(filePath, []byte(p.Dump()), 0600)
|
||||
}
|
||||
|
||||
// saveStreamUpstreamConfig 生成并保存 Stream Upstream 配置
|
||||
func (s *App) saveStreamUpstreamConfig(filePath string, upstream *StreamUpstream) error {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(fmt.Sprintf("upstream %s {\n", upstream.Name))
|
||||
|
||||
// 负载均衡算法
|
||||
if upstream.Algo != "" {
|
||||
sb.WriteString(fmt.Sprintf(" %s;\n", upstream.Algo))
|
||||
}
|
||||
|
||||
// resolver 配置
|
||||
if len(upstream.Resolver) > 0 {
|
||||
sb.WriteString(fmt.Sprintf(" resolver %s;\n", strings.Join(upstream.Resolver, " ")))
|
||||
if upstream.ResolverTimeout > 0 {
|
||||
sb.WriteString(fmt.Sprintf(" resolver_timeout %s;\n", formatNginxDuration(upstream.ResolverTimeout)))
|
||||
}
|
||||
}
|
||||
|
||||
// 服务器列表
|
||||
var addrs []string
|
||||
for addr := range upstream.Servers {
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
sort.Strings(addrs)
|
||||
|
||||
for _, addr := range addrs {
|
||||
options := upstream.Servers[addr]
|
||||
if options != "" {
|
||||
sb.WriteString(fmt.Sprintf(" server %s %s;\n", addr, options))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf(" server %s;\n", addr))
|
||||
}
|
||||
}
|
||||
|
||||
sb.WriteString("}\n")
|
||||
|
||||
return os.WriteFile(filePath, []byte(sb.String()), 0600)
|
||||
}
|
||||
|
||||
// parseNginxDuration 解析 Nginx 时间格式(如 10s, 1m, 1h)
|
||||
func parseNginxDuration(value string) time.Duration {
|
||||
if value == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 尝试解析带单位的时间
|
||||
value = strings.TrimSpace(value)
|
||||
if len(value) == 0 {
|
||||
return 0
|
||||
}
|
||||
if len(value) == 1 {
|
||||
value += "s" // 单个字符,默认为秒
|
||||
}
|
||||
|
||||
unit := value[len(value)-1]
|
||||
numStr := value[:len(value)-1]
|
||||
|
||||
var num int
|
||||
_, _ = fmt.Sscanf(numStr, "%d", &num)
|
||||
|
||||
switch unit {
|
||||
case 's':
|
||||
return time.Duration(num) * time.Second
|
||||
case 'm':
|
||||
return time.Duration(num) * time.Minute
|
||||
case 'h':
|
||||
return time.Duration(num) * time.Hour
|
||||
case 'd':
|
||||
return time.Duration(num) * 24 * time.Hour
|
||||
default:
|
||||
// 没有单位,尝试直接解析为秒
|
||||
_, _ = fmt.Sscanf(value, "%d", &num)
|
||||
return time.Duration(num) * time.Second
|
||||
}
|
||||
}
|
||||
|
||||
// formatNginxDuration 格式化时间为 Nginx 格式
|
||||
func formatNginxDuration(d time.Duration) string {
|
||||
if d == 0 {
|
||||
return "0s"
|
||||
}
|
||||
|
||||
seconds := int(d.Seconds())
|
||||
if seconds%3600 == 0 {
|
||||
return fmt.Sprintf("%dh", seconds/3600)
|
||||
}
|
||||
if seconds%60 == 0 {
|
||||
return fmt.Sprintf("%dm", seconds/60)
|
||||
}
|
||||
return fmt.Sprintf("%ds", seconds)
|
||||
}
|
||||
|
||||
// streamDir 返回 stream 配置目录
|
||||
func (s *App) streamDir() string {
|
||||
return filepath.Join(app.Root, "server/nginx/conf/stream")
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package openresty
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/acepanel/panel/internal/apps/nginx"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
nginx *nginx.App
|
||||
}
|
||||
|
||||
func NewApp(t *gotext.Locale) *App {
|
||||
return &App{
|
||||
nginx: nginx.NewApp(t),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.nginx.Route(r)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package percona
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/acepanel/panel/internal/apps/mysql"
|
||||
"github.com/acepanel/panel/internal/biz"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
mysql *mysql.App
|
||||
}
|
||||
|
||||
func NewApp(t *gotext.Locale, setting biz.SettingRepo, databaseServer biz.DatabaseServerRepo) *App {
|
||||
return &App{
|
||||
mysql: mysql.NewApp(t, setting, databaseServer),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.mysql.Route(r)
|
||||
}
|
||||
@@ -1,434 +0,0 @@
|
||||
package php
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
"github.com/tnb-labs/panel/pkg/shell"
|
||||
"github.com/tnb-labs/panel/pkg/types"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
version uint
|
||||
taskRepo biz.TaskRepo
|
||||
}
|
||||
|
||||
func NewApp(task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
taskRepo: task,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(version uint) func(r chi.Router) {
|
||||
return func(r chi.Router) {
|
||||
php := new(App)
|
||||
php.version = version
|
||||
php.taskRepo = s.taskRepo
|
||||
r.Post("/setCli", php.SetCli)
|
||||
r.Get("/config", php.GetConfig)
|
||||
r.Post("/config", php.UpdateConfig)
|
||||
r.Get("/fpmConfig", php.GetFPMConfig)
|
||||
r.Post("/fpmConfig", php.UpdateFPMConfig)
|
||||
r.Get("/load", php.Load)
|
||||
r.Get("/errorLog", php.ErrorLog)
|
||||
r.Get("/slowLog", php.SlowLog)
|
||||
r.Post("/clearErrorLog", php.ClearErrorLog)
|
||||
r.Post("/clearSlowLog", php.ClearSlowLog)
|
||||
r.Get("/extensions", php.ExtensionList)
|
||||
r.Post("/extensions", php.InstallExtension)
|
||||
r.Delete("/extensions", php.UninstallExtension)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) SetCli(w http.ResponseWriter, r *http.Request) {
|
||||
if _, err := shell.Execf("ln -sf %s/server/php/%d/bin/php /usr/bin/php", app.Root, s.version); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
config, err := io.Read(fmt.Sprintf("%s/server/php/%d/etc/php.ini", app.Root, s.version))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, config)
|
||||
}
|
||||
|
||||
func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[UpdateConfig](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write(fmt.Sprintf("%s/server/php/%d/etc/php.ini", app.Root, s.version), req.Config, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *App) GetFPMConfig(w http.ResponseWriter, r *http.Request) {
|
||||
config, err := io.Read(fmt.Sprintf("%s/server/php/%d/etc/php-fpm.conf", app.Root, s.version))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, config)
|
||||
}
|
||||
|
||||
func (s *App) UpdateFPMConfig(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[UpdateConfig](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write(fmt.Sprintf("%s/server/php/%d/etc/php-fpm.conf", app.Root, s.version), req.Config, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
var raw map[string]any
|
||||
client := resty.New().SetTimeout(10 * time.Second)
|
||||
_, err := client.R().SetResult(&raw).Get(fmt.Sprintf("http://127.0.0.1/phpfpm_status/%d?json", s.version))
|
||||
if err != nil {
|
||||
service.Success(w, []types.NV{})
|
||||
return
|
||||
}
|
||||
|
||||
dataKeys := []string{"应用池", "工作模式", "启动时间", "接受连接", "监听队列", "最大监听队列", "监听队列长度", "空闲进程数量", "活动进程数量", "总进程数量", "最大活跃进程数量", "达到进程上限次数", "慢请求"}
|
||||
rawKeys := []string{"pool", "process manager", "start time", "accepted conn", "listen queue", "max listen queue", "listen queue len", "idle processes", "active processes", "total processes", "max active processes", "max children reached", "slow requests"}
|
||||
|
||||
loads := make([]types.NV, 0)
|
||||
for i := range dataKeys {
|
||||
v, ok := raw[rawKeys[i]]
|
||||
if ok {
|
||||
loads = append(loads, types.NV{
|
||||
Name: dataKeys[i],
|
||||
Value: cast.ToString(v),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
service.Success(w, loads)
|
||||
}
|
||||
|
||||
func (s *App) ErrorLog(w http.ResponseWriter, r *http.Request) {
|
||||
service.Success(w, fmt.Sprintf("%s/server/php/%d/var/log/php-fpm.log", app.Root, s.version))
|
||||
}
|
||||
|
||||
func (s *App) SlowLog(w http.ResponseWriter, r *http.Request) {
|
||||
service.Success(w, fmt.Sprintf("%s/server/php/%d/var/log/slow.log", app.Root, s.version))
|
||||
}
|
||||
|
||||
func (s *App) ClearErrorLog(w http.ResponseWriter, r *http.Request) {
|
||||
if _, err := shell.Execf("echo '' > %s/server/php/%d/var/log/php-fpm.log", app.Root, s.version); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *App) ClearSlowLog(w http.ResponseWriter, r *http.Request) {
|
||||
if _, err := shell.Execf("echo '' > %s/server/php/%d/var/log/slow.log", app.Root, s.version); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *App) ExtensionList(w http.ResponseWriter, r *http.Request) {
|
||||
extensions := s.getExtensions()
|
||||
raw, err := shell.Execf("%s/server/php/%d/bin/php -m", app.Root, s.version)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
extensionMap := make(map[string]*Extension)
|
||||
for i := range extensions {
|
||||
extensionMap[extensions[i].Slug] = &extensions[i]
|
||||
}
|
||||
|
||||
rawExtensionList := strings.Split(raw, "\n")
|
||||
for _, item := range rawExtensionList {
|
||||
if ext, exists := extensionMap[item]; exists && !strings.Contains(item, "[") && item != "" {
|
||||
ext.Installed = true
|
||||
}
|
||||
}
|
||||
|
||||
service.Success(w, extensions)
|
||||
}
|
||||
|
||||
func (s *App) InstallExtension(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[ExtensionSlug](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !s.checkExtension(req.Slug) {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "扩展不存在")
|
||||
return
|
||||
}
|
||||
|
||||
cmd := fmt.Sprintf(`curl -fsLm 10 --retry 3 'https://dl.cdn.haozi.net/panel/php_exts/%s.sh' | bash -s -- 'install' '%d' >> '/tmp/%s.log' 2>&1`, url.PathEscape(req.Slug), s.version, req.Slug)
|
||||
officials := []string{"fileinfo", "exif", "imap", "pgsql", "pdo_pgsql", "zip", "bz2", "readline", "snmp", "ldap", "enchant", "pspell", "calendar", "gmp", "sysvmsg", "sysvsem", "sysvshm", "xsl", "intl", "gettext"}
|
||||
if slices.Contains(officials, req.Slug) {
|
||||
cmd = fmt.Sprintf(`curl -fsLm 10 --retry 3 'https://dl.cdn.haozi.net/panel/php_exts/official.sh' | bash -s -- 'install' '%d' '%s' >> '/tmp/%s.log' 2>&1`, s.version, req.Slug, req.Slug)
|
||||
}
|
||||
|
||||
task := new(biz.Task)
|
||||
task.Name = fmt.Sprintf("安装PHP-%d扩展 %s", s.version, req.Slug)
|
||||
task.Status = biz.TaskStatusWaiting
|
||||
task.Shell = cmd
|
||||
task.Log = "/tmp/" + req.Slug + ".log"
|
||||
if err = s.taskRepo.Push(task); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *App) UninstallExtension(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[ExtensionSlug](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !s.checkExtension(req.Slug) {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "扩展不存在")
|
||||
return
|
||||
}
|
||||
|
||||
cmd := fmt.Sprintf(`curl -fsLm 10 --retry 3 'https://dl.cdn.haozi.net/panel/php_exts/%s.sh' | bash -s -- 'uninstall' '%d' >> '/tmp/%s.log' 2>&1`, url.PathEscape(req.Slug), s.version, req.Slug)
|
||||
officials := []string{"fileinfo", "exif", "imap", "pgsql", "pdo_pgsql", "zip", "bz2", "readline", "snmp", "ldap", "enchant", "pspell", "calendar", "gmp", "sysvmsg", "sysvsem", "sysvshm", "xsl", "intl", "gettext"}
|
||||
if slices.Contains(officials, req.Slug) {
|
||||
cmd = fmt.Sprintf(`curl -fsLm 10 --retry 3 'https://dl.cdn.haozi.net/panel/php_exts/official.sh' | bash -s -- 'uninstall' '%d' '%s' >> '/tmp/%s.log' 2>&1`, s.version, req.Slug, req.Slug)
|
||||
}
|
||||
|
||||
task := new(biz.Task)
|
||||
task.Name = fmt.Sprintf("卸载PHP-%d扩展 %s", s.version, req.Slug)
|
||||
task.Status = biz.TaskStatusWaiting
|
||||
task.Shell = cmd
|
||||
task.Log = "/tmp/" + req.Slug + ".log"
|
||||
if err = s.taskRepo.Push(task); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *App) getExtensions() []Extension {
|
||||
extensions := []Extension{
|
||||
{
|
||||
Name: "fileinfo",
|
||||
Slug: "fileinfo",
|
||||
Description: "Fileinfo 是一个用于识别文件类型的库",
|
||||
},
|
||||
{
|
||||
Name: "OPcache",
|
||||
Slug: "Zend OPcache",
|
||||
Description: "OPcache 通过将 PHP 脚本预编译的字节码存储到共享内存中来提升 PHP 的性能,存储预编译字节码可以省去每次加载和解析 PHP 脚本的开销",
|
||||
},
|
||||
{
|
||||
Name: "Redis",
|
||||
Slug: "redis",
|
||||
Description: "PhpRedis 是一个用 C 语言编写的 PHP 模块,用来连接并操作 Redis 数据库上的数据(需先安装 igbinary 拓展)",
|
||||
},
|
||||
{
|
||||
Name: "Memcached",
|
||||
Slug: "memcached",
|
||||
Description: "Memcached 使用 libmemcached 库连接 Memcached 服务器",
|
||||
},
|
||||
{
|
||||
Name: "ImageMagick",
|
||||
Slug: "imagick",
|
||||
Description: "ImageMagick 是一个免费的创建、编辑、合成图片的软件",
|
||||
},
|
||||
{
|
||||
Name: "exif",
|
||||
Slug: "exif",
|
||||
Description: "通过 exif 扩展,您可以操作图像元数据",
|
||||
},
|
||||
{
|
||||
Name: "pgsql",
|
||||
Slug: "pgsql",
|
||||
Description: "pgsql 是一个驱动程序,它启用从 PHP 到 PostgreSQL 数据库的访问(需先安装 PostgreSQL 应用)",
|
||||
},
|
||||
{
|
||||
Name: "pdo_pgsql",
|
||||
Slug: "pdo_pgsql",
|
||||
Description: "pdo_pgsql 是一个驱动程序,它实现了 PHP 数据对象(PDO)接口以启用从 PHP 到 PostgreSQL 数据库的访问(需先安装 PostgreSQL 应用)",
|
||||
},
|
||||
{
|
||||
Name: "imap",
|
||||
Slug: "imap",
|
||||
Description: "IMAP 扩展允许 PHP 读取、搜索、删除、下载和管理邮件",
|
||||
},
|
||||
{
|
||||
Name: "zip",
|
||||
Slug: "zip",
|
||||
Description: "Zip 是一个用于处理 ZIP 文件的库",
|
||||
},
|
||||
{
|
||||
Name: "bz2",
|
||||
Slug: "bz2",
|
||||
Description: "Bzip2 是一个用于压缩和解压缩文件的库",
|
||||
},
|
||||
{
|
||||
Name: "readline",
|
||||
Slug: "readline",
|
||||
Description: "Readline 是一个库,它提供了一种用于处理文本的接口",
|
||||
},
|
||||
{
|
||||
Name: "snmp",
|
||||
Slug: "snmp",
|
||||
Description: "SNMP 是一种用于网络管理的协议",
|
||||
},
|
||||
{
|
||||
Name: "ldap",
|
||||
Slug: "ldap",
|
||||
Description: "LDAP 是一种用于访问目录服务的协议",
|
||||
},
|
||||
{
|
||||
Name: "enchant",
|
||||
Slug: "enchant",
|
||||
Description: "Enchant 是一个拼写检查库",
|
||||
},
|
||||
{
|
||||
Name: "pspell",
|
||||
Slug: "pspell",
|
||||
Description: "Pspell 是一个拼写检查库",
|
||||
},
|
||||
{
|
||||
Name: "calendar",
|
||||
Slug: "calendar",
|
||||
Description: "Calendar 是一个用于处理日期的库",
|
||||
},
|
||||
{
|
||||
Name: "gmp",
|
||||
Slug: "gmp",
|
||||
Description: "GMP 是一个用于处理大整数的库",
|
||||
},
|
||||
{
|
||||
Name: "sysvmsg",
|
||||
Slug: "sysvmsg",
|
||||
Description: "Sysvmsg 是一个用于处理 System V 消息队列的库",
|
||||
},
|
||||
{
|
||||
Name: "sysvsem",
|
||||
Slug: "sysvsem",
|
||||
Description: "Sysvsem 是一个用于处理 System V 信号量的库",
|
||||
},
|
||||
{
|
||||
Name: "sysvshm",
|
||||
Slug: "sysvshm",
|
||||
Description: "Sysvshm 是一个用于处理 System V 共享内存的库",
|
||||
},
|
||||
{
|
||||
Name: "xsl",
|
||||
Slug: "xsl",
|
||||
Description: "XSL 是一个用于处理 XML 文档的库",
|
||||
},
|
||||
{
|
||||
Name: "intl",
|
||||
Slug: "intl",
|
||||
Description: "Intl 是一个用于处理国际化和本地化的库",
|
||||
},
|
||||
{
|
||||
Name: "gettext",
|
||||
Slug: "gettext",
|
||||
Description: "Gettext 是一个用于处理多语言的库",
|
||||
},
|
||||
{
|
||||
Name: "igbinary",
|
||||
Slug: "igbinary",
|
||||
Description: "Igbinary 是一个用于序列化和反序列化数据的库",
|
||||
},
|
||||
}
|
||||
|
||||
// ionCube Swoole 不支持 PHP 8.4
|
||||
if cast.ToUint(s.version) < 84 {
|
||||
extensions = append(extensions, Extension{
|
||||
Name: "ionCube",
|
||||
Slug: "ionCube Loader",
|
||||
Description: "ionCube 是一个专业级的 PHP 加密解密工具(需在 OPcache 之后安装)",
|
||||
})
|
||||
extensions = append(extensions, Extension{
|
||||
Name: "Swoole",
|
||||
Slug: "swoole",
|
||||
Description: "Swoole 是一个用于构建高性能的异步并发服务器的 PHP 扩展",
|
||||
})
|
||||
}
|
||||
// Swow 不支持 PHP 8.0 以下版本且目前不支持 PHP 8.4
|
||||
if cast.ToUint(s.version) >= 80 && cast.ToUint(s.version) < 84 {
|
||||
extensions = append(extensions, Extension{
|
||||
Name: "Swow",
|
||||
Slug: "Swow",
|
||||
Description: "Swow 是一个用于构建高性能的异步并发服务器的 PHP 扩展",
|
||||
})
|
||||
}
|
||||
// PHP 8.4 移除了 pspell 和 imap 并且不再建议使用
|
||||
if cast.ToUint(s.version) >= 84 {
|
||||
extensions = slices.DeleteFunc(extensions, func(extension Extension) bool {
|
||||
return extension.Slug == "pspell" || extension.Slug == "imap"
|
||||
})
|
||||
}
|
||||
|
||||
raw, _ := shell.Execf("%s/server/php/%d/bin/php -m", app.Root, s.version)
|
||||
extensionMap := make(map[string]*Extension)
|
||||
for i := range extensions {
|
||||
extensionMap[extensions[i].Slug] = &extensions[i]
|
||||
}
|
||||
|
||||
rawExtensionList := strings.Split(raw, "\n")
|
||||
for _, item := range rawExtensionList {
|
||||
if ext, exists := extensionMap[item]; exists && !strings.Contains(item, "[") && item != "" {
|
||||
ext.Installed = true
|
||||
}
|
||||
}
|
||||
|
||||
return extensions
|
||||
}
|
||||
|
||||
func (s *App) checkExtension(slug string) bool {
|
||||
extensions := s.getExtensions()
|
||||
|
||||
for _, item := range extensions {
|
||||
if item.Slug == slug {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package php
|
||||
|
||||
type UpdateConfig struct {
|
||||
Config string `form:"config" json:"config" validate:"required"`
|
||||
}
|
||||
|
||||
type ExtensionSlug struct {
|
||||
Slug string `form:"slug" json:"slug" validate:"required"`
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package php74
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/apps/php"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(task),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.php.Route(74)(r)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package php80
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/apps/php"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(task),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.php.Route(80)(r)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package php81
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/apps/php"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(task),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.php.Route(81)(r)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package php82
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/apps/php"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(task),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.php.Route(82)(r)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package php83
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/apps/php"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(task),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.php.Route(83)(r)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package php84
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/apps/php"
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
php *php.App
|
||||
}
|
||||
|
||||
func NewApp(task biz.TaskRepo) *App {
|
||||
return &App{
|
||||
php: php.NewApp(task),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
s.php.Route(84)(r)
|
||||
}
|
||||
@@ -8,21 +8,26 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/libtnb/chix"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/tnb-labs/panel/pkg/firewall"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
"github.com/tnb-labs/panel/pkg/shell"
|
||||
"github.com/tnb-labs/panel/pkg/systemctl"
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
"github.com/acepanel/panel/pkg/firewall"
|
||||
"github.com/acepanel/panel/pkg/io"
|
||||
"github.com/acepanel/panel/pkg/shell"
|
||||
"github.com/acepanel/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
type App struct{}
|
||||
type App struct {
|
||||
t *gotext.Locale
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
func NewApp(t *gotext.Locale) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
@@ -35,7 +40,7 @@ func (s *App) Route(r chi.Router) {
|
||||
func (s *App) Info(w http.ResponseWriter, r *http.Request) {
|
||||
files, err := os.ReadDir(fmt.Sprintf("%s/server/phpmyadmin", app.Root))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "找不到 phpMyAdmin 目录")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("phpMyAdmin directory not found"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -46,18 +51,18 @@ func (s *App) Info(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
if len(phpmyadmin) == 0 {
|
||||
service.Error(w, http.StatusInternalServerError, "找不到 phpMyAdmin 目录")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("phpMyAdmin directory not found"))
|
||||
return
|
||||
}
|
||||
|
||||
conf, err := io.Read(fmt.Sprintf("%s/server/vhost/phpmyadmin.conf", app.Root))
|
||||
conf, err := io.Read(fmt.Sprintf("%s/sites/phpmyadmin/config/nginx.conf", app.Root))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
match := regexp.MustCompile(`listen\s+(\d+);`).FindStringSubmatch(conf)
|
||||
if len(match) == 0 {
|
||||
service.Error(w, http.StatusInternalServerError, "找不到 phpMyAdmin 端口")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("phpMyAdmin port not found"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -74,14 +79,14 @@ func (s *App) UpdatePort(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
conf, err := io.Read(fmt.Sprintf("%s/server/vhost/phpmyadmin.conf", app.Root))
|
||||
conf, err := io.Read(fmt.Sprintf("%s/sites/phpmyadmin/config/nginx.conf", app.Root))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
conf = regexp.MustCompile(`listen\s+(\d+);`).ReplaceAllString(conf, "listen "+cast.ToString(req.Port)+";")
|
||||
if err = io.Write(fmt.Sprintf("%s/server/vhost/phpmyadmin.conf", app.Root), conf, 0644); err != nil {
|
||||
service.ErrorSystem(w)
|
||||
if err = io.Write(fmt.Sprintf("%s/sites/phpmyadmin/config/nginx.conf", app.Root), conf, 0600); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -100,7 +105,7 @@ func (s *App) UpdatePort(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if err = systemctl.Reload("nginx"); err != nil {
|
||||
_, err = shell.Execf("nginx -t")
|
||||
service.Error(w, http.StatusInternalServerError, "重载OpenResty失败:%v", err)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload nginx: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -108,7 +113,7 @@ func (s *App) UpdatePort(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
config, err := io.Read(fmt.Sprintf("%s/server/vhost/phpmyadmin.conf", app.Root))
|
||||
config, err := io.Read(fmt.Sprintf("%s/sites/phpmyadmin/config/nginx.conf", app.Root))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
@@ -124,14 +129,14 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write(fmt.Sprintf("%s/server/vhost/phpmyadmin.conf", app.Root), req.Config, 0644); err != nil {
|
||||
if err = io.Write(fmt.Sprintf("%s/sites/phpmyadmin/config/nginx.conf", app.Root), req.Config, 0600); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = systemctl.Reload("nginx"); err != nil {
|
||||
_, err = shell.Execf("nginx -t")
|
||||
service.Error(w, http.StatusInternalServerError, "重载OpenResty失败:%v", err)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload nginx: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
"github.com/tnb-labs/panel/pkg/systemctl"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
"github.com/acepanel/panel/pkg/io"
|
||||
"github.com/acepanel/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
type App struct{}
|
||||
@@ -17,10 +17,10 @@ func NewApp() *App {
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
r.Get("/registryConfig", s.GetRegistryConfig)
|
||||
r.Post("/registryConfig", s.UpdateRegistryConfig)
|
||||
r.Get("/storageConfig", s.GetStorageConfig)
|
||||
r.Post("/storageConfig", s.UpdateStorageConfig)
|
||||
r.Get("/registry_config", s.GetRegistryConfig)
|
||||
r.Post("/registry_config", s.UpdateRegistryConfig)
|
||||
r.Get("/storage_config", s.GetStorageConfig)
|
||||
r.Post("/storage_config", s.UpdateStorageConfig)
|
||||
}
|
||||
|
||||
func (s *App) GetRegistryConfig(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
+107
-32
@@ -3,32 +3,46 @@ package postgresql
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/acepanel/panel/pkg/db"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
"github.com/tnb-labs/panel/pkg/shell"
|
||||
"github.com/tnb-labs/panel/pkg/systemctl"
|
||||
"github.com/tnb-labs/panel/pkg/types"
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/biz"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
"github.com/acepanel/panel/pkg/io"
|
||||
"github.com/acepanel/panel/pkg/shell"
|
||||
"github.com/acepanel/panel/pkg/systemctl"
|
||||
"github.com/acepanel/panel/pkg/types"
|
||||
)
|
||||
|
||||
type App struct{}
|
||||
type App struct {
|
||||
t *gotext.Locale
|
||||
settingRepo biz.SettingRepo
|
||||
databaseServerRepo biz.DatabaseServerRepo
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
func NewApp(t *gotext.Locale, setting biz.SettingRepo, databaseServer biz.DatabaseServerRepo) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
settingRepo: setting,
|
||||
databaseServerRepo: databaseServer,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
r.Get("/config", s.GetConfig)
|
||||
r.Post("/config", s.UpdateConfig)
|
||||
r.Get("/userConfig", s.GetUserConfig)
|
||||
r.Post("/userConfig", s.UpdateUserConfig)
|
||||
r.Get("/user_config", s.GetUserConfig)
|
||||
r.Post("/user_config", s.UpdateUserConfig)
|
||||
r.Get("/load", s.Load)
|
||||
r.Get("/log", s.Log)
|
||||
r.Post("/clearLog", s.ClearLog)
|
||||
r.Post("/clear_log", s.ClearLog)
|
||||
r.Get("/postgres_password", s.GetPostgresPassword)
|
||||
r.Post("/postgres_password", s.SetPostgresPassword)
|
||||
}
|
||||
|
||||
// GetConfig 获取配置
|
||||
@@ -36,7 +50,7 @@ func (s *App) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
// 获取配置
|
||||
config, err := io.Read(fmt.Sprintf("%s/server/postgresql/data/postgresql.conf", app.Root))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取PostgreSQL配置失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -52,12 +66,12 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err = io.Write(fmt.Sprintf("%s/server/postgresql/data/postgresql.conf", app.Root), req.Config, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入PostgreSQL配置失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = systemctl.Reload("postgresql"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "重载服务失败: %v", err)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload PostgreSQL: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -69,7 +83,7 @@ func (s *App) GetUserConfig(w http.ResponseWriter, r *http.Request) {
|
||||
// 获取配置
|
||||
config, err := io.Read(fmt.Sprintf("%s/server/postgresql/data/pg_hba.conf", app.Root))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取PostgreSQL配置失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -85,12 +99,12 @@ func (s *App) UpdateUserConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err = io.Write(fmt.Sprintf("%s/server/postgresql/data/pg_hba.conf", app.Root), req.Config, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入PostgreSQL配置失败")
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = systemctl.Reload("postgresql"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "重载服务失败: %v", err)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to reload PostgreSQL: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -105,38 +119,54 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
start, err := shell.Execf(`echo "select pg_postmaster_start_time();" | su - postgres -c "psql" | sed -n 3p | cut -d'.' -f1`)
|
||||
postgresPassword, err := s.settingRepo.Get(biz.SettingKeyPostgresPassword)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取PostgreSQL启动时间失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to load PostgreSQL postgres password: %v", err))
|
||||
return
|
||||
}
|
||||
pid, err := shell.Execf(`echo "select pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)
|
||||
|
||||
if err = os.Setenv("PGPASSWORD", postgresPassword); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to set PGPASSWORD env: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
start, err := shell.Execf(`psql -h 127.0.0.1 -U postgres -t -c "select pg_postmaster_start_time();" | head -1 | cut -d'.' -f1`)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取PostgreSQL进程PID失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL start time: %v", err))
|
||||
return
|
||||
}
|
||||
pid, err := shell.Execf(`psql -h 127.0.0.1 -U postgres -t -c "select pg_backend_pid();"`)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL backend pid: %v", err))
|
||||
return
|
||||
}
|
||||
process, err := shell.Execf(`ps aux | grep postgres | grep -v grep | wc -l`)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取PostgreSQL进程数失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL process: %v", err))
|
||||
return
|
||||
}
|
||||
connections, err := shell.Execf(`echo "SELECT count(*) FROM pg_stat_activity WHERE NOT pid=pg_backend_pid();" | su - postgres -c "psql" | sed -n 3p`)
|
||||
connections, err := shell.Execf(`psql -h 127.0.0.1 -U postgres -t -c "SELECT count(*) FROM pg_stat_activity WHERE NOT pid=pg_backend_pid();"`)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取PostgreSQL连接数失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL connections: %v", err))
|
||||
return
|
||||
}
|
||||
storage, err := shell.Execf(`echo "select pg_size_pretty(pg_database_size('postgres'));" | su - postgres -c "psql" | sed -n 3p`)
|
||||
storage, err := shell.Execf(`psql -h 127.0.0.1 -U postgres -t -c "select pg_size_pretty(pg_database_size('postgres'));"`)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取PostgreSQL空间占用失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get PostgreSQL database size: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err = os.Unsetenv("PGPASSWORD"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to unset PGPASSWORD env: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
data := []types.NV{
|
||||
{Name: "启动时间", Value: start},
|
||||
{Name: "进程 PID", Value: pid},
|
||||
{Name: "进程数", Value: process},
|
||||
{Name: "总连接数", Value: connections},
|
||||
{Name: "空间占用", Value: storage},
|
||||
{Name: s.t.Get("Start Time"), Value: start},
|
||||
{Name: s.t.Get("Process PID"), Value: pid},
|
||||
{Name: s.t.Get("Process Count"), Value: process},
|
||||
{Name: s.t.Get("Total Connections"), Value: connections},
|
||||
{Name: s.t.Get("Storage Usage"), Value: storage},
|
||||
}
|
||||
|
||||
service.Success(w, data)
|
||||
@@ -156,3 +186,48 @@ func (s *App) ClearLog(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
// GetPostgresPassword 获取 postgres 用户密码
|
||||
func (s *App) GetPostgresPassword(w http.ResponseWriter, r *http.Request) {
|
||||
password, err := s.settingRepo.Get(biz.SettingKeyPostgresPassword)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get postgres password: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, password)
|
||||
}
|
||||
|
||||
// SetPostgresPassword 设置 postgres 用户密码
|
||||
func (s *App) SetPostgresPassword(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[SetPostgresPassword](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
oldPassword, _ := s.settingRepo.Get(biz.SettingKeyPostgresPassword)
|
||||
postgres, err := db.NewPostgres("postgres", oldPassword, "127.0.0.1", 5432)
|
||||
if err != nil {
|
||||
// 直接修改密码
|
||||
if _, err = shell.Execf(`su - postgres -c "psql -c \"ALTER USER postgres WITH PASSWORD '%s';\""`, req.Password); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to set postgres password: %v", err))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
defer postgres.Close()
|
||||
if err = postgres.UserPassword("postgres", req.Password); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to set postgres password: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = s.settingRepo.Set(biz.SettingKeyPostgresPassword, req.Password); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to set postgres password: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
_ = s.databaseServerRepo.UpdatePassword("local_postgresql", req.Password)
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
@@ -3,3 +3,7 @@ package postgresql
|
||||
type UpdateConfig struct {
|
||||
Config string `form:"config" json:"config" validate:"required"`
|
||||
}
|
||||
|
||||
type SetPostgresPassword struct {
|
||||
Password string `form:"password" json:"password" validate:"required|password"`
|
||||
}
|
||||
|
||||
@@ -6,21 +6,26 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/libtnb/chix"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/tnb-labs/panel/pkg/firewall"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
"github.com/tnb-labs/panel/pkg/shell"
|
||||
"github.com/tnb-labs/panel/pkg/systemctl"
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
"github.com/acepanel/panel/pkg/firewall"
|
||||
"github.com/acepanel/panel/pkg/io"
|
||||
"github.com/acepanel/panel/pkg/shell"
|
||||
"github.com/acepanel/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
type App struct{}
|
||||
type App struct {
|
||||
t *gotext.Locale
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
func NewApp(t *gotext.Locale) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
@@ -76,18 +81,10 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
|
||||
req.Path = "/" + req.Path
|
||||
}
|
||||
if !io.Exists(req.Path) {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "目录不存在")
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("directory %s does not exist", req.Path))
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Chmod(req.Path, 0755); err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "修改目录权限失败")
|
||||
return
|
||||
}
|
||||
if err = io.Chown(req.Path, "www", "www"); err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "修改目录权限失败")
|
||||
return
|
||||
}
|
||||
if _, err = shell.Execf(`yes '%s' | pure-pw useradd '%s' -u www -g www -d '%s'`, req.Password, req.Username, req.Path); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
@@ -144,7 +141,7 @@ func (s *App) ChangePassword(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *App) GetPort(w http.ResponseWriter, r *http.Request) {
|
||||
port, err := shell.Execf(`cat %s/server/pure-ftpd/etc/pure-ftpd.conf | grep "Bind" | awk '{print $2}' | awk -F "," '{print $2}'`, app.Root)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取PureFtpd端口失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get port: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+30
-25
@@ -7,19 +7,24 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
"github.com/tnb-labs/panel/pkg/shell"
|
||||
"github.com/tnb-labs/panel/pkg/systemctl"
|
||||
"github.com/tnb-labs/panel/pkg/types"
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
"github.com/acepanel/panel/pkg/io"
|
||||
"github.com/acepanel/panel/pkg/shell"
|
||||
"github.com/acepanel/panel/pkg/systemctl"
|
||||
"github.com/acepanel/panel/pkg/types"
|
||||
)
|
||||
|
||||
type App struct{}
|
||||
type App struct {
|
||||
t *gotext.Locale
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
func NewApp(t *gotext.Locale) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
@@ -31,7 +36,7 @@ func (s *App) Route(r chi.Router) {
|
||||
func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
status, err := systemctl.Status("redis")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取 Redis 状态失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get redis status: %v", err))
|
||||
return
|
||||
}
|
||||
if !status {
|
||||
@@ -46,7 +51,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
re := regexp.MustCompile(`requirepass\s+(.+)`)
|
||||
re := regexp.MustCompile(`^requirepass\s+(.+)`)
|
||||
matches := re.FindStringSubmatch(config)
|
||||
if len(matches) == 2 {
|
||||
withPassword = " -a " + matches[1]
|
||||
@@ -54,7 +59,7 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
raw, err := shell.Execf("redis-cli%s info", withPassword)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取 Redis 负载失败")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get redis info: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -69,19 +74,19 @@ func (s *App) Load(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
data := []types.NV{
|
||||
{Name: "TCP 端口", Value: dataRaw["tcp_port"]},
|
||||
{Name: "已运行天数", Value: dataRaw["uptime_in_days"]},
|
||||
{Name: "连接的客户端数", Value: dataRaw["connected_clients"]},
|
||||
{Name: "已分配的内存总量", Value: dataRaw["used_memory_human"]},
|
||||
{Name: "占用内存总量", Value: dataRaw["used_memory_rss_human"]},
|
||||
{Name: "占用内存峰值", Value: dataRaw["used_memory_peak_human"]},
|
||||
{Name: "内存碎片比率", Value: dataRaw["mem_fragmentation_ratio"]},
|
||||
{Name: "运行以来连接过的客户端的总数", Value: dataRaw["total_connections_received"]},
|
||||
{Name: "运行以来执行过的命令的总数", Value: dataRaw["total_commands_processed"]},
|
||||
{Name: "每秒执行的命令数", Value: dataRaw["instantaneous_ops_per_sec"]},
|
||||
{Name: "查找数据库键成功次数", Value: dataRaw["keyspace_hits"]},
|
||||
{Name: "查找数据库键失败次数", Value: dataRaw["keyspace_misses"]},
|
||||
{Name: "最近一次 fork() 操作耗费的毫秒数", Value: dataRaw["latest_fork_usec"]},
|
||||
{Name: s.t.Get("TCP Port"), Value: dataRaw["tcp_port"]},
|
||||
{Name: s.t.Get("Uptime in Days"), Value: dataRaw["uptime_in_days"]},
|
||||
{Name: s.t.Get("Connected Clients"), Value: dataRaw["connected_clients"]},
|
||||
{Name: s.t.Get("Total Allocated Memory"), Value: dataRaw["used_memory_human"]},
|
||||
{Name: s.t.Get("Total Memory Usage"), Value: dataRaw["used_memory_rss_human"]},
|
||||
{Name: s.t.Get("Peak Memory Usage"), Value: dataRaw["used_memory_peak_human"]},
|
||||
{Name: s.t.Get("Memory Fragmentation Ratio"), Value: dataRaw["mem_fragmentation_ratio"]},
|
||||
{Name: s.t.Get("Total Connections Received"), Value: dataRaw["total_connections_received"]},
|
||||
{Name: s.t.Get("Total Commands Processed"), Value: dataRaw["total_commands_processed"]},
|
||||
{Name: s.t.Get("Commands Per Second"), Value: dataRaw["instantaneous_ops_per_sec"]},
|
||||
{Name: s.t.Get("Keyspace Hits"), Value: dataRaw["keyspace_hits"]},
|
||||
{Name: s.t.Get("Keyspace Misses"), Value: dataRaw["keyspace_misses"]},
|
||||
{Name: s.t.Get("Latest Fork Time (ms)"), Value: dataRaw["latest_fork_usec"]},
|
||||
}
|
||||
|
||||
service.Success(w, data)
|
||||
|
||||
+22
-17
@@ -7,19 +7,24 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/go-rat/utils/str"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/libtnb/chix"
|
||||
"github.com/libtnb/utils/str"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
"github.com/tnb-labs/panel/pkg/shell"
|
||||
"github.com/tnb-labs/panel/pkg/systemctl"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
"github.com/acepanel/panel/pkg/io"
|
||||
"github.com/acepanel/panel/pkg/shell"
|
||||
"github.com/acepanel/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
type App struct{}
|
||||
type App struct {
|
||||
t *gotext.Locale
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
func NewApp(t *gotext.Locale) *App {
|
||||
return &App{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
@@ -74,7 +79,7 @@ func (s *App) List(w http.ResponseWriter, r *http.Request) {
|
||||
currentModule.AuthUser = value
|
||||
currentModule.Secret, err = shell.Execf(`grep -E '^%s:.*$' /etc/rsyncd.secrets | awk -F ':' '{print $2}'`, currentModule.AuthUser)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取模块%s的密钥失败", currentModule.AuthUser)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get the secret key for module %s", currentModule.AuthUser))
|
||||
return
|
||||
}
|
||||
case "hosts allow":
|
||||
@@ -109,7 +114,7 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if strings.Contains(config, "["+req.Name+"]") {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "模块%s已存在", req.Name)
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("module %s already exists", req.Name))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -128,7 +133,7 @@ secrets file = /etc/rsyncd.secrets
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
if err = io.WriteAppend("/etc/rsyncd.secrets", fmt.Sprintf(`%s:%s\n`, req.AuthUser, req.Secret), 0600); err != nil {
|
||||
if err = io.WriteAppend("/etc/rsyncd.secrets", fmt.Sprintf("%s:%s\n", req.AuthUser, req.Secret), 0600); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
@@ -154,12 +159,12 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if !strings.Contains(config, "["+req.Name+"]") {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "模块%s不存在", req.Name)
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("module %s does not exist", req.Name))
|
||||
return
|
||||
}
|
||||
|
||||
module := str.Cut(config, "# "+req.Name+"-START", "# "+req.Name+"-END")
|
||||
config = strings.Replace(config, "\n# "+req.Name+"-START"+module+"# "+req.Name+"-END", "", -1)
|
||||
config = strings.ReplaceAll(config, "\n# "+req.Name+"-START"+module+"# "+req.Name+"-END", "")
|
||||
|
||||
match := regexp.MustCompile(`auth users = ([^\n]+)`).FindStringSubmatch(module)
|
||||
if len(match) == 2 {
|
||||
@@ -196,7 +201,7 @@ func (s *App) Update(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if !strings.Contains(config, "["+req.Name+"]") {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "模块%s不存在", req.Name)
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("module %s does not exist", req.Name))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -211,7 +216,7 @@ secrets file = /etc/rsyncd.secrets
|
||||
# ` + req.Name + `-END`
|
||||
|
||||
module := str.Cut(config, "# "+req.Name+"-START", "# "+req.Name+"-END")
|
||||
config = strings.Replace(config, "# "+req.Name+"-START"+module+"# "+req.Name+"-END", newConf, -1)
|
||||
config = strings.ReplaceAll(config, "# "+req.Name+"-START"+module+"# "+req.Name+"-END", newConf)
|
||||
|
||||
match := regexp.MustCompile(`auth users = ([^\n]+)`).FindStringSubmatch(module)
|
||||
if len(match) == 2 {
|
||||
@@ -226,7 +231,7 @@ secrets file = /etc/rsyncd.secrets
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
if err = io.WriteAppend("/etc/rsyncd.secrets", fmt.Sprintf(`%s:%s\n`, req.AuthUser, req.Secret), 0600); err != nil {
|
||||
if err = io.WriteAppend("/etc/rsyncd.secrets", fmt.Sprintf("%s:%s\n", req.AuthUser, req.Secret), 0600); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
+86
-83
@@ -1,29 +1,29 @@
|
||||
package s3fs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/libtnb/chix"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/biz"
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
"github.com/tnb-labs/panel/pkg/shell"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
"github.com/acepanel/panel/pkg/io"
|
||||
"github.com/acepanel/panel/pkg/shell"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
settingRepo biz.SettingRepo
|
||||
t *gotext.Locale
|
||||
}
|
||||
|
||||
func NewApp(setting biz.SettingRepo) *App {
|
||||
func NewApp(t *gotext.Locale) *App {
|
||||
return &App{
|
||||
settingRepo: setting,
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,19 +35,13 @@ func (s *App) Route(r chi.Router) {
|
||||
|
||||
// List 所有 S3fs 挂载
|
||||
func (s *App) List(w http.ResponseWriter, r *http.Request) {
|
||||
var s3fsList []Mount
|
||||
list, err := s.settingRepo.Get("s3fs", "[]")
|
||||
list, err := s.mounts()
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to get s3fs list")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get s3fs list: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err = json.Unmarshal([]byte(list), &s3fsList); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to get s3fs list")
|
||||
return
|
||||
}
|
||||
|
||||
paged, total := service.Paginate(r, s3fsList)
|
||||
paged, total := service.Paginate(r, list)
|
||||
|
||||
service.Success(w, chix.M{
|
||||
"total": total,
|
||||
@@ -65,36 +59,31 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// 检查下地域节点中是否包含bucket,如果包含了,肯定是错误的
|
||||
if strings.Contains(req.URL, req.Bucket) {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "endpoint should not contain bucket")
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("endpoint should not contain bucket"))
|
||||
return
|
||||
}
|
||||
|
||||
// 检查挂载目录是否存在且为空
|
||||
if !io.Exists(req.Path) {
|
||||
if err = os.MkdirAll(req.Path, 0755); err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "failed to create mount path: %v", err)
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("failed to create mount path: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
if !io.Empty(req.Path) {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "mount path is not empty")
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("mount path is not empty"))
|
||||
return
|
||||
}
|
||||
|
||||
var s3fsList []Mount
|
||||
list, err := s.settingRepo.Get("s3fs", "[]")
|
||||
list, err := s.mounts()
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to get s3fs list")
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal([]byte(list), &s3fsList); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to get s3fs list")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get s3fs list: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
for _, s := range s3fsList {
|
||||
if s.Path == req.Path {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "mount path already exists")
|
||||
for _, item := range list {
|
||||
if item.Path == req.Path {
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("mount path already exists"))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -102,7 +91,7 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
|
||||
id := time.Now().UnixMicro()
|
||||
password := req.Ak + ":" + req.Sk
|
||||
if err = io.Write("/etc/passwd-s3fs-"+cast.ToString(id), password, 0600); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to create passwd file: %v", err)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to create passwd file: %v", err))
|
||||
return
|
||||
}
|
||||
if _, err = shell.Execf(`echo 's3fs#%s %s fuse _netdev,allow_other,nonempty,url=%s,passwd_file=/etc/passwd-s3fs-%s 0 0' >> /etc/fstab`, req.Bucket, req.Path, req.URL, cast.ToString(id)); err != nil {
|
||||
@@ -111,29 +100,12 @@ func (s *App) Create(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
if _, err = shell.Execf("mount -a"); err != nil {
|
||||
_, _ = shell.Execf(`sed -i 's@^s3fs#%s\s%s.*$@@g' /etc/fstab`, req.Bucket, req.Path)
|
||||
service.Error(w, http.StatusInternalServerError, "invalid /etc/fstab: %v", err)
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
if _, err = shell.Execf(`df -h | grep '%s'`, req.Path); err != nil {
|
||||
_, _ = shell.Execf(`sed -i 's@^s3fs#%s\s%s.*$@@g' /etc/fstab`, req.Bucket, req.Path)
|
||||
service.Error(w, http.StatusInternalServerError, "mount failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
s3fsList = append(s3fsList, Mount{
|
||||
ID: id,
|
||||
Path: req.Path,
|
||||
Bucket: req.Bucket,
|
||||
Url: req.URL,
|
||||
})
|
||||
encoded, err := json.Marshal(s3fsList)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to add s3fs mount")
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.settingRepo.Set("s3fs", string(encoded)); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to add s3fs mount")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("mount failed: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -148,43 +120,38 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
var s3fsList []Mount
|
||||
list, err := s.settingRepo.Get("s3fs", "[]")
|
||||
list, err := s.mounts()
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to get s3fs list")
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal([]byte(list), &s3fsList); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to get s3fs list")
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get s3fs list: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
var mount Mount
|
||||
for _, item := range s3fsList {
|
||||
for _, item := range list {
|
||||
if item.ID == req.ID {
|
||||
mount = item
|
||||
break
|
||||
}
|
||||
}
|
||||
if mount.ID == 0 {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "mount not found")
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("mount not found"))
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = shell.Execf(`fusermount -uz '%s'`, mount.Path); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
if _, err = shell.Execf(`umount -lfq '%s'`, mount.Path); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
_, _ = shell.Execf(`fusermount -uz '%s'`, mount.Path)
|
||||
_, err2 := shell.Execf(`umount -lf '%s'`, mount.Path)
|
||||
// 卸载之后再检查下是否还有挂载
|
||||
if _, err = shell.Execf(`df -h | grep '%s'`, mount.Path); err == nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, s.t.Get("failed to unmount: %v", err2))
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = shell.Execf(`sed -i 's@^s3fs#%s\s%s.*$@@g' /etc/fstab`, mount.Bucket, mount.Path); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
if _, err = shell.Execf("mount -a"); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "invalid /etc/fstab: %v", err)
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
if err = io.Remove("/etc/passwd-s3fs-" + cast.ToString(mount.ID)); err != nil {
|
||||
@@ -192,21 +159,57 @@ func (s *App) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
var newS3fsList []Mount
|
||||
for _, item := range s3fsList {
|
||||
if item.ID != mount.ID {
|
||||
newS3fsList = append(newS3fsList, item)
|
||||
}
|
||||
}
|
||||
encoded, err := json.Marshal(newS3fsList)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to delete s3fs mount")
|
||||
return
|
||||
}
|
||||
if err = s.settingRepo.Set("s3fs", string(encoded)); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "failed to delete s3fs mount")
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
func (s *App) mounts() ([]Mount, error) {
|
||||
re := regexp.MustCompile(`^s3fs#(.*?)\s+(.*?)\s+fuse.*?url=(.*?),passwd_file=/etc/passwd-s3fs-(.*?)\s+`)
|
||||
fstab, err := os.ReadFile("/etc/fstab")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lines := strings.Split(string(fstab), "\n")
|
||||
|
||||
var mounts []Mount
|
||||
|
||||
ids, err := shell.Exec("find /etc -maxdepth 1 -name 'passwd-s3fs-*'")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, id := range strings.Split(ids, "\n") {
|
||||
if id == "" {
|
||||
continue
|
||||
}
|
||||
id = strings.TrimPrefix(id, "/etc/passwd-s3fs-")
|
||||
id = strings.TrimSuffix(id, "\n")
|
||||
id = strings.TrimSpace(id)
|
||||
if id == "" {
|
||||
continue
|
||||
}
|
||||
mount := Mount{
|
||||
ID: cast.ToInt64(id),
|
||||
}
|
||||
for _, line := range lines {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(line, id) {
|
||||
matches := re.FindStringSubmatch(line)
|
||||
if len(matches) == 5 {
|
||||
mount.Bucket = matches[1]
|
||||
mount.Path = matches[2]
|
||||
mount.URL = matches[3]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if mount.ID == 0 || mount.Path == "" || mount.Bucket == "" || mount.URL == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
mounts = append(mounts, mount)
|
||||
}
|
||||
|
||||
return mounts, nil
|
||||
}
|
||||
|
||||
@@ -4,5 +4,5 @@ type Mount struct {
|
||||
ID int64 `json:"id"`
|
||||
Path string `json:"path"`
|
||||
Bucket string `json:"bucket"`
|
||||
Url string `json:"url"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
@@ -5,21 +5,23 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/libtnb/chix"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
"github.com/tnb-labs/panel/pkg/os"
|
||||
"github.com/tnb-labs/panel/pkg/shell"
|
||||
"github.com/tnb-labs/panel/pkg/systemctl"
|
||||
"github.com/acepanel/panel/internal/service"
|
||||
"github.com/acepanel/panel/pkg/io"
|
||||
"github.com/acepanel/panel/pkg/os"
|
||||
"github.com/acepanel/panel/pkg/shell"
|
||||
"github.com/acepanel/panel/pkg/systemctl"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
t *gotext.Locale
|
||||
name string
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
func NewApp(t *gotext.Locale) *App {
|
||||
var name string
|
||||
if os.IsRHEL() {
|
||||
name = "supervisord"
|
||||
@@ -28,13 +30,14 @@ func NewApp() *App {
|
||||
}
|
||||
|
||||
return &App{
|
||||
t: t,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
r.Get("/service", s.Service)
|
||||
r.Post("/clearLog", s.ClearLog)
|
||||
r.Post("/clear_log", s.ClearLog)
|
||||
r.Get("/config", s.GetConfig)
|
||||
r.Post("/config", s.UpdateConfig)
|
||||
r.Get("/processes", s.Processes)
|
||||
@@ -42,7 +45,7 @@ func (s *App) Route(r chi.Router) {
|
||||
r.Post("/processes/{process}/stop", s.StopProcess)
|
||||
r.Post("/processes/{process}/restart", s.RestartProcess)
|
||||
r.Get("/processes/{process}/log", s.ProcessLog)
|
||||
r.Post("/processes/{process}/clearLog", s.ClearProcessLog)
|
||||
r.Post("/processes/{process}/clear_log", s.ClearProcessLog)
|
||||
r.Get("/processes/{process}", s.ProcessConfig)
|
||||
r.Post("/processes/{process}", s.UpdateProcessConfig)
|
||||
r.Delete("/processes/{process}", s.DeleteProcess)
|
||||
@@ -56,7 +59,7 @@ func (s *App) Service(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// ClearLog 清空日志
|
||||
func (s *App) ClearLog(w http.ResponseWriter, r *http.Request) {
|
||||
if _, err := shell.Execf(`echo "" > /var/log/supervisor/supervisord.log`); err != nil {
|
||||
if _, err := shell.Execf(`cat /dev/null > /var/log/supervisor/supervisord.log`); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
@@ -102,7 +105,7 @@ func (s *App) UpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err = systemctl.Restart(s.name); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "重启 %s 服务失败: %v", s.name, err)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to restart %s: %v", s.name, err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -156,8 +159,8 @@ func (s *App) StartProcess(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = shell.Execf(`supervisorctl start %s`, req.Process); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
if out, err := shell.Execf(`supervisorctl start %s`, req.Process); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v %s", err, out)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -172,8 +175,8 @@ func (s *App) StopProcess(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = shell.Execf(`supervisorctl stop %s`, req.Process); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
if out, err := shell.Execf(`supervisorctl stop %s`, req.Process); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v %s", err, out)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -188,8 +191,8 @@ func (s *App) RestartProcess(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = shell.Execf(`supervisorctl restart %s`, req.Process); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
if out, err := shell.Execf(`supervisorctl restart %s`, req.Process); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v %s", err, out)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -212,7 +215,7 @@ func (s *App) ProcessLog(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "无法从进程 %s 的配置文件中获取日志路径", req.Process)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get log path for process %s: %v", req.Process, err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -235,11 +238,11 @@ func (s *App) ClearProcessLog(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "无法从进程 %s 的配置文件中获取日志路径", req.Process)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get log path for process %s: %v", req.Process, err))
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = shell.Execf(`echo "" > '%s'`, logPath); err != nil {
|
||||
if _, err = shell.Execf(`cat /dev/null > '%s'`, logPath); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
@@ -343,8 +346,8 @@ func (s *App) DeleteProcess(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = shell.Execf(`supervisorctl stop '%s'`, req.Process); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
if out, err := shell.Execf(`supervisorctl stop '%s'`, req.Process); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v %s", err, out)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -352,7 +355,7 @@ func (s *App) DeleteProcess(w http.ResponseWriter, r *http.Request) {
|
||||
if os.IsRHEL() {
|
||||
logPath, err = shell.Execf(`cat '/etc/supervisord.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, req.Process)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "无法从进程 %s 的配置文件中获取日志路径", req.Process)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get log path for process %s: %v", req.Process, err))
|
||||
return
|
||||
}
|
||||
if err = io.Remove(`/etc/supervisord.d/` + req.Process + `.conf`); err != nil {
|
||||
@@ -362,7 +365,7 @@ func (s *App) DeleteProcess(w http.ResponseWriter, r *http.Request) {
|
||||
} else {
|
||||
logPath, err = shell.Execf(`cat '/etc/supervisor/conf.d/%s.conf' | grep stdout_logfile= | awk -F "=" '{print $2}'`, req.Process)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "无法从进程 %s 的配置文件中获取日志路径", req.Process)
|
||||
service.Error(w, http.StatusInternalServerError, s.t.Get("failed to get log path for process %s: %v", req.Process, err))
|
||||
return
|
||||
}
|
||||
if err = io.Remove(`/etc/supervisor/conf.d/` + req.Process + `.conf`); err != nil {
|
||||
|
||||
@@ -1,337 +0,0 @@
|
||||
package toolbox
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-rat/chix"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/app"
|
||||
"github.com/tnb-labs/panel/internal/service"
|
||||
"github.com/tnb-labs/panel/pkg/io"
|
||||
"github.com/tnb-labs/panel/pkg/ntp"
|
||||
"github.com/tnb-labs/panel/pkg/shell"
|
||||
"github.com/tnb-labs/panel/pkg/tools"
|
||||
"github.com/tnb-labs/panel/pkg/types"
|
||||
)
|
||||
|
||||
type App struct{}
|
||||
|
||||
func NewApp() *App {
|
||||
return &App{}
|
||||
}
|
||||
|
||||
func (s *App) Route(r chi.Router) {
|
||||
r.Get("/dns", s.GetDNS)
|
||||
r.Post("/dns", s.UpdateDNS)
|
||||
r.Get("/swap", s.GetSWAP)
|
||||
r.Post("/swap", s.UpdateSWAP)
|
||||
r.Get("/timezone", s.GetTimezone)
|
||||
r.Post("/timezone", s.UpdateTimezone)
|
||||
r.Post("/time", s.UpdateTime)
|
||||
r.Post("/syncTime", s.SyncTime)
|
||||
r.Get("/hostname", s.GetHostname)
|
||||
r.Post("/hostname", s.UpdateHostname)
|
||||
r.Get("/hosts", s.GetHosts)
|
||||
r.Post("/hosts", s.UpdateHosts)
|
||||
r.Post("/rootPassword", s.UpdateRootPassword)
|
||||
}
|
||||
|
||||
// GetDNS 获取 DNS 信息
|
||||
func (s *App) GetDNS(w http.ResponseWriter, r *http.Request) {
|
||||
raw, err := io.Read("/etc/resolv.conf")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
match := regexp.MustCompile(`nameserver\s+(\S+)`).FindAllStringSubmatch(raw, -1)
|
||||
if len(match) == 0 {
|
||||
service.Error(w, http.StatusInternalServerError, "找不到 DNS 信息")
|
||||
return
|
||||
}
|
||||
|
||||
var dns []string
|
||||
for _, m := range match {
|
||||
dns = append(dns, m[1])
|
||||
}
|
||||
|
||||
service.Success(w, dns)
|
||||
}
|
||||
|
||||
// UpdateDNS 设置 DNS 信息
|
||||
func (s *App) UpdateDNS(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[DNS](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var dns string
|
||||
dns += "nameserver " + req.DNS1 + "\n"
|
||||
dns += "nameserver " + req.DNS2 + "\n"
|
||||
|
||||
if err := io.Write("/etc/resolv.conf", dns, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入 DNS 信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
// GetSWAP 获取 SWAP 信息
|
||||
func (s *App) GetSWAP(w http.ResponseWriter, r *http.Request) {
|
||||
var total, used, free string
|
||||
var size int64
|
||||
if io.Exists(filepath.Join(app.Root, "swap")) {
|
||||
file, err := io.FileInfo(filepath.Join(app.Root, "swap"))
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取 SWAP 信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
size = file.Size() / 1024 / 1024
|
||||
total = tools.FormatBytes(float64(file.Size()))
|
||||
} else {
|
||||
size = 0
|
||||
total = "0.00 B"
|
||||
}
|
||||
|
||||
raw, err := shell.Execf("free | grep Swap")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取 SWAP 信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
match := regexp.MustCompile(`Swap:\s+(\d+)\s+(\d+)\s+(\d+)`).FindStringSubmatch(raw)
|
||||
if len(match) >= 4 {
|
||||
used = tools.FormatBytes(cast.ToFloat64(match[2]) * 1024)
|
||||
free = tools.FormatBytes(cast.ToFloat64(match[3]) * 1024)
|
||||
}
|
||||
|
||||
service.Success(w, chix.M{
|
||||
"total": total,
|
||||
"size": size,
|
||||
"used": used,
|
||||
"free": free,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateSWAP 设置 SWAP 信息
|
||||
func (s *App) UpdateSWAP(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[SWAP](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if io.Exists(filepath.Join(app.Root, "swap")) {
|
||||
if _, err = shell.Execf("swapoff '%s'", filepath.Join(app.Root, "swap")); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
if _, err = shell.Execf("rm -f '%s'", filepath.Join(app.Root, "swap")); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
if _, err = shell.Execf(`sed -i "\|^%s|d" /etc/fstab`, filepath.Join(app.Root, "swap")); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if req.Size > 1 {
|
||||
var free string
|
||||
free, err = shell.Execf("df -k %s | awk '{print $4}' | tail -n 1", app.Root)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取硬盘空间失败")
|
||||
return
|
||||
}
|
||||
if cast.ToInt64(free)*1024 < req.Size*1024*1024 {
|
||||
service.Error(w, http.StatusInternalServerError, "硬盘空间不足,当前剩余%s", tools.FormatBytes(cast.ToFloat64(free)))
|
||||
return
|
||||
}
|
||||
|
||||
btrfsCheck, _ := shell.Execf("df -T %s | awk '{print $2}' | tail -n 1", app.Root)
|
||||
if strings.Contains(btrfsCheck, "btrfs") {
|
||||
if _, err = shell.Execf("btrfs filesystem mkswapfile --size %dM --uuid clear %s", req.Size, filepath.Join(app.Root, "swap")); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if _, err = shell.Execf("dd if=/dev/zero of=%s bs=1M count=%d", filepath.Join(app.Root, "swap"), req.Size); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
if _, err = shell.Execf("mkswap -f '%s'", filepath.Join(app.Root, "swap")); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
if err = io.Chmod(filepath.Join(app.Root, "swap"), 0600); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "设置 SWAP 权限失败")
|
||||
return
|
||||
}
|
||||
}
|
||||
if _, err = shell.Execf("swapon '%s'", filepath.Join(app.Root, "swap")); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
if _, err = shell.Execf("echo '%s swap swap defaults 0 0' >> /etc/fstab", filepath.Join(app.Root, "swap")); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
// GetTimezone 获取时区
|
||||
func (s *App) GetTimezone(w http.ResponseWriter, r *http.Request) {
|
||||
raw, err := shell.Execf("timedatectl | grep zone")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取时区信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
match := regexp.MustCompile(`zone:\s+(\S+)`).FindStringSubmatch(raw)
|
||||
if len(match) == 0 {
|
||||
service.Error(w, http.StatusInternalServerError, "找不到时区信息")
|
||||
return
|
||||
}
|
||||
|
||||
zonesRaw, err := shell.Execf("timedatectl list-timezones")
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "获取时区列表失败")
|
||||
return
|
||||
}
|
||||
zones := strings.Split(zonesRaw, "\n")
|
||||
|
||||
var zonesList []types.LV
|
||||
for _, z := range zones {
|
||||
zonesList = append(zonesList, types.LV{
|
||||
Label: z,
|
||||
Value: z,
|
||||
})
|
||||
}
|
||||
|
||||
service.Success(w, chix.M{
|
||||
"timezone": match[1],
|
||||
"timezones": zonesList,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateTimezone 设置时区
|
||||
func (s *App) UpdateTimezone(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[Timezone](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = shell.Execf("timedatectl set-timezone '%s'", req.Timezone); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
// UpdateTime 设置时间
|
||||
func (s *App) UpdateTime(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[Time](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = ntp.UpdateSystemTime(req.Time); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
|
||||
}
|
||||
|
||||
// SyncTime 同步时间
|
||||
func (s *App) SyncTime(w http.ResponseWriter, r *http.Request) {
|
||||
now, err := ntp.Now()
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = ntp.UpdateSystemTime(now); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
// GetHostname 获取主机名
|
||||
func (s *App) GetHostname(w http.ResponseWriter, r *http.Request) {
|
||||
hostname, _ := io.Read("/etc/hostname")
|
||||
service.Success(w, strings.TrimSpace(hostname))
|
||||
}
|
||||
|
||||
// UpdateHostname 设置主机名
|
||||
func (s *App) UpdateHostname(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[Hostname](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = shell.Execf("hostnamectl set-hostname '%s'", req.Hostname); err != nil {
|
||||
// 直接写 /etc/hostname
|
||||
if err = io.Write("/etc/hostname", req.Hostname, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入主机名失败")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
// GetHosts 获取 hosts 信息
|
||||
func (s *App) GetHosts(w http.ResponseWriter, r *http.Request) {
|
||||
hosts, _ := io.Read("/etc/hosts")
|
||||
service.Success(w, hosts)
|
||||
}
|
||||
|
||||
// UpdateHosts 设置 hosts 信息
|
||||
func (s *App) UpdateHosts(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[Hosts](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = io.Write("/etc/hosts", req.Hosts, 0644); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "写入 hosts 信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
|
||||
// UpdateRootPassword 设置 root 密码
|
||||
func (s *App) UpdateRootPassword(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := service.Bind[Password](r)
|
||||
if err != nil {
|
||||
service.Error(w, http.StatusUnprocessableEntity, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
req.Password = strings.ReplaceAll(req.Password, `'`, `\'`)
|
||||
if _, err = shell.Execf(`yes '%s' | passwd root`, req.Password); err != nil {
|
||||
service.Error(w, http.StatusInternalServerError, "%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Success(w, nil)
|
||||
}
|
||||
+10
-7
@@ -3,21 +3,23 @@ package biz
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/tnb-labs/panel/pkg/api"
|
||||
"github.com/acepanel/panel/pkg/api"
|
||||
"github.com/acepanel/panel/pkg/types"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Slug string `gorm:"not null;unique" json:"slug"`
|
||||
Channel string `gorm:"not null" json:"channel"`
|
||||
Version string `gorm:"not null" json:"version"`
|
||||
Show bool `gorm:"not null" json:"show"`
|
||||
ShowOrder int `gorm:"not null" json:"show_order"`
|
||||
Slug string `gorm:"not null;default:'';unique" json:"slug"`
|
||||
Channel string `gorm:"not null;default:''" json:"channel"`
|
||||
Version string `gorm:"not null;default:''" json:"version"`
|
||||
Show bool `gorm:"not null;default:false" json:"show"`
|
||||
ShowOrder int `gorm:"not null;default:0" json:"show_order"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type AppRepo interface {
|
||||
Categories() []types.LV
|
||||
All() api.Apps
|
||||
Get(slug string) (*api.App, error)
|
||||
UpdateExist(slug string) bool
|
||||
@@ -25,9 +27,10 @@ type AppRepo interface {
|
||||
GetInstalled(slug string) (*App, error)
|
||||
GetInstalledAll(query string, cond ...string) ([]*App, error)
|
||||
GetHomeShow() ([]map[string]string, error)
|
||||
IsInstalled(query string, cond ...string) (bool, error)
|
||||
IsInstalled(query string, cond ...any) (bool, error)
|
||||
Install(channel, slug string) error
|
||||
UnInstall(slug string) error
|
||||
Update(slug string) error
|
||||
UpdateShow(slug string, show bool) error
|
||||
UpdateOrder(slugs []string) error
|
||||
}
|
||||
|
||||
+12
-6
@@ -1,6 +1,10 @@
|
||||
package biz
|
||||
|
||||
import "github.com/tnb-labs/panel/pkg/types"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/acepanel/panel/pkg/types"
|
||||
)
|
||||
|
||||
type BackupType string
|
||||
|
||||
@@ -15,12 +19,14 @@ const (
|
||||
|
||||
type BackupRepo interface {
|
||||
List(typ BackupType) ([]*types.BackupFile, error)
|
||||
Create(typ BackupType, target string, path ...string) error
|
||||
Delete(typ BackupType, name string) error
|
||||
Restore(typ BackupType, backup, target string) error
|
||||
ClearExpired(path, prefix string, save int) error
|
||||
Create(ctx context.Context, typ BackupType, target string, account uint) error
|
||||
CreatePanel() error
|
||||
Delete(ctx context.Context, typ BackupType, name string) error
|
||||
Restore(ctx context.Context, typ BackupType, backup, target string) error
|
||||
ClearExpired(path, prefix string, save uint) error
|
||||
ClearStorageExpired(account uint, typ BackupType, prefix string, save uint) error
|
||||
CutoffLog(path, target string) error
|
||||
GetPath(typ BackupType) (string, error)
|
||||
GetDefaultPath(typ BackupType) string
|
||||
FixPanel() error
|
||||
UpdatePanel(version, url, checksum string) error
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/libtnb/utils/crypt"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/acepanel/panel/internal/app"
|
||||
"github.com/acepanel/panel/internal/http/request"
|
||||
"github.com/acepanel/panel/pkg/types"
|
||||
)
|
||||
|
||||
type BackupStorageType string
|
||||
|
||||
const (
|
||||
BackupStorageTypeLocal BackupStorageType = "local"
|
||||
BackupStorageTypeS3 BackupStorageType = "s3"
|
||||
BackupStorageTypeSFTP BackupStorageType = "sftp"
|
||||
BackupStorageTypeWebDAV BackupStorageType = "webdav"
|
||||
)
|
||||
|
||||
type BackupStorage struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Type BackupStorageType `gorm:"not null;default:''" json:"type"`
|
||||
Name string `gorm:"not null;default:''" json:"name"`
|
||||
Info types.BackupStorageInfo `gorm:"not null;default:'{}';serializer:json" json:"info"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (r *BackupStorage) BeforeSave(tx *gorm.DB) error {
|
||||
crypter, err := crypt.NewXChacha20Poly1305([]byte(app.Key))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch r.Type {
|
||||
case BackupStorageTypeS3:
|
||||
r.Info.AccessKey, err = crypter.Encrypt([]byte(r.Info.AccessKey))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Info.SecretKey, err = crypter.Encrypt([]byte(r.Info.SecretKey))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case BackupStorageTypeSFTP:
|
||||
r.Info.Username, err = crypter.Encrypt([]byte(r.Info.Username))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.Info.Password != "" {
|
||||
r.Info.Password, err = crypter.Encrypt([]byte(r.Info.Password))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if r.Info.PrivateKey != "" {
|
||||
r.Info.PrivateKey, err = crypter.Encrypt([]byte(r.Info.PrivateKey))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case BackupStorageTypeWebDAV:
|
||||
r.Info.Username, err = crypter.Encrypt([]byte(r.Info.Username))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Info.Password, err = crypter.Encrypt([]byte(r.Info.Password))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *BackupStorage) AfterFind(tx *gorm.DB) error {
|
||||
crypter, err := crypt.NewXChacha20Poly1305([]byte(app.Key))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch r.Type {
|
||||
case BackupStorageTypeS3:
|
||||
accessKey, err := crypter.Decrypt(r.Info.AccessKey)
|
||||
if err == nil {
|
||||
r.Info.AccessKey = string(accessKey)
|
||||
}
|
||||
secretKey, err := crypter.Decrypt(r.Info.SecretKey)
|
||||
if err == nil {
|
||||
r.Info.SecretKey = string(secretKey)
|
||||
}
|
||||
return nil
|
||||
case BackupStorageTypeSFTP:
|
||||
username, err := crypter.Decrypt(r.Info.Username)
|
||||
if err == nil {
|
||||
r.Info.Username = string(username)
|
||||
}
|
||||
if r.Info.Password != "" {
|
||||
password, err := crypter.Decrypt(r.Info.Password)
|
||||
if err == nil {
|
||||
r.Info.Password = string(password)
|
||||
}
|
||||
}
|
||||
if r.Info.PrivateKey != "" {
|
||||
privateKey, err := crypter.Decrypt(r.Info.PrivateKey)
|
||||
if err == nil {
|
||||
r.Info.PrivateKey = string(privateKey)
|
||||
}
|
||||
}
|
||||
case BackupStorageTypeWebDAV:
|
||||
username, err := crypter.Decrypt(r.Info.Username)
|
||||
if err == nil {
|
||||
r.Info.Username = string(username)
|
||||
}
|
||||
password, err := crypter.Decrypt(r.Info.Password)
|
||||
if err == nil {
|
||||
r.Info.Password = string(password)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type BackupAccountRepo interface {
|
||||
List(page, limit uint) ([]*BackupStorage, int64, error)
|
||||
Get(id uint) (*BackupStorage, error)
|
||||
Create(ctx context.Context, req *request.BackupStorageCreate) (*BackupStorage, error)
|
||||
Update(ctx context.Context, req *request.BackupStorageUpdate) error
|
||||
Delete(ctx context.Context, id uint) error
|
||||
}
|
||||
@@ -5,13 +5,15 @@ import "time"
|
||||
type CacheKey string
|
||||
|
||||
const (
|
||||
CacheKeyApps CacheKey = "apps"
|
||||
CacheKeyRewrites CacheKey = "rewrites"
|
||||
CacheKeyCategories CacheKey = "categories"
|
||||
CacheKeyApps CacheKey = "apps"
|
||||
CacheKeyEnvironment CacheKey = "environment"
|
||||
CacheKeyTemplates CacheKey = "templates"
|
||||
)
|
||||
|
||||
type Cache struct {
|
||||
Key CacheKey `gorm:"primaryKey" json:"key"`
|
||||
Value string `gorm:"not null" json:"value"`
|
||||
Value string `gorm:"not null;default:''" json:"value"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
@@ -19,6 +21,8 @@ type Cache struct {
|
||||
type CacheRepo interface {
|
||||
Get(key CacheKey, defaultValue ...string) (string, error)
|
||||
Set(key CacheKey, value string) error
|
||||
UpdateCategories() error
|
||||
UpdateApps() error
|
||||
UpdateRewrites() error
|
||||
UpdateEnvironments() error
|
||||
UpdateTemplates() error
|
||||
}
|
||||
|
||||
+26
-19
@@ -1,26 +1,31 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/http/request"
|
||||
"github.com/tnb-labs/panel/pkg/acme"
|
||||
"github.com/tnb-labs/panel/pkg/types"
|
||||
mholtacme "github.com/mholt/acmez/v3/acme"
|
||||
|
||||
"github.com/acepanel/panel/internal/http/request"
|
||||
"github.com/acepanel/panel/pkg/acme"
|
||||
"github.com/acepanel/panel/pkg/types"
|
||||
)
|
||||
|
||||
type Cert struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
AccountID uint `gorm:"not null" json:"account_id"` // 关联的 ACME 账户 ID
|
||||
WebsiteID uint `gorm:"not null" json:"website_id"` // 关联的网站 ID
|
||||
DNSID uint `gorm:"not null" json:"dns_id"` // 关联的 DNS ID
|
||||
Type string `gorm:"not null" json:"type"` // 证书类型 (P256, P384, 2048, 3072, 4096)
|
||||
Domains []string `gorm:"not null;serializer:json" json:"domains"`
|
||||
AutoRenew bool `gorm:"not null" json:"auto_renew"` // 自动续签
|
||||
CertURL string `gorm:"not null" json:"cert_url"` // 证书 URL (续签时使用)
|
||||
Cert string `gorm:"not null" json:"cert"` // 证书内容
|
||||
Key string `gorm:"not null" json:"key"` // 私钥内容
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
AccountID uint `gorm:"not null;default:0" json:"account_id"` // 关联的 ACME 账户 ID
|
||||
WebsiteID uint `gorm:"not null;default:0" json:"website_id"` // 关联的网站 ID
|
||||
DNSID uint `gorm:"not null;default:0" json:"dns_id"` // 关联的 DNS ID
|
||||
Type string `gorm:"not null;default:''" json:"type"` // 证书类型 (P256, P384, 2048, 3072, 4096)
|
||||
Domains []string `gorm:"not null;default:'[]';serializer:json" json:"domains"`
|
||||
AutoRenewal bool `gorm:"not null;default:false" json:"auto_renewal"` // 自动续签
|
||||
RenewalInfo mholtacme.RenewalInfo `gorm:"not null;default:'{}';serializer:json" json:"renewal_info"` // 续签信息
|
||||
CertURL string `gorm:"not null;default:''" json:"cert_url"` // 证书 URL (续签时使用)
|
||||
Cert string `gorm:"not null;default:''" json:"cert"` // 证书内容
|
||||
Key string `gorm:"not null;default:''" json:"key"` // 私钥内容
|
||||
Script string `gorm:"not null;default:''" json:"script"` // 部署脚本
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
Website *Website `gorm:"foreignKey:WebsiteID" json:"website"`
|
||||
Account *CertAccount `gorm:"foreignKey:AccountID" json:"account"`
|
||||
@@ -31,14 +36,16 @@ type CertRepo interface {
|
||||
List(page, limit uint) ([]*types.CertList, int64, error)
|
||||
Get(id uint) (*Cert, error)
|
||||
GetByWebsite(WebsiteID uint) (*Cert, error)
|
||||
Upload(req *request.CertUpload) (*Cert, error)
|
||||
Create(req *request.CertCreate) (*Cert, error)
|
||||
Update(req *request.CertUpdate) error
|
||||
Delete(id uint) error
|
||||
Upload(ctx context.Context, req *request.CertUpload) (*Cert, error)
|
||||
Create(ctx context.Context, req *request.CertCreate) (*Cert, error)
|
||||
Update(ctx context.Context, req *request.CertUpdate) error
|
||||
Delete(ctx context.Context, id uint) error
|
||||
ObtainAuto(id uint) (*acme.Certificate, error)
|
||||
ObtainManual(id uint) (*acme.Certificate, error)
|
||||
ObtainPanel(account *CertAccount, ips []string) ([]byte, []byte, error)
|
||||
ObtainSelfSigned(id uint) error
|
||||
Renew(id uint) (*acme.Certificate, error)
|
||||
RefreshRenewalInfo(id uint) (mholtacme.RenewalInfo, error)
|
||||
ManualDNS(id uint) ([]acme.DNSRecord, error)
|
||||
Deploy(ID, WebsiteID uint) error
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/http/request"
|
||||
"github.com/acepanel/panel/internal/http/request"
|
||||
)
|
||||
|
||||
type CertAccount struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Email string `gorm:"not null" json:"email"`
|
||||
CA string `gorm:"not null" json:"ca"` // CA 提供商 (letsencrypt, zerossl, sslcom, google, buypass)
|
||||
Kid string `gorm:"not null" json:"kid"`
|
||||
HmacEncoded string `gorm:"not null" json:"hmac_encoded"`
|
||||
PrivateKey string `gorm:"not null" json:"private_key"`
|
||||
KeyType string `gorm:"not null" json:"key_type"` // 密钥类型 (P256, P384, 2048, 3072, 4096)
|
||||
Email string `gorm:"not null;default:''" json:"email"`
|
||||
CA string `gorm:"not null;default:'letsencrypt'" json:"ca"` // CA 提供商 (letsencrypt, zerossl, sslcom, google, buypass)
|
||||
Kid string `gorm:"not null;default:''" json:"kid"`
|
||||
HmacEncoded string `gorm:"not null;default:''" json:"hmac_encoded"`
|
||||
PrivateKey string `gorm:"not null;default:''" json:"private_key"`
|
||||
KeyType string `gorm:"not null;default:'P256'" json:"key_type"` // 密钥类型 (P256, P384, 2048, 3072, 4096)
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
@@ -24,7 +25,7 @@ type CertAccountRepo interface {
|
||||
List(page, limit uint) ([]*CertAccount, int64, error)
|
||||
GetDefault(userID uint) (*CertAccount, error)
|
||||
Get(id uint) (*CertAccount, error)
|
||||
Create(req *request.CertAccountCreate) (*CertAccount, error)
|
||||
Update(req *request.CertAccountUpdate) error
|
||||
Delete(id uint) error
|
||||
Create(ctx context.Context, req *request.CertAccountCreate) (*CertAccount, error)
|
||||
Update(ctx context.Context, req *request.CertAccountUpdate) error
|
||||
Delete(ctx context.Context, id uint) error
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/tnb-labs/panel/internal/http/request"
|
||||
"github.com/tnb-labs/panel/pkg/acme"
|
||||
"github.com/acepanel/panel/internal/http/request"
|
||||
"github.com/acepanel/panel/pkg/acme"
|
||||
)
|
||||
|
||||
type CertDNS struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"not null" json:"name"` // 备注名称
|
||||
Type string `gorm:"not null" json:"type"` // DNS 提供商 (tencent, aliyun, cloudflare)
|
||||
Data acme.DNSParam `gorm:"not null;serializer:json" json:"dns_param"`
|
||||
Name string `gorm:"not null;default:''" json:"name"` // 备注名称
|
||||
Type acme.DnsType `gorm:"not null;default:'aliyun'" json:"type"` // DNS 提供商
|
||||
Data acme.DNSParam `gorm:"not null;default:'{}';serializer:json" json:"dns_param"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
@@ -21,7 +22,7 @@ type CertDNS struct {
|
||||
type CertDNSRepo interface {
|
||||
List(page, limit uint) ([]*CertDNS, int64, error)
|
||||
Get(id uint) (*CertDNS, error)
|
||||
Create(req *request.CertDNSCreate) (*CertDNS, error)
|
||||
Update(req *request.CertDNSUpdate) error
|
||||
Delete(id uint) error
|
||||
Create(ctx context.Context, req *request.CertDNSCreate) (*CertDNS, error)
|
||||
Update(ctx context.Context, req *request.CertDNSUpdate) error
|
||||
Delete(ctx context.Context, id uint) error
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"github.com/tnb-labs/panel/internal/http/request"
|
||||
"github.com/tnb-labs/panel/pkg/types"
|
||||
"github.com/acepanel/panel/internal/http/request"
|
||||
"github.com/acepanel/panel/pkg/types"
|
||||
)
|
||||
|
||||
type ContainerRepo interface {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package biz
|
||||
|
||||
import "github.com/acepanel/panel/pkg/types"
|
||||
|
||||
type ContainerComposeRepo interface {
|
||||
List() ([]types.ContainerCompose, error)
|
||||
Get(name string) (string, []types.KV, error)
|
||||
Create(name, compose string, envs []types.KV) error
|
||||
Update(name, compose string, envs []types.KV) error
|
||||
Up(name string, force bool) error
|
||||
Down(name string) error
|
||||
Remove(name string) error
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"github.com/tnb-labs/panel/internal/http/request"
|
||||
"github.com/tnb-labs/panel/pkg/types"
|
||||
"github.com/acepanel/panel/internal/http/request"
|
||||
"github.com/acepanel/panel/pkg/types"
|
||||
)
|
||||
|
||||
type ContainerImageRepo interface {
|
||||
List() ([]types.ContainerImage, error)
|
||||
Exist(name string) (bool, error)
|
||||
Pull(req *request.ContainerImagePull) error
|
||||
Remove(id string) error
|
||||
Prune() error
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"github.com/tnb-labs/panel/internal/http/request"
|
||||
"github.com/tnb-labs/panel/pkg/types"
|
||||
"github.com/acepanel/panel/internal/http/request"
|
||||
"github.com/acepanel/panel/pkg/types"
|
||||
)
|
||||
|
||||
type ContainerNetworkRepo interface {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"github.com/tnb-labs/panel/internal/http/request"
|
||||
"github.com/tnb-labs/panel/pkg/types"
|
||||
"github.com/acepanel/panel/internal/http/request"
|
||||
"github.com/acepanel/panel/pkg/types"
|
||||
)
|
||||
|
||||
type ContainerVolumeRepo interface {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user