mirror of
https://github.com/rustfs/rustfs.git
synced 2026-01-17 17:40:38 +00:00
Compare commits
1186 Commits
1.0.0-alph
...
dev_object
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9948b1f709 | ||
|
|
4d67c1d0a6 | ||
|
|
ff264f3385 | ||
|
|
ff4da5cd47 | ||
|
|
4cc915fb45 | ||
|
|
751abeca2b | ||
|
|
5ab2ce3cfe | ||
|
|
4d26fa48d7 | ||
|
|
5c65368729 | ||
|
|
c16ce7b61b | ||
|
|
25388ba70c | ||
|
|
9cc34f9f01 | ||
|
|
02ad3d3832 | ||
|
|
1c5ba761ef | ||
|
|
7fec5b250a | ||
|
|
c861635332 | ||
|
|
f4d0a81e06 | ||
|
|
b6a5094382 | ||
|
|
9865b5aa3e | ||
|
|
5850c3e8a3 | ||
|
|
b52198d7f2 | ||
|
|
58c3a46138 | ||
|
|
bd4e7c23bb | ||
|
|
66c2a2fd93 | ||
|
|
cdd41752e0 | ||
|
|
bf407715d0 | ||
|
|
5e3c97b0e9 | ||
|
|
2f20fe9749 | ||
|
|
5e13fc3be2 | ||
|
|
1d45123968 | ||
|
|
03af4369bd | ||
|
|
88b2d893dc | ||
|
|
756e70cc93 | ||
|
|
d2a0c9fd62 | ||
|
|
d6de724517 | ||
|
|
8dfb3643ec | ||
|
|
d2857467e0 | ||
|
|
8887b7ea90 | ||
|
|
18ca25fbfd | ||
|
|
f3c1116935 | ||
|
|
2fd5ef75cf | ||
|
|
c673fa0e3d | ||
|
|
707406e4c5 | ||
|
|
8a7a6599c8 | ||
|
|
881afbfae6 | ||
|
|
e050fffcae | ||
|
|
41e378fc28 | ||
|
|
751be4bdf6 | ||
|
|
659869e8ad | ||
|
|
52da2569dd | ||
|
|
c9ff699030 | ||
|
|
3a32517f79 | ||
|
|
d5539bf22f | ||
|
|
1993a7036f | ||
|
|
0782f05388 | ||
|
|
2f697d0635 | ||
|
|
ad2c1a15b7 | ||
|
|
5ef7571211 | ||
|
|
b0174122c8 | ||
|
|
7f70ab3cd6 | ||
|
|
fefa8cb2c8 | ||
|
|
ae4c23e316 | ||
|
|
fffc79e981 | ||
|
|
4cd2c8e99c | ||
|
|
a8bd8809b0 | ||
|
|
6f7d279fce | ||
|
|
bcbd72d849 | ||
|
|
b73e4ac7e8 | ||
|
|
ea8dfa43a4 | ||
|
|
b50e88a5a0 | ||
|
|
b0447bb692 | ||
|
|
9ec22255e0 | ||
|
|
249a46bc8e | ||
|
|
920551ca92 | ||
|
|
f056b3fb44 | ||
|
|
b675e01707 | ||
|
|
1c93a5e4e0 | ||
|
|
d65c58d9de | ||
|
|
28a8977308 | ||
|
|
35cf7db9ed | ||
|
|
e1513aa00e | ||
|
|
670636b3aa | ||
|
|
2b62560f48 | ||
|
|
01a340d596 | ||
|
|
3a6e3d49b3 | ||
|
|
86a99d214c | ||
|
|
7fe325f47e | ||
|
|
671263e22c | ||
|
|
e4e923b4b2 | ||
|
|
87482e82f4 | ||
|
|
902993a133 | ||
|
|
609e55a5a2 | ||
|
|
1e3287f610 | ||
|
|
27c6030f09 | ||
|
|
1e00db816c | ||
|
|
366fd98aeb | ||
|
|
f1ef7149e3 | ||
|
|
ab88166990 | ||
|
|
34dadee8a6 | ||
|
|
021dc36f2f | ||
|
|
09b24d5d41 | ||
|
|
947ced1d92 | ||
|
|
8f01696dbe | ||
|
|
bfb4c6dfd4 | ||
|
|
992b0c2cb6 | ||
|
|
01220f88d9 | ||
|
|
42e020c0bb | ||
|
|
4e5d6ff772 | ||
|
|
ea4a225d70 | ||
|
|
d4bd8b66a9 | ||
|
|
defdcf528f | ||
|
|
69cb6ead25 | ||
|
|
0403c23492 | ||
|
|
413d581d7c | ||
|
|
1777994de7 | ||
|
|
e8efd8ef79 | ||
|
|
1380598a0c | ||
|
|
142281e96a | ||
|
|
af6bb3ae5b | ||
|
|
9734d4cfa5 | ||
|
|
9ad152c14f | ||
|
|
b40b6a17f4 | ||
|
|
eae1ccc629 | ||
|
|
295b502e2a | ||
|
|
a3214222af | ||
|
|
257e858017 | ||
|
|
9c90426032 | ||
|
|
5c7b105b4a | ||
|
|
a032c401d1 | ||
|
|
a608e0dd65 | ||
|
|
2ef98ee43a | ||
|
|
8d4e68fe5f | ||
|
|
4e5345d01f | ||
|
|
8a09a81a69 | ||
|
|
62d994c103 | ||
|
|
41373960bc | ||
|
|
bf543f9628 | ||
|
|
e24d74b1f0 | ||
|
|
f81bef28df | ||
|
|
7730e6cd3a | ||
|
|
a18f549c1f | ||
|
|
3592ffb791 | ||
|
|
ad5bb38e2b | ||
|
|
6434728aea | ||
|
|
df7e690c47 | ||
|
|
65be8145ff | ||
|
|
4ac4b35c5e | ||
|
|
ab54ff49eb | ||
|
|
a9de8e0d53 | ||
|
|
cebe84a896 | ||
|
|
96de156763 | ||
|
|
93f1b5dbf1 | ||
|
|
ff908f19e7 | ||
|
|
9b85384305 | ||
|
|
ca0bfcfc7e | ||
|
|
70a3c49b61 | ||
|
|
c43876ee46 | ||
|
|
3bd25b63c8 | ||
|
|
797d7218ee | ||
|
|
21289797c2 | ||
|
|
ff6d2fe84e | ||
|
|
f220b04074 | ||
|
|
e0c02fa5bc | ||
|
|
1a8574c96f | ||
|
|
f2c9464eb0 | ||
|
|
22d085a5c9 | ||
|
|
d0bc3dec23 | ||
|
|
0a0fb41037 | ||
|
|
845f503739 | ||
|
|
143017f925 | ||
|
|
38377f81ed | ||
|
|
f80595e048 | ||
|
|
d5ba9cdf28 | ||
|
|
a39f622402 | ||
|
|
26d4726181 | ||
|
|
0b4c050b21 | ||
|
|
cf56a0650e | ||
|
|
31adf1486d | ||
|
|
be5bdc0b20 | ||
|
|
bf1bb0823b | ||
|
|
45249ccf28 | ||
|
|
022304d046 | ||
|
|
3b2b4f08fc | ||
|
|
e9a31279bd | ||
|
|
6f50b69c5f | ||
|
|
e0bfedae75 | ||
|
|
c1355481cb | ||
|
|
7c119d6c92 | ||
|
|
eeabaa71fb | ||
|
|
2ec135c9c0 | ||
|
|
21bff0075f | ||
|
|
2a86608ee9 | ||
|
|
84ae72f71d | ||
|
|
6be4eb0322 | ||
|
|
df5e3dad27 | ||
|
|
f31d9c3f97 | ||
|
|
301a7a20c5 | ||
|
|
fc67fbfadb | ||
|
|
89709184c2 | ||
|
|
2949b7d203 | ||
|
|
4e68d72de9 | ||
|
|
02cef27230 | ||
|
|
ba5cbcb3f1 | ||
|
|
922330ded2 | ||
|
|
87c63193ae | ||
|
|
835b7fbac5 | ||
|
|
fe456be075 | ||
|
|
b492342e55 | ||
|
|
b80f64d9b4 | ||
|
|
dbbcd2a21b | ||
|
|
fc9d433038 | ||
|
|
d852f62757 | ||
|
|
b0f14e69f6 | ||
|
|
6584bf9607 | ||
|
|
84f9989752 | ||
|
|
a01897211e | ||
|
|
88159507e3 | ||
|
|
91eb469a7d | ||
|
|
e251ffe85d | ||
|
|
91cfc335ba | ||
|
|
3985330da3 | ||
|
|
d8f9161ae8 | ||
|
|
aef13a77a3 | ||
|
|
44a514246b | ||
|
|
5364779766 | ||
|
|
7eefafaad5 | ||
|
|
6e17a91919 | ||
|
|
6f2b4b34b9 | ||
|
|
dbb5625199 | ||
|
|
89785dc06b | ||
|
|
46c3134487 | ||
|
|
b1a6da8d73 | ||
|
|
dde551ef94 | ||
|
|
65f036442a | ||
|
|
2167f5e728 | ||
|
|
9fc284bef3 | ||
|
|
b57b92e382 | ||
|
|
0d28c1cbf8 | ||
|
|
e3af3d1b94 | ||
|
|
7411283fc8 | ||
|
|
c331365ebf | ||
|
|
0e9d1d63d3 | ||
|
|
4e9e63dff6 | ||
|
|
ad528935ad | ||
|
|
e634ffdd23 | ||
|
|
165660e106 | ||
|
|
14425be416 | ||
|
|
47afb1a651 | ||
|
|
95d59d7206 | ||
|
|
e2b7a9772c | ||
|
|
5ccb08e73f | ||
|
|
778ca76607 | ||
|
|
c78338df9f | ||
|
|
adf401e09a | ||
|
|
bb6324b4e5 | ||
|
|
9b6170e94f | ||
|
|
8d865eb048 | ||
|
|
8fefb8f7cb | ||
|
|
443fbeee38 | ||
|
|
31addf01dd | ||
|
|
2a6554a3c6 | ||
|
|
691a0bed5d | ||
|
|
5088f51236 | ||
|
|
aeb1beab17 | ||
|
|
d4b4ef1108 | ||
|
|
16bbe517b4 | ||
|
|
f1add9dc58 | ||
|
|
2363a3706f | ||
|
|
08e8c7258c | ||
|
|
5df2bdb063 | ||
|
|
115d9ea780 | ||
|
|
70b65343ab | ||
|
|
77c7146677 | ||
|
|
980ecb8bd3 | ||
|
|
0408f29301 | ||
|
|
1d7a97197a | ||
|
|
5152be44f0 | ||
|
|
a36730220e | ||
|
|
54069d7cc3 | ||
|
|
abf3c5a5b5 | ||
|
|
c2dd6e2a6a | ||
|
|
0cd10a9352 | ||
|
|
3cc4eb55b3 | ||
|
|
c1f781256f | ||
|
|
4fa63bec31 | ||
|
|
56ce9d2776 | ||
|
|
c16bd2fec1 | ||
|
|
e25d444cdf | ||
|
|
58b08ddbd1 | ||
|
|
015eeb0c9f | ||
|
|
5e36f7f107 | ||
|
|
662563c216 | ||
|
|
35065bc65a | ||
|
|
b9cf1765f8 | ||
|
|
15c815cae6 | ||
|
|
85368b38d3 | ||
|
|
61ea8fc988 | ||
|
|
928ccb1c80 | ||
|
|
48ec993b60 | ||
|
|
983f4f0316 | ||
|
|
b03d17bc97 | ||
|
|
cba5eb2cc3 | ||
|
|
b221458d4d | ||
|
|
de5fdb9299 | ||
|
|
fe7abc2611 | ||
|
|
344fc6fb91 | ||
|
|
5a34318947 | ||
|
|
6c363c8149 | ||
|
|
76948cae65 | ||
|
|
689d3ae03e | ||
|
|
8f0e239f3d | ||
|
|
a4a5170c1c | ||
|
|
0f87cb1e72 | ||
|
|
b7a7dcead8 | ||
|
|
1cfd459b83 | ||
|
|
8d3d11b07e | ||
|
|
e3a81df926 | ||
|
|
3cc2f25450 | ||
|
|
fd7e3cabca | ||
|
|
79be7370ed | ||
|
|
375012bd17 | ||
|
|
7c186a84c6 | ||
|
|
59a66bdbd6 | ||
|
|
4e05fab8e0 | ||
|
|
39950a4ca2 | ||
|
|
a770600fa7 | ||
|
|
b39b8838cc | ||
|
|
aca64b8c36 | ||
|
|
f50c320fda | ||
|
|
00de1d2c89 | ||
|
|
1b5a181c1c | ||
|
|
074c66fa00 | ||
|
|
a4aa197a37 | ||
|
|
25e70692ec | ||
|
|
ec2dd25f92 | ||
|
|
bded37e2be | ||
|
|
188875933e | ||
|
|
24559537ab | ||
|
|
7f4f40f3ba | ||
|
|
9c5abf6831 | ||
|
|
59c64d4458 | ||
|
|
c7394e1147 | ||
|
|
ed340c1345 | ||
|
|
2e4f444847 | ||
|
|
a4fda6d21e | ||
|
|
c615d35cd0 | ||
|
|
3a404c347c | ||
|
|
456a56cd80 | ||
|
|
587c7d41e9 | ||
|
|
6616a4862f | ||
|
|
f70b654c35 | ||
|
|
a0261b4c82 | ||
|
|
4e6f1e787b | ||
|
|
173bacdb23 | ||
|
|
27e5cafa22 | ||
|
|
1408913afe | ||
|
|
15f5962dc0 | ||
|
|
7ebbb91553 | ||
|
|
6d6a8a2d6e | ||
|
|
f4f764218d | ||
|
|
a7305de3ad | ||
|
|
38411c675d | ||
|
|
b3d43ce795 | ||
|
|
bcfcb97c8d | ||
|
|
8790e1e4db | ||
|
|
a2284af3ea | ||
|
|
b145dfd437 | ||
|
|
4d423b6510 | ||
|
|
cd238f9b5a | ||
|
|
9a6178beee | ||
|
|
5a636d3254 | ||
|
|
5a5e1f6bd1 | ||
|
|
811a9e3964 | ||
|
|
cfff691a58 | ||
|
|
b64e78246d | ||
|
|
8b7c33814f | ||
|
|
6321dd5c72 | ||
|
|
ff67a4382d | ||
|
|
bbee212b38 | ||
|
|
2f94beff93 | ||
|
|
9c062f18b6 | ||
|
|
49b68327bb | ||
|
|
8fb44cd89e | ||
|
|
07ffb83356 | ||
|
|
87790721c5 | ||
|
|
900722e800 | ||
|
|
c5d6bc20f8 | ||
|
|
6176f0b00e | ||
|
|
4dc2551e1d | ||
|
|
de4aec519b | ||
|
|
bcb9db7c59 | ||
|
|
93dcfc0cdd | ||
|
|
2f25c74b32 | ||
|
|
cebf5699a4 | ||
|
|
3d4760181d | ||
|
|
1ead472da2 | ||
|
|
4dee6d5c76 | ||
|
|
f0bf2bed74 | ||
|
|
4032e5459e | ||
|
|
1ad3a76c3c | ||
|
|
47b9d45ff8 | ||
|
|
f6b9964dcd | ||
|
|
1453a0fc5a | ||
|
|
6d344d2aff | ||
|
|
cc4a8a8e51 | ||
|
|
db1d0cf3df | ||
|
|
35a7bc310b | ||
|
|
8983db39ae | ||
|
|
ae06d87f10 | ||
|
|
d724c6132d | ||
|
|
283b2981e3 | ||
|
|
3b2df514a7 | ||
|
|
9b0f498add | ||
|
|
eedeb188f2 | ||
|
|
6727e15055 | ||
|
|
e758ef4022 | ||
|
|
4e09e0a11a | ||
|
|
693b06eefd | ||
|
|
2e4a63c4a0 | ||
|
|
1d58a07f29 | ||
|
|
53d4cb81de | ||
|
|
aaf39a4301 | ||
|
|
f50406c789 | ||
|
|
6356b3409a | ||
|
|
ec268dcb5a | ||
|
|
2ae714be31 | ||
|
|
666a3db95d | ||
|
|
895317ec80 | ||
|
|
47c4e2ac73 | ||
|
|
8413ac6be3 | ||
|
|
8aaa916b31 | ||
|
|
6a8a653f9d | ||
|
|
9866f959c1 | ||
|
|
c9f6da9732 | ||
|
|
381f5f0a53 | ||
|
|
1bed82420e | ||
|
|
f965662514 | ||
|
|
da6ec62be5 | ||
|
|
b73439bce1 | ||
|
|
23be31e1f2 | ||
|
|
eaa8ce4bf7 | ||
|
|
c0658a8223 | ||
|
|
373d97ce97 | ||
|
|
2568582271 | ||
|
|
2d285ca504 | ||
|
|
ea0393f4c0 | ||
|
|
ff18894d5d | ||
|
|
b4caff13cf | ||
|
|
c70994ee9d | ||
|
|
75fd8da685 | ||
|
|
54ca354d7e | ||
|
|
218e06a3fe | ||
|
|
a95c9b32d8 | ||
|
|
48f2fdc45d | ||
|
|
ace5857959 | ||
|
|
1022bbec1e | ||
|
|
9c19e1aa41 | ||
|
|
c338ea3c86 | ||
|
|
8cb4d72a7b | ||
|
|
2b1dcb77cc | ||
|
|
f64d6751a6 | ||
|
|
e25cd9b5f6 | ||
|
|
40f6185b24 | ||
|
|
6ee30b8bfc | ||
|
|
8284d7d92c | ||
|
|
67ae916968 | ||
|
|
990db0636a | ||
|
|
9ba6f89dc5 | ||
|
|
20e7dc919f | ||
|
|
79351da42a | ||
|
|
cb9ecd890c | ||
|
|
ca4a936c6e | ||
|
|
d7417d841f | ||
|
|
e65bee049a | ||
|
|
87d6083807 | ||
|
|
d8198cc01f | ||
|
|
1505429c5f | ||
|
|
24fe747b69 | ||
|
|
4c0fbd238b | ||
|
|
df4b285e46 | ||
|
|
1744d8f23a | ||
|
|
3695bce742 | ||
|
|
0295229e7e | ||
|
|
0122343075 | ||
|
|
475d758645 | ||
|
|
0dd21e9075 | ||
|
|
4a0bc24aa6 | ||
|
|
3d5cfab7c5 | ||
|
|
dddfb8cbc2 | ||
|
|
ec9b1262cb | ||
|
|
c844d16c2e | ||
|
|
b3890cd07d | ||
|
|
5aa8616268 | ||
|
|
784e3c82f8 | ||
|
|
984faa2e85 | ||
|
|
b562bd20bb | ||
|
|
ad6c7ca623 | ||
|
|
9e57981114 | ||
|
|
41e7dfea55 | ||
|
|
f2767239af | ||
|
|
bf5c2b6a9c | ||
|
|
7e7ab2cab3 | ||
|
|
66910bdfee | ||
|
|
c9f9edfcf5 | ||
|
|
cb9457ae09 | ||
|
|
3aee1598a9 | ||
|
|
a9dbd5f341 | ||
|
|
0ff795f67a | ||
|
|
93f7370157 | ||
|
|
cb4732fe2f | ||
|
|
e65b7975fb | ||
|
|
c0243ce329 | ||
|
|
12a1e667e2 | ||
|
|
6ec7748677 | ||
|
|
671f2b7473 | ||
|
|
1968bdc219 | ||
|
|
59c28b4f86 | ||
|
|
672f06eb80 | ||
|
|
b9712afd29 | ||
|
|
0531437c94 | ||
|
|
bb3ea541a2 | ||
|
|
e4e2fa23ce | ||
|
|
682f3009ba | ||
|
|
9bfd259b03 | ||
|
|
3266af774c | ||
|
|
36f8101c21 | ||
|
|
dd83f870b9 | ||
|
|
2c3a9a3bc8 | ||
|
|
10a4769115 | ||
|
|
dc292ca657 | ||
|
|
7fc3a500d3 | ||
|
|
ccb0f15655 | ||
|
|
426f4cf569 | ||
|
|
2585a09219 | ||
|
|
83edaef6d3 | ||
|
|
1551360dda | ||
|
|
977cef9d29 | ||
|
|
f7b63ebac1 | ||
|
|
c90a98e427 | ||
|
|
2cd2f99723 | ||
|
|
b5a1f6fcd6 | ||
|
|
b99a9b68dc | ||
|
|
3ec7f90d95 | ||
|
|
86b4cae95d | ||
|
|
c3dd28c510 | ||
|
|
bec2b2a5e0 | ||
|
|
d01d3f9c96 | ||
|
|
057b6b63b3 | ||
|
|
b522e15328 | ||
|
|
86247e59a0 | ||
|
|
7e0926febf | ||
|
|
141ad15a48 | ||
|
|
39483d90d1 | ||
|
|
c0f0823076 | ||
|
|
ebee564436 | ||
|
|
11d99845ce | ||
|
|
f0fe3e30af | ||
|
|
21713ebea7 | ||
|
|
f4f818277e | ||
|
|
3316a6e073 | ||
|
|
6246e8a675 | ||
|
|
861a714014 | ||
|
|
343aeda8ba | ||
|
|
53c1184c1f | ||
|
|
43b52b32f2 | ||
|
|
d1521c021d | ||
|
|
bd7f82ce45 | ||
|
|
3ad460c6e9 | ||
|
|
175a636755 | ||
|
|
9e482e9dd4 | ||
|
|
ce4842afd7 | ||
|
|
ffa49aecea | ||
|
|
a19c75f1ee | ||
|
|
55bf4d9597 | ||
|
|
f0149c3e04 | ||
|
|
2e1742c1f4 | ||
|
|
a0e956e61f | ||
|
|
907c606bbd | ||
|
|
baa8b62dc3 | ||
|
|
c5b8a20092 | ||
|
|
5afc578951 | ||
|
|
92a28b1639 | ||
|
|
5f6ee3f9f2 | ||
|
|
8033cc7d72 | ||
|
|
3138f23648 | ||
|
|
fffe24bd2c | ||
|
|
bc05217322 | ||
|
|
0dbc66af7a | ||
|
|
130a719465 | ||
|
|
bdf11fe3b3 | ||
|
|
1e8bea42c7 | ||
|
|
acb6f72ee7 | ||
|
|
74220acb2e | ||
|
|
a15bda1bce | ||
|
|
f8f6d7d9be | ||
|
|
0135ddcb36 | ||
|
|
cd306cd450 | ||
|
|
c4823c4c89 | ||
|
|
cf99ca2cf8 | ||
|
|
9c0364b0c3 | ||
|
|
39ffccbf45 | ||
|
|
dc42003969 | ||
|
|
55583294c9 | ||
|
|
73c3f202d0 | ||
|
|
b0b01c105f | ||
|
|
ba0b1c93d2 | ||
|
|
e5ba415ec5 | ||
|
|
74434317d3 | ||
|
|
558d077ef6 | ||
|
|
354fa5470c | ||
|
|
dcac6fae70 | ||
|
|
e9b00bb369 | ||
|
|
623869ce25 | ||
|
|
052a27ddaf | ||
|
|
7133e492b6 | ||
|
|
e7b94e9698 | ||
|
|
6af228d82b | ||
|
|
705d51c818 | ||
|
|
469d2cc321 | ||
|
|
056b611125 | ||
|
|
55827b0720 | ||
|
|
a8b8ea10d1 | ||
|
|
8f98bb6897 | ||
|
|
3c76ee9109 | ||
|
|
93f06e431e | ||
|
|
1e04c65f76 | ||
|
|
b4696f8c60 | ||
|
|
cced40b5cb | ||
|
|
fb24c4550b | ||
|
|
11fee7db63 | ||
|
|
c28dc2f441 | ||
|
|
fefd96a5d3 | ||
|
|
e7042f4429 | ||
|
|
ad51ab6273 | ||
|
|
2b05500ae3 | ||
|
|
5dafee2c0f | ||
|
|
7370973a79 | ||
|
|
e1703d287b | ||
|
|
8a0a25f0bf | ||
|
|
b350323440 | ||
|
|
420df51e47 | ||
|
|
7f74ae6e79 | ||
|
|
2e04dee223 | ||
|
|
20e434be5a | ||
|
|
caec2767ab | ||
|
|
73b003b0bc | ||
|
|
352416e309 | ||
|
|
47d3d4f845 | ||
|
|
b08878c75a | ||
|
|
1b99041e5f | ||
|
|
8eb6a6fc8a | ||
|
|
96e3b5c9e0 | ||
|
|
8f8d353eb2 | ||
|
|
86497d41e0 | ||
|
|
4129700cf3 | ||
|
|
5d1f9d5cd9 | ||
|
|
8465f10648 | ||
|
|
8b2450cdcf | ||
|
|
baf03dffd4 | ||
|
|
807be52e8f | ||
|
|
bd42ff1393 | ||
|
|
b167a7b384 | ||
|
|
9f2b3a27fa | ||
|
|
865f0aca80 | ||
|
|
c3546a1931 | ||
|
|
b0e65f71d4 | ||
|
|
f763218864 | ||
|
|
9c4fc0c55c | ||
|
|
893fa3131b | ||
|
|
0a49a986a4 | ||
|
|
0930ec2d3d | ||
|
|
e20f556f37 | ||
|
|
f78b0d82e9 | ||
|
|
e4a58abd7b | ||
|
|
ff28415863 | ||
|
|
975d15a8ef | ||
|
|
653d2753d4 | ||
|
|
d301765263 | ||
|
|
a03f9a0f3e | ||
|
|
a80f26ec36 | ||
|
|
c6088d97d3 | ||
|
|
e9339a16b1 | ||
|
|
4de6c838b4 | ||
|
|
3cf262afbf | ||
|
|
36f9592a10 | ||
|
|
cc57d2a073 | ||
|
|
9d7baa855e | ||
|
|
01d7bbe0bd | ||
|
|
f470b380b8 | ||
|
|
e33956d584 | ||
|
|
821af69bbe | ||
|
|
d5cac7acbf | ||
|
|
185a24ff12 | ||
|
|
f6a9df54d2 | ||
|
|
28b273c38e | ||
|
|
cdedd1b8bf | ||
|
|
abd875983f | ||
|
|
30c2f1d1f8 | ||
|
|
a65c2dbd8f | ||
|
|
72eb8d0d73 | ||
|
|
90b884fe64 | ||
|
|
29e9683c35 | ||
|
|
870ab67be4 | ||
|
|
ecc73efe2b | ||
|
|
345d4d2f95 | ||
|
|
97fd6ca57b | ||
|
|
e021b911d9 | ||
|
|
6bb9737237 | ||
|
|
f0594cec77 | ||
|
|
eef8271d07 | ||
|
|
5dab98ec49 | ||
|
|
b3b9f128e2 | ||
|
|
3a0efcdbc7 | ||
|
|
13a7220af9 | ||
|
|
83d3a7a8b1 | ||
|
|
de315e4b7e | ||
|
|
d053662cf6 | ||
|
|
390da76f68 | ||
|
|
04d480012c | ||
|
|
b6ebb66966 | ||
|
|
602b721c0e | ||
|
|
33f0221c9e | ||
|
|
51b47fc389 | ||
|
|
5e6d636f82 | ||
|
|
24a8be7848 | ||
|
|
7b2f775723 | ||
|
|
5a5990f201 | ||
|
|
572e912c22 | ||
|
|
087b01a243 | ||
|
|
15d9665ac2 | ||
|
|
cc694a4c2b | ||
|
|
2071df6f58 | ||
|
|
6dc0d4e301 | ||
|
|
fad9582eac | ||
|
|
52edbfc6dc | ||
|
|
e9999befa9 | ||
|
|
b622ff69e2 | ||
|
|
3e8189f092 | ||
|
|
99918b8c62 | ||
|
|
ac8600451f | ||
|
|
bf8fac7809 | ||
|
|
1ebd16e2ce | ||
|
|
210558b353 | ||
|
|
f73e85d7ec | ||
|
|
de16e0b35d | ||
|
|
fc8d2a3a3e | ||
|
|
7862430a18 | ||
|
|
1dd0860bef | ||
|
|
ea3b182e2f | ||
|
|
68799ad044 | ||
|
|
1f2cb78b9d | ||
|
|
a127fafc26 | ||
|
|
44f673317f | ||
|
|
a93f03df54 | ||
|
|
e8e74052c2 | ||
|
|
06a129de07 | ||
|
|
5931d1bbfd | ||
|
|
dc95a7bd9c | ||
|
|
708c4d7251 | ||
|
|
c1c44ba070 | ||
|
|
2c3e1facc1 | ||
|
|
472883ab03 | ||
|
|
f8958f9335 | ||
|
|
e508b6b38e | ||
|
|
e931b5a6ad | ||
|
|
de25c7af4b | ||
|
|
b53bb46bdf | ||
|
|
d0a06d0b92 | ||
|
|
9f498ecce1 | ||
|
|
e9bfa182b1 | ||
|
|
13c0353dae | ||
|
|
4241a702b6 | ||
|
|
34a05c9bde | ||
|
|
eb300839ca | ||
|
|
4aaeac88aa | ||
|
|
fac9640263 | ||
|
|
e1b02b270a | ||
|
|
d41c9c90a9 | ||
|
|
fc5a3715c7 | ||
|
|
269aeac18c | ||
|
|
bc76582c96 | ||
|
|
bb6a6a3172 | ||
|
|
192898995d | ||
|
|
b5c824ebbd | ||
|
|
1b966ecaf6 | ||
|
|
15e5333b09 | ||
|
|
51d3b8dfea | ||
|
|
883e6b17d2 | ||
|
|
c849fac54e | ||
|
|
61c1521fcb | ||
|
|
6c53ee503c | ||
|
|
46b1f80611 | ||
|
|
1201649474 | ||
|
|
9d7f8ee80a | ||
|
|
870ac2f371 | ||
|
|
4ff41d9416 | ||
|
|
52bb25cf2a | ||
|
|
2acfecfafc | ||
|
|
52aabcbdfc | ||
|
|
76cb6e7806 | ||
|
|
e5e6ac6064 | ||
|
|
a65cf09830 | ||
|
|
c77a0d1ef9 | ||
|
|
5cf1bcef28 | ||
|
|
4dd65ee9b2 | ||
|
|
967e7b65b1 | ||
|
|
bb84439dc6 | ||
|
|
aa66366ea2 | ||
|
|
4994dd026c | ||
|
|
0d43debd32 | ||
|
|
18821d3feb | ||
|
|
a65ad217a3 | ||
|
|
1b29a6ba3a | ||
|
|
77fdec48a9 | ||
|
|
b21846cf76 | ||
|
|
e4599113fa | ||
|
|
394667637c | ||
|
|
5a99a9a78a | ||
|
|
21c3c36d7c | ||
|
|
cea15032c9 | ||
|
|
e73a055bc3 | ||
|
|
ce93715287 | ||
|
|
09d3a0edc3 | ||
|
|
805d6c9de2 | ||
|
|
7f5ffab148 | ||
|
|
a551cbcedd | ||
|
|
d27b74a9ab | ||
|
|
a69ad3bbdb | ||
|
|
8d57a375ac | ||
|
|
84324e4340 | ||
|
|
3f58472ea7 | ||
|
|
a383d271e5 | ||
|
|
cc70b9766f | ||
|
|
c92a45a386 | ||
|
|
abda820c0d | ||
|
|
5320b8f260 | ||
|
|
dd2bf7085a | ||
|
|
f70365534a | ||
|
|
b744a29df6 | ||
|
|
77905adc56 | ||
|
|
192200a2cf | ||
|
|
5cd7441052 | ||
|
|
81bba2a7a0 | ||
|
|
447884250e | ||
|
|
0bf5e1be22 | ||
|
|
d7004cf045 | ||
|
|
c57db2454c | ||
|
|
9a0916aca0 | ||
|
|
b8d66ffe92 | ||
|
|
5838df0cde | ||
|
|
37bd73591f | ||
|
|
0dc8ce65b3 | ||
|
|
f977545374 | ||
|
|
719e1091b6 | ||
|
|
b3f951a5d7 | ||
|
|
c07817f0ec | ||
|
|
ef6262867f | ||
|
|
88572a28f6 | ||
|
|
965b7b7fa8 | ||
|
|
f825ef4d85 | ||
|
|
c943779387 | ||
|
|
c11e6314a3 | ||
|
|
f86d2127d1 | ||
|
|
b18c3e60ea | ||
|
|
271c709be6 | ||
|
|
173bd3f679 | ||
|
|
66cac921b9 | ||
|
|
3a1d0cffaf | ||
|
|
6a6e4c5dd4 | ||
|
|
5b5082fd1a | ||
|
|
4e5297e12b | ||
|
|
a29399fde4 | ||
|
|
6b1217d862 | ||
|
|
bfd7d80b9f | ||
|
|
3cb233f2de | ||
|
|
6e3a8bc8aa | ||
|
|
a6a582ec08 | ||
|
|
6d08904602 | ||
|
|
8aa9fbf47b | ||
|
|
7ebe59502a | ||
|
|
2b0d2a1ad0 | ||
|
|
139d107bad | ||
|
|
87534580d6 | ||
|
|
1619024818 | ||
|
|
cd22fda858 | ||
|
|
ee873711b7 | ||
|
|
a519a2f9a2 | ||
|
|
a834b5e926 | ||
|
|
5b133a8e53 | ||
|
|
251e1a06c5 | ||
|
|
7d59544b61 | ||
|
|
ba83b04da4 | ||
|
|
1cf1354abf | ||
|
|
9ecb045131 | ||
|
|
2fe9d75cb5 | ||
|
|
641497a88c | ||
|
|
2fcc4f0db1 | ||
|
|
96948e8b8d | ||
|
|
17cdf17473 | ||
|
|
766d33ec94 | ||
|
|
1484b696c5 | ||
|
|
3053cd9238 | ||
|
|
18dd8c8ee1 | ||
|
|
0d6b43e02e | ||
|
|
d5c02a28c2 | ||
|
|
77eab865a0 | ||
|
|
308e16afa0 | ||
|
|
39a4d6bc5b | ||
|
|
823da21de1 | ||
|
|
fc7c584719 | ||
|
|
11ccd098ed | ||
|
|
1ea83b8748 | ||
|
|
bd0c404396 | ||
|
|
f117dd453d | ||
|
|
26a6a004c1 | ||
|
|
8707518433 | ||
|
|
fdd12fc6b6 | ||
|
|
04989f3554 | ||
|
|
c4ed189e28 | ||
|
|
2cb425c9fc | ||
|
|
029964d886 | ||
|
|
526effa743 | ||
|
|
5dca424132 | ||
|
|
c76a633de3 | ||
|
|
b6db68b064 | ||
|
|
0b1cfb3b43 | ||
|
|
4c14710596 | ||
|
|
1006cd9b56 | ||
|
|
d55e369a7f | ||
|
|
3edcac4d3c | ||
|
|
70ff8e22f2 | ||
|
|
b7824075a3 | ||
|
|
96644d3430 | ||
|
|
6c8b88eb7e | ||
|
|
33aa966937 | ||
|
|
80776c2442 | ||
|
|
c9a52da723 | ||
|
|
a79b788b27 | ||
|
|
31981f473b | ||
|
|
7194bbe18f | ||
|
|
c600d85e7a | ||
|
|
de7d80f1a7 | ||
|
|
8d287f9106 | ||
|
|
24b90ae6f8 | ||
|
|
65d4ae8e65 | ||
|
|
f76cde683d | ||
|
|
8970fc912f | ||
|
|
144da0a212 | ||
|
|
ac96a99b9f | ||
|
|
4b3a5e9bcb | ||
|
|
8a8d33b437 | ||
|
|
e0eb3860fa | ||
|
|
67aeadff42 | ||
|
|
5140ce43d1 | ||
|
|
fe3cb81209 | ||
|
|
0c7aaeeee5 | ||
|
|
9afaada3fb | ||
|
|
a697ffe442 | ||
|
|
d720064919 | ||
|
|
b09e3ed8c7 | ||
|
|
556b650133 | ||
|
|
8da69d6ad1 | ||
|
|
47182c0544 | ||
|
|
5dcd1e9aca | ||
|
|
e60933819a | ||
|
|
18dd4db147 | ||
|
|
a601be3af0 | ||
|
|
ac60c9984c | ||
|
|
0c3f6b8ffa | ||
|
|
6b4b662d2c | ||
|
|
facd77dbf8 | ||
|
|
8b6aa1dedd | ||
|
|
11a87bab8c | ||
|
|
fa92edff2f | ||
|
|
3686afafc0 | ||
|
|
86f50f7262 | ||
|
|
c3d0e621a7 | ||
|
|
4e1d1bdc2f | ||
|
|
fc018cb5bb | ||
|
|
a2b661965a | ||
|
|
ab9bd0cc9b | ||
|
|
7e3aee4a1a | ||
|
|
2a0d7a047d | ||
|
|
59b38976fa | ||
|
|
a1bba52b49 | ||
|
|
f0d1488cf9 | ||
|
|
cc79d5590a | ||
|
|
3caeb73bb8 | ||
|
|
6b226847aa | ||
|
|
9d30d8c8d8 | ||
|
|
635cf9d568 | ||
|
|
ee517f09ec | ||
|
|
124d3f6a4f | ||
|
|
d6bd189a5b | ||
|
|
05746b346a | ||
|
|
c0ff822fc6 | ||
|
|
38846b36a0 | ||
|
|
28c357a6ad | ||
|
|
68c02f88ef | ||
|
|
01705a2e0d | ||
|
|
3a1f814ef2 | ||
|
|
2a3bc4c5ea | ||
|
|
2acc3e19ef | ||
|
|
6db989620a | ||
|
|
6111c43a65 | ||
|
|
bec61d8ad6 | ||
|
|
599491f578 | ||
|
|
dd18d57a9b | ||
|
|
b5daa5c51f | ||
|
|
dec3b45d70 | ||
|
|
88c1e76300 | ||
|
|
852cbd8b65 | ||
|
|
ccde55c4e3 | ||
|
|
6651dcd873 | ||
|
|
953f31662b | ||
|
|
97ad2285b9 | ||
|
|
3f1d99273f | ||
|
|
b1df4aa615 | ||
|
|
a006cad19e | ||
|
|
cc86719898 | ||
|
|
480ef9005a | ||
|
|
128fe42d98 | ||
|
|
51fe557b2f | ||
|
|
e42a3315ae | ||
|
|
6b41e8e7bc | ||
|
|
0d1686412e | ||
|
|
04635ae321 | ||
|
|
300d006e6b | ||
|
|
ca85999d8f | ||
|
|
7b9467a972 | ||
|
|
9aa2c7bedd | ||
|
|
51e2333890 | ||
|
|
472df9dbb9 | ||
|
|
819e417d17 | ||
|
|
a430771e03 | ||
|
|
bb83f4bef3 | ||
|
|
4058d66644 | ||
|
|
c73dbd31d2 | ||
|
|
41152cbd56 | ||
|
|
15ef8aabf4 | ||
|
|
fd85572b87 | ||
|
|
8fbff623e2 | ||
|
|
13bf39004e | ||
|
|
403c50b233 | ||
|
|
1849e8ef6a | ||
|
|
d1989c6990 | ||
|
|
40ab19b380 | ||
|
|
fb69061f99 | ||
|
|
6c630cb33a | ||
|
|
3dfa4d83b9 | ||
|
|
85915a2f11 | ||
|
|
8acc1127e5 | ||
|
|
e5b75ca056 | ||
|
|
c3cee07c78 | ||
|
|
48e2c86d5e | ||
|
|
bc79ea515d | ||
|
|
94f98c35b8 | ||
|
|
b15d01c59d | ||
|
|
32592289dc | ||
|
|
dfdfded371 | ||
|
|
e0e31ba5e1 | ||
|
|
4f1306fe5d | ||
|
|
56d05e3dca | ||
|
|
67f6726c25 | ||
|
|
4519c01fd0 | ||
|
|
4db7d77381 | ||
|
|
0417e3035d | ||
|
|
33c4249013 | ||
|
|
6c2071eeb9 | ||
|
|
ad9dad49e3 | ||
|
|
0b8b70a990 | ||
|
|
8d1dca3b8e | ||
|
|
c071a19d54 | ||
|
|
4a0419520f | ||
|
|
ed6ba83768 | ||
|
|
5986bc3b39 | ||
|
|
12cd756920 | ||
|
|
08c238957d | ||
|
|
d08fc72052 | ||
|
|
d2c09c2182 | ||
|
|
c063b19418 | ||
|
|
ca19c4849e | ||
|
|
cc22a9f7ef | ||
|
|
b9e907b401 | ||
|
|
4324c3e006 | ||
|
|
b630dfb229 | ||
|
|
527ef74b6d | ||
|
|
47d1b545d5 | ||
|
|
6c8a41ece1 | ||
|
|
0dcd704c55 | ||
|
|
d1d38cb2e2 | ||
|
|
906c27a9ba | ||
|
|
acaad6c58c | ||
|
|
83e0e6d8ab | ||
|
|
c9dfef95fb | ||
|
|
62fe12fed7 | ||
|
|
106fc7f063 | ||
|
|
7e4b782aed | ||
|
|
59866b8867 | ||
|
|
ea04ce8545 | ||
|
|
df6cbb4660 | ||
|
|
9ab742168c | ||
|
|
f51cabc3d9 | ||
|
|
c8fdbcf548 | ||
|
|
9ac0565a19 | ||
|
|
30cd97004f | ||
|
|
4f189b6216 | ||
|
|
28e7498e9a | ||
|
|
af59922112 | ||
|
|
327fb687ca | ||
|
|
44a7d8d315 | ||
|
|
3224a39ed8 | ||
|
|
c1a3442cd5 | ||
|
|
ad85245743 | ||
|
|
df82aa5ef1 | ||
|
|
16767cc36d | ||
|
|
c494167bee | ||
|
|
a8ee7d45d5 | ||
|
|
536519328c | ||
|
|
1e902f4688 | ||
|
|
4afd8872d0 | ||
|
|
d0d82adc85 | ||
|
|
4b5dbbf4c3 | ||
|
|
d7264dc7d2 | ||
|
|
90ac002c42 | ||
|
|
495a6ffa05 | ||
|
|
0876e8845c | ||
|
|
872017243f | ||
|
|
4a514f4ab3 | ||
|
|
80688be059 | ||
|
|
16f7368514 | ||
|
|
3f2d94687f | ||
|
|
ad9a20337b | ||
|
|
79a0992773 | ||
|
|
55c6b1b498 | ||
|
|
31e13a966a | ||
|
|
ca915a5f6d | ||
|
|
9778c91ac7 | ||
|
|
24544a30a1 | ||
|
|
dc041da402 | ||
|
|
8aa966a31a | ||
|
|
ea70c607eb | ||
|
|
0f9b8df1b8 | ||
|
|
c9e2319c3a | ||
|
|
e85b469df7 | ||
|
|
de59cd2deb | ||
|
|
91db1fc3dc | ||
|
|
9d7e2b439c | ||
|
|
85ffa447f3 | ||
|
|
9880a0e039 | ||
|
|
b14826f05c | ||
|
|
36fa900817 | ||
|
|
50d61d96d9 | ||
|
|
64c37260b7 | ||
|
|
0b8c698392 | ||
|
|
65bde5555c | ||
|
|
5794017cdd | ||
|
|
a9239be2e1 | ||
|
|
fdfa68be2e | ||
|
|
2649b0edb5 | ||
|
|
ed1e3676b1 | ||
|
|
1215ed152b | ||
|
|
938ad4ec44 | ||
|
|
21bceecccb | ||
|
|
e1f265c940 | ||
|
|
dbf0d46a08 | ||
|
|
7eca5ea9dc | ||
|
|
0206b84b63 | ||
|
|
15675927ad | ||
|
|
4e2d5c7107 | ||
|
|
5d3166168e | ||
|
|
570ada9050 | ||
|
|
617bd38a01 | ||
|
|
43e5e7dd55 | ||
|
|
de9f69dd54 | ||
|
|
241c80880c | ||
|
|
2e5c3b2d09 | ||
|
|
85daac92b5 | ||
|
|
aeb81908c7 | ||
|
|
2c1d0bf792 | ||
|
|
d9c8871da0 | ||
|
|
0a5c9c8376 | ||
|
|
d68f7c7b72 | ||
|
|
f7b0bfa76a | ||
|
|
df7e31380b | ||
|
|
a2df4128ed |
@@ -1,117 +1,45 @@
|
|||||||
# RustFS Project AI Coding Rules
|
# RustFS Project Cursor Rules
|
||||||
|
|
||||||
## 🚨🚨🚨 CRITICAL DEVELOPMENT RULES - ZERO TOLERANCE 🚨🚨🚨
|
## ⚠️ CRITICAL DEVELOPMENT RULES ⚠️
|
||||||
|
|
||||||
### ⛔️ ABSOLUTE PROHIBITION: NEVER COMMIT DIRECTLY TO MASTER/MAIN BRANCH ⛔️
|
### 🚨 NEVER COMMIT DIRECTLY TO MASTER/MAIN BRANCH 🚨
|
||||||
|
- **This is the most important rule - NEVER modify code directly on main or master branch**
|
||||||
**🔥 THIS IS THE MOST CRITICAL RULE - VIOLATION WILL RESULT IN IMMEDIATE REVERSAL 🔥**
|
- **Always work on feature branches and use pull requests for all changes**
|
||||||
|
- **Any direct commits to master/main branch are strictly forbidden**
|
||||||
- **🚫 ZERO DIRECT COMMITS TO MAIN/MASTER BRANCH - ABSOLUTELY FORBIDDEN**
|
- Before starting any development, always:
|
||||||
- **🚫 ANY DIRECT COMMIT TO MAIN BRANCH MUST BE IMMEDIATELY REVERTED**
|
1. `git checkout main` (switch to main branch)
|
||||||
- **🚫 NO EXCEPTIONS FOR HOTFIXES, EMERGENCIES, OR URGENT CHANGES**
|
2. `git pull` (get latest changes)
|
||||||
- **🚫 NO EXCEPTIONS FOR SMALL CHANGES, TYPOS, OR DOCUMENTATION UPDATES**
|
3. `git checkout -b feat/your-feature-name` (create and switch to feature branch)
|
||||||
- **🚫 NO EXCEPTIONS FOR ANYONE - MAINTAINERS, CONTRIBUTORS, OR ADMINS**
|
4. Make your changes on the feature branch
|
||||||
|
5. Commit and push to the feature branch
|
||||||
### 📋 MANDATORY WORKFLOW - STRICTLY ENFORCED
|
6. Create a pull request for review
|
||||||
|
|
||||||
**EVERY SINGLE CHANGE MUST FOLLOW THIS WORKFLOW:**
|
|
||||||
|
|
||||||
1. **Check current branch**: `git branch` (MUST NOT be on main/master)
|
|
||||||
2. **Switch to main**: `git checkout main`
|
|
||||||
3. **Pull latest**: `git pull origin main`
|
|
||||||
4. **Create feature branch**: `git checkout -b feat/your-feature-name`
|
|
||||||
5. **Make changes ONLY on feature branch**
|
|
||||||
6. **Test thoroughly before committing**
|
|
||||||
7. **Commit and push to feature branch**: `git push origin feat/your-feature-name`
|
|
||||||
8. **Create Pull Request**: Use `gh pr create` (MANDATORY)
|
|
||||||
9. **Wait for PR approval**: NO self-merging allowed
|
|
||||||
10. **Merge through GitHub interface**: ONLY after approval
|
|
||||||
|
|
||||||
### 🔒 ENFORCEMENT MECHANISMS
|
|
||||||
|
|
||||||
- **Branch protection rules**: Main branch is protected
|
|
||||||
- **Pre-commit hooks**: Will block direct commits to main
|
|
||||||
- **CI/CD checks**: All PRs must pass before merging
|
|
||||||
- **Code review requirement**: At least one approval needed
|
|
||||||
- **Automated reversal**: Direct commits to main will be automatically reverted
|
|
||||||
|
|
||||||
## 🎯 Core AI Development Principles
|
|
||||||
|
|
||||||
### Five Execution Steps
|
|
||||||
|
|
||||||
#### 1. Task Analysis and Planning
|
|
||||||
- **Clear Objectives**: Deeply understand task requirements and expected results before starting coding
|
|
||||||
- **Plan Development**: List specific files, components, and functions that need modification, explaining the reasons for changes
|
|
||||||
- **Risk Assessment**: Evaluate the impact of changes on existing functionality, develop rollback plans
|
|
||||||
|
|
||||||
#### 2. Precise Code Location
|
|
||||||
- **File Identification**: Determine specific files and line numbers that need modification
|
|
||||||
- **Impact Analysis**: Avoid modifying irrelevant files, clearly state the reason for each file modification
|
|
||||||
- **Minimization Principle**: Unless explicitly required by the task, do not create new abstraction layers or refactor existing code
|
|
||||||
|
|
||||||
#### 3. Minimal Code Changes
|
|
||||||
- **Focus on Core**: Only write code directly required by the task
|
|
||||||
- **Avoid Redundancy**: Do not add unnecessary logs, comments, tests, or error handling
|
|
||||||
- **Isolation**: Ensure new code does not interfere with existing functionality, maintain code independence
|
|
||||||
|
|
||||||
#### 4. Strict Code Review
|
|
||||||
- **Correctness Check**: Verify the correctness and completeness of code logic
|
|
||||||
- **Style Consistency**: Ensure code conforms to established project coding style
|
|
||||||
- **Side Effect Assessment**: Evaluate the impact of changes on downstream systems
|
|
||||||
|
|
||||||
#### 5. Clear Delivery Documentation
|
|
||||||
- **Change Summary**: Detailed explanation of all modifications and reasons
|
|
||||||
- **File List**: List all modified files and their specific changes
|
|
||||||
- **Risk Statement**: Mark any assumptions or potential risk points
|
|
||||||
|
|
||||||
### Core Principles
|
|
||||||
- **🎯 Precise Execution**: Strictly follow task requirements, no arbitrary innovation
|
|
||||||
- **⚡ Efficient Development**: Avoid over-design, only do necessary work
|
|
||||||
- **🛡️ Safe and Reliable**: Always follow development processes, ensure code quality and system stability
|
|
||||||
- **🔒 Cautious Modification**: Only modify when clearly knowing what needs to be changed and having confidence
|
|
||||||
|
|
||||||
### Additional AI Behavior Rules
|
|
||||||
|
|
||||||
1. **Use English for all code comments and documentation** - All comments, variable names, function names, documentation, and user-facing text in code should be in English
|
|
||||||
2. **Clean up temporary scripts after use** - Any temporary scripts, test files, or helper files created during AI work should be removed after task completion
|
|
||||||
3. **Only make confident modifications** - Do not make speculative changes or "convenient" modifications outside the task scope. If uncertain about a change, ask for clarification rather than guessing
|
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
RustFS is a high-performance distributed object storage system written in Rust, compatible with S3 API. The project adopts a modular architecture, supporting erasure coding storage, multi-tenant management, observability, and other enterprise-level features.
|
RustFS is a high-performance distributed object storage system written in Rust, compatible with S3 API. The project adopts a modular architecture, supporting erasure coding storage, multi-tenant management, observability, and other enterprise-level features.
|
||||||
|
|
||||||
## Core Architecture Principles
|
## Core Architecture Principles
|
||||||
|
|
||||||
### 1. Modular Design
|
### 1. Modular Design
|
||||||
|
|
||||||
- Project uses Cargo workspace structure, containing multiple independent crates
|
- Project uses Cargo workspace structure, containing multiple independent crates
|
||||||
- Core modules: `rustfs` (main service), `ecstore` (erasure coding storage), `common` (shared components)
|
- Core modules: `rustfs` (main service), `ecstore` (erasure coding storage), `common` (shared components)
|
||||||
- Functional modules: `iam` (identity management), `madmin` (management interface), `crypto` (encryption), etc.
|
- Functional modules: `iam` (identity management), `madmin` (management interface), `crypto` (encryption), etc.
|
||||||
- Tool modules: `cli` (command line tool), `crates/*` (utility libraries)
|
- Tool modules: `cli` (command line tool), `crates/*` (utility libraries)
|
||||||
|
|
||||||
### 2. Asynchronous Programming Pattern
|
### 2. Asynchronous Programming Pattern
|
||||||
|
|
||||||
- Comprehensive use of `tokio` async runtime
|
- Comprehensive use of `tokio` async runtime
|
||||||
- Prioritize `async/await` syntax
|
- Prioritize `async/await` syntax
|
||||||
- Use `async-trait` for async methods in traits
|
- Use `async-trait` for async methods in traits
|
||||||
- Avoid blocking operations, use `spawn_blocking` when necessary
|
- Avoid blocking operations, use `spawn_blocking` when necessary
|
||||||
|
|
||||||
### 3. Error Handling Strategy
|
### 3. Error Handling Strategy
|
||||||
|
- Use unified error type `common::error::Error`
|
||||||
- **Use modular, type-safe error handling with `thiserror`**
|
- Support error chains and context information
|
||||||
- Each module should define its own error type using `thiserror::Error` derive macro
|
- Use `thiserror` to define specific error types
|
||||||
- Support error chains and context information through `#[from]` and `#[source]` attributes
|
- Error conversion uses `downcast_ref` for type checking
|
||||||
- Use `Result<T>` type aliases for consistency within each module
|
|
||||||
- Error conversion between modules should use explicit `From` implementations
|
|
||||||
- Follow the pattern: `pub type Result<T> = core::result::Result<T, Error>`
|
|
||||||
- Use `#[error("description")]` attributes for clear error messages
|
|
||||||
- Support error downcasting when needed through `other()` helper methods
|
|
||||||
- Implement `Clone` for errors when required by the domain logic
|
|
||||||
|
|
||||||
## Code Style Guidelines
|
## Code Style Guidelines
|
||||||
|
|
||||||
### 1. Formatting Configuration
|
### 1. Formatting Configuration
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
max_width = 130
|
max_width = 130
|
||||||
fn_call_width = 90
|
fn_call_width = 90
|
||||||
@@ -173,8 +101,49 @@ make pre-commit
|
|||||||
make setup-hooks
|
make setup-hooks
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Naming Conventions
|
#### 🔒 Automated Pre-commit Hooks
|
||||||
|
|
||||||
|
This project includes a pre-commit hook that automatically runs before each commit to ensure:
|
||||||
|
|
||||||
|
- ✅ Code is properly formatted (`cargo fmt --all --check`)
|
||||||
|
- ✅ No clippy warnings (`cargo clippy --all-targets --all-features -- -D warnings`)
|
||||||
|
- ✅ Code compiles successfully (`cargo check --all-targets`)
|
||||||
|
|
||||||
|
**Setting Up Pre-commit Hooks** (MANDATORY for all developers):
|
||||||
|
|
||||||
|
Run this command once after cloning the repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make setup-hooks
|
||||||
|
```
|
||||||
|
|
||||||
|
Or manually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x .git/hooks/pre-commit
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 🚫 Commit Prevention
|
||||||
|
|
||||||
|
If your code doesn't meet the formatting requirements, the pre-commit hook will:
|
||||||
|
|
||||||
|
1. **Block the commit** and show clear error messages
|
||||||
|
2. **Provide exact commands** to fix the issues
|
||||||
|
3. **Guide you through** the resolution process
|
||||||
|
|
||||||
|
Example output when formatting fails:
|
||||||
|
|
||||||
|
```
|
||||||
|
❌ Code formatting check failed!
|
||||||
|
💡 Please run 'cargo fmt --all' to format your code before committing.
|
||||||
|
|
||||||
|
🔧 Quick fix:
|
||||||
|
cargo fmt --all
|
||||||
|
git add .
|
||||||
|
git commit
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Naming Conventions
|
||||||
- Use `snake_case` for functions, variables, modules
|
- Use `snake_case` for functions, variables, modules
|
||||||
- Use `PascalCase` for types, traits, enums
|
- Use `PascalCase` for types, traits, enums
|
||||||
- Constants use `SCREAMING_SNAKE_CASE`
|
- Constants use `SCREAMING_SNAKE_CASE`
|
||||||
@@ -184,7 +153,6 @@ make setup-hooks
|
|||||||
- Choose names that clearly express the purpose and intent
|
- Choose names that clearly express the purpose and intent
|
||||||
|
|
||||||
### 4. Type Declaration Guidelines
|
### 4. Type Declaration Guidelines
|
||||||
|
|
||||||
- **Prefer type inference over explicit type declarations** when the type is obvious from context
|
- **Prefer type inference over explicit type declarations** when the type is obvious from context
|
||||||
- Let the Rust compiler infer types whenever possible to reduce verbosity and improve maintainability
|
- Let the Rust compiler infer types whenever possible to reduce verbosity and improve maintainability
|
||||||
- Only specify types explicitly when:
|
- Only specify types explicitly when:
|
||||||
@@ -193,8 +161,38 @@ make setup-hooks
|
|||||||
- Required for API boundaries (function signatures, public struct fields)
|
- Required for API boundaries (function signatures, public struct fields)
|
||||||
- Needed to resolve ambiguity between multiple possible types
|
- Needed to resolve ambiguity between multiple possible types
|
||||||
|
|
||||||
### 5. Documentation Comments
|
**Good examples (prefer these):**
|
||||||
|
```rust
|
||||||
|
// Compiler can infer the type
|
||||||
|
let items = vec![1, 2, 3, 4];
|
||||||
|
let config = Config::default();
|
||||||
|
let result = process_data(&input);
|
||||||
|
|
||||||
|
// Iterator chains with clear context
|
||||||
|
let filtered: Vec<_> = items.iter().filter(|&&x| x > 2).collect();
|
||||||
|
```
|
||||||
|
|
||||||
|
**Avoid unnecessary explicit types:**
|
||||||
|
```rust
|
||||||
|
// Unnecessary - type is obvious
|
||||||
|
let items: Vec<i32> = vec![1, 2, 3, 4];
|
||||||
|
let config: Config = Config::default();
|
||||||
|
let result: ProcessResult = process_data(&input);
|
||||||
|
```
|
||||||
|
|
||||||
|
**When explicit types are beneficial:**
|
||||||
|
```rust
|
||||||
|
// API boundaries - always specify types
|
||||||
|
pub fn process_data(input: &[u8]) -> Result<ProcessResult, Error> { ... }
|
||||||
|
|
||||||
|
// Ambiguous cases - explicit type needed
|
||||||
|
let value: f64 = "3.14".parse().unwrap();
|
||||||
|
|
||||||
|
// Complex generic types - explicit for clarity
|
||||||
|
let cache: HashMap<String, Arc<Mutex<CacheEntry>>> = HashMap::new();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Documentation Comments
|
||||||
- Public APIs must have documentation comments
|
- Public APIs must have documentation comments
|
||||||
- Use `///` for documentation comments
|
- Use `///` for documentation comments
|
||||||
- Complex functions add `# Examples` and `# Parameters` descriptions
|
- Complex functions add `# Examples` and `# Parameters` descriptions
|
||||||
@@ -203,7 +201,6 @@ make setup-hooks
|
|||||||
- Avoid meaningless comments like "debug 111" or placeholder text
|
- Avoid meaningless comments like "debug 111" or placeholder text
|
||||||
|
|
||||||
### 6. Import Guidelines
|
### 6. Import Guidelines
|
||||||
|
|
||||||
- Standard library imports first
|
- Standard library imports first
|
||||||
- Third-party crate imports in the middle
|
- Third-party crate imports in the middle
|
||||||
- Project internal imports last
|
- Project internal imports last
|
||||||
@@ -212,7 +209,6 @@ make setup-hooks
|
|||||||
## Asynchronous Programming Guidelines
|
## Asynchronous Programming Guidelines
|
||||||
|
|
||||||
### 1. Trait Definition
|
### 1. Trait Definition
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait StorageAPI: Send + Sync {
|
pub trait StorageAPI: Send + Sync {
|
||||||
@@ -221,7 +217,6 @@ pub trait StorageAPI: Send + Sync {
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 2. Error Handling
|
### 2. Error Handling
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Use ? operator to propagate errors
|
// Use ? operator to propagate errors
|
||||||
async fn example_function() -> Result<()> {
|
async fn example_function() -> Result<()> {
|
||||||
@@ -232,7 +227,6 @@ async fn example_function() -> Result<()> {
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 3. Concurrency Control
|
### 3. Concurrency Control
|
||||||
|
|
||||||
- Use `Arc` and `Mutex`/`RwLock` for shared state management
|
- Use `Arc` and `Mutex`/`RwLock` for shared state management
|
||||||
- Prioritize async locks from `tokio::sync`
|
- Prioritize async locks from `tokio::sync`
|
||||||
- Avoid holding locks for long periods
|
- Avoid holding locks for long periods
|
||||||
@@ -240,7 +234,6 @@ async fn example_function() -> Result<()> {
|
|||||||
## Logging and Tracing Guidelines
|
## Logging and Tracing Guidelines
|
||||||
|
|
||||||
### 1. Tracing Usage
|
### 1. Tracing Usage
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[tracing::instrument(skip(self, data))]
|
#[tracing::instrument(skip(self, data))]
|
||||||
async fn process_data(&self, data: &[u8]) -> Result<()> {
|
async fn process_data(&self, data: &[u8]) -> Result<()> {
|
||||||
@@ -250,7 +243,6 @@ async fn process_data(&self, data: &[u8]) -> Result<()> {
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 2. Log Levels
|
### 2. Log Levels
|
||||||
|
|
||||||
- `error!`: System errors requiring immediate attention
|
- `error!`: System errors requiring immediate attention
|
||||||
- `warn!`: Warning information that may affect functionality
|
- `warn!`: Warning information that may affect functionality
|
||||||
- `info!`: Important business information
|
- `info!`: Important business information
|
||||||
@@ -258,7 +250,6 @@ async fn process_data(&self, data: &[u8]) -> Result<()> {
|
|||||||
- `trace!`: Detailed execution paths
|
- `trace!`: Detailed execution paths
|
||||||
|
|
||||||
### 3. Structured Logging
|
### 3. Structured Logging
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
info!(
|
info!(
|
||||||
counter.rustfs_api_requests_total = 1_u64,
|
counter.rustfs_api_requests_total = 1_u64,
|
||||||
@@ -271,75 +262,45 @@ info!(
|
|||||||
## Error Handling Guidelines
|
## Error Handling Guidelines
|
||||||
|
|
||||||
### 1. Error Type Definition
|
### 1. Error Type Definition
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Use thiserror for module-specific error types
|
#[derive(Debug, thiserror::Error)]
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum MyError {
|
pub enum MyError {
|
||||||
#[error("IO error: {0}")]
|
#[error("IO error: {0}")]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
|
|
||||||
#[error("Storage error: {0}")]
|
|
||||||
Storage(#[from] ecstore::error::StorageError),
|
|
||||||
|
|
||||||
#[error("Custom error: {message}")]
|
#[error("Custom error: {message}")]
|
||||||
Custom { message: String },
|
Custom { message: String },
|
||||||
|
|
||||||
#[error("File not found: {path}")]
|
|
||||||
FileNotFound { path: String },
|
|
||||||
|
|
||||||
#[error("Invalid configuration: {0}")]
|
|
||||||
InvalidConfig(String),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide Result type alias for the module
|
|
||||||
pub type Result<T> = core::result::Result<T, MyError>;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Error Helper Methods
|
### 2. Error Conversion
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
impl MyError {
|
pub fn to_s3_error(err: Error) -> S3Error {
|
||||||
/// Create error from any compatible error type
|
if let Some(storage_err) = err.downcast_ref::<StorageError>() {
|
||||||
pub fn other<E>(error: E) -> Self
|
match storage_err {
|
||||||
where
|
StorageError::ObjectNotFound(bucket, object) => {
|
||||||
E: Into<Box<dyn std::error::Error + Send + Sync>>,
|
s3_error!(NoSuchKey, "{}/{}", bucket, object)
|
||||||
{
|
}
|
||||||
MyError::Io(std::io::Error::other(error))
|
// Other error types...
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// Default error handling
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Error Context and Propagation
|
### 3. Error Context
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Use ? operator for clean error propagation
|
// Add error context
|
||||||
async fn example_function() -> Result<()> {
|
.map_err(|e| Error::from_string(format!("Failed to process {}: {}", path, e)))?
|
||||||
let data = read_file("path").await?;
|
|
||||||
process_data(data).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add context to errors
|
|
||||||
fn process_with_context(path: &str) -> Result<()> {
|
|
||||||
std::fs::read(path)
|
|
||||||
.map_err(|e| MyError::Custom {
|
|
||||||
message: format!("Failed to read {}: {}", path, e)
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Performance Optimization Guidelines
|
## Performance Optimization Guidelines
|
||||||
|
|
||||||
### 1. Memory Management
|
### 1. Memory Management
|
||||||
|
|
||||||
- Use `Bytes` instead of `Vec<u8>` for zero-copy operations
|
- Use `Bytes` instead of `Vec<u8>` for zero-copy operations
|
||||||
- Avoid unnecessary cloning, use reference passing
|
- Avoid unnecessary cloning, use reference passing
|
||||||
- Use `Arc` for sharing large objects
|
- Use `Arc` for sharing large objects
|
||||||
|
|
||||||
### 2. Concurrency Optimization
|
### 2. Concurrency Optimization
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Use join_all for concurrent operations
|
// Use join_all for concurrent operations
|
||||||
let futures = disks.iter().map(|disk| disk.operation());
|
let futures = disks.iter().map(|disk| disk.operation());
|
||||||
@@ -347,14 +308,12 @@ let results = join_all(futures).await;
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 3. Caching Strategy
|
### 3. Caching Strategy
|
||||||
|
- Use `lazy_static` or `OnceCell` for global caching
|
||||||
- Use `LazyLock` for global caching
|
|
||||||
- Implement LRU cache to avoid memory leaks
|
- Implement LRU cache to avoid memory leaks
|
||||||
|
|
||||||
## Testing Guidelines
|
## Testing Guidelines
|
||||||
|
|
||||||
### 1. Unit Tests
|
### 1. Unit Tests
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
@@ -376,12 +335,10 @@ mod tests {
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 2. Integration Tests
|
### 2. Integration Tests
|
||||||
|
|
||||||
- Use `e2e_test` module for end-to-end testing
|
- Use `e2e_test` module for end-to-end testing
|
||||||
- Simulate real storage environments
|
- Simulate real storage environments
|
||||||
|
|
||||||
### 3. Test Quality Standards
|
### 3. Test Quality Standards
|
||||||
|
|
||||||
- Write meaningful test cases that verify actual functionality
|
- Write meaningful test cases that verify actual functionality
|
||||||
- Avoid placeholder or debug content like "debug 111", "test test", etc.
|
- Avoid placeholder or debug content like "debug 111", "test test", etc.
|
||||||
- Use descriptive test names that clearly indicate what is being tested
|
- Use descriptive test names that clearly indicate what is being tested
|
||||||
@@ -391,11 +348,9 @@ mod tests {
|
|||||||
## Cross-Platform Compatibility Guidelines
|
## Cross-Platform Compatibility Guidelines
|
||||||
|
|
||||||
### 1. CPU Architecture Compatibility
|
### 1. CPU Architecture Compatibility
|
||||||
|
|
||||||
- **Always consider multi-platform and different CPU architecture compatibility** when writing code
|
- **Always consider multi-platform and different CPU architecture compatibility** when writing code
|
||||||
- Support major architectures: x86_64, aarch64 (ARM64), and other target platforms
|
- Support major architectures: x86_64, aarch64 (ARM64), and other target platforms
|
||||||
- Use conditional compilation for architecture-specific code:
|
- Use conditional compilation for architecture-specific code:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
fn optimized_x86_64_function() { /* x86_64 specific implementation */ }
|
fn optimized_x86_64_function() { /* x86_64 specific implementation */ }
|
||||||
@@ -408,19 +363,16 @@ fn generic_function() { /* Generic fallback implementation */ }
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 2. Platform-Specific Dependencies
|
### 2. Platform-Specific Dependencies
|
||||||
|
|
||||||
- Use feature flags for platform-specific dependencies
|
- Use feature flags for platform-specific dependencies
|
||||||
- Provide fallback implementations for unsupported platforms
|
- Provide fallback implementations for unsupported platforms
|
||||||
- Test on multiple architectures in CI/CD pipeline
|
- Test on multiple architectures in CI/CD pipeline
|
||||||
|
|
||||||
### 3. Endianness Considerations
|
### 3. Endianness Considerations
|
||||||
|
|
||||||
- Use explicit byte order conversion when dealing with binary data
|
- Use explicit byte order conversion when dealing with binary data
|
||||||
- Prefer `to_le_bytes()`, `from_le_bytes()` for consistent little-endian format
|
- Prefer `to_le_bytes()`, `from_le_bytes()` for consistent little-endian format
|
||||||
- Use `byteorder` crate for complex binary format handling
|
- Use `byteorder` crate for complex binary format handling
|
||||||
|
|
||||||
### 4. SIMD and Performance Optimizations
|
### 4. SIMD and Performance Optimizations
|
||||||
|
|
||||||
- Use portable SIMD libraries like `wide` or `packed_simd`
|
- Use portable SIMD libraries like `wide` or `packed_simd`
|
||||||
- Provide fallback implementations for non-SIMD architectures
|
- Provide fallback implementations for non-SIMD architectures
|
||||||
- Use runtime feature detection when appropriate
|
- Use runtime feature detection when appropriate
|
||||||
@@ -428,12 +380,10 @@ fn generic_function() { /* Generic fallback implementation */ }
|
|||||||
## Security Guidelines
|
## Security Guidelines
|
||||||
|
|
||||||
### 1. Memory Safety
|
### 1. Memory Safety
|
||||||
|
|
||||||
- Disable `unsafe` code (workspace.lints.rust.unsafe_code = "deny")
|
- Disable `unsafe` code (workspace.lints.rust.unsafe_code = "deny")
|
||||||
- Use `rustls` instead of `openssl`
|
- Use `rustls` instead of `openssl`
|
||||||
|
|
||||||
### 2. Authentication and Authorization
|
### 2. Authentication and Authorization
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Use IAM system for permission checks
|
// Use IAM system for permission checks
|
||||||
let identity = iam.authenticate(&access_key, &secret_key).await?;
|
let identity = iam.authenticate(&access_key, &secret_key).await?;
|
||||||
@@ -443,13 +393,11 @@ iam.authorize(&identity, &action, &resource).await?;
|
|||||||
## Configuration Management Guidelines
|
## Configuration Management Guidelines
|
||||||
|
|
||||||
### 1. Environment Variables
|
### 1. Environment Variables
|
||||||
|
|
||||||
- Use `RUSTFS_` prefix
|
- Use `RUSTFS_` prefix
|
||||||
- Support both configuration files and environment variables
|
- Support both configuration files and environment variables
|
||||||
- Provide reasonable default values
|
- Provide reasonable default values
|
||||||
|
|
||||||
### 2. Configuration Structure
|
### 2. Configuration Structure
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
@@ -463,12 +411,10 @@ pub struct Config {
|
|||||||
## Dependency Management Guidelines
|
## Dependency Management Guidelines
|
||||||
|
|
||||||
### 1. Workspace Dependencies
|
### 1. Workspace Dependencies
|
||||||
|
|
||||||
- Manage versions uniformly at workspace level
|
- Manage versions uniformly at workspace level
|
||||||
- Use `workspace = true` to inherit configuration
|
- Use `workspace = true` to inherit configuration
|
||||||
|
|
||||||
### 2. Feature Flags
|
### 2. Feature Flags
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
[features]
|
[features]
|
||||||
default = ["file"]
|
default = ["file"]
|
||||||
@@ -479,18 +425,15 @@ kafka = ["dep:rdkafka"]
|
|||||||
## Deployment and Operations Guidelines
|
## Deployment and Operations Guidelines
|
||||||
|
|
||||||
### 1. Containerization
|
### 1. Containerization
|
||||||
|
|
||||||
- Provide Dockerfile and docker-compose configuration
|
- Provide Dockerfile and docker-compose configuration
|
||||||
- Support multi-stage builds to optimize image size
|
- Support multi-stage builds to optimize image size
|
||||||
|
|
||||||
### 2. Observability
|
### 2. Observability
|
||||||
|
|
||||||
- Integrate OpenTelemetry for distributed tracing
|
- Integrate OpenTelemetry for distributed tracing
|
||||||
- Support Prometheus metrics collection
|
- Support Prometheus metrics collection
|
||||||
- Provide Grafana dashboards
|
- Provide Grafana dashboards
|
||||||
|
|
||||||
### 3. Health Checks
|
### 3. Health Checks
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Implement health check endpoint
|
// Implement health check endpoint
|
||||||
async fn health_check() -> Result<HealthStatus> {
|
async fn health_check() -> Result<HealthStatus> {
|
||||||
@@ -501,7 +444,6 @@ async fn health_check() -> Result<HealthStatus> {
|
|||||||
## Code Review Checklist
|
## Code Review Checklist
|
||||||
|
|
||||||
### 1. **Code Formatting and Quality (MANDATORY)**
|
### 1. **Code Formatting and Quality (MANDATORY)**
|
||||||
|
|
||||||
- [ ] **Code is properly formatted** (`cargo fmt --all --check` passes)
|
- [ ] **Code is properly formatted** (`cargo fmt --all --check` passes)
|
||||||
- [ ] **All clippy warnings are resolved** (`cargo clippy --all-targets --all-features -- -D warnings` passes)
|
- [ ] **All clippy warnings are resolved** (`cargo clippy --all-targets --all-features -- -D warnings` passes)
|
||||||
- [ ] **Code compiles successfully** (`cargo check --all-targets` passes)
|
- [ ] **Code compiles successfully** (`cargo check --all-targets` passes)
|
||||||
@@ -509,32 +451,27 @@ async fn health_check() -> Result<HealthStatus> {
|
|||||||
- [ ] **No formatting-related changes** mixed with functional changes (separate commits)
|
- [ ] **No formatting-related changes** mixed with functional changes (separate commits)
|
||||||
|
|
||||||
### 2. Functionality
|
### 2. Functionality
|
||||||
|
|
||||||
- [ ] Are all error cases properly handled?
|
- [ ] Are all error cases properly handled?
|
||||||
- [ ] Is there appropriate logging?
|
- [ ] Is there appropriate logging?
|
||||||
- [ ] Is there necessary test coverage?
|
- [ ] Is there necessary test coverage?
|
||||||
|
|
||||||
### 3. Performance
|
### 3. Performance
|
||||||
|
|
||||||
- [ ] Are unnecessary memory allocations avoided?
|
- [ ] Are unnecessary memory allocations avoided?
|
||||||
- [ ] Are async operations used correctly?
|
- [ ] Are async operations used correctly?
|
||||||
- [ ] Are there potential deadlock risks?
|
- [ ] Are there potential deadlock risks?
|
||||||
|
|
||||||
### 4. Security
|
### 4. Security
|
||||||
|
|
||||||
- [ ] Are input parameters properly validated?
|
- [ ] Are input parameters properly validated?
|
||||||
- [ ] Are there appropriate permission checks?
|
- [ ] Are there appropriate permission checks?
|
||||||
- [ ] Is information leakage avoided?
|
- [ ] Is information leakage avoided?
|
||||||
|
|
||||||
### 5. Cross-Platform Compatibility
|
### 5. Cross-Platform Compatibility
|
||||||
|
|
||||||
- [ ] Does the code work on different CPU architectures (x86_64, aarch64)?
|
- [ ] Does the code work on different CPU architectures (x86_64, aarch64)?
|
||||||
- [ ] Are platform-specific features properly gated with conditional compilation?
|
- [ ] Are platform-specific features properly gated with conditional compilation?
|
||||||
- [ ] Is byte order handling correct for binary data?
|
- [ ] Is byte order handling correct for binary data?
|
||||||
- [ ] Are there appropriate fallback implementations for unsupported platforms?
|
- [ ] Are there appropriate fallback implementations for unsupported platforms?
|
||||||
|
|
||||||
### 6. Code Commits and Documentation
|
### 6. Code Commits and Documentation
|
||||||
|
|
||||||
- [ ] Does it comply with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)?
|
- [ ] Does it comply with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)?
|
||||||
- [ ] Are commit messages concise and under 72 characters for the title line?
|
- [ ] Are commit messages concise and under 72 characters for the title line?
|
||||||
- [ ] Commit titles should be concise and in English, avoid Chinese
|
- [ ] Commit titles should be concise and in English, avoid Chinese
|
||||||
@@ -543,7 +480,6 @@ async fn health_check() -> Result<HealthStatus> {
|
|||||||
## Common Patterns and Best Practices
|
## Common Patterns and Best Practices
|
||||||
|
|
||||||
### 1. Resource Management
|
### 1. Resource Management
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Use RAII pattern for resource management
|
// Use RAII pattern for resource management
|
||||||
pub struct ResourceGuard {
|
pub struct ResourceGuard {
|
||||||
@@ -558,7 +494,6 @@ impl Drop for ResourceGuard {
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 2. Dependency Injection
|
### 2. Dependency Injection
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Use dependency injection pattern
|
// Use dependency injection pattern
|
||||||
pub struct Service {
|
pub struct Service {
|
||||||
@@ -568,7 +503,6 @@ pub struct Service {
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 3. Graceful Shutdown
|
### 3. Graceful Shutdown
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Implement graceful shutdown
|
// Implement graceful shutdown
|
||||||
async fn shutdown_gracefully(shutdown_rx: &mut Receiver<()>) {
|
async fn shutdown_gracefully(shutdown_rx: &mut Receiver<()>) {
|
||||||
@@ -587,116 +521,59 @@ async fn shutdown_gracefully(shutdown_rx: &mut Receiver<()>) {
|
|||||||
## Domain-Specific Guidelines
|
## Domain-Specific Guidelines
|
||||||
|
|
||||||
### 1. Storage Operations
|
### 1. Storage Operations
|
||||||
|
|
||||||
- All storage operations must support erasure coding
|
- All storage operations must support erasure coding
|
||||||
- Implement read/write quorum mechanisms
|
- Implement read/write quorum mechanisms
|
||||||
- Support data integrity verification
|
- Support data integrity verification
|
||||||
|
|
||||||
### 2. Network Communication
|
### 2. Network Communication
|
||||||
|
|
||||||
- Use gRPC for internal service communication
|
- Use gRPC for internal service communication
|
||||||
- HTTP/HTTPS support for S3-compatible API
|
- HTTP/HTTPS support for S3-compatible API
|
||||||
- Implement connection pooling and retry mechanisms
|
- Implement connection pooling and retry mechanisms
|
||||||
|
|
||||||
### 3. Metadata Management
|
### 3. Metadata Management
|
||||||
|
|
||||||
- Use FlatBuffers for serialization
|
- Use FlatBuffers for serialization
|
||||||
- Support version control and migration
|
- Support version control and migration
|
||||||
- Implement metadata caching
|
- Implement metadata caching
|
||||||
|
|
||||||
## Branch Management and Development Workflow
|
These rules should serve as guiding principles when developing the RustFS project, ensuring code quality, performance, and maintainability.
|
||||||
|
|
||||||
### Branch Management
|
### 4. Code Operations
|
||||||
|
|
||||||
- **🚨 CRITICAL: NEVER modify code directly on main or master branch - THIS IS ABSOLUTELY FORBIDDEN 🚨**
|
#### Branch Management
|
||||||
- **⚠️ ANY DIRECT COMMITS TO MASTER/MAIN WILL BE REJECTED AND MUST BE REVERTED IMMEDIATELY ⚠️**
|
- **🚨 CRITICAL: NEVER modify code directly on main or master branch - THIS IS ABSOLUTELY FORBIDDEN 🚨**
|
||||||
- **🔒 ALL CHANGES MUST GO THROUGH PULL REQUESTS - NO DIRECT COMMITS TO MAIN UNDER ANY CIRCUMSTANCES 🔒**
|
- **⚠️ ANY DIRECT COMMITS TO MASTER/MAIN WILL BE REJECTED AND MUST BE REVERTED IMMEDIATELY ⚠️**
|
||||||
- **Always work on feature branches - NO EXCEPTIONS**
|
- **Always work on feature branches - NO EXCEPTIONS**
|
||||||
- Always check the .rules.md file before starting to ensure you understand the project guidelines
|
- Always check the .cursorrules file before starting to ensure you understand the project guidelines
|
||||||
- **MANDATORY workflow for ALL changes:**
|
- **MANDATORY workflow for ALL changes:**
|
||||||
1. `git checkout main` (switch to main branch)
|
1. `git checkout main` (switch to main branch)
|
||||||
2. `git pull` (get latest changes)
|
2. `git pull` (get latest changes)
|
||||||
3. `git checkout -b feat/your-feature-name` (create and switch to feature branch)
|
3. `git checkout -b feat/your-feature-name` (create and switch to feature branch)
|
||||||
4. Make your changes ONLY on the feature branch
|
4. Make your changes ONLY on the feature branch
|
||||||
5. Test thoroughly before committing
|
5. Test thoroughly before committing
|
||||||
6. Commit and push to the feature branch
|
6. Commit and push to the feature branch
|
||||||
7. **Create a pull request for code review - THIS IS THE ONLY WAY TO MERGE TO MAIN**
|
7. Create a pull request for code review
|
||||||
8. **Wait for PR approval before merging - NEVER merge your own PRs without review**
|
- Use descriptive branch names following the pattern: `feat/feature-name`, `fix/issue-name`, `refactor/component-name`, etc.
|
||||||
- Use descriptive branch names following the pattern: `feat/feature-name`, `fix/issue-name`, `refactor/component-name`, etc.
|
- **Double-check current branch before ANY commit: `git branch` to ensure you're NOT on main/master**
|
||||||
- **Double-check current branch before ANY commit: `git branch` to ensure you're NOT on main/master**
|
- Ensure all changes are made on feature branches and merged through pull requests
|
||||||
- **Pull Request Requirements:**
|
|
||||||
- All changes must be submitted via PR regardless of size or urgency
|
|
||||||
- PRs must include comprehensive description and testing information
|
|
||||||
- PRs must pass all CI/CD checks before merging
|
|
||||||
- PRs require at least one approval from code reviewers
|
|
||||||
- Even hotfixes and emergency changes must go through PR process
|
|
||||||
- **Enforcement:**
|
|
||||||
- Main branch should be protected with branch protection rules
|
|
||||||
- Direct pushes to main should be blocked by repository settings
|
|
||||||
- Any accidental direct commits to main must be immediately reverted via PR
|
|
||||||
|
|
||||||
### Development Workflow
|
#### Development Workflow
|
||||||
|
- Use English for all code comments, documentation, and variable names
|
||||||
## 🎯 **Core Development Principles**
|
- Write meaningful and descriptive names for variables, functions, and methods
|
||||||
|
- Avoid meaningless test content like "debug 111" or placeholder values
|
||||||
- **🔴 Every change must be precise - don't modify unless you're confident**
|
- Before each change, carefully read the existing code to ensure you understand the code structure and implementation, do not break existing logic implementation, do not introduce new issues
|
||||||
- Carefully analyze code logic and ensure complete understanding before making changes
|
- Ensure each change provides sufficient test cases to guarantee code correctness
|
||||||
- When uncertain, prefer asking users or consulting documentation over blind modifications
|
- Do not arbitrarily modify numbers and constants in test cases, carefully analyze their meaning to ensure test case correctness
|
||||||
- Use small iterative steps, modify only necessary parts at a time
|
- When writing or modifying tests, check existing test cases to ensure they have scientific naming and rigorous logic testing, if not compliant, modify test cases to ensure scientific and rigorous testing
|
||||||
- Evaluate impact scope before changes to ensure no new issues are introduced
|
- **Before committing any changes, run `cargo clippy --all-targets --all-features -- -D warnings` to ensure all code passes Clippy checks**
|
||||||
|
- After each development completion, first git add . then git commit -m "feat: feature description" or "fix: issue description", ensure compliance with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
|
||||||
- **🚀 GitHub PR creation prioritizes gh command usage**
|
- **Keep commit messages concise and under 72 characters** for the title line, use body for detailed explanations if needed
|
||||||
- Prefer using `gh pr create` command to create Pull Requests
|
- After each development completion, first git push to remote repository
|
||||||
- Avoid having users manually create PRs through web interface
|
- After each change completion, summarize the changes, do not create summary files, provide a brief change description, ensure compliance with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
|
||||||
- Provide clear and professional PR titles and descriptions
|
- Provide change descriptions needed for PR in the conversation, ensure compliance with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
|
||||||
- Using `gh` commands ensures better integration and automation
|
- **Always provide PR descriptions in English** after completing any changes, including:
|
||||||
|
- Clear and concise title following Conventional Commits format
|
||||||
## 📝 **Code Quality Requirements**
|
- Detailed description of what was changed and why
|
||||||
|
- List of key changes and improvements
|
||||||
- Use English for all code comments, documentation, and variable names
|
- Any breaking changes or migration notes if applicable
|
||||||
- Write meaningful and descriptive names for variables, functions, and methods
|
- Testing information and verification steps
|
||||||
- Avoid meaningless test content like "debug 111" or placeholder values
|
- **Provide PR descriptions in copyable markdown format** enclosed in code blocks for easy one-click copying
|
||||||
- Before each change, carefully read the existing code to ensure you understand the code structure and implementation, do not break existing logic implementation, do not introduce new issues
|
|
||||||
- Ensure each change provides sufficient test cases to guarantee code correctness
|
|
||||||
- Do not arbitrarily modify numbers and constants in test cases, carefully analyze their meaning to ensure test case correctness
|
|
||||||
- When writing or modifying tests, check existing test cases to ensure they have scientific naming and rigorous logic testing, if not compliant, modify test cases to ensure scientific and rigorous testing
|
|
||||||
- **Before committing any changes, run `cargo clippy --all-targets --all-features -- -D warnings` to ensure all code passes Clippy checks**
|
|
||||||
- After each development completion, first git add . then git commit -m "feat: feature description" or "fix: issue description", ensure compliance with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
|
|
||||||
- **Keep commit messages concise and under 72 characters** for the title line, use body for detailed explanations if needed
|
|
||||||
- After each development completion, first git push to remote repository
|
|
||||||
- After each change completion, summarize the changes, do not create summary files, provide a brief change description, ensure compliance with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
|
|
||||||
- Provide change descriptions needed for PR in the conversation, ensure compliance with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
|
|
||||||
- **Always provide PR descriptions in English** after completing any changes, including:
|
|
||||||
- Clear and concise title following Conventional Commits format
|
|
||||||
- Detailed description of what was changed and why
|
|
||||||
- List of key changes and improvements
|
|
||||||
- Any breaking changes or migration notes if applicable
|
|
||||||
- Testing information and verification steps
|
|
||||||
- **Provide PR descriptions in copyable markdown format** enclosed in code blocks for easy one-click copying
|
|
||||||
|
|
||||||
## 🚫 AI Documentation Generation Restrictions
|
|
||||||
|
|
||||||
### Forbidden Summary Documents
|
|
||||||
|
|
||||||
- **Strictly forbidden to create any form of AI-generated summary documents**
|
|
||||||
- **Do not create documents containing large amounts of emoji, detailed formatting tables and typical AI style**
|
|
||||||
- **Do not generate the following types of documents in the project:**
|
|
||||||
- Benchmark summary documents (BENCHMARK*.md)
|
|
||||||
- Implementation comparison analysis documents (IMPLEMENTATION_COMPARISON*.md)
|
|
||||||
- Performance analysis report documents
|
|
||||||
- Architecture summary documents
|
|
||||||
- Feature comparison documents
|
|
||||||
- Any documents with large amounts of emoji and formatted content
|
|
||||||
- **If documentation is needed, only create when explicitly requested by the user, and maintain a concise and practical style**
|
|
||||||
- **Documentation should focus on actually needed information, avoiding excessive formatting and decorative content**
|
|
||||||
- **Any discovered AI-generated summary documents should be immediately deleted**
|
|
||||||
|
|
||||||
### Allowed Documentation Types
|
|
||||||
|
|
||||||
- README.md (project introduction, keep concise)
|
|
||||||
- Technical documentation (only create when explicitly needed)
|
|
||||||
- User manual (only create when explicitly needed)
|
|
||||||
- API documentation (generated from code)
|
|
||||||
- Changelog (CHANGELOG.md)
|
|
||||||
|
|
||||||
These rules should serve as guiding principles when developing the RustFS project, ensuring code quality, performance, and maintainability.
|
|
||||||
29
.docker/Dockerfile.devenv
Normal file
29
.docker/Dockerfile.devenv
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
FROM m.daocloud.io/docker.io/library/ubuntu:22.04
|
||||||
|
|
||||||
|
ENV LANG C.UTF-8
|
||||||
|
|
||||||
|
RUN sed -i s@http://.*archive.ubuntu.com@http://repo.huaweicloud.com@g /etc/apt/sources.list
|
||||||
|
|
||||||
|
RUN apt-get clean && apt-get update && apt-get install wget git curl unzip gcc pkg-config libssl-dev lld libdbus-1-dev libwayland-dev libwebkit2gtk-4.1-dev libxdo-dev -y
|
||||||
|
|
||||||
|
# install protoc
|
||||||
|
RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v30.2/protoc-30.2-linux-x86_64.zip \
|
||||||
|
&& unzip protoc-30.2-linux-x86_64.zip -d protoc3 \
|
||||||
|
&& mv protoc3/bin/* /usr/local/bin/ && chmod +x /usr/local/bin/protoc && mv protoc3/include/* /usr/local/include/ && rm -rf protoc-30.2-linux-x86_64.zip protoc3
|
||||||
|
|
||||||
|
# install flatc
|
||||||
|
RUN wget https://github.com/google/flatbuffers/releases/download/v25.2.10/Linux.flatc.binary.g++-13.zip \
|
||||||
|
&& unzip Linux.flatc.binary.g++-13.zip \
|
||||||
|
&& mv flatc /usr/local/bin/ && chmod +x /usr/local/bin/flatc && rm -rf Linux.flatc.binary.g++-13.zip
|
||||||
|
|
||||||
|
# install rust
|
||||||
|
ENV RUSTUP_DIST_SERVER="https://rsproxy.cn"
|
||||||
|
ENV RUSTUP_UPDATE_ROOT="https://rsproxy.cn/rustup"
|
||||||
|
RUN curl -o rustup-init.sh --proto '=https' --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh \
|
||||||
|
&& sh rustup-init.sh -y && rm -rf rustup-init.sh
|
||||||
|
|
||||||
|
COPY .docker/cargo.config.toml /root/.cargo/config.toml
|
||||||
|
|
||||||
|
WORKDIR /root/s3-rustfs
|
||||||
|
|
||||||
|
CMD [ "bash", "-c", "while true; do sleep 1; done" ]
|
||||||
35
.docker/Dockerfile.rockylinux9.3
Normal file
35
.docker/Dockerfile.rockylinux9.3
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
FROM m.daocloud.io/docker.io/library/rockylinux:9.3 AS builder
|
||||||
|
|
||||||
|
ENV LANG C.UTF-8
|
||||||
|
|
||||||
|
RUN sed -e 's|^mirrorlist=|#mirrorlist=|g' \
|
||||||
|
-e 's|^#baseurl=http://dl.rockylinux.org/$contentdir|baseurl=https://mirrors.ustc.edu.cn/rocky|g' \
|
||||||
|
-i.bak \
|
||||||
|
/etc/yum.repos.d/rocky-extras.repo \
|
||||||
|
/etc/yum.repos.d/rocky.repo
|
||||||
|
|
||||||
|
RUN dnf makecache
|
||||||
|
|
||||||
|
RUN yum install wget git unzip gcc openssl-devel pkgconf-pkg-config -y
|
||||||
|
|
||||||
|
# install protoc
|
||||||
|
RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v30.2/protoc-30.2-linux-x86_64.zip \
|
||||||
|
&& unzip protoc-30.2-linux-x86_64.zip -d protoc3 \
|
||||||
|
&& mv protoc3/bin/* /usr/local/bin/ && chmod +x /usr/local/bin/protoc \
|
||||||
|
&& rm -rf protoc-30.2-linux-x86_64.zip protoc3
|
||||||
|
|
||||||
|
# install flatc
|
||||||
|
RUN wget https://github.com/google/flatbuffers/releases/download/v25.2.10/Linux.flatc.binary.g++-13.zip \
|
||||||
|
&& unzip Linux.flatc.binary.g++-13.zip \
|
||||||
|
&& mv flatc /usr/local/bin/ && chmod +x /usr/local/bin/flatc \
|
||||||
|
&& rm -rf Linux.flatc.binary.g++-13.zip
|
||||||
|
|
||||||
|
# install rust
|
||||||
|
ENV RUSTUP_DIST_SERVER="https://rsproxy.cn"
|
||||||
|
ENV RUSTUP_UPDATE_ROOT="https://rsproxy.cn/rustup"
|
||||||
|
RUN curl -o rustup-init.sh --proto '=https' --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh \
|
||||||
|
&& sh rustup-init.sh -y && rm -rf rustup-init.sh
|
||||||
|
|
||||||
|
COPY .docker/cargo.config.toml /root/.cargo/config.toml
|
||||||
|
|
||||||
|
WORKDIR /root/s3-rustfs
|
||||||
22
.docker/Dockerfile.rustyvault
Normal file
22
.docker/Dockerfile.rustyvault
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
FROM vault:1.13
|
||||||
|
|
||||||
|
# Configure Vault for dev mode
|
||||||
|
ENV VAULT_DEV_ROOT_TOKEN_ID=rustfs-root-token
|
||||||
|
ENV VAULT_DEV_LISTEN_ADDRESS=0.0.0.0:8200
|
||||||
|
|
||||||
|
# Install curl for health checks
|
||||||
|
USER root
|
||||||
|
RUN apk add --no-cache curl jq
|
||||||
|
|
||||||
|
# Copy the Vault initialization script
|
||||||
|
COPY vault-init.sh /usr/local/bin/vault-init.sh
|
||||||
|
RUN chmod +x /usr/local/bin/vault-init.sh
|
||||||
|
|
||||||
|
# Switch back to vault user
|
||||||
|
USER vault
|
||||||
|
|
||||||
|
# Expose Vault port
|
||||||
|
EXPOSE 8200
|
||||||
|
|
||||||
|
# Start Vault in dev mode and run the initialization script
|
||||||
|
ENTRYPOINT ["sh", "-c", "vault server -dev & sleep 5 && vault-init.sh"]
|
||||||
27
.docker/Dockerfile.ubuntu22.04
Normal file
27
.docker/Dockerfile.ubuntu22.04
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
FROM m.daocloud.io/docker.io/library/ubuntu:22.04
|
||||||
|
|
||||||
|
ENV LANG C.UTF-8
|
||||||
|
|
||||||
|
RUN sed -i s@http://.*archive.ubuntu.com@http://repo.huaweicloud.com@g /etc/apt/sources.list
|
||||||
|
|
||||||
|
RUN apt-get clean && apt-get update && apt-get install wget git curl unzip gcc pkg-config libssl-dev lld libdbus-1-dev libwayland-dev libwebkit2gtk-4.1-dev libxdo-dev -y
|
||||||
|
|
||||||
|
# install protoc
|
||||||
|
RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v30.2/protoc-30.2-linux-x86_64.zip \
|
||||||
|
&& unzip protoc-30.2-linux-x86_64.zip -d protoc3 \
|
||||||
|
&& mv protoc3/bin/* /usr/local/bin/ && chmod +x /usr/local/bin/protoc && mv protoc3/include/* /usr/local/include/ && rm -rf protoc-30.2-linux-x86_64.zip protoc3
|
||||||
|
|
||||||
|
# install flatc
|
||||||
|
RUN wget https://github.com/google/flatbuffers/releases/download/v25.2.10/Linux.flatc.binary.g++-13.zip \
|
||||||
|
&& unzip Linux.flatc.binary.g++-13.zip \
|
||||||
|
&& mv flatc /usr/local/bin/ && chmod +x /usr/local/bin/flatc && rm -rf Linux.flatc.binary.g++-13.zip
|
||||||
|
|
||||||
|
# install rust
|
||||||
|
ENV RUSTUP_DIST_SERVER="https://rsproxy.cn"
|
||||||
|
ENV RUSTUP_UPDATE_ROOT="https://rsproxy.cn/rustup"
|
||||||
|
RUN curl -o rustup-init.sh --proto '=https' --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh \
|
||||||
|
&& sh rustup-init.sh -y && rm -rf rustup-init.sh
|
||||||
|
|
||||||
|
COPY .docker/cargo.config.toml /root/.cargo/config.toml
|
||||||
|
|
||||||
|
WORKDIR /root/s3-rustfs
|
||||||
@@ -1,261 +0,0 @@
|
|||||||
# RustFS Docker Images
|
|
||||||
|
|
||||||
This directory contains Docker configuration files and supporting infrastructure for building and running RustFS container images.
|
|
||||||
|
|
||||||
## 📁 Directory Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
rustfs/
|
|
||||||
├── Dockerfile # Production image (Alpine + pre-built binaries)
|
|
||||||
├── Dockerfile.source # Development image (Debian + source build)
|
|
||||||
├── docker-buildx.sh # Multi-architecture build script
|
|
||||||
├── Makefile # Build automation with simplified commands
|
|
||||||
└── .docker/ # Supporting infrastructure
|
|
||||||
├── observability/ # Monitoring and observability configs
|
|
||||||
├── compose/ # Docker Compose configurations
|
|
||||||
├── mqtt/ # MQTT broker configs
|
|
||||||
└── openobserve-otel/ # OpenObserve + OpenTelemetry configs
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎯 Image Variants
|
|
||||||
|
|
||||||
### Core Images
|
|
||||||
|
|
||||||
| Image | Base OS | Build Method | Size | Use Case |
|
|
||||||
|-------|---------|--------------|------|----------|
|
|
||||||
| `production` (default) | Alpine 3.18 | GitHub Releases | Smallest | Production deployment |
|
|
||||||
| `source` | Debian Bookworm | Source build | Medium | Custom builds with cross-compilation |
|
|
||||||
| `dev` | Debian Bookworm | Development tools | Large | Interactive development |
|
|
||||||
|
|
||||||
## 🚀 Usage Examples
|
|
||||||
|
|
||||||
### Quick Start (Production)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Default production image (Alpine + GitHub Releases)
|
|
||||||
docker run -p 9000:9000 rustfs/rustfs:latest
|
|
||||||
|
|
||||||
# Specific version
|
|
||||||
docker run -p 9000:9000 rustfs/rustfs:1.2.3
|
|
||||||
```
|
|
||||||
|
|
||||||
### Complete Tag Strategy Examples
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Stable Releases
|
|
||||||
docker run rustfs/rustfs:1.2.3 # Main version (production)
|
|
||||||
docker run rustfs/rustfs:1.2.3-production # Explicit production variant
|
|
||||||
docker run rustfs/rustfs:1.2.3-source # Source build variant
|
|
||||||
docker run rustfs/rustfs:latest # Latest stable
|
|
||||||
|
|
||||||
# Prerelease Versions
|
|
||||||
docker run rustfs/rustfs:1.3.0-alpha.2 # Specific alpha version
|
|
||||||
docker run rustfs/rustfs:alpha # Latest alpha
|
|
||||||
docker run rustfs/rustfs:beta # Latest beta
|
|
||||||
docker run rustfs/rustfs:rc # Latest release candidate
|
|
||||||
|
|
||||||
# Development Versions
|
|
||||||
docker run rustfs/rustfs:dev # Latest main branch development
|
|
||||||
docker run rustfs/rustfs:dev-13e4a0b # Specific commit
|
|
||||||
docker run rustfs/rustfs:dev-latest # Latest development
|
|
||||||
docker run rustfs/rustfs:main-latest # Main branch latest
|
|
||||||
```
|
|
||||||
|
|
||||||
### Development Environment
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Quick setup using Makefile (recommended)
|
|
||||||
make docker-dev-local # Build development image locally
|
|
||||||
make dev-env-start # Start development container
|
|
||||||
|
|
||||||
# Manual Docker commands
|
|
||||||
docker run -it -v $(pwd):/workspace -p 9000:9000 rustfs/rustfs:latest-dev
|
|
||||||
|
|
||||||
# Build from source locally
|
|
||||||
docker build -f Dockerfile.source -t rustfs:custom .
|
|
||||||
|
|
||||||
# Development with hot reload
|
|
||||||
docker-compose up rustfs-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🏗️ Build Arguments and Scripts
|
|
||||||
|
|
||||||
### Using Makefile Commands (Recommended)
|
|
||||||
|
|
||||||
The easiest way to build images using simplified commands:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Development images (build from source)
|
|
||||||
make docker-dev-local # Build for local use (single arch)
|
|
||||||
make docker-dev # Build multi-arch (for CI/CD)
|
|
||||||
make docker-dev-push REGISTRY=xxx # Build and push to registry
|
|
||||||
|
|
||||||
# Production images (using pre-built binaries)
|
|
||||||
make docker-buildx # Build multi-arch production images
|
|
||||||
make docker-buildx-push # Build and push production images
|
|
||||||
make docker-buildx-version VERSION=v1.0.0 # Build specific version
|
|
||||||
|
|
||||||
# Development environment
|
|
||||||
make dev-env-start # Start development container
|
|
||||||
make dev-env-stop # Stop development container
|
|
||||||
make dev-env-restart # Restart development container
|
|
||||||
|
|
||||||
# Help
|
|
||||||
make help-docker # Show all Docker-related commands
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using docker-buildx.sh (Advanced)
|
|
||||||
|
|
||||||
For direct script usage and advanced scenarios:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build latest version for all architectures
|
|
||||||
./docker-buildx.sh
|
|
||||||
|
|
||||||
# Build and push to registry
|
|
||||||
./docker-buildx.sh --push
|
|
||||||
|
|
||||||
# Build specific version
|
|
||||||
./docker-buildx.sh --release v1.2.3
|
|
||||||
|
|
||||||
# Build and push specific version
|
|
||||||
./docker-buildx.sh --release v1.2.3 --push
|
|
||||||
```
|
|
||||||
|
|
||||||
### Manual Docker Builds
|
|
||||||
|
|
||||||
All images support dynamic version selection:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build production image with latest release
|
|
||||||
docker build --build-arg RELEASE="latest" -t rustfs:latest .
|
|
||||||
|
|
||||||
# Build from source with specific target
|
|
||||||
docker build -f Dockerfile.source \
|
|
||||||
--build-arg TARGETPLATFORM="linux/amd64" \
|
|
||||||
-t rustfs:source .
|
|
||||||
|
|
||||||
# Development build
|
|
||||||
docker build -f Dockerfile.source -t rustfs:dev .
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 Binary Download Sources
|
|
||||||
|
|
||||||
### Unified GitHub Releases
|
|
||||||
|
|
||||||
The production image downloads from GitHub Releases for reliability and transparency:
|
|
||||||
|
|
||||||
- ✅ **production** → GitHub Releases API with automatic latest detection
|
|
||||||
- ✅ **Checksum verification** → SHA256SUMS validation when available
|
|
||||||
- ✅ **Multi-architecture** → Supports amd64 and arm64
|
|
||||||
|
|
||||||
### Source Build
|
|
||||||
|
|
||||||
The source variant compiles from source code with advanced features:
|
|
||||||
|
|
||||||
- 🔧 **Cross-compilation** → Supports multiple target platforms via `TARGETPLATFORM`
|
|
||||||
- ⚡ **Build caching** → sccache for faster compilation
|
|
||||||
- 🎯 **Optimized builds** → Release optimizations with LTO and symbol stripping
|
|
||||||
|
|
||||||
## 📋 Architecture Support
|
|
||||||
|
|
||||||
All variants support multi-architecture builds:
|
|
||||||
|
|
||||||
- **linux/amd64** (x86_64)
|
|
||||||
- **linux/arm64** (aarch64)
|
|
||||||
|
|
||||||
Architecture is automatically detected during build using Docker's `TARGETARCH` build argument.
|
|
||||||
|
|
||||||
## 🔐 Security Features
|
|
||||||
|
|
||||||
- **Checksum Verification**: Production image verifies SHA256SUMS when available
|
|
||||||
- **Non-root User**: All images run as user `rustfs` (UID 1000)
|
|
||||||
- **Minimal Runtime**: Production image only includes necessary dependencies
|
|
||||||
- **Secure Defaults**: No hardcoded credentials or keys
|
|
||||||
|
|
||||||
## 🛠️ Development Workflow
|
|
||||||
|
|
||||||
### Quick Start with Makefile (Recommended)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Start development environment
|
|
||||||
make dev-env-start
|
|
||||||
|
|
||||||
# 2. Your development container is now running with:
|
|
||||||
# - Port 9000 exposed for RustFS
|
|
||||||
# - Port 9010 exposed for admin console
|
|
||||||
# - Current directory mounted as /workspace
|
|
||||||
|
|
||||||
# 3. Stop when done
|
|
||||||
make dev-env-stop
|
|
||||||
```
|
|
||||||
|
|
||||||
### Manual Development Setup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build development image from source
|
|
||||||
make docker-dev-local
|
|
||||||
|
|
||||||
# Or use traditional Docker commands
|
|
||||||
docker build -f Dockerfile.source -t rustfs:dev .
|
|
||||||
|
|
||||||
# Run with development tools
|
|
||||||
docker run -it -v $(pwd):/workspace -p 9000:9000 rustfs:dev bash
|
|
||||||
|
|
||||||
# Or use docker-compose for complex setups
|
|
||||||
docker-compose up rustfs-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Common Development Tasks
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build and test locally
|
|
||||||
make build # Build binary natively
|
|
||||||
make docker-dev-local # Build development Docker image
|
|
||||||
make test # Run tests
|
|
||||||
make fmt # Format code
|
|
||||||
make clippy # Run linter
|
|
||||||
|
|
||||||
# Get help
|
|
||||||
make help # General help
|
|
||||||
make help-docker # Docker-specific help
|
|
||||||
make help-build # Build-specific help
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🚀 CI/CD Integration
|
|
||||||
|
|
||||||
The project uses GitHub Actions for automated multi-architecture Docker builds:
|
|
||||||
|
|
||||||
### Automated Builds
|
|
||||||
|
|
||||||
- **Tags**: Automatic builds triggered on version tags (e.g., `v1.2.3`)
|
|
||||||
- **Main Branch**: Development builds with `dev-latest` and `main-latest` tags
|
|
||||||
- **Pull Requests**: Test builds without registry push
|
|
||||||
|
|
||||||
### Build Variants
|
|
||||||
|
|
||||||
Each build creates three image variants:
|
|
||||||
|
|
||||||
- `rustfs/rustfs:v1.2.3` (production - Alpine-based)
|
|
||||||
- `rustfs/rustfs:v1.2.3-source` (source build - Debian-based)
|
|
||||||
- `rustfs/rustfs:v1.2.3-dev` (development - Debian-based with tools)
|
|
||||||
|
|
||||||
### Manual Builds
|
|
||||||
|
|
||||||
Trigger custom builds via GitHub Actions:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Use workflow_dispatch to build specific versions
|
|
||||||
# Available options: latest, main-latest, dev-latest, v1.2.3, dev-abc123
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📦 Supporting Infrastructure
|
|
||||||
|
|
||||||
The `.docker/` directory contains supporting configuration files:
|
|
||||||
|
|
||||||
- **observability/** - Prometheus, Grafana, OpenTelemetry configs
|
|
||||||
- **compose/** - Multi-service Docker Compose setups
|
|
||||||
- **mqtt/** - MQTT broker configurations
|
|
||||||
- **openobserve-otel/** - Log aggregation and tracing setup
|
|
||||||
|
|
||||||
See individual README files in each subdirectory for specific usage instructions.
|
|
||||||
13
.docker/cargo.config.toml
Normal file
13
.docker/cargo.config.toml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[source.crates-io]
|
||||||
|
registry = "https://github.com/rust-lang/crates.io-index"
|
||||||
|
replace-with = 'rsproxy-sparse'
|
||||||
|
|
||||||
|
[source.rsproxy]
|
||||||
|
registry = "https://rsproxy.cn/crates.io-index"
|
||||||
|
[registries.rsproxy]
|
||||||
|
index = "https://rsproxy.cn/crates.io-index"
|
||||||
|
[source.rsproxy-sparse]
|
||||||
|
registry = "sparse+https://rsproxy.cn/index/"
|
||||||
|
|
||||||
|
[net]
|
||||||
|
git-fetch-with-cli = true
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
# Docker Compose Configurations
|
|
||||||
|
|
||||||
This directory contains specialized Docker Compose configurations for different use cases.
|
|
||||||
|
|
||||||
## 📁 Configuration Files
|
|
||||||
|
|
||||||
This directory contains specialized Docker Compose configurations and their associated Dockerfiles, keeping related files organized together.
|
|
||||||
|
|
||||||
### Main Configuration (Root Directory)
|
|
||||||
|
|
||||||
- **`../../docker-compose.yml`** - **Default Production Setup**
|
|
||||||
- Complete production-ready configuration
|
|
||||||
- Includes RustFS server + full observability stack
|
|
||||||
- Supports multiple profiles: `dev`, `observability`, `cache`, `proxy`
|
|
||||||
- Recommended for most users
|
|
||||||
|
|
||||||
### Specialized Configurations
|
|
||||||
|
|
||||||
- **`docker-compose.cluster.yaml`** - **Distributed Testing**
|
|
||||||
- 4-node cluster setup for testing distributed storage
|
|
||||||
- Uses local compiled binaries
|
|
||||||
- Simulates multi-node environment
|
|
||||||
- Ideal for development and cluster testing
|
|
||||||
|
|
||||||
- **`docker-compose.observability.yaml`** - **Observability Focus**
|
|
||||||
- Specialized setup for testing observability features
|
|
||||||
- Includes OpenTelemetry, Jaeger, Prometheus, Loki, Grafana
|
|
||||||
- Uses `../../Dockerfile.source` for builds
|
|
||||||
- Perfect for observability development
|
|
||||||
|
|
||||||
## 🚀 Usage Examples
|
|
||||||
|
|
||||||
### Production Setup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Start main service
|
|
||||||
docker-compose up -d
|
|
||||||
|
|
||||||
# Start with development profile
|
|
||||||
docker-compose --profile dev up -d
|
|
||||||
|
|
||||||
# Start with full observability
|
|
||||||
docker-compose --profile observability up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
### Cluster Testing
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build and start 4-node cluster (run from project root)
|
|
||||||
cd .docker/compose
|
|
||||||
docker-compose -f docker-compose.cluster.yaml up -d
|
|
||||||
|
|
||||||
# Or run directly from project root
|
|
||||||
docker-compose -f .docker/compose/docker-compose.cluster.yaml up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
### Observability Testing
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Start observability-focused environment (run from project root)
|
|
||||||
cd .docker/compose
|
|
||||||
docker-compose -f docker-compose.observability.yaml up -d
|
|
||||||
|
|
||||||
# Or run directly from project root
|
|
||||||
docker-compose -f .docker/compose/docker-compose.observability.yaml up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 Configuration Overview
|
|
||||||
|
|
||||||
| Configuration | Nodes | Storage | Observability | Use Case |
|
|
||||||
|---------------|-------|---------|---------------|----------|
|
|
||||||
| **Main** | 1 | Volume mounts | Full stack | Production |
|
|
||||||
| **Cluster** | 4 | HTTP endpoints | Basic | Testing |
|
|
||||||
| **Observability** | 4 | Local data | Advanced | Development |
|
|
||||||
|
|
||||||
## 📝 Notes
|
|
||||||
|
|
||||||
- Always ensure you have built the required binaries before starting cluster tests
|
|
||||||
- The main configuration is sufficient for most use cases
|
|
||||||
- Specialized configurations are for specific testing scenarios
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
# Copyright 2024 RustFS Team
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
services:
|
|
||||||
node0:
|
|
||||||
image: rustfs/rustfs:latest # Replace with your image name and label
|
|
||||||
container_name: node0
|
|
||||||
hostname: node0
|
|
||||||
environment:
|
|
||||||
- RUSTFS_VOLUMES=http://node{0...3}:9000/data/rustfs{0...3}
|
|
||||||
- RUSTFS_ADDRESS=0.0.0.0:9000
|
|
||||||
- RUSTFS_CONSOLE_ENABLE=true
|
|
||||||
- RUSTFS_ACCESS_KEY=rustfsadmin
|
|
||||||
- RUSTFS_SECRET_KEY=rustfsadmin
|
|
||||||
platform: linux/amd64
|
|
||||||
ports:
|
|
||||||
- "9000:9000" # Map port 9001 of the host to port 9000 of the container
|
|
||||||
volumes:
|
|
||||||
- ../../target/x86_64-unknown-linux-gnu/release/rustfs:/app/rustfs
|
|
||||||
command: "/app/rustfs"
|
|
||||||
|
|
||||||
node1:
|
|
||||||
image: rustfs/rustfs:latest
|
|
||||||
container_name: node1
|
|
||||||
hostname: node1
|
|
||||||
environment:
|
|
||||||
- RUSTFS_VOLUMES=http://node{0...3}:9000/data/rustfs{0...3}
|
|
||||||
- RUSTFS_ADDRESS=0.0.0.0:9000
|
|
||||||
- RUSTFS_CONSOLE_ENABLE=true
|
|
||||||
- RUSTFS_ACCESS_KEY=rustfsadmin
|
|
||||||
- RUSTFS_SECRET_KEY=rustfsadmin
|
|
||||||
platform: linux/amd64
|
|
||||||
ports:
|
|
||||||
- "9001:9000" # Map port 9002 of the host to port 9000 of the container
|
|
||||||
volumes:
|
|
||||||
- ../../target/x86_64-unknown-linux-gnu/release/rustfs:/app/rustfs
|
|
||||||
command: "/app/rustfs"
|
|
||||||
|
|
||||||
node2:
|
|
||||||
image: rustfs/rustfs:latest
|
|
||||||
container_name: node2
|
|
||||||
hostname: node2
|
|
||||||
environment:
|
|
||||||
- RUSTFS_VOLUMES=http://node{0...3}:9000/data/rustfs{0...3}
|
|
||||||
- RUSTFS_ADDRESS=0.0.0.0:9000
|
|
||||||
- RUSTFS_CONSOLE_ENABLE=true
|
|
||||||
- RUSTFS_ACCESS_KEY=rustfsadmin
|
|
||||||
- RUSTFS_SECRET_KEY=rustfsadmin
|
|
||||||
platform: linux/amd64
|
|
||||||
ports:
|
|
||||||
- "9002:9000" # Map port 9003 of the host to port 9000 of the container
|
|
||||||
volumes:
|
|
||||||
- ../../target/x86_64-unknown-linux-gnu/release/rustfs:/app/rustfs
|
|
||||||
command: "/app/rustfs"
|
|
||||||
|
|
||||||
node3:
|
|
||||||
image: rustfs/rustfs:latest
|
|
||||||
container_name: node3
|
|
||||||
hostname: node3
|
|
||||||
environment:
|
|
||||||
- RUSTFS_VOLUMES=http://node{0...3}:9000/data/rustfs{0...3}
|
|
||||||
- RUSTFS_ADDRESS=0.0.0.0:9000
|
|
||||||
- RUSTFS_CONSOLE_ENABLE=true
|
|
||||||
- RUSTFS_ACCESS_KEY=rustfsadmin
|
|
||||||
- RUSTFS_SECRET_KEY=rustfsadmin
|
|
||||||
platform: linux/amd64
|
|
||||||
ports:
|
|
||||||
- "9003:9000" # Map port 9004 of the host to port 9000 of the container
|
|
||||||
volumes:
|
|
||||||
- ../../target/x86_64-unknown-linux-gnu/release/rustfs:/app/rustfs
|
|
||||||
command: "/app/rustfs"
|
|
||||||
26
.docker/kms/docker-compose.yml
Normal file
26
.docker/kms/docker-compose.yml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
services:
|
||||||
|
rustyvault:
|
||||||
|
build:
|
||||||
|
context: ./.docker
|
||||||
|
dockerfile: Dockerfile.rustyvault
|
||||||
|
container_name: rustyvault
|
||||||
|
hostname: rustyvault
|
||||||
|
ports:
|
||||||
|
- "8200:8200" # Vault API port
|
||||||
|
volumes:
|
||||||
|
- vault-data:/vault/data
|
||||||
|
- vault-config:/vault/config
|
||||||
|
cap_add:
|
||||||
|
- IPC_LOCK # Allow the vault to lock sensitive data in memory
|
||||||
|
environment:
|
||||||
|
- VAULT_DEV_ROOT_TOKEN_ID=rustfs-root-token
|
||||||
|
- VAULT_ADDR=http://0.0.0.0:8200
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-s", "http://127.0.0.1:8200/v1/sys/health"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
driver: bridge
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# 节点配置
|
|
||||||
node.name = "emqx@127.0.0.1"
|
|
||||||
node.cookie = "aBcDeFgHiJkLmNoPqRsTuVwXyZ012345"
|
|
||||||
node.data_dir = "/opt/emqx/data"
|
|
||||||
|
|
||||||
# 日志配置
|
|
||||||
log.console = {level = info, enable = true}
|
|
||||||
log.file = {path = "/opt/emqx/log/emqx.log", enable = true, level = info}
|
|
||||||
|
|
||||||
# MQTT TCP 监听器
|
|
||||||
listeners.tcp.default = {bind = "0.0.0.0:1883", max_connections = 1000000, enable = true}
|
|
||||||
|
|
||||||
# MQTT SSL 监听器
|
|
||||||
listeners.ssl.default = {bind = "0.0.0.0:8883", enable = false}
|
|
||||||
|
|
||||||
# MQTT WebSocket 监听器
|
|
||||||
listeners.ws.default = {bind = "0.0.0.0:8083", enable = true}
|
|
||||||
|
|
||||||
# MQTT WebSocket SSL 监听器
|
|
||||||
listeners.wss.default = {bind = "0.0.0.0:8084", enable = false}
|
|
||||||
|
|
||||||
# 管理控制台
|
|
||||||
dashboard.listeners.http = {bind = "0.0.0.0:18083", enable = true}
|
|
||||||
|
|
||||||
# HTTP API
|
|
||||||
management.listeners.http = {bind = "0.0.0.0:8081", enable = true}
|
|
||||||
|
|
||||||
# 认证配置
|
|
||||||
authentication = [
|
|
||||||
{enable = true, mechanism = password_based, backend = built_in_database, user_id_type = username}
|
|
||||||
]
|
|
||||||
|
|
||||||
# 授权配置
|
|
||||||
authorization.sources = [{type = built_in_database, enable = true}]
|
|
||||||
|
|
||||||
# 持久化消息存储
|
|
||||||
message.storage.backend = built_in_database
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
-name emqx@127.0.0.1
|
|
||||||
-setcookie aBcDeFgHiJkLmNoPqRsTuVwXyZ012345
|
|
||||||
+P 2097152
|
|
||||||
+t 1048576
|
|
||||||
+zdbbl 32768
|
|
||||||
-kernel inet_dist_listen_min 6000
|
|
||||||
-kernel inet_dist_listen_max 6100
|
|
||||||
-smp enable
|
|
||||||
-mnesia dir "/opt/emqx/data/mnesia"
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
# Copyright 2024 RustFS Team
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
services:
|
|
||||||
emqx:
|
|
||||||
image: emqx/emqx:latest
|
|
||||||
container_name: emqx
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
- EMQX_NODE__NAME=emqx@127.0.0.1
|
|
||||||
- EMQX_NODE__COOKIE=aBcDeFgHiJkLmNoPqRsTuVwXyZ012345
|
|
||||||
- EMQX_NODE__DATA_DIR=/opt/emqx/data
|
|
||||||
- EMQX_LOG__CONSOLE__LEVEL=info
|
|
||||||
- EMQX_LOG__CONSOLE__ENABLE=true
|
|
||||||
- EMQX_LOG__FILE__PATH=/opt/emqx/log/emqx.log
|
|
||||||
- EMQX_LOG__FILE__LEVEL=info
|
|
||||||
- EMQX_LOG__FILE__ENABLE=true
|
|
||||||
- EMQX_LISTENERS__TCP__DEFAULT__BIND=0.0.0.0:1883
|
|
||||||
- EMQX_LISTENERS__TCP__DEFAULT__MAX_CONNECTIONS=1000000
|
|
||||||
- EMQX_LISTENERS__TCP__DEFAULT__ENABLE=true
|
|
||||||
- EMQX_LISTENERS__SSL__DEFAULT__BIND=0.0.0.0:8883
|
|
||||||
- EMQX_LISTENERS__SSL__DEFAULT__ENABLE=false
|
|
||||||
- EMQX_LISTENERS__WS__DEFAULT__BIND=0.0.0.0:8083
|
|
||||||
- EMQX_LISTENERS__WS__DEFAULT__ENABLE=true
|
|
||||||
- EMQX_LISTENERS__WSS__DEFAULT__BIND=0.0.0.0:8084
|
|
||||||
- EMQX_LISTENERS__WSS__DEFAULT__ENABLE=false
|
|
||||||
- EMQX_DASHBOARD__LISTENERS__HTTP__BIND=0.0.0.0:18083
|
|
||||||
- EMQX_DASHBOARD__LISTENERS__HTTP__ENABLE=true
|
|
||||||
- EMQX_MANAGEMENT__LISTENERS__HTTP__BIND=0.0.0.0:8081
|
|
||||||
- EMQX_MANAGEMENT__LISTENERS__HTTP__ENABLE=true
|
|
||||||
- EMQX_AUTHENTICATION__1__ENABLE=true
|
|
||||||
- EMQX_AUTHENTICATION__1__MECHANISM=password_based
|
|
||||||
- EMQX_AUTHENTICATION__1__BACKEND=built_in_database
|
|
||||||
- EMQX_AUTHENTICATION__1__USER_ID_TYPE=username
|
|
||||||
- EMQX_AUTHORIZATION__SOURCES__1__TYPE=built_in_database
|
|
||||||
- EMQX_AUTHORIZATION__SOURCES__1__ENABLE=true
|
|
||||||
ports:
|
|
||||||
- "1883:1883" # MQTT TCP
|
|
||||||
- "8883:8883" # MQTT SSL
|
|
||||||
- "8083:8083" # MQTT WebSocket
|
|
||||||
- "8084:8084" # MQTT WebSocket SSL
|
|
||||||
- "18083:18083" # Web 管理控制台
|
|
||||||
- "8081:8081" # HTTP API
|
|
||||||
volumes:
|
|
||||||
- ./data:/opt/emqx/data
|
|
||||||
- ./log:/opt/emqx/log
|
|
||||||
- ./config:/opt/emqx/etc
|
|
||||||
networks:
|
|
||||||
- mqtt-net
|
|
||||||
healthcheck:
|
|
||||||
test: [ "CMD", "/opt/emqx/bin/emqx_ctl", "status" ]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 3
|
|
||||||
logging:
|
|
||||||
driver: "json-file"
|
|
||||||
options:
|
|
||||||
max-size: "100m"
|
|
||||||
max-file: "3"
|
|
||||||
|
|
||||||
networks:
|
|
||||||
mqtt-net:
|
|
||||||
driver: bridge
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# Copyright 2024 RustFS Team
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
services:
|
|
||||||
emqx:
|
|
||||||
image: emqx/emqx:latest
|
|
||||||
container_name: emqx
|
|
||||||
ports:
|
|
||||||
- "1883:1883"
|
|
||||||
- "8083:8083"
|
|
||||||
- "8084:8084"
|
|
||||||
- "8883:8883"
|
|
||||||
- "18083:18083"
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
networks:
|
|
||||||
default:
|
|
||||||
driver: bridge
|
|
||||||
@@ -22,6 +22,21 @@ docker compose -f docker-compose.yml up -d
|
|||||||
|
|
||||||
## 配置可观测性
|
## 配置可观测性
|
||||||
|
|
||||||
```shell
|
### 创建配置文件
|
||||||
export RUSTFS_OBS_ENDPOINT="http://localhost:4317" # OpenTelemetry Collector 地址
|
|
||||||
```
|
1. 进入 `deploy/config` 目录
|
||||||
|
2. 复制示例配置:`cp obs.toml.example obs.toml`
|
||||||
|
3. 编辑 `obs.toml` 配置文件,修改以下关键参数:
|
||||||
|
|
||||||
|
| 配置项 | 说明 | 示例值 |
|
||||||
|
|-----------------|----------------------------|-----------------------|
|
||||||
|
| endpoint | OpenTelemetry Collector 地址 | http://localhost:4317 |
|
||||||
|
| service_name | 服务名称 | rustfs |
|
||||||
|
| service_version | 服务版本 | 1.0.0 |
|
||||||
|
| environment | 运行环境 | production |
|
||||||
|
| meter_interval | 指标导出间隔 (秒) | 30 |
|
||||||
|
| sample_ratio | 采样率 | 1.0 |
|
||||||
|
| use_stdout | 是否输出到控制台 | true/false |
|
||||||
|
| logger_level | 日志级别 | info |
|
||||||
|
|
||||||
|
```
|
||||||
34
.docker/observability/config/obs-multi.toml
Normal file
34
.docker/observability/config/obs-multi.toml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[observability]
|
||||||
|
endpoint = "http://otel-collector:4317" # Default is "http://localhost:4317" if not specified
|
||||||
|
use_stdout = false # Output with stdout, true output, false no output
|
||||||
|
sample_ratio = 2.0
|
||||||
|
meter_interval = 30
|
||||||
|
service_name = "rustfs"
|
||||||
|
service_version = "0.1.0"
|
||||||
|
environments = "production"
|
||||||
|
logger_level = "debug"
|
||||||
|
local_logging_enabled = true
|
||||||
|
|
||||||
|
#[[sinks]]
|
||||||
|
#type = "Kafka"
|
||||||
|
#brokers = "localhost:9092"
|
||||||
|
#topic = "logs"
|
||||||
|
#batch_size = 100 # Default is 100 if not specified
|
||||||
|
#batch_timeout_ms = 1000 # Default is 1000ms if not specified
|
||||||
|
#
|
||||||
|
#[[sinks]]
|
||||||
|
#type = "Webhook"
|
||||||
|
#endpoint = "http://localhost:8080/webhook"
|
||||||
|
#auth_token = ""
|
||||||
|
#batch_size = 100 # Default is 3 if not specified
|
||||||
|
#batch_timeout_ms = 1000 # Default is 100ms if not specified
|
||||||
|
|
||||||
|
[[sinks]]
|
||||||
|
type = "File"
|
||||||
|
path = "/root/data/logs/rustfs.log"
|
||||||
|
buffer_size = 100 # Default is 8192 bytes if not specified
|
||||||
|
flush_interval_ms = 1000
|
||||||
|
flush_threshold = 100
|
||||||
|
|
||||||
|
[logger]
|
||||||
|
queue_capacity = 10
|
||||||
34
.docker/observability/config/obs.toml
Normal file
34
.docker/observability/config/obs.toml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[observability]
|
||||||
|
endpoint = "http://localhost:4317" # Default is "http://localhost:4317" if not specified
|
||||||
|
use_stdout = false # Output with stdout, true output, false no output
|
||||||
|
sample_ratio = 2.0
|
||||||
|
meter_interval = 30
|
||||||
|
service_name = "rustfs"
|
||||||
|
service_version = "0.1.0"
|
||||||
|
environments = "production"
|
||||||
|
logger_level = "debug"
|
||||||
|
local_logging_enabled = true
|
||||||
|
|
||||||
|
#[[sinks]]
|
||||||
|
#type = "Kafka"
|
||||||
|
#brokers = "localhost:9092"
|
||||||
|
#topic = "logs"
|
||||||
|
#batch_size = 100 # Default is 100 if not specified
|
||||||
|
#batch_timeout_ms = 1000 # Default is 1000ms if not specified
|
||||||
|
#
|
||||||
|
#[[sinks]]
|
||||||
|
#type = "Webhook"
|
||||||
|
#endpoint = "http://localhost:8080/webhook"
|
||||||
|
#auth_token = ""
|
||||||
|
#batch_size = 100 # Default is 3 if not specified
|
||||||
|
#batch_timeout_ms = 1000 # Default is 100ms if not specified
|
||||||
|
|
||||||
|
[[sinks]]
|
||||||
|
type = "File"
|
||||||
|
path = "/root/data/logs/rustfs.log"
|
||||||
|
buffer_size = 100 # Default is 8192 bytes if not specified
|
||||||
|
flush_interval_ms = 1000
|
||||||
|
flush_threshold = 100
|
||||||
|
|
||||||
|
[logger]
|
||||||
|
queue_capacity = 10
|
||||||
@@ -1,178 +1,65 @@
|
|||||||
# Copyright 2024 RustFS Team
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
tempo-init:
|
|
||||||
image: busybox:latest
|
|
||||||
command: [ "sh", "-c", "chown -R 10001:10001 /var/tempo" ]
|
|
||||||
volumes:
|
|
||||||
- ./tempo-data:/var/tempo
|
|
||||||
user: root
|
|
||||||
networks:
|
|
||||||
- otel-network
|
|
||||||
restart: "no"
|
|
||||||
|
|
||||||
tempo:
|
|
||||||
image: grafana/tempo:latest
|
|
||||||
user: "10001" # The container must be started with root to execute chown in the script
|
|
||||||
command: [ "-config.file=/etc/tempo.yaml" ] # This is passed as a parameter to the entry point script
|
|
||||||
volumes:
|
|
||||||
- ./tempo.yaml:/etc/tempo.yaml:ro
|
|
||||||
- ./tempo-data:/var/tempo
|
|
||||||
ports:
|
|
||||||
- "3200:3200" # tempo
|
|
||||||
- "24317:4317" # otlp grpc
|
|
||||||
- "24318:4318" # otlp http
|
|
||||||
restart: unless-stopped
|
|
||||||
networks:
|
|
||||||
- otel-network
|
|
||||||
healthcheck:
|
|
||||||
test: [ "CMD", "wget", "--spider", "-q", "http://localhost:3200/metrics" ]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 3
|
|
||||||
start_period: 15s
|
|
||||||
|
|
||||||
otel-collector:
|
otel-collector:
|
||||||
image: otel/opentelemetry-collector-contrib:latest
|
image: ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib:0.127.0
|
||||||
environment:
|
environment:
|
||||||
- TZ=Asia/Shanghai
|
- TZ=Asia/Shanghai
|
||||||
volumes:
|
volumes:
|
||||||
- ./otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml:ro
|
- ./otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml
|
||||||
ports:
|
ports:
|
||||||
- "1888:1888" # pprof
|
- 1888:1888
|
||||||
- "8888:8888" # Prometheus metrics for Collector
|
- 8888:8888
|
||||||
- "8889:8889" # Prometheus metrics for application indicators
|
- 8889:8889
|
||||||
- "13133:13133" # health check
|
- 13133:13133
|
||||||
- "4317:4317" # OTLP gRPC
|
- 4317:4317
|
||||||
- "4318:4318" # OTLP HTTP
|
- 4318:4318
|
||||||
- "55679:55679" # zpages
|
- 55679:55679
|
||||||
networks:
|
networks:
|
||||||
- otel-network
|
- otel-network
|
||||||
depends_on:
|
|
||||||
jaeger:
|
|
||||||
condition: service_started
|
|
||||||
tempo:
|
|
||||||
condition: service_started
|
|
||||||
prometheus:
|
|
||||||
condition: service_started
|
|
||||||
loki:
|
|
||||||
condition: service_started
|
|
||||||
healthcheck:
|
|
||||||
test: [ "CMD", "wget", "--spider", "-q", "http://localhost:13133" ]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 3
|
|
||||||
|
|
||||||
jaeger:
|
jaeger:
|
||||||
image: jaegertracing/jaeger:latest
|
image: jaegertracing/jaeger:2.6.0
|
||||||
environment:
|
environment:
|
||||||
- TZ=Asia/Shanghai
|
- TZ=Asia/Shanghai
|
||||||
- SPAN_STORAGE_TYPE=memory
|
|
||||||
- COLLECTOR_OTLP_ENABLED=true
|
|
||||||
ports:
|
ports:
|
||||||
- "16686:16686" # Web UI
|
- "16686:16686"
|
||||||
- "14317:4317" # OTLP gRPC
|
- "14317:4317"
|
||||||
- "14318:4318" # OTLP HTTP
|
- "14318:4318"
|
||||||
- "18888:8888" # collector
|
|
||||||
networks:
|
networks:
|
||||||
- otel-network
|
- otel-network
|
||||||
healthcheck:
|
|
||||||
test: [ "CMD", "wget", "--spider", "-q", "http://localhost:16686" ]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 3
|
|
||||||
prometheus:
|
prometheus:
|
||||||
image: prom/prometheus:latest
|
image: prom/prometheus:v3.4.1
|
||||||
environment:
|
environment:
|
||||||
- TZ=Asia/Shanghai
|
- TZ=Asia/Shanghai
|
||||||
volumes:
|
volumes:
|
||||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||||
- ./prometheus-data:/prometheus
|
|
||||||
ports:
|
ports:
|
||||||
- "9090:9090"
|
- "9090:9090"
|
||||||
command:
|
|
||||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
|
||||||
- '--web.enable-otlp-receiver' # Enable OTLP
|
|
||||||
- '--web.enable-remote-write-receiver' # Enable remote write
|
|
||||||
- '--enable-feature=promql-experimental-functions' # Enable info()
|
|
||||||
- '--storage.tsdb.min-block-duration=15m' # Minimum block duration
|
|
||||||
- '--storage.tsdb.max-block-duration=1h' # Maximum block duration
|
|
||||||
- '--log.level=info'
|
|
||||||
- '--storage.tsdb.retention.time=30d'
|
|
||||||
- '--storage.tsdb.path=/prometheus'
|
|
||||||
- '--web.console.libraries=/usr/share/prometheus/console_libraries'
|
|
||||||
- '--web.console.templates=/usr/share/prometheus/consoles'
|
|
||||||
restart: unless-stopped
|
|
||||||
networks:
|
networks:
|
||||||
- otel-network
|
- otel-network
|
||||||
healthcheck:
|
|
||||||
test: [ "CMD", "wget", "--spider", "-q", "http://localhost:9090/-/healthy" ]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 3
|
|
||||||
loki:
|
loki:
|
||||||
image: grafana/loki:latest
|
image: grafana/loki:3.5.1
|
||||||
environment:
|
environment:
|
||||||
- TZ=Asia/Shanghai
|
- TZ=Asia/Shanghai
|
||||||
volumes:
|
volumes:
|
||||||
- ./loki-config.yaml:/etc/loki/local-config.yaml:ro
|
- ./loki-config.yaml:/etc/loki/local-config.yaml
|
||||||
ports:
|
ports:
|
||||||
- "3100:3100"
|
- "3100:3100"
|
||||||
command: -config.file=/etc/loki/local-config.yaml
|
command: -config.file=/etc/loki/local-config.yaml
|
||||||
networks:
|
networks:
|
||||||
- otel-network
|
- otel-network
|
||||||
healthcheck:
|
|
||||||
test: [ "CMD", "wget", "--spider", "-q", "http://localhost:3100/ready" ]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 3
|
|
||||||
grafana:
|
grafana:
|
||||||
image: grafana/grafana:latest
|
image: grafana/grafana:12.0.1
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000" # Web UI
|
- "3000:3000" # Web UI
|
||||||
volumes:
|
|
||||||
- ./grafana-datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml
|
|
||||||
environment:
|
environment:
|
||||||
- GF_SECURITY_ADMIN_PASSWORD=admin
|
- GF_SECURITY_ADMIN_PASSWORD=admin
|
||||||
- GF_SECURITY_ADMIN_USER=admin
|
|
||||||
- TZ=Asia/Shanghai
|
- TZ=Asia/Shanghai
|
||||||
- GF_INSTALL_PLUGINS=grafana-pyroscope-datasource
|
|
||||||
restart: unless-stopped
|
|
||||||
networks:
|
networks:
|
||||||
- otel-network
|
- otel-network
|
||||||
depends_on:
|
|
||||||
- prometheus
|
|
||||||
- tempo
|
|
||||||
- loki
|
|
||||||
healthcheck:
|
|
||||||
test: [ "CMD", "wget", "--spider", "-q", "http://localhost:3000/api/health" ]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 3
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
prometheus-data:
|
|
||||||
tempo-data:
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
otel-network:
|
otel-network:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
name: "network_otel_config"
|
name: "network_otel_config"
|
||||||
ipam:
|
|
||||||
config:
|
|
||||||
- subnet: 172.28.0.0/16
|
|
||||||
driver_opts:
|
driver_opts:
|
||||||
com.docker.network.enable_ipv6: "true"
|
com.docker.network.enable_ipv6: "true"
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
apiVersion: 1
|
|
||||||
|
|
||||||
datasources:
|
|
||||||
- name: Prometheus
|
|
||||||
type: prometheus
|
|
||||||
uid: prometheus
|
|
||||||
access: proxy
|
|
||||||
orgId: 1
|
|
||||||
url: http://prometheus:9090
|
|
||||||
basicAuth: false
|
|
||||||
isDefault: false
|
|
||||||
version: 1
|
|
||||||
editable: false
|
|
||||||
jsonData:
|
|
||||||
httpMethod: GET
|
|
||||||
- name: Tempo
|
|
||||||
type: tempo
|
|
||||||
access: proxy
|
|
||||||
orgId: 1
|
|
||||||
url: http://tempo:3200
|
|
||||||
basicAuth: false
|
|
||||||
isDefault: true
|
|
||||||
version: 1
|
|
||||||
editable: false
|
|
||||||
apiVersion: 1
|
|
||||||
uid: tempo
|
|
||||||
jsonData:
|
|
||||||
httpMethod: GET
|
|
||||||
serviceMap:
|
|
||||||
datasourceUid: prometheus
|
|
||||||
streamingEnabled:
|
|
||||||
search: true
|
|
||||||
tracesToLogsV2:
|
|
||||||
# Field with an internal link pointing to a logs data source in Grafana.
|
|
||||||
# datasourceUid value must match the uid value of the logs data source.
|
|
||||||
datasourceUid: 'loki'
|
|
||||||
spanStartTimeShift: '-1h'
|
|
||||||
spanEndTimeShift: '1h'
|
|
||||||
tags: [ 'job', 'instance', 'pod', 'namespace' ]
|
|
||||||
filterByTraceID: false
|
|
||||||
filterBySpanID: false
|
|
||||||
customQuery: true
|
|
||||||
query: 'method="$${__span.tags.method}"'
|
|
||||||
tracesToMetrics:
|
|
||||||
datasourceUid: 'prometheus'
|
|
||||||
spanStartTimeShift: '-1h'
|
|
||||||
spanEndTimeShift: '1h'
|
|
||||||
tags: [ { key: 'service.name', value: 'service' }, { key: 'job' } ]
|
|
||||||
queries:
|
|
||||||
- name: 'Sample query'
|
|
||||||
query: 'sum(rate(traces_spanmetrics_latency_bucket{$$__tags}[5m]))'
|
|
||||||
tracesToProfiles:
|
|
||||||
datasourceUid: 'grafana-pyroscope-datasource'
|
|
||||||
tags: [ 'job', 'instance', 'pod', 'namespace' ]
|
|
||||||
profileTypeId: 'process_cpu:cpu:nanoseconds:cpu:nanoseconds'
|
|
||||||
customQuery: true
|
|
||||||
query: 'method="$${__span.tags.method}"'
|
|
||||||
serviceMap:
|
|
||||||
datasourceUid: 'prometheus'
|
|
||||||
nodeGraph:
|
|
||||||
enabled: true
|
|
||||||
search:
|
|
||||||
hide: false
|
|
||||||
traceQuery:
|
|
||||||
timeShiftEnabled: true
|
|
||||||
spanStartTimeShift: '-1h'
|
|
||||||
spanEndTimeShift: '1h'
|
|
||||||
spanBar:
|
|
||||||
type: 'Tag'
|
|
||||||
tag: 'http.path'
|
|
||||||
streamingEnabled:
|
|
||||||
search: true
|
|
||||||
- name: Jaeger
|
|
||||||
type: jaeger
|
|
||||||
uid: Jaeger
|
|
||||||
url: http://jaeger:16686
|
|
||||||
basicAuth: false
|
|
||||||
access: proxy
|
|
||||||
readOnly: false
|
|
||||||
isDefault: false
|
|
||||||
jsonData:
|
|
||||||
tracesToLogsV2:
|
|
||||||
# Field with an internal link pointing to a logs data source in Grafana.
|
|
||||||
# datasourceUid value must match the uid value of the logs data source.
|
|
||||||
datasourceUid: 'loki'
|
|
||||||
spanStartTimeShift: '1h'
|
|
||||||
spanEndTimeShift: '-1h'
|
|
||||||
tags: [ 'job', 'instance', 'pod', 'namespace' ]
|
|
||||||
filterByTraceID: false
|
|
||||||
filterBySpanID: false
|
|
||||||
customQuery: true
|
|
||||||
query: 'method="$${__span.tags.method}"'
|
|
||||||
tracesToMetrics:
|
|
||||||
datasourceUid: 'Prometheus'
|
|
||||||
spanStartTimeShift: '1h'
|
|
||||||
spanEndTimeShift: '-1h'
|
|
||||||
tags: [ { key: 'service.name', value: 'service' }, { key: 'job' } ]
|
|
||||||
queries:
|
|
||||||
- name: 'Sample query'
|
|
||||||
query: 'sum(rate(traces_spanmetrics_latency_bucket{$$__tags}[5m]))'
|
|
||||||
nodeGraph:
|
|
||||||
enabled: true
|
|
||||||
traceQuery:
|
|
||||||
timeShiftEnabled: true
|
|
||||||
spanStartTimeShift: '1h'
|
|
||||||
spanEndTimeShift: '-1h'
|
|
||||||
spanBar:
|
|
||||||
type: 'None'
|
|
||||||
@@ -1,17 +1,3 @@
|
|||||||
# Copyright 2024 RustFS Team
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
service:
|
service:
|
||||||
extensions: [ jaeger_storage, jaeger_query, remote_sampling, healthcheckv2 ]
|
extensions: [ jaeger_storage, jaeger_query, remote_sampling, healthcheckv2 ]
|
||||||
pipelines:
|
pipelines:
|
||||||
@@ -64,11 +50,10 @@ extensions:
|
|||||||
backends:
|
backends:
|
||||||
some_store:
|
some_store:
|
||||||
memory:
|
memory:
|
||||||
max_traces: 1000000
|
max_traces: 100000
|
||||||
max_events: 100000
|
|
||||||
another_store:
|
another_store:
|
||||||
memory:
|
memory:
|
||||||
max_traces: 1000000
|
max_traces: 100000
|
||||||
metric_backends:
|
metric_backends:
|
||||||
some_metrics_storage:
|
some_metrics_storage:
|
||||||
prometheus:
|
prometheus:
|
||||||
@@ -103,7 +88,6 @@ receivers:
|
|||||||
|
|
||||||
processors:
|
processors:
|
||||||
batch:
|
batch:
|
||||||
metadata_keys: [ "span.kind", "http.method", "http.status_code", "db.system", "db.statement", "messaging.system", "messaging.destination", "messaging.operation","span.events","span.links" ]
|
|
||||||
# Adaptive Sampling Processor is required to support adaptive sampling.
|
# Adaptive Sampling Processor is required to support adaptive sampling.
|
||||||
# It expects remote_sampling extension with `adaptive:` config to be enabled.
|
# It expects remote_sampling extension with `adaptive:` config to be enabled.
|
||||||
adaptive_sampling:
|
adaptive_sampling:
|
||||||
|
|||||||
@@ -1,17 +1,3 @@
|
|||||||
# Copyright 2024 RustFS Team
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
auth_enabled: false
|
auth_enabled: false
|
||||||
|
|
||||||
server:
|
server:
|
||||||
@@ -41,9 +27,6 @@ query_range:
|
|||||||
|
|
||||||
limits_config:
|
limits_config:
|
||||||
metric_aggregation_enabled: true
|
metric_aggregation_enabled: true
|
||||||
max_line_size: 256KB
|
|
||||||
max_line_size_truncate: false
|
|
||||||
allow_structured_metadata: true
|
|
||||||
|
|
||||||
schema_config:
|
schema_config:
|
||||||
configs:
|
configs:
|
||||||
@@ -54,7 +37,6 @@ schema_config:
|
|||||||
index:
|
index:
|
||||||
prefix: index_
|
prefix: index_
|
||||||
period: 24h
|
period: 24h
|
||||||
row_shards: 16
|
|
||||||
|
|
||||||
pattern_ingester:
|
pattern_ingester:
|
||||||
enabled: true
|
enabled: true
|
||||||
@@ -67,7 +49,6 @@ ruler:
|
|||||||
frontend:
|
frontend:
|
||||||
encoding: protobuf
|
encoding: protobuf
|
||||||
|
|
||||||
|
|
||||||
# By default, Loki will send anonymous, but uniquely-identifiable usage and configuration
|
# By default, Loki will send anonymous, but uniquely-identifiable usage and configuration
|
||||||
# analytics to Grafana Labs. These statistics are sent to https://stats.grafana.org/
|
# analytics to Grafana Labs. These statistics are sent to https://stats.grafana.org/
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,122 +1,57 @@
|
|||||||
# Copyright 2024 RustFS Team
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
receivers:
|
receivers:
|
||||||
otlp:
|
otlp:
|
||||||
protocols:
|
protocols:
|
||||||
grpc: # OTLP gRPC receiver
|
grpc: # OTLP gRPC 接收器
|
||||||
endpoint: 0.0.0.0:4317
|
endpoint: 0.0.0.0:4317
|
||||||
http: # OTLP HTTP receiver
|
http: # OTLP HTTP 接收器
|
||||||
endpoint: 0.0.0.0:4318
|
endpoint: 0.0.0.0:4318
|
||||||
|
|
||||||
processors:
|
processors:
|
||||||
batch: # Batch processor to improve throughput
|
batch: # 批处理处理器,提升吞吐量
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
send_batch_size: 1000
|
send_batch_size: 1000
|
||||||
metadata_keys: [ ]
|
|
||||||
metadata_cardinality_limit: 1000
|
|
||||||
memory_limiter:
|
memory_limiter:
|
||||||
check_interval: 1s
|
check_interval: 1s
|
||||||
limit_mib: 512
|
limit_mib: 512
|
||||||
transform/logs:
|
|
||||||
log_statements:
|
|
||||||
- context: log
|
|
||||||
statements:
|
|
||||||
# Extract Body as attribute "message"
|
|
||||||
- set(attributes["message"], body.string)
|
|
||||||
# Retain the original Body
|
|
||||||
- set(attributes["log.body"], body.string)
|
|
||||||
|
|
||||||
exporters:
|
exporters:
|
||||||
otlp/traces: # OTLP exporter for trace data
|
otlp/traces: # OTLP 导出器,用于跟踪数据
|
||||||
endpoint: "http://jaeger:4317" # OTLP gRPC endpoint for Jaeger
|
endpoint: "jaeger:4317" # Jaeger 的 OTLP gRPC 端点
|
||||||
tls:
|
tls:
|
||||||
insecure: true # TLS is disabled in the development environment and a certificate needs to be configured in the production environment.
|
insecure: true # 开发环境禁用 TLS,生产环境需配置证书
|
||||||
compression: gzip # Enable compression to reduce network bandwidth
|
prometheus: # Prometheus 导出器,用于指标数据
|
||||||
retry_on_failure:
|
endpoint: "0.0.0.0:8889" # Prometheus 刮取端点
|
||||||
enabled: true # Enable retry on failure
|
namespace: "rustfs" # 指标前缀
|
||||||
initial_interval: 1s # Initial interval for retry
|
send_timestamps: true # 发送时间戳
|
||||||
max_interval: 30s # Maximum interval for retry
|
# enable_open_metrics: true
|
||||||
max_elapsed_time: 300s # Maximum elapsed time for retry
|
loki: # Loki 导出器,用于日志数据
|
||||||
sending_queue:
|
# endpoint: "http://loki:3100/otlp/v1/logs"
|
||||||
enabled: true # Enable sending queue
|
endpoint: "http://loki:3100/loki/api/v1/push"
|
||||||
num_consumers: 10 # Number of consumers
|
|
||||||
queue_size: 5000 # Queue size
|
|
||||||
otlp/tempo: # OTLP exporter for trace data
|
|
||||||
endpoint: "http://tempo:4317" # OTLP gRPC endpoint for tempo
|
|
||||||
tls:
|
|
||||||
insecure: true # TLS is disabled in the development environment and a certificate needs to be configured in the production environment.
|
|
||||||
compression: gzip # Enable compression to reduce network bandwidth
|
|
||||||
retry_on_failure:
|
|
||||||
enabled: true # Enable retry on failure
|
|
||||||
initial_interval: 1s # Initial interval for retry
|
|
||||||
max_interval: 30s # Maximum interval for retry
|
|
||||||
max_elapsed_time: 300s # Maximum elapsed time for retry
|
|
||||||
sending_queue:
|
|
||||||
enabled: true # Enable sending queue
|
|
||||||
num_consumers: 10 # Number of consumers
|
|
||||||
queue_size: 5000 # Queue size
|
|
||||||
prometheus: # Prometheus exporter for metrics data
|
|
||||||
endpoint: "0.0.0.0:8889" # Prometheus scraping endpoint
|
|
||||||
namespace: "metrics" # indicator prefix
|
|
||||||
send_timestamps: true # Send timestamp
|
|
||||||
metric_expiration: 5m # Metric expiration time
|
|
||||||
resource_to_telemetry_conversion:
|
|
||||||
enabled: true # Enable resource to telemetry conversion
|
|
||||||
otlphttp/loki: # Loki exporter for log data
|
|
||||||
endpoint: "http://loki:3100/otlp"
|
|
||||||
tls:
|
tls:
|
||||||
insecure: true
|
insecure: true
|
||||||
compression: gzip # Enable compression to reduce network bandwidth
|
|
||||||
extensions:
|
extensions:
|
||||||
health_check:
|
health_check:
|
||||||
endpoint: 0.0.0.0:13133
|
|
||||||
pprof:
|
pprof:
|
||||||
endpoint: 0.0.0.0:1888
|
|
||||||
zpages:
|
zpages:
|
||||||
endpoint: 0.0.0.0:55679
|
|
||||||
service:
|
service:
|
||||||
extensions: [ health_check, pprof, zpages ] # Enable extension
|
extensions: [ health_check, pprof, zpages ] # 启用扩展
|
||||||
pipelines:
|
pipelines:
|
||||||
traces:
|
traces:
|
||||||
receivers: [ otlp ]
|
receivers: [ otlp ]
|
||||||
processors: [ memory_limiter, batch ]
|
processors: [ memory_limiter,batch ]
|
||||||
exporters: [ otlp/traces, otlp/tempo ]
|
exporters: [ otlp/traces ]
|
||||||
metrics:
|
metrics:
|
||||||
receivers: [ otlp ]
|
receivers: [ otlp ]
|
||||||
processors: [ batch ]
|
processors: [ batch ]
|
||||||
exporters: [ prometheus ]
|
exporters: [ prometheus ]
|
||||||
logs:
|
logs:
|
||||||
receivers: [ otlp ]
|
receivers: [ otlp ]
|
||||||
processors: [ batch, transform/logs ]
|
processors: [ batch ]
|
||||||
exporters: [ otlphttp/loki ]
|
exporters: [ loki ]
|
||||||
telemetry:
|
telemetry:
|
||||||
logs:
|
logs:
|
||||||
level: "debug" # Collector log level
|
level: "info" # Collector 日志级别
|
||||||
encoding: "json" # Log encoding: console or json
|
|
||||||
metrics:
|
metrics:
|
||||||
level: "detailed" # Can be basic, normal, detailed
|
address: "0.0.0.0:8888" # Collector 自身指标暴露
|
||||||
readers:
|
|
||||||
- periodic:
|
|
||||||
exporter:
|
|
||||||
otlp:
|
|
||||||
protocol: http/protobuf
|
|
||||||
endpoint: http://otel-collector:4318
|
|
||||||
- pull:
|
|
||||||
exporter:
|
|
||||||
prometheus:
|
|
||||||
host: '0.0.0.0'
|
|
||||||
port: 8888
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
*
|
|
||||||
@@ -1,65 +1,11 @@
|
|||||||
# Copyright 2024 RustFS Team
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
global:
|
global:
|
||||||
scrape_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
|
scrape_interval: 5s # 刮取间隔
|
||||||
evaluation_interval: 15s
|
|
||||||
external_labels:
|
|
||||||
cluster: 'rustfs-dev' # Label to identify the cluster
|
|
||||||
relica: '1' # Replica identifier
|
|
||||||
|
|
||||||
scrape_configs:
|
scrape_configs:
|
||||||
- job_name: 'otel-collector-internal'
|
- job_name: 'otel-collector'
|
||||||
static_configs:
|
static_configs:
|
||||||
- targets: [ 'otel-collector:8888' ] # Scrape metrics from Collector
|
- targets: ['otel-collector:8888'] # 从 Collector 刮取指标
|
||||||
scrape_interval: 10s
|
- job_name: 'otel-metrics'
|
||||||
- job_name: 'rustfs-app-metrics'
|
|
||||||
static_configs:
|
static_configs:
|
||||||
- targets: [ 'otel-collector:8889' ] # Application indicators
|
- targets: ['otel-collector:8889'] # 应用指标
|
||||||
scrape_interval: 15s
|
|
||||||
metric_relabel_configs:
|
|
||||||
- job_name: 'tempo'
|
|
||||||
static_configs:
|
|
||||||
- targets: [ 'tempo:3200' ] # Scrape metrics from Tempo
|
|
||||||
- job_name: 'jaeger'
|
|
||||||
static_configs:
|
|
||||||
- targets: [ 'jaeger:8888' ] # Jaeger admin port
|
|
||||||
|
|
||||||
otlp:
|
|
||||||
# Recommended attributes to be promoted to labels.
|
|
||||||
promote_resource_attributes:
|
|
||||||
- service.instance.id
|
|
||||||
- service.name
|
|
||||||
- service.namespace
|
|
||||||
- cloud.availability_zone
|
|
||||||
- cloud.region
|
|
||||||
- container.name
|
|
||||||
- deployment.environment.name
|
|
||||||
- k8s.cluster.name
|
|
||||||
- k8s.container.name
|
|
||||||
- k8s.cronjob.name
|
|
||||||
- k8s.daemonset.name
|
|
||||||
- k8s.deployment.name
|
|
||||||
- k8s.job.name
|
|
||||||
- k8s.namespace.name
|
|
||||||
- k8s.pod.name
|
|
||||||
- k8s.replicaset.name
|
|
||||||
- k8s.statefulset.name
|
|
||||||
# Ingest OTLP data keeping all characters in metric/label names.
|
|
||||||
translation_strategy: NoUTF8EscapingWithSuffixes
|
|
||||||
|
|
||||||
storage:
|
|
||||||
# OTLP is a push-based protocol, Out of order samples is a common scenario.
|
|
||||||
tsdb:
|
|
||||||
out_of_order_time_window: 30m
|
|
||||||
|
|||||||
1
.docker/observability/tempo-data/.gitignore
vendored
1
.docker/observability/tempo-data/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
*
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
stream_over_http_enabled: true
|
|
||||||
server:
|
|
||||||
http_listen_port: 3200
|
|
||||||
log_level: info
|
|
||||||
|
|
||||||
query_frontend:
|
|
||||||
search:
|
|
||||||
duration_slo: 5s
|
|
||||||
throughput_bytes_slo: 1.073741824e+09
|
|
||||||
metadata_slo:
|
|
||||||
duration_slo: 5s
|
|
||||||
throughput_bytes_slo: 1.073741824e+09
|
|
||||||
trace_by_id:
|
|
||||||
duration_slo: 5s
|
|
||||||
|
|
||||||
distributor:
|
|
||||||
receivers:
|
|
||||||
otlp:
|
|
||||||
protocols:
|
|
||||||
grpc:
|
|
||||||
endpoint: "0.0.0.0:4317"
|
|
||||||
http:
|
|
||||||
endpoint: "0.0.0.0:4318"
|
|
||||||
|
|
||||||
ingester:
|
|
||||||
max_block_duration: 5m # cut the headblock when this much time passes. this is being set for demo purposes and should probably be left alone normally
|
|
||||||
|
|
||||||
compactor:
|
|
||||||
compaction:
|
|
||||||
block_retention: 1h # overall Tempo trace retention. set for demo purposes
|
|
||||||
|
|
||||||
metrics_generator:
|
|
||||||
registry:
|
|
||||||
external_labels:
|
|
||||||
source: tempo
|
|
||||||
cluster: docker-compose
|
|
||||||
storage:
|
|
||||||
path: /var/tempo/generator/wal
|
|
||||||
remote_write:
|
|
||||||
- url: http://prometheus:9090/api/v1/write
|
|
||||||
send_exemplars: true
|
|
||||||
traces_storage:
|
|
||||||
path: /var/tempo/generator/traces
|
|
||||||
|
|
||||||
storage:
|
|
||||||
trace:
|
|
||||||
backend: local # backend configuration to use
|
|
||||||
wal:
|
|
||||||
path: /var/tempo/wal # where to store the wal locally
|
|
||||||
local:
|
|
||||||
path: /var/tempo/blocks
|
|
||||||
|
|
||||||
overrides:
|
|
||||||
defaults:
|
|
||||||
metrics_generator:
|
|
||||||
processors: [ service-graphs, span-metrics, local-blocks ] # enables metrics generator
|
|
||||||
generate_native_histograms: both
|
|
||||||
@@ -1,17 +1,3 @@
|
|||||||
# Copyright 2024 RustFS Team
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
openobserve:
|
openobserve:
|
||||||
image: public.ecr.aws/zinclabs/openobserve:latest
|
image: public.ecr.aws/zinclabs/openobserve:latest
|
||||||
|
|||||||
@@ -1,17 +1,3 @@
|
|||||||
# Copyright 2024 RustFS Team
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
receivers:
|
receivers:
|
||||||
otlp:
|
otlp:
|
||||||
protocols:
|
protocols:
|
||||||
|
|||||||
71
.docker/vault-init.sh
Normal file
71
.docker/vault-init.sh
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# vault-init.sh - Initialize Vault for RustFS SSE-KMS
|
||||||
|
|
||||||
|
# Wait for Vault to start
|
||||||
|
until curl -s http://127.0.0.1:8200/v1/sys/health | grep "initialized" > /dev/null; do
|
||||||
|
echo "Waiting for Vault to start..."
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
# Set the Vault token
|
||||||
|
export VAULT_TOKEN="$VAULT_DEV_ROOT_TOKEN_ID"
|
||||||
|
export VAULT_ADDR="http://127.0.0.1:8200"
|
||||||
|
|
||||||
|
echo "Vault is running and initialized"
|
||||||
|
|
||||||
|
# Enable the Transit secrets engine (for encryption operations)
|
||||||
|
vault secrets enable transit
|
||||||
|
echo "Transit secrets engine enabled"
|
||||||
|
|
||||||
|
# Create a key for RustFS encryption
|
||||||
|
vault write -f transit/keys/rustfs-encryption-key
|
||||||
|
echo "Created rustfs-encryption-key"
|
||||||
|
|
||||||
|
# Create another key for RustFS with rotation capability
|
||||||
|
vault write -f transit/keys/rustfs-rotating-key
|
||||||
|
echo "Created rustfs-rotating-key"
|
||||||
|
|
||||||
|
# Set up key rotation policy
|
||||||
|
vault write transit/keys/rustfs-rotating-key/config auto_rotate_period="30d"
|
||||||
|
echo "Set up auto rotation for rustfs-rotating-key"
|
||||||
|
|
||||||
|
# Create a policy for RustFS to access these keys
|
||||||
|
cat > /tmp/rustfs-policy.hcl << EOF
|
||||||
|
# Policy for RustFS encryption operations
|
||||||
|
path "transit/encrypt/rustfs-encryption-key" {
|
||||||
|
capabilities = ["create", "update"]
|
||||||
|
}
|
||||||
|
|
||||||
|
path "transit/decrypt/rustfs-encryption-key" {
|
||||||
|
capabilities = ["create", "update"]
|
||||||
|
}
|
||||||
|
|
||||||
|
path "transit/encrypt/rustfs-rotating-key" {
|
||||||
|
capabilities = ["create", "update"]
|
||||||
|
}
|
||||||
|
|
||||||
|
path "transit/decrypt/rustfs-rotating-key" {
|
||||||
|
capabilities = ["create", "update"]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create the policy
|
||||||
|
vault policy write rustfs-encryption-policy /tmp/rustfs-policy.hcl
|
||||||
|
echo "Created rustfs-encryption-policy"
|
||||||
|
|
||||||
|
# Create a token for RustFS to use
|
||||||
|
RUSTFS_TOKEN=$(vault token create -policy=rustfs-encryption-policy -field=token)
|
||||||
|
echo "Created token for RustFS: $RUSTFS_TOKEN"
|
||||||
|
|
||||||
|
# Store the token for RustFS to use
|
||||||
|
echo "RUSTFS_KMS_VAULT_TOKEN=$RUSTFS_TOKEN" > /vault/config/rustfs-kms.env
|
||||||
|
echo "RUSTFS_KMS_VAULT_ENDPOINT=http://rustyvault:8200" >> /vault/config/rustfs-kms.env
|
||||||
|
echo "RUSTFS_KMS_VAULT_KEY_NAME=rustfs-encryption-key" >> /vault/config/rustfs-kms.env
|
||||||
|
|
||||||
|
echo "RustFS KMS configuration has been created"
|
||||||
|
echo "============================================"
|
||||||
|
echo "Vault is ready for use with RustFS SSE-KMS"
|
||||||
|
echo "============================================"
|
||||||
|
|
||||||
|
# Keep the container running
|
||||||
|
tail -f /dev/null
|
||||||
@@ -1 +0,0 @@
|
|||||||
target
|
|
||||||
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,38 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
**Desktop (please complete the following information):**
|
|
||||||
- OS: [e.g. iOS]
|
|
||||||
- Browser [e.g. chrome, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Smartphone (please complete the following information):**
|
|
||||||
- Device: [e.g. iPhone6]
|
|
||||||
- OS: [e.g. iOS8.1]
|
|
||||||
- Browser [e.g. stock browser, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
||||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
||||||
95
.github/actions/setup/action.yml
vendored
95
.github/actions/setup/action.yml
vendored
@@ -1,93 +1,54 @@
|
|||||||
# Copyright 2024 RustFS Team
|
name: "setup"
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
name: "Setup Rust Environment"
|
description: "setup environment for rustfs"
|
||||||
description: "Setup Rust development environment with caching for RustFS"
|
|
||||||
|
|
||||||
inputs:
|
inputs:
|
||||||
rust-version:
|
rust-version:
|
||||||
description: "Rust version to install"
|
required: true
|
||||||
required: false
|
|
||||||
default: "stable"
|
default: "stable"
|
||||||
|
description: "Rust version to use"
|
||||||
cache-shared-key:
|
cache-shared-key:
|
||||||
description: "Shared cache key for Rust dependencies"
|
required: true
|
||||||
required: false
|
default: ""
|
||||||
default: "rustfs-deps"
|
description: "Cache key for shared cache"
|
||||||
cache-save-if:
|
cache-save-if:
|
||||||
description: "Condition for saving cache"
|
required: true
|
||||||
required: false
|
default: true
|
||||||
default: "true"
|
description: "Cache save condition"
|
||||||
install-cross-tools:
|
run-os:
|
||||||
description: "Install cross-compilation tools"
|
required: true
|
||||||
required: false
|
default: "ubuntu-latest"
|
||||||
default: "false"
|
description: "Running system"
|
||||||
target:
|
|
||||||
description: "Target architecture to add"
|
|
||||||
required: false
|
|
||||||
default: ""
|
|
||||||
github-token:
|
|
||||||
description: "GitHub token for API access"
|
|
||||||
required: false
|
|
||||||
default: ""
|
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- name: Install system dependencies (Ubuntu)
|
- name: Install system dependencies
|
||||||
if: runner.os == 'Linux'
|
if: inputs.run-os == 'ubuntu-latest'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt update
|
||||||
sudo apt-get install -y \
|
sudo apt install -y musl-tools build-essential lld libdbus-1-dev libwayland-dev libwebkit2gtk-4.1-dev libxdo-dev
|
||||||
musl-tools \
|
|
||||||
build-essential \
|
|
||||||
pkg-config \
|
|
||||||
libssl-dev
|
|
||||||
|
|
||||||
- name: Install protoc
|
- uses: arduino/setup-protoc@v3
|
||||||
uses: arduino/setup-protoc@v3
|
|
||||||
with:
|
with:
|
||||||
version: "33.1"
|
version: "30.2"
|
||||||
repo-token: ${{ inputs.github-token }}
|
|
||||||
|
|
||||||
- name: Install flatc
|
- uses: Nugine/setup-flatc@v1
|
||||||
uses: Nugine/setup-flatc@v1
|
|
||||||
with:
|
with:
|
||||||
version: "25.9.23"
|
version: "24.3.25"
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- uses: dtolnay/rust-toolchain@master
|
||||||
uses: dtolnay/rust-toolchain@stable
|
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ inputs.rust-version }}
|
toolchain: ${{ inputs.rust-version }}
|
||||||
targets: ${{ inputs.target }}
|
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
|
|
||||||
- name: Install Zig
|
- uses: Swatinem/rust-cache@v2
|
||||||
if: inputs.install-cross-tools == 'true'
|
|
||||||
uses: mlugg/setup-zig@v2
|
|
||||||
|
|
||||||
- name: Install cargo-zigbuild
|
|
||||||
if: inputs.install-cross-tools == 'true'
|
|
||||||
uses: taiki-e/install-action@cargo-zigbuild
|
|
||||||
|
|
||||||
- name: Install cargo-nextest
|
|
||||||
uses: taiki-e/install-action@cargo-nextest
|
|
||||||
|
|
||||||
- name: Setup Rust cache
|
|
||||||
uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
with:
|
||||||
cache-all-crates: true
|
|
||||||
cache-on-failure: true
|
cache-on-failure: true
|
||||||
|
cache-all-crates: true
|
||||||
shared-key: ${{ inputs.cache-shared-key }}
|
shared-key: ${{ inputs.cache-shared-key }}
|
||||||
save-if: ${{ inputs.cache-save-if }}
|
save-if: ${{ inputs.cache-save-if }}
|
||||||
|
|
||||||
|
- uses: mlugg/setup-zig@v2
|
||||||
|
- uses: taiki-e/install-action@cargo-zigbuild
|
||||||
|
|||||||
26
.github/dependabot.yml
vendored
26
.github/dependabot.yml
vendored
@@ -1,17 +1,3 @@
|
|||||||
# Copyright 2024 RustFS Team
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
# To get started with Dependabot version updates, you'll need to specify which
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
# package ecosystems to update and where the package manifests are located.
|
# package ecosystems to update and where the package manifests are located.
|
||||||
# Please see the documentation for all configuration options:
|
# Please see the documentation for all configuration options:
|
||||||
@@ -22,18 +8,8 @@ updates:
|
|||||||
- package-ecosystem: "cargo" # See documentation for possible values
|
- package-ecosystem: "cargo" # See documentation for possible values
|
||||||
directory: "/" # Location of package manifests
|
directory: "/" # Location of package manifests
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "monthly"
|
||||||
day: "monday"
|
|
||||||
timezone: "Asia/Shanghai"
|
|
||||||
time: "08:00"
|
|
||||||
groups:
|
groups:
|
||||||
s3s:
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
patterns:
|
|
||||||
- "s3s"
|
|
||||||
- "s3s-*"
|
|
||||||
dependencies:
|
dependencies:
|
||||||
patterns:
|
patterns:
|
||||||
- "*"
|
- "*"
|
||||||
|
|||||||
37
.github/pull_request_template.md
vendored
37
.github/pull_request_template.md
vendored
@@ -1,37 +0,0 @@
|
|||||||
<!--
|
|
||||||
Pull Request Template for RustFS
|
|
||||||
-->
|
|
||||||
|
|
||||||
## Type of Change
|
|
||||||
- [ ] New Feature
|
|
||||||
- [ ] Bug Fix
|
|
||||||
- [ ] Documentation
|
|
||||||
- [ ] Performance Improvement
|
|
||||||
- [ ] Test/CI
|
|
||||||
- [ ] Refactor
|
|
||||||
- [ ] Other:
|
|
||||||
|
|
||||||
## Related Issues
|
|
||||||
<!-- List related Issue numbers, e.g. #123 -->
|
|
||||||
|
|
||||||
## Summary of Changes
|
|
||||||
<!-- Briefly describe the main changes and motivation for this PR -->
|
|
||||||
|
|
||||||
## Checklist
|
|
||||||
- [ ] I have read and followed the [CONTRIBUTING.md](CONTRIBUTING.md) guidelines
|
|
||||||
- [ ] Passed `make pre-commit`
|
|
||||||
- [ ] Added/updated necessary tests
|
|
||||||
- [ ] Documentation updated (if needed)
|
|
||||||
- [ ] CI/CD passed (if applicable)
|
|
||||||
|
|
||||||
## Impact
|
|
||||||
- [ ] Breaking change (compatibility)
|
|
||||||
- [ ] Requires doc/config/deployment update
|
|
||||||
- [ ] Other impact:
|
|
||||||
|
|
||||||
## Additional Notes
|
|
||||||
<!-- Any extra information for reviewers -->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Thank you for your contribution! Please ensure your PR follows the community standards ([CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)) and sign the CLA if this is your first contribution.
|
|
||||||
80
.github/workflows/audit.yml
vendored
80
.github/workflows/audit.yml
vendored
@@ -1,81 +1,25 @@
|
|||||||
# Copyright 2024 RustFS Team
|
name: Audit
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
name: Security Audit
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches:
|
||||||
paths:
|
- main
|
||||||
|
paths:
|
||||||
- '**/Cargo.toml'
|
- '**/Cargo.toml'
|
||||||
- '**/Cargo.lock'
|
- '**/Cargo.lock'
|
||||||
- '.github/workflows/audit.yml'
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main ]
|
branches:
|
||||||
paths:
|
- main
|
||||||
|
paths:
|
||||||
- '**/Cargo.toml'
|
- '**/Cargo.toml'
|
||||||
- '**/Cargo.lock'
|
- '**/Cargo.lock'
|
||||||
- '.github/workflows/audit.yml'
|
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 0 * * 0' # Weekly on Sunday at midnight UTC
|
- cron: '0 0 * * 0' # at midnight of each sunday
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
env:
|
|
||||||
CARGO_TERM_COLOR: always
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
security-audit:
|
audit:
|
||||||
name: Security Audit
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 15
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v5
|
- uses: taiki-e/install-action@cargo-audit
|
||||||
|
- run: cargo audit -D warnings
|
||||||
- name: Install cargo-audit
|
|
||||||
uses: taiki-e/install-action@v2
|
|
||||||
with:
|
|
||||||
tool: cargo-audit
|
|
||||||
|
|
||||||
- name: Run security audit
|
|
||||||
run: |
|
|
||||||
cargo audit -D warnings --json | tee audit-results.json
|
|
||||||
|
|
||||||
- name: Upload audit results
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: security-audit-results-${{ github.run_number }}
|
|
||||||
path: audit-results.json
|
|
||||||
retention-days: 30
|
|
||||||
|
|
||||||
dependency-review:
|
|
||||||
name: Dependency Review
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: Dependency Review
|
|
||||||
uses: actions/dependency-review-action@v4
|
|
||||||
with:
|
|
||||||
fail-on-severity: moderate
|
|
||||||
comment-summary-in-pr: true
|
|
||||||
|
|||||||
1140
.github/workflows/build.yml
vendored
1140
.github/workflows/build.yml
vendored
File diff suppressed because it is too large
Load Diff
207
.github/workflows/ci.yml
vendored
207
.github/workflows/ci.yml
vendored
@@ -1,74 +1,21 @@
|
|||||||
# Copyright 2024 RustFS Team
|
name: CI
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
name: Continuous Integration
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches:
|
||||||
paths-ignore:
|
- main
|
||||||
- "**.md"
|
|
||||||
- "**.txt"
|
|
||||||
- "docs/**"
|
|
||||||
- "deploy/**"
|
|
||||||
- "scripts/dev_*.sh"
|
|
||||||
- "scripts/probe.sh"
|
|
||||||
- "LICENSE*"
|
|
||||||
- ".gitignore"
|
|
||||||
- ".dockerignore"
|
|
||||||
- "README*"
|
|
||||||
- "**/*.png"
|
|
||||||
- "**/*.jpg"
|
|
||||||
- "**/*.svg"
|
|
||||||
- ".github/workflows/build.yml"
|
|
||||||
- ".github/workflows/docker.yml"
|
|
||||||
- ".github/workflows/audit.yml"
|
|
||||||
- ".github/workflows/performance.yml"
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main ]
|
branches:
|
||||||
paths-ignore:
|
- main
|
||||||
- "**.md"
|
|
||||||
- "**.txt"
|
|
||||||
- "docs/**"
|
|
||||||
- "deploy/**"
|
|
||||||
- "scripts/dev_*.sh"
|
|
||||||
- "scripts/probe.sh"
|
|
||||||
- "LICENSE*"
|
|
||||||
- ".gitignore"
|
|
||||||
- ".dockerignore"
|
|
||||||
- "README*"
|
|
||||||
- "**/*.png"
|
|
||||||
- "**/*.jpg"
|
|
||||||
- "**/*.svg"
|
|
||||||
- ".github/workflows/build.yml"
|
|
||||||
- ".github/workflows/docker.yml"
|
|
||||||
- ".github/workflows/audit.yml"
|
|
||||||
- ".github/workflows/performance.yml"
|
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 0 * * 0" # Weekly on Sunday at midnight UTC
|
- cron: '0 0 * * 0' # at midnight of each sunday
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
RUST_BACKTRACE: 1
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
skip-check:
|
skip-check:
|
||||||
name: Skip Duplicate Actions
|
|
||||||
permissions:
|
permissions:
|
||||||
actions: write
|
actions: write
|
||||||
contents: read
|
contents: read
|
||||||
@@ -76,96 +23,100 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
||||||
steps:
|
steps:
|
||||||
- name: Skip duplicate actions
|
- id: skip_check
|
||||||
id: skip_check
|
|
||||||
uses: fkirc/skip-duplicate-actions@v5
|
uses: fkirc/skip-duplicate-actions@v5
|
||||||
with:
|
with:
|
||||||
concurrent_skipping: "same_content_newer"
|
concurrent_skipping: 'same_content_newer'
|
||||||
cancel_others: true
|
cancel_others: true
|
||||||
paths_ignore: '["*.md", "docs/**", "deploy/**"]'
|
paths_ignore: '["*.md"]'
|
||||||
# Never skip release events and tag pushes
|
|
||||||
do_not_skip: '["workflow_dispatch", "schedule", "merge_group", "release", "push"]'
|
|
||||||
|
|
||||||
|
# Quality checks for pull requests
|
||||||
typos:
|
pr-checks:
|
||||||
name: Typos
|
name: Pull Request Quality Checks
|
||||||
runs-on: ubuntu-latest
|
if: github.event_name == 'pull_request'
|
||||||
|
runs-on: self-hosted
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: ./.github/actions/setup
|
||||||
- name: Typos check with custom config file
|
|
||||||
uses: crate-ci/typos@master
|
|
||||||
|
|
||||||
test-and-lint:
|
- name: Format Check
|
||||||
name: Test and Lint
|
|
||||||
needs: skip-check
|
|
||||||
if: needs.skip-check.outputs.should_skip != 'true'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 60
|
|
||||||
steps:
|
|
||||||
- name: Delete huge unnecessary tools folder
|
|
||||||
run: rm -rf /opt/hostedtoolcache
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: Setup Rust environment
|
|
||||||
uses: ./.github/actions/setup
|
|
||||||
with:
|
|
||||||
rust-version: stable
|
|
||||||
cache-shared-key: ci-test-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
cache-save-if: ${{ github.ref == 'refs/heads/main' }}
|
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
run: |
|
|
||||||
cargo nextest run --all --exclude e2e_test
|
|
||||||
cargo test --all --doc
|
|
||||||
|
|
||||||
- name: Check code formatting
|
|
||||||
run: cargo fmt --all --check
|
run: cargo fmt --all --check
|
||||||
|
|
||||||
- name: Run clippy lints
|
- name: Lint Check
|
||||||
|
run: cargo check --all-targets
|
||||||
|
|
||||||
|
- name: Clippy Check
|
||||||
run: cargo clippy --all-targets --all-features -- -D warnings
|
run: cargo clippy --all-targets --all-features -- -D warnings
|
||||||
|
|
||||||
e2e-tests:
|
- name: Unit Tests
|
||||||
name: End-to-End Tests
|
run: cargo test --all --exclude e2e_test
|
||||||
|
|
||||||
|
develop:
|
||||||
needs: skip-check
|
needs: skip-check
|
||||||
if: needs.skip-check.outputs.should_skip != 'true'
|
if: needs.skip-check.outputs.should_skip != 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: self-hosted
|
||||||
timeout-minutes: 30
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v5
|
- uses: ./.github/actions/setup
|
||||||
|
|
||||||
- name: Setup Rust environment
|
- name: Format
|
||||||
uses: ./.github/actions/setup
|
run: cargo fmt --all --check
|
||||||
with:
|
|
||||||
rust-version: stable
|
|
||||||
cache-shared-key: ci-e2e-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
cache-save-if: ${{ github.ref == 'refs/heads/main' }}
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Install s3s-e2e test tool
|
- name: Lint
|
||||||
uses: taiki-e/cache-cargo-install-action@v2
|
run: cargo check --all-targets
|
||||||
with:
|
|
||||||
tool: s3s-e2e
|
|
||||||
git: https://github.com/Nugine/s3s.git
|
|
||||||
rev: b7714bfaa17ddfa9b23ea01774a1e7bbdbfc2ca3
|
|
||||||
|
|
||||||
- name: Build debug binary
|
- name: Clippy
|
||||||
|
run: cargo clippy --all-targets --all-features -- -D warnings
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: cargo test --all --exclude e2e_test
|
||||||
|
|
||||||
|
- name: Build debug
|
||||||
run: |
|
run: |
|
||||||
touch rustfs/build.rs
|
touch rustfs/build.rs
|
||||||
cargo build -p rustfs --bins
|
cargo build -p rustfs --bins
|
||||||
|
|
||||||
- name: Run end-to-end tests
|
- name: Pack artifacts
|
||||||
run: |
|
run: |
|
||||||
s3s-e2e --version
|
mkdir -p ./target/artifacts
|
||||||
./scripts/e2e-run.sh ./target/debug/rustfs /tmp/rustfs
|
cp target/debug/rustfs ./target/artifacts/rustfs-debug
|
||||||
|
|
||||||
- name: Upload test logs
|
- uses: actions/upload-artifact@v4
|
||||||
if: failure()
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
with:
|
||||||
name: e2e-test-logs-${{ github.run_number }}
|
name: rustfs
|
||||||
|
path: ./target/artifacts/*
|
||||||
|
|
||||||
|
s3s-e2e:
|
||||||
|
name: E2E (s3s-e2e)
|
||||||
|
needs:
|
||||||
|
- skip-check
|
||||||
|
- develop
|
||||||
|
if: needs.skip-check.outputs.should_skip != 'true'
|
||||||
|
runs-on: self-hosted
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
cache-on-failure: true
|
||||||
|
cache-all-crates: true
|
||||||
|
|
||||||
|
- name: Install s3s-e2e
|
||||||
|
run: |
|
||||||
|
cargo install s3s-e2e --git https://github.com/Nugine/s3s.git
|
||||||
|
s3s-e2e --version
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: rustfs
|
||||||
|
path: ./target/artifacts
|
||||||
|
|
||||||
|
- name: Run s3s-e2e
|
||||||
|
timeout-minutes: 10
|
||||||
|
run: |
|
||||||
|
./scripts/e2e-run.sh ./target/artifacts/rustfs-debug /tmp/rustfs
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: s3s-e2e.logs
|
||||||
path: /tmp/rustfs.log
|
path: /tmp/rustfs.log
|
||||||
retention-days: 3
|
|
||||||
|
|||||||
443
.github/workflows/docker.yml
vendored
443
.github/workflows/docker.yml
vendored
@@ -1,443 +0,0 @@
|
|||||||
# Copyright 2024 RustFS Team
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
# Docker Images Workflow
|
|
||||||
#
|
|
||||||
# This workflow builds Docker images using pre-built binaries from the build workflow.
|
|
||||||
#
|
|
||||||
# Trigger Types:
|
|
||||||
# 1. workflow_run: Automatically triggered when "Build and Release" workflow completes
|
|
||||||
# 2. workflow_dispatch: Manual trigger for standalone Docker builds
|
|
||||||
#
|
|
||||||
# Key Features:
|
|
||||||
# - Only triggers when Linux builds (x86_64 + aarch64) are successful
|
|
||||||
# - Independent of macOS/Windows build status
|
|
||||||
# - Uses workflow_run event for precise control
|
|
||||||
# - Only builds Docker images for releases and prereleases (development builds are skipped)
|
|
||||||
|
|
||||||
name: Docker Images
|
|
||||||
|
|
||||||
# Permissions needed for workflow_run event and Docker registry access
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
|
|
||||||
on:
|
|
||||||
# Automatically triggered when build workflow completes
|
|
||||||
workflow_run:
|
|
||||||
workflows: [ "Build and Release" ]
|
|
||||||
types: [ completed ]
|
|
||||||
# Manual trigger with same parameters for consistency
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
push_images:
|
|
||||||
description: "Push images to registries"
|
|
||||||
required: false
|
|
||||||
default: true
|
|
||||||
type: boolean
|
|
||||||
version:
|
|
||||||
description: "Version to build (latest for stable release, or specific version like v1.0.0, v1.0.0-alpha1)"
|
|
||||||
required: false
|
|
||||||
default: "latest"
|
|
||||||
type: string
|
|
||||||
force_rebuild:
|
|
||||||
description: "Force rebuild even if binary exists (useful for testing)"
|
|
||||||
required: false
|
|
||||||
default: false
|
|
||||||
type: boolean
|
|
||||||
|
|
||||||
env:
|
|
||||||
CONCLUSION: ${{ github.event.workflow_run.conclusion }}
|
|
||||||
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
|
|
||||||
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
|
||||||
TRIGGERING_EVENT: ${{ github.event.workflow_run.event }}
|
|
||||||
DOCKERHUB_USERNAME: rustfs
|
|
||||||
CARGO_TERM_COLOR: always
|
|
||||||
REGISTRY_DOCKERHUB: rustfs/rustfs
|
|
||||||
REGISTRY_GHCR: ghcr.io/${{ github.repository }}
|
|
||||||
DOCKER_PLATFORMS: linux/amd64,linux/arm64
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
# Check if we should build Docker images
|
|
||||||
build-check:
|
|
||||||
name: Docker Build Check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
should_build: ${{ steps.check.outputs.should_build }}
|
|
||||||
should_push: ${{ steps.check.outputs.should_push }}
|
|
||||||
build_type: ${{ steps.check.outputs.build_type }}
|
|
||||||
version: ${{ steps.check.outputs.version }}
|
|
||||||
short_sha: ${{ steps.check.outputs.short_sha }}
|
|
||||||
is_prerelease: ${{ steps.check.outputs.is_prerelease }}
|
|
||||||
create_latest: ${{ steps.check.outputs.create_latest }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
# For workflow_run events, checkout the specific commit that triggered the workflow
|
|
||||||
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
|
|
||||||
|
|
||||||
- name: Check build conditions
|
|
||||||
id: check
|
|
||||||
run: |
|
|
||||||
should_build=false
|
|
||||||
should_push=false
|
|
||||||
build_type="none"
|
|
||||||
version=""
|
|
||||||
short_sha=""
|
|
||||||
is_prerelease=false
|
|
||||||
create_latest=false
|
|
||||||
|
|
||||||
if [[ "${{ github.event_name }}" == "workflow_run" ]]; then
|
|
||||||
# Triggered by build workflow completion
|
|
||||||
echo "🔗 Triggered by build workflow completion"
|
|
||||||
|
|
||||||
# Check if the triggering workflow was successful
|
|
||||||
# If the workflow succeeded, it means ALL builds (including Linux x86_64 and aarch64) succeeded
|
|
||||||
if [[ "$CONCLUSION" == "success" ]]; then
|
|
||||||
echo "✅ Build workflow succeeded, all builds including Linux are successful"
|
|
||||||
should_build=true
|
|
||||||
should_push=true
|
|
||||||
else
|
|
||||||
echo "❌ Build workflow failed (conclusion: $CONCLUSION), skipping Docker build"
|
|
||||||
should_build=false
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Extract version info from commit message or use commit SHA
|
|
||||||
# Use Git to generate consistent short SHA (ensures uniqueness like build.yml)
|
|
||||||
short_sha=$(git rev-parse --short "$HEAD_SHA")
|
|
||||||
|
|
||||||
# Determine build type based on triggering workflow event and ref
|
|
||||||
triggering_event="$TRIGGERING_EVENT"
|
|
||||||
head_branch="$HEAD_BRANCH"
|
|
||||||
|
|
||||||
echo "🔍 Analyzing triggering workflow:"
|
|
||||||
echo " 📋 Event: $triggering_event"
|
|
||||||
echo " 🌿 Head branch: $head_branch"
|
|
||||||
echo " 📎 Head SHA: $HEAD_SHA"
|
|
||||||
|
|
||||||
# Check if this was triggered by a tag push
|
|
||||||
if [[ "$triggering_event" == "push" ]]; then
|
|
||||||
# For tag pushes, head_branch will be like "refs/tags/v1.0.0" or just "v1.0.0"
|
|
||||||
if [[ "$head_branch" == refs/tags/* ]]; then
|
|
||||||
# Extract tag name from refs/tags/TAG_NAME
|
|
||||||
tag_name="${head_branch#refs/tags/}"
|
|
||||||
version="$tag_name"
|
|
||||||
elif [[ "$head_branch" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+ ]]; then
|
|
||||||
# Direct tag name like "v1.0.0" or "1.0.0-alpha.1"
|
|
||||||
version="$head_branch"
|
|
||||||
elif [[ "$head_branch" == "main" ]]; then
|
|
||||||
# Regular branch push to main
|
|
||||||
build_type="development"
|
|
||||||
version="dev-${short_sha}"
|
|
||||||
should_build=false
|
|
||||||
echo "⏭️ Skipping Docker build for development version (main branch push)"
|
|
||||||
else
|
|
||||||
# Other branch push
|
|
||||||
build_type="development"
|
|
||||||
version="dev-${short_sha}"
|
|
||||||
should_build=false
|
|
||||||
echo "⏭️ Skipping Docker build for development version (branch: $head_branch)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If we extracted a version (tag), determine release type
|
|
||||||
if [[ -n "$version" ]] && [[ "$version" != "dev-${short_sha}" ]]; then
|
|
||||||
# Remove 'v' prefix if present for consistent version format
|
|
||||||
if [[ "$version" == v* ]]; then
|
|
||||||
version="${version#v}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$version" == *"alpha"* ]] || [[ "$version" == *"beta"* ]] || [[ "$version" == *"rc"* ]]; then
|
|
||||||
build_type="prerelease"
|
|
||||||
is_prerelease=true
|
|
||||||
# TODO: 临时修改 - 当前允许 alpha 版本也创建 latest 标签
|
|
||||||
# 等版本稳定后,需要移除下面这行,恢复原有逻辑(只有稳定版本才创建 latest)
|
|
||||||
if [[ "$version" == *"alpha"* ]]; then
|
|
||||||
create_latest=true
|
|
||||||
echo "🧪 Building Docker image for prerelease: $version (临时允许创建 latest 标签)"
|
|
||||||
else
|
|
||||||
echo "🧪 Building Docker image for prerelease: $version"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
build_type="release"
|
|
||||||
create_latest=true
|
|
||||||
echo "🚀 Building Docker image for release: $version"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
# Non-push events
|
|
||||||
build_type="development"
|
|
||||||
version="dev-${short_sha}"
|
|
||||||
should_build=false
|
|
||||||
echo "⏭️ Skipping Docker build for development version (event: $triggering_event)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "🔄 Build triggered by workflow_run:"
|
|
||||||
echo " 📋 Conclusion: $CONCLUSION"
|
|
||||||
echo " 🌿 Branch: $HEAD_BRANCH"
|
|
||||||
echo " 📎 SHA: $HEAD_SHA"
|
|
||||||
echo " 🎯 Event: $TRIGGERING_EVENT"
|
|
||||||
|
|
||||||
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
|
||||||
# Manual trigger
|
|
||||||
input_version="${{ github.event.inputs.version }}"
|
|
||||||
version="${input_version}"
|
|
||||||
should_push="${{ github.event.inputs.push_images }}"
|
|
||||||
should_build=true
|
|
||||||
|
|
||||||
# Get short SHA
|
|
||||||
short_sha=$(git rev-parse --short HEAD)
|
|
||||||
|
|
||||||
echo "🎯 Manual Docker build triggered:"
|
|
||||||
echo " 📋 Requested version: $input_version"
|
|
||||||
echo " 🔧 Force rebuild: ${{ github.event.inputs.force_rebuild }}"
|
|
||||||
echo " 🚀 Push images: $should_push"
|
|
||||||
|
|
||||||
case "$input_version" in
|
|
||||||
"latest")
|
|
||||||
build_type="release"
|
|
||||||
create_latest=true
|
|
||||||
echo "🚀 Building with latest stable release version"
|
|
||||||
;;
|
|
||||||
# Prerelease versions (must match first, more specific)
|
|
||||||
v*alpha*|v*beta*|v*rc*|*alpha*|*beta*|*rc*)
|
|
||||||
build_type="prerelease"
|
|
||||||
is_prerelease=true
|
|
||||||
# TODO: 临时修改 - 当前允许 alpha 版本也创建 latest 标签
|
|
||||||
# 等版本稳定后,需要移除下面的 if 块,恢复原有逻辑
|
|
||||||
if [[ "$input_version" == *"alpha"* ]]; then
|
|
||||||
create_latest=true
|
|
||||||
echo "🧪 Building with prerelease version: $input_version (临时允许创建 latest 标签)"
|
|
||||||
else
|
|
||||||
echo "🧪 Building with prerelease version: $input_version"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
# Release versions (match after prereleases, more general)
|
|
||||||
v[0-9]*|[0-9]*.*.*)
|
|
||||||
build_type="release"
|
|
||||||
create_latest=true
|
|
||||||
echo "📦 Building with specific release version: $input_version"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
# Invalid version for Docker build
|
|
||||||
should_build=false
|
|
||||||
echo "❌ Invalid version for Docker build: $input_version"
|
|
||||||
echo "⚠️ Only release versions (latest, v1.0.0, 1.0.0) and prereleases (v1.0.0-alpha1, 1.0.0-beta2) are supported"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "should_build=$should_build" >> $GITHUB_OUTPUT
|
|
||||||
echo "should_push=$should_push" >> $GITHUB_OUTPUT
|
|
||||||
echo "build_type=$build_type" >> $GITHUB_OUTPUT
|
|
||||||
echo "version=$version" >> $GITHUB_OUTPUT
|
|
||||||
echo "short_sha=$short_sha" >> $GITHUB_OUTPUT
|
|
||||||
echo "is_prerelease=$is_prerelease" >> $GITHUB_OUTPUT
|
|
||||||
echo "create_latest=$create_latest" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
echo "🐳 Docker Build Summary:"
|
|
||||||
echo " - Should build: $should_build"
|
|
||||||
echo " - Should push: $should_push"
|
|
||||||
echo " - Build type: $build_type"
|
|
||||||
echo " - Version: $version"
|
|
||||||
echo " - Short SHA: $short_sha"
|
|
||||||
echo " - Is prerelease: $is_prerelease"
|
|
||||||
echo " - Create latest: $create_latest"
|
|
||||||
|
|
||||||
# Build multi-arch Docker images
|
|
||||||
# Strategy: Build images using pre-built binaries from dl.rustfs.com
|
|
||||||
# Supports both release and dev channel binaries based on build context
|
|
||||||
# Only runs when should_build is true (which includes workflow success check)
|
|
||||||
build-docker:
|
|
||||||
name: Build Docker Images
|
|
||||||
needs: build-check
|
|
||||||
if: needs.build-check.outputs.should_build == 'true'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 60
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ env.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
|
|
||||||
# - name: Login to GitHub Container Registry
|
|
||||||
# uses: docker/login-action@v3
|
|
||||||
# with:
|
|
||||||
# registry: ghcr.io
|
|
||||||
# username: ${{ github.actor }}
|
|
||||||
# password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Extract metadata and generate tags
|
|
||||||
id: meta
|
|
||||||
run: |
|
|
||||||
BUILD_TYPE="${{ needs.build-check.outputs.build_type }}"
|
|
||||||
VERSION="${{ needs.build-check.outputs.version }}"
|
|
||||||
SHORT_SHA="${{ needs.build-check.outputs.short_sha }}"
|
|
||||||
CREATE_LATEST="${{ needs.build-check.outputs.create_latest }}"
|
|
||||||
|
|
||||||
# Convert version format for Dockerfile compatibility
|
|
||||||
case "$VERSION" in
|
|
||||||
"latest")
|
|
||||||
# For stable latest, use RELEASE=latest + release CHANNEL
|
|
||||||
DOCKER_RELEASE="latest"
|
|
||||||
DOCKER_CHANNEL="release"
|
|
||||||
;;
|
|
||||||
v*)
|
|
||||||
# For versioned releases (v1.0.0), remove 'v' prefix for Dockerfile
|
|
||||||
DOCKER_RELEASE="${VERSION#v}"
|
|
||||||
DOCKER_CHANNEL="release"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
# For other versions, pass as-is
|
|
||||||
DOCKER_RELEASE="${VERSION}"
|
|
||||||
DOCKER_CHANNEL="release"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo "docker_release=$DOCKER_RELEASE" >> $GITHUB_OUTPUT
|
|
||||||
echo "docker_channel=$DOCKER_CHANNEL" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
echo "🐳 Docker build parameters:"
|
|
||||||
echo " - Original version: $VERSION"
|
|
||||||
echo " - Docker RELEASE: $DOCKER_RELEASE"
|
|
||||||
echo " - Docker CHANNEL: $DOCKER_CHANNEL"
|
|
||||||
|
|
||||||
# Generate tags based on build type
|
|
||||||
# Only support release and prerelease builds (no development builds)
|
|
||||||
TAGS="${{ env.REGISTRY_DOCKERHUB }}:${VERSION}"
|
|
||||||
|
|
||||||
# Add channel tags for prereleases and latest for stable
|
|
||||||
if [[ "$CREATE_LATEST" == "true" ]]; then
|
|
||||||
# TODO: 临时修改 - 当前 alpha 版本也会创建 latest 标签
|
|
||||||
# 等版本稳定后,这里的逻辑保持不变,但上游的 CREATE_LATEST 设置需要恢复
|
|
||||||
# Stable release (以及临时的 alpha 版本)
|
|
||||||
TAGS="$TAGS,${{ env.REGISTRY_DOCKERHUB }}:latest"
|
|
||||||
elif [[ "$BUILD_TYPE" == "prerelease" ]]; then
|
|
||||||
# Prerelease channel tags (alpha, beta, rc)
|
|
||||||
if [[ "$VERSION" == *"alpha"* ]]; then
|
|
||||||
CHANNEL="alpha"
|
|
||||||
elif [[ "$VERSION" == *"beta"* ]]; then
|
|
||||||
CHANNEL="beta"
|
|
||||||
elif [[ "$VERSION" == *"rc"* ]]; then
|
|
||||||
CHANNEL="rc"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "$CHANNEL" ]]; then
|
|
||||||
TAGS="$TAGS,${{ env.REGISTRY_DOCKERHUB }}:${CHANNEL}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Output tags
|
|
||||||
echo "tags=$TAGS" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Generate labels
|
|
||||||
LABELS="org.opencontainers.image.title=RustFS"
|
|
||||||
LABELS="$LABELS,org.opencontainers.image.description=RustFS distributed object storage system"
|
|
||||||
LABELS="$LABELS,org.opencontainers.image.version=$VERSION"
|
|
||||||
LABELS="$LABELS,org.opencontainers.image.revision=${{ github.sha }}"
|
|
||||||
LABELS="$LABELS,org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}"
|
|
||||||
LABELS="$LABELS,org.opencontainers.image.created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
|
|
||||||
LABELS="$LABELS,org.opencontainers.image.build-type=$BUILD_TYPE"
|
|
||||||
|
|
||||||
echo "labels=$LABELS" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
echo "🐳 Generated Docker tags:"
|
|
||||||
echo "$TAGS" | tr ',' '\n' | sed 's/^/ - /'
|
|
||||||
echo "📋 Build type: $BUILD_TYPE"
|
|
||||||
echo "🔖 Version: $VERSION"
|
|
||||||
|
|
||||||
- name: Build and push Docker image
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: Dockerfile
|
|
||||||
platforms: ${{ env.DOCKER_PLATFORMS }}
|
|
||||||
push: ${{ needs.build-check.outputs.should_push == 'true' }}
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
cache-from: |
|
|
||||||
type=gha,scope=docker-binary
|
|
||||||
cache-to: |
|
|
||||||
type=gha,mode=max,scope=docker-binary
|
|
||||||
build-args: |
|
|
||||||
BUILDTIME=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
|
|
||||||
VERSION=${{ needs.build-check.outputs.version }}
|
|
||||||
BUILD_TYPE=${{ needs.build-check.outputs.build_type }}
|
|
||||||
REVISION=${{ github.sha }}
|
|
||||||
RELEASE=${{ steps.meta.outputs.docker_release }}
|
|
||||||
CHANNEL=${{ steps.meta.outputs.docker_channel }}
|
|
||||||
BUILDKIT_INLINE_CACHE=1
|
|
||||||
# Enable advanced BuildKit features for better performance
|
|
||||||
provenance: false
|
|
||||||
sbom: false
|
|
||||||
# Add retry mechanism by splitting the build process
|
|
||||||
no-cache: false
|
|
||||||
pull: true
|
|
||||||
|
|
||||||
# Note: Manifest creation is no longer needed as we only build one variant
|
|
||||||
# Multi-arch manifests are automatically created by docker/build-push-action
|
|
||||||
|
|
||||||
# Docker build summary
|
|
||||||
docker-summary:
|
|
||||||
name: Docker Build Summary
|
|
||||||
needs: [ build-check, build-docker ]
|
|
||||||
if: always() && needs.build-check.outputs.should_build == 'true'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Docker build completion summary
|
|
||||||
run: |
|
|
||||||
BUILD_TYPE="${{ needs.build-check.outputs.build_type }}"
|
|
||||||
VERSION="${{ needs.build-check.outputs.version }}"
|
|
||||||
CREATE_LATEST="${{ needs.build-check.outputs.create_latest }}"
|
|
||||||
|
|
||||||
echo "🐳 Docker build completed successfully!"
|
|
||||||
echo "📦 Build type: $BUILD_TYPE"
|
|
||||||
echo "🔢 Version: $VERSION"
|
|
||||||
echo "🚀 Strategy: Images using pre-built binaries (release channel only)"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
case "$BUILD_TYPE" in
|
|
||||||
"release")
|
|
||||||
echo "🚀 Release Docker image has been built with ${VERSION} tags"
|
|
||||||
echo "✅ This image is ready for production use"
|
|
||||||
if [[ "$CREATE_LATEST" == "true" ]]; then
|
|
||||||
echo "🏷️ Latest tag has been created for stable release"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
"prerelease")
|
|
||||||
echo "🧪 Prerelease Docker image has been built with ${VERSION} tags"
|
|
||||||
echo "⚠️ This is a prerelease image - use with caution"
|
|
||||||
# TODO: 临时修改 - alpha 版本当前会创建 latest 标签
|
|
||||||
# 等版本稳定后,需要恢复下面的提示信息
|
|
||||||
if [[ "$VERSION" == *"alpha"* ]] && [[ "$CREATE_LATEST" == "true" ]]; then
|
|
||||||
echo "🏷️ Latest tag has been created for alpha version (临时措施)"
|
|
||||||
else
|
|
||||||
echo "🚫 Latest tag NOT created for prerelease"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "❌ Unexpected build type: $BUILD_TYPE"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
81
.github/workflows/helm-package.yml
vendored
81
.github/workflows/helm-package.yml
vendored
@@ -1,81 +0,0 @@
|
|||||||
name: Publish helm chart to artifacthub
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_run:
|
|
||||||
workflows: ["Build and Release"]
|
|
||||||
types: [completed]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
env:
|
|
||||||
new_version: ${{ github.event.workflow_run.head_branch }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-helm-package:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
# Only run on successful builds triggered by tag pushes (version format: x.y.z or x.y.z-suffix)
|
|
||||||
if: |
|
|
||||||
github.event.workflow_run.conclusion == 'success' &&
|
|
||||||
github.event.workflow_run.event == 'push' &&
|
|
||||||
contains(github.event.workflow_run.head_branch, '.')
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout helm chart repo
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Replace chart appversion
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
set -x
|
|
||||||
old_version=$(grep "^appVersion:" helm/rustfs/Chart.yaml | awk '{print $2}')
|
|
||||||
sed -i "s/$old_version/$new_version/g" helm/rustfs/Chart.yaml
|
|
||||||
sed -i "/^image:/,/^[^ ]/ s/tag:.*/tag: "$new_version"/" helm/rustfs/values.yaml
|
|
||||||
|
|
||||||
- name: Set up Helm
|
|
||||||
uses: azure/setup-helm@v4.3.0
|
|
||||||
|
|
||||||
- name: Package Helm Chart
|
|
||||||
run: |
|
|
||||||
cp helm/README.md helm/rustfs/
|
|
||||||
package_version=$(echo $new_version | awk -F '-' '{print $2}' | awk -F '.' '{print $NF}')
|
|
||||||
helm package ./helm/rustfs --destination helm/rustfs/ --version "0.0.$package_version"
|
|
||||||
|
|
||||||
- name: Upload helm package as artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: helm-package
|
|
||||||
path: helm/rustfs/*.tgz
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
publish-helm-package:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [build-helm-package]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout helm package repo
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
repository: rustfs/helm
|
|
||||||
token: ${{ secrets.RUSTFS_HELM_PACKAGE }}
|
|
||||||
|
|
||||||
- name: Download helm package
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: helm-package
|
|
||||||
path: ./
|
|
||||||
|
|
||||||
- name: Set up helm
|
|
||||||
uses: azure/setup-helm@v4.3.0
|
|
||||||
|
|
||||||
- name: Generate index
|
|
||||||
run: helm repo index . --url https://charts.rustfs.com
|
|
||||||
|
|
||||||
- name: Push helm package and index file
|
|
||||||
run: |
|
|
||||||
git config --global user.name "${{ secrets.USERNAME }}"
|
|
||||||
git config --global user.email "${{ secrets.EMAIL_ADDRESS }}"
|
|
||||||
git status .
|
|
||||||
git add .
|
|
||||||
git commit -m "Update rustfs helm package with $new_version."
|
|
||||||
git push origin main
|
|
||||||
36
.github/workflows/issue-translator.yml
vendored
36
.github/workflows/issue-translator.yml
vendored
@@ -1,36 +0,0 @@
|
|||||||
# Copyright 2024 RustFS Team
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
name: "issue-translator"
|
|
||||||
on:
|
|
||||||
issue_comment:
|
|
||||||
types: [ created ]
|
|
||||||
issues:
|
|
||||||
types: [ opened ]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
issues: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: usthe/issues-translate-action@v2.7
|
|
||||||
with:
|
|
||||||
IS_MODIFY_TITLE: false
|
|
||||||
# not require, default false, . Decide whether to modify the issue title
|
|
||||||
# if true, the robot account @Issues-translate-bot must have modification permissions, invite @Issues-translate-bot to your project or use your custom bot.
|
|
||||||
CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically.
|
|
||||||
# not require. Customize the translation robot prefix message.
|
|
||||||
142
.github/workflows/performance.yml
vendored
142
.github/workflows/performance.yml
vendored
@@ -1,142 +0,0 @@
|
|||||||
# Copyright 2024 RustFS Team
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
name: Performance Testing
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ main ]
|
|
||||||
paths:
|
|
||||||
- "**/*.rs"
|
|
||||||
- "**/Cargo.toml"
|
|
||||||
- "**/Cargo.lock"
|
|
||||||
- ".github/workflows/performance.yml"
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
profile_duration:
|
|
||||||
description: "Profiling duration in seconds"
|
|
||||||
required: false
|
|
||||||
default: "120"
|
|
||||||
type: string
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
env:
|
|
||||||
CARGO_TERM_COLOR: always
|
|
||||||
RUST_BACKTRACE: 1
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
performance-profile:
|
|
||||||
name: Performance Profiling
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 30
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: Setup Rust environment
|
|
||||||
uses: ./.github/actions/setup
|
|
||||||
with:
|
|
||||||
rust-version: nightly
|
|
||||||
cache-shared-key: perf-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
cache-save-if: ${{ github.ref == 'refs/heads/main' }}
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Install additional nightly components
|
|
||||||
run: rustup component add llvm-tools-preview
|
|
||||||
|
|
||||||
- name: Install samply profiler
|
|
||||||
uses: taiki-e/cache-cargo-install-action@v2
|
|
||||||
with:
|
|
||||||
tool: samply
|
|
||||||
|
|
||||||
- name: Configure kernel for profiling
|
|
||||||
run: echo '1' | sudo tee /proc/sys/kernel/perf_event_paranoid
|
|
||||||
|
|
||||||
- name: Prepare test environment
|
|
||||||
run: |
|
|
||||||
# Create test volumes
|
|
||||||
for i in {0..4}; do
|
|
||||||
mkdir -p ./target/volume/test$i
|
|
||||||
done
|
|
||||||
|
|
||||||
# Set environment variables
|
|
||||||
echo "RUSTFS_VOLUMES=./target/volume/test{0...4}" >> $GITHUB_ENV
|
|
||||||
echo "RUST_LOG=rustfs=info,ecstore=info,s3s=info,iam=info,rustfs-obs=info" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Verify console static assets
|
|
||||||
run: |
|
|
||||||
# Console static assets are already embedded in the repository
|
|
||||||
echo "Console static assets size: $(du -sh rustfs/static/)"
|
|
||||||
echo "Console static assets are embedded via rust-embed, no external download needed"
|
|
||||||
|
|
||||||
- name: Build with profiling optimizations
|
|
||||||
run: |
|
|
||||||
RUSTFLAGS="-C force-frame-pointers=yes -C debug-assertions=off" \
|
|
||||||
cargo +nightly build --profile profiling -p rustfs --bins
|
|
||||||
|
|
||||||
- name: Run performance profiling
|
|
||||||
id: profiling
|
|
||||||
run: |
|
|
||||||
DURATION="${{ github.event.inputs.profile_duration || '120' }}"
|
|
||||||
echo "Running profiling for ${DURATION} seconds..."
|
|
||||||
|
|
||||||
timeout "${DURATION}s" samply record \
|
|
||||||
--output samply-profile.json \
|
|
||||||
./target/profiling/rustfs ${RUSTFS_VOLUMES} || true
|
|
||||||
|
|
||||||
if [ -f "samply-profile.json" ]; then
|
|
||||||
echo "profile_generated=true" >> $GITHUB_OUTPUT
|
|
||||||
echo "Profile generated successfully"
|
|
||||||
else
|
|
||||||
echo "profile_generated=false" >> $GITHUB_OUTPUT
|
|
||||||
echo "::warning::Profile data not generated"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Upload profile data
|
|
||||||
if: steps.profiling.outputs.profile_generated == 'true'
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: performance-profile-${{ github.run_number }}
|
|
||||||
path: samply-profile.json
|
|
||||||
retention-days: 30
|
|
||||||
|
|
||||||
benchmark:
|
|
||||||
name: Benchmark Tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 45
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: Setup Rust environment
|
|
||||||
uses: ./.github/actions/setup
|
|
||||||
with:
|
|
||||||
rust-version: stable
|
|
||||||
cache-shared-key: bench-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
cache-save-if: ${{ github.ref == 'refs/heads/main' }}
|
|
||||||
|
|
||||||
- name: Run benchmarks
|
|
||||||
run: |
|
|
||||||
cargo bench --package ecstore --bench comparison_benchmark -- --output-format json | \
|
|
||||||
tee benchmark-results.json
|
|
||||||
|
|
||||||
- name: Upload benchmark results
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: benchmark-results-${{ github.run_number }}
|
|
||||||
path: benchmark-results.json
|
|
||||||
retention-days: 7
|
|
||||||
68
.github/workflows/samply.yml
vendored
Normal file
68
.github/workflows/samply.yml
vendored
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
name: Profile with Samply
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
workflow_dispatch:
|
||||||
|
jobs:
|
||||||
|
profile:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: dtolnay/rust-toolchain@nightly
|
||||||
|
with:
|
||||||
|
components: llvm-tools-preview
|
||||||
|
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
|
- name: Install samply
|
||||||
|
uses: taiki-e/cache-cargo-install-action@v2
|
||||||
|
with:
|
||||||
|
tool: samply
|
||||||
|
|
||||||
|
- name: Configure kernel for profiling
|
||||||
|
run: echo '1' | sudo tee /proc/sys/kernel/perf_event_paranoid
|
||||||
|
|
||||||
|
- name: Create test volumes
|
||||||
|
run: |
|
||||||
|
for i in {0..4}; do
|
||||||
|
mkdir -p ./target/volume/test$i
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Set environment variables
|
||||||
|
run: |
|
||||||
|
echo "RUSTFS_VOLUMES=./target/volume/test{0...4}" >> $GITHUB_ENV
|
||||||
|
echo "RUST_LOG=rustfs=info,ecstore=info,s3s=info,iam=info,rustfs-obs=info" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Download static files
|
||||||
|
run: |
|
||||||
|
curl -L "https://dl.rustfs.com/artifacts/console/rustfs-console-latest.zip" -o tempfile.zip && unzip -o tempfile.zip -d ./rustfs/static && rm tempfile.zip
|
||||||
|
|
||||||
|
- name: Build with profiling
|
||||||
|
run: |
|
||||||
|
RUSTFLAGS="-C force-frame-pointers=yes" cargo +nightly build --profile profiling -p rustfs --bins
|
||||||
|
|
||||||
|
- name: Run samply with timeout
|
||||||
|
id: samply_record
|
||||||
|
run: |
|
||||||
|
timeout 120s samply record --output samply.json ./target/profiling/rustfs ${RUSTFS_VOLUMES}
|
||||||
|
if [ -f "samply.json" ]; then
|
||||||
|
echo "profile_generated=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "profile_generated=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "::error::Failed to generate profile data"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Upload profile data
|
||||||
|
if: steps.samply_record.outputs.profile_generated == 'true'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: samply-profile-${{ github.run_number }}
|
||||||
|
path: samply.json
|
||||||
|
retention-days: 7
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -10,6 +10,7 @@ rustfs/static/*
|
|||||||
!rustfs/static/.gitkeep
|
!rustfs/static/.gitkeep
|
||||||
vendor
|
vendor
|
||||||
cli/rustfs-gui/embedded-rustfs/rustfs
|
cli/rustfs-gui/embedded-rustfs/rustfs
|
||||||
|
deploy/config/obs.toml
|
||||||
*.log
|
*.log
|
||||||
deploy/certs/*
|
deploy/certs/*
|
||||||
*jsonl
|
*jsonl
|
||||||
@@ -18,9 +19,3 @@ deploy/certs/*
|
|||||||
.cargo
|
.cargo
|
||||||
profile.json
|
profile.json
|
||||||
.docker/openobserve-otel/data
|
.docker/openobserve-otel/data
|
||||||
*.zst
|
|
||||||
.secrets
|
|
||||||
*.go
|
|
||||||
*.pb
|
|
||||||
*.svg
|
|
||||||
deploy/logs/*.log.*
|
|
||||||
87
.vscode/launch.json
vendored
87
.vscode/launch.json
vendored
@@ -20,23 +20,33 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"RUST_LOG": "rustfs=debug,ecstore=info,s3s=debug,iam=debug",
|
"RUST_LOG": "rustfs=debug,ecstore=info,s3s=debug",
|
||||||
"RUSTFS_SKIP_BACKGROUND_TASK": "on",
|
"RUSTFS_VOLUMES": "./target/volume/test{0...3}",
|
||||||
//"RUSTFS_OBS_LOG_DIRECTORY": "./deploy/logs",
|
"RUSTFS_ADDRESS": "[::]:9000",
|
||||||
// "RUSTFS_POLICY_PLUGIN_URL":"http://localhost:8181/v1/data/rustfs/authz/allow",
|
"RUSTFS_CONSOLE_ENABLE": "true",
|
||||||
// "RUSTFS_POLICY_PLUGIN_AUTH_TOKEN":"your-opa-token"
|
"RUSTFS_CONSOLE_ADDRESS": "[::]:9002",
|
||||||
|
"RUSTFS_SERVER_DOMAINS": "localhost:9000",
|
||||||
|
"RUSTFS_TLS_PATH": "./deploy/certs",
|
||||||
|
"RUSTFS_OBS_CONFIG": "./deploy/config/obs.example.toml",
|
||||||
|
"RUSTFS__OBSERVABILITY__ENDPOINT": "http://localhost:4317",
|
||||||
|
"RUSTFS__OBSERVABILITY__USE_STDOUT": "true",
|
||||||
|
"RUSTFS__OBSERVABILITY__SAMPLE_RATIO": "2.0",
|
||||||
|
"RUSTFS__OBSERVABILITY__METER_INTERVAL": "30",
|
||||||
|
"RUSTFS__OBSERVABILITY__SERVICE_NAME": "rustfs",
|
||||||
|
"RUSTFS__OBSERVABILITY__SERVICE_VERSION": "0.1.0",
|
||||||
|
"RUSTFS__OBSERVABILITY__ENVIRONMENT": "develop",
|
||||||
|
"RUSTFS__OBSERVABILITY__LOGGER_LEVEL": "info",
|
||||||
|
"RUSTFS__SINKS__FILE__ENABLED": "true",
|
||||||
|
"RUSTFS__SINKS__FILE__PATH": "./deploy/logs/rustfs.log",
|
||||||
|
"RUSTFS__SINKS__WEBHOOK__ENABLED": "false",
|
||||||
|
"RUSTFS__SINKS__WEBHOOK__ENDPOINT": "",
|
||||||
|
"RUSTFS__SINKS__WEBHOOK__AUTH_TOKEN": "",
|
||||||
|
"RUSTFS__SINKS__KAFKA__ENABLED": "false",
|
||||||
|
"RUSTFS__SINKS__KAFKA__BOOTSTRAP_SERVERS": "",
|
||||||
|
"RUSTFS__SINKS__KAFKA__TOPIC": "",
|
||||||
|
"RUSTFS__LOGGER__QUEUE_CAPACITY": "10"
|
||||||
|
|
||||||
},
|
},
|
||||||
"args": [
|
|
||||||
"--access-key",
|
|
||||||
"rustfsadmin",
|
|
||||||
"--secret-key",
|
|
||||||
"rustfsadmin",
|
|
||||||
"--address",
|
|
||||||
"0.0.0.0:9010",
|
|
||||||
"--server-domains",
|
|
||||||
"127.0.0.1:9010",
|
|
||||||
"./target/volume/test{1...4}"
|
|
||||||
],
|
|
||||||
"cwd": "${workspaceFolder}"
|
"cwd": "${workspaceFolder}"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -76,51 +86,6 @@
|
|||||||
},
|
},
|
||||||
"args": [],
|
"args": [],
|
||||||
"cwd": "${workspaceFolder}"
|
"cwd": "${workspaceFolder}"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Debug executable target/debug/rustfs",
|
|
||||||
"type": "lldb",
|
|
||||||
"request": "launch",
|
|
||||||
"program": "${workspaceFolder}/target/debug/rustfs",
|
|
||||||
"args": [],
|
|
||||||
"cwd": "${workspaceFolder}",
|
|
||||||
//"stopAtEntry": false,
|
|
||||||
//"preLaunchTask": "cargo build",
|
|
||||||
"env": {
|
|
||||||
"RUSTFS_ACCESS_KEY": "rustfsadmin",
|
|
||||||
"RUSTFS_SECRET_KEY": "rustfsadmin",
|
|
||||||
"RUSTFS_VOLUMES": "./target/volume/test{1...4}",
|
|
||||||
"RUSTFS_ADDRESS": ":9000",
|
|
||||||
"RUSTFS_CONSOLE_ENABLE": "true",
|
|
||||||
// "RUSTFS_OBS_TRACE_ENDPOINT": "http://127.0.0.1:4318/v1/traces", // jeager otlp http endpoint
|
|
||||||
// "RUSTFS_OBS_METRIC_ENDPOINT": "http://127.0.0.1:4318/v1/metrics", // default otlp http endpoint
|
|
||||||
// "RUSTFS_OBS_LOG_ENDPOINT": "http://127.0.0.1:4318/v1/logs", // default otlp http endpoint
|
|
||||||
"RUSTFS_CONSOLE_ADDRESS": "127.0.0.1:9001",
|
|
||||||
"RUSTFS_OBS_LOG_DIRECTORY": "./target/logs",
|
|
||||||
},
|
|
||||||
"sourceLanguages": [
|
|
||||||
"rust"
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Debug executable target/debug/test",
|
|
||||||
"type": "lldb",
|
|
||||||
"request": "launch",
|
|
||||||
"program": "${workspaceFolder}/target/debug/deps/lifecycle_integration_test-5915cbfcab491b3b",
|
|
||||||
"args": [
|
|
||||||
"--skip",
|
|
||||||
"test_lifecycle_expiry_basic",
|
|
||||||
"--skip",
|
|
||||||
"test_lifecycle_expiry_deletemarker",
|
|
||||||
//"--skip",
|
|
||||||
//"test_lifecycle_transition_basic",
|
|
||||||
],
|
|
||||||
"cwd": "${workspaceFolder}",
|
|
||||||
//"stopAtEntry": false,
|
|
||||||
//"preLaunchTask": "cargo build",
|
|
||||||
"sourceLanguages": [
|
|
||||||
"rust"
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
24
AGENTS.md
24
AGENTS.md
@@ -1,24 +0,0 @@
|
|||||||
# Repository Guidelines
|
|
||||||
|
|
||||||
## Communication Rules
|
|
||||||
- Respond to the user in Chinese; use English in all other contexts.
|
|
||||||
|
|
||||||
## Project Structure & Module Organization
|
|
||||||
The workspace root hosts shared dependencies in `Cargo.toml`. The service binary lives under `rustfs/src/main.rs`, while reusable crates sit in `crates/` (`crypto`, `iam`, `kms`, and `e2e_test`). Local fixtures for standalone flows reside in `test_standalone/`, deployment manifests are under `deploy/`, Docker assets sit at the root, and automation lives in `scripts/`. Skim each crate’s README or module docs before contributing changes.
|
|
||||||
|
|
||||||
## Build, Test, and Development Commands
|
|
||||||
Run `cargo check --all-targets` for fast validation. Build release binaries via `cargo build --release` or the pipeline-aligned `make build`. Use `./build-rustfs.sh --dev` for iterative development and `./build-rustfs.sh --platform <target>` for cross-compiles. Prefer `make pre-commit` before pushing to cover formatting, clippy, checks, and tests.
|
|
||||||
Always ensure `cargo fmt --all --check`, `cargo test --workspace --exclude e2e_test`, and `cargo clippy --all-targets --all-features -- -D warnings` complete successfully after each code change to keep the tree healthy and warning-free.
|
|
||||||
|
|
||||||
## Coding Style & Naming Conventions
|
|
||||||
Formatting follows the repo `rustfmt.toml` (130-column width). Use `snake_case` for items, `PascalCase` for types, and `SCREAMING_SNAKE_CASE` for constants. Avoid `unwrap()` or `expect()` outside tests; bubble errors with `Result` and crate-specific `thiserror` types. Keep async code non-blocking and offload CPU-heavy work with `tokio::task::spawn_blocking` when necessary.
|
|
||||||
|
|
||||||
## Testing Guidelines
|
|
||||||
Co-locate unit tests with their modules and give behavior-led names such as `handles_expired_token`. Integration suites belong in each crate’s `tests/` directory, while exhaustive end-to-end scenarios live in `crates/e2e_test/`. Run `cargo test --workspace --exclude e2e_test` during iteration, `cargo nextest run --all --exclude e2e_test` when available, and finish with `cargo test --all` before requesting review. Use `NO_PROXY=127.0.0.1,localhost HTTP_PROXY= HTTPS_PROXY=` for KMS e2e tests.
|
|
||||||
When fixing bugs or adding features, include regression tests that capture the new behavior so future changes cannot silently break it.
|
|
||||||
|
|
||||||
## Commit & Pull Request Guidelines
|
|
||||||
Work on feature branches (e.g., `feat/...`) after syncing `main`. Follow Conventional Commits under 72 characters (e.g., `feat: add kms key rotation`). Each commit must compile, format cleanly, and pass `make pre-commit`. Open PRs with a concise summary, note verification commands, link relevant issues, and wait for reviewer approval.
|
|
||||||
|
|
||||||
## Security & Configuration Tips
|
|
||||||
Do not commit secrets or cloud credentials; prefer environment variables or vault tooling. Review IAM- and KMS-related changes with a second maintainer. Confirm proxy settings before running sensitive tests to avoid leaking traffic outside localhost.
|
|
||||||
88
CLA.md
88
CLA.md
@@ -1,88 +0,0 @@
|
|||||||
RustFS Individual Contributor License Agreement
|
|
||||||
|
|
||||||
Thank you for your interest in contributing documentation and related software code to a project hosted or managed by
|
|
||||||
RustFS. In order to clarify the intellectual property license granted with Contributions from any person or entity,
|
|
||||||
RustFS must have a Contributor License Agreement ("CLA") on file that has been signed by each Contributor, indicating
|
|
||||||
agreement to the license terms below. This version of the Contributor License Agreement allows an individual to submit
|
|
||||||
Contributions to the applicable project. If you are making a submission on behalf of a legal entity, then you should
|
|
||||||
sign the separate Corporate Contributor License Agreement.
|
|
||||||
|
|
||||||
You accept and agree to the following terms and conditions for Your present and future Contributions submitted to
|
|
||||||
RustFS. You hereby irrevocably assign and transfer to RustFS all right, title, and interest in and to Your
|
|
||||||
Contributions, including all copyrights and other intellectual property rights therein.
|
|
||||||
|
|
||||||
Definitions
|
|
||||||
|
|
||||||
“You” (or “Your”) shall mean the copyright owner or legal entity authorized by the copyright owner that is making this
|
|
||||||
Agreement with RustFS. For legal entities, the entity making a Contribution and all other entities that control, are
|
|
||||||
controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes
|
|
||||||
of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such
|
|
||||||
entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares,
|
|
||||||
or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
“Contribution” shall mean any original work of authorship, including any modifications or additions to an existing work,
|
|
||||||
that is intentionally submitted by You to RustFS for inclusion in, or documentation of, any of the products or projects
|
|
||||||
owned or managed by RustFS (the "Work"), including without limitation any Work described in Schedule A. For the purposes
|
|
||||||
of this definition, "submitted" means any form of electronic or written communication sent to RustFS or its
|
|
||||||
representatives, including but not limited to communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, RustFS for the purpose of discussing and improving the
|
|
||||||
Work.
|
|
||||||
|
|
||||||
Assignment of Copyright
|
|
||||||
|
|
||||||
Subject to the terms and conditions of this Agreement, You hereby irrevocably assign and transfer to RustFS all right,
|
|
||||||
title, and interest in and to Your Contributions, including all copyrights and other intellectual property rights
|
|
||||||
therein, for the entire term of such rights, including all renewals and extensions. You agree to execute all documents
|
|
||||||
and take all actions as may be reasonably necessary to vest in RustFS the ownership of Your Contributions and to assist
|
|
||||||
RustFS in perfecting, maintaining, and enforcing its rights in Your Contributions.
|
|
||||||
|
|
||||||
Grant of Patent License
|
|
||||||
|
|
||||||
Subject to the terms and conditions of this Agreement, You hereby grant to RustFS and to recipients of documentation and
|
|
||||||
software distributed by RustFS a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as
|
|
||||||
stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the
|
|
||||||
Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your
|
|
||||||
Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was
|
|
||||||
submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or
|
|
||||||
counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes
|
|
||||||
direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for
|
|
||||||
that Contribution or Work shall terminate as of the date such litigation is filed.
|
|
||||||
|
|
||||||
You represent that you are legally entitled to grant the above assignment and license.
|
|
||||||
|
|
||||||
You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of
|
|
||||||
others). You represent that Your Contribution submissions include complete details of any third-party license or other
|
|
||||||
restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which
|
|
||||||
are associated with any part of Your Contributions.
|
|
||||||
|
|
||||||
You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You
|
|
||||||
may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You
|
|
||||||
provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
|
||||||
including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE.
|
|
||||||
|
|
||||||
Should You wish to submit work that is not Your original creation, You may submit it to RustFS separately from any
|
|
||||||
Contribution, identifying the complete details of its source and of any license or other restriction (including, but not
|
|
||||||
limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously
|
|
||||||
marking the work as "Submitted on behalf of a third-party: [named here]”.
|
|
||||||
|
|
||||||
You agree to notify RustFS of any facts or circumstances of which you become aware that would make these representations
|
|
||||||
inaccurate in any respect.
|
|
||||||
|
|
||||||
Modification of CLA
|
|
||||||
|
|
||||||
RustFS reserves the right to update or modify this CLA in the future. Any updates or modifications to this CLA shall
|
|
||||||
apply only to Contributions made after the effective date of the revised CLA. Contributions made prior to the update
|
|
||||||
shall remain governed by the version of the CLA that was in effect at the time of submission. It is not necessary for
|
|
||||||
all Contributors to re-sign the CLA when the CLA is updated or modified.
|
|
||||||
|
|
||||||
Governing Law and Dispute Resolution
|
|
||||||
|
|
||||||
This Agreement will be governed by and construed in accordance with the laws of the People's Republic of China excluding
|
|
||||||
that body of laws known as conflict of laws. The parties expressly agree that the United Nations Convention on Contracts
|
|
||||||
for the International Sale of Goods will not apply. Any legal action or proceeding arising under this Agreement will be
|
|
||||||
brought exclusively in the courts located in Beijing, China, and the parties hereby irrevocably consent to the personal
|
|
||||||
jurisdiction and venue therein.
|
|
||||||
|
|
||||||
For your reading convenience, this Agreement is written in parallel English and Chinese sections. To the extent there is
|
|
||||||
a conflict between the English and Chinese sections, the English sections shall govern.
|
|
||||||
275
CLAUDE.md
275
CLAUDE.md
@@ -1,275 +0,0 @@
|
|||||||
# CLAUDE.md
|
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
RustFS is a high-performance distributed object storage software built with Rust, providing S3-compatible APIs and
|
|
||||||
advanced features like data lakes, AI, and big data support. It's designed as an alternative to MinIO with better
|
|
||||||
performance and a more business-friendly Apache 2.0 license.
|
|
||||||
|
|
||||||
## Build Commands
|
|
||||||
|
|
||||||
### Primary Build Commands
|
|
||||||
|
|
||||||
- `cargo build --release` - Build the main RustFS binary
|
|
||||||
- `./build-rustfs.sh` - Recommended build script that handles console resources and cross-platform compilation
|
|
||||||
- `./build-rustfs.sh --dev` - Development build with debug symbols
|
|
||||||
- `make build` or `just build` - Use Make/Just for standardized builds
|
|
||||||
|
|
||||||
### Platform-Specific Builds
|
|
||||||
|
|
||||||
- `./build-rustfs.sh --platform x86_64-unknown-linux-musl` - Build for musl target
|
|
||||||
- `./build-rustfs.sh --platform aarch64-unknown-linux-gnu` - Build for ARM64
|
|
||||||
- `make build-musl` or `just build-musl` - Build musl variant
|
|
||||||
- `make build-cross-all` - Build all supported architectures
|
|
||||||
|
|
||||||
### Testing Commands
|
|
||||||
|
|
||||||
- `cargo test --workspace --exclude e2e_test` - Run unit tests (excluding e2e tests)
|
|
||||||
- `cargo nextest run --all --exclude e2e_test` - Use nextest if available (faster)
|
|
||||||
- `cargo test --all --doc` - Run documentation tests
|
|
||||||
- `make test` or `just test` - Run full test suite
|
|
||||||
- `make pre-commit` - Run all quality checks (fmt, clippy, check, test)
|
|
||||||
|
|
||||||
### End-to-End Testing
|
|
||||||
|
|
||||||
- `cargo test --package e2e_test` - Run all e2e tests
|
|
||||||
- `./scripts/run_e2e_tests.sh` - Run e2e tests via script
|
|
||||||
- `./scripts/run_scanner_benchmarks.sh` - Run scanner performance benchmarks
|
|
||||||
|
|
||||||
### KMS-Specific Testing (with proxy bypass)
|
|
||||||
|
|
||||||
-
|
|
||||||
`NO_PROXY=127.0.0.1,localhost HTTP_PROXY= HTTPS_PROXY= http_proxy= https_proxy= cargo test --package e2e_test test_local_kms_end_to_end -- --nocapture --test-threads=1` -
|
|
||||||
Run complete KMS end-to-end test
|
|
||||||
-
|
|
||||||
`NO_PROXY=127.0.0.1,localhost HTTP_PROXY= HTTPS_PROXY= http_proxy= https_proxy= cargo test --package e2e_test kms:: -- --nocapture --test-threads=1` -
|
|
||||||
Run all KMS tests
|
|
||||||
- `cargo test --package e2e_test test_local_kms_key_isolation -- --nocapture --test-threads=1` - Test KMS key isolation
|
|
||||||
- `cargo test --package e2e_test test_local_kms_large_file -- --nocapture --test-threads=1` - Test KMS with large files
|
|
||||||
|
|
||||||
### Code Quality
|
|
||||||
|
|
||||||
- `cargo fmt --all` - Format code
|
|
||||||
- `cargo clippy --all-targets --all-features -- -D warnings` - Lint code
|
|
||||||
- `make pre-commit` or `just pre-commit` - Run all quality checks (fmt, clippy, check, test)
|
|
||||||
|
|
||||||
### Quick Development Commands
|
|
||||||
|
|
||||||
- `make help` or `just help` - Show all available commands with descriptions
|
|
||||||
- `make help-build` - Show detailed build options and cross-compilation help
|
|
||||||
- `make help-docker` - Show comprehensive Docker build and deployment options
|
|
||||||
- `./scripts/dev_deploy.sh <IP>` - Deploy development build to remote server
|
|
||||||
- `./scripts/run.sh` - Start local development server
|
|
||||||
- `./scripts/probe.sh` - Health check and connectivity testing
|
|
||||||
|
|
||||||
### Docker Build Commands
|
|
||||||
|
|
||||||
- `make docker-buildx` - Build multi-architecture production images
|
|
||||||
- `make docker-dev-local` - Build development image for local use
|
|
||||||
- `./docker-buildx.sh --push` - Build and push production images
|
|
||||||
|
|
||||||
## Architecture Overview
|
|
||||||
|
|
||||||
### Core Components
|
|
||||||
|
|
||||||
**Main Binary (`rustfs/`):**
|
|
||||||
|
|
||||||
- Entry point at `rustfs/src/main.rs`
|
|
||||||
- Core modules: admin, auth, config, server, storage, license management, profiling
|
|
||||||
- HTTP server with S3-compatible APIs
|
|
||||||
- Service state management and graceful shutdown
|
|
||||||
- Parallel service initialization with DNS resolver, bucket metadata, and IAM
|
|
||||||
|
|
||||||
**Key Crates (`crates/`):**
|
|
||||||
|
|
||||||
- `ecstore` - Erasure coding storage implementation (core storage layer)
|
|
||||||
- `iam` - Identity and Access Management
|
|
||||||
- `kms` - Key Management Service for encryption and key handling
|
|
||||||
- `madmin` - Management dashboard and admin API interface
|
|
||||||
- `s3select-api` & `s3select-query` - S3 Select API and query engine
|
|
||||||
- `config` - Configuration management with notify features
|
|
||||||
- `crypto` - Cryptography and security features
|
|
||||||
- `lock` - Distributed locking implementation
|
|
||||||
- `filemeta` - File metadata management
|
|
||||||
- `rio` - Rust I/O utilities and abstractions
|
|
||||||
- `common` - Shared utilities and data structures
|
|
||||||
- `protos` - Protocol buffer definitions
|
|
||||||
- `audit-logger` - Audit logging for file operations
|
|
||||||
- `notify` - Event notification system
|
|
||||||
- `obs` - Observability utilities
|
|
||||||
- `workers` - Worker thread pools and task scheduling
|
|
||||||
- `appauth` - Application authentication and authorization
|
|
||||||
- `ahm` - Asynchronous Hash Map for concurrent data structures
|
|
||||||
- `mcp` - MCP server for S3 operations
|
|
||||||
- `signer` - Client request signing utilities
|
|
||||||
- `checksums` - Client checksum calculation utilities
|
|
||||||
- `utils` - General utility functions and helpers
|
|
||||||
- `zip` - ZIP file handling and compression
|
|
||||||
- `targets` - Target-specific configurations and utilities
|
|
||||||
|
|
||||||
### Build System
|
|
||||||
|
|
||||||
- Cargo workspace with 25+ crates (including new KMS functionality)
|
|
||||||
- Custom `build-rustfs.sh` script for advanced build options
|
|
||||||
- Multi-architecture Docker builds via `docker-buildx.sh`
|
|
||||||
- Both Make and Just task runners supported with comprehensive help
|
|
||||||
- Cross-compilation support for multiple Linux targets
|
|
||||||
- Automated CI/CD with GitHub Actions for testing, building, and Docker publishing
|
|
||||||
- Performance benchmarking and audit workflows
|
|
||||||
|
|
||||||
### Key Dependencies
|
|
||||||
|
|
||||||
- `axum` - HTTP framework for S3 API server
|
|
||||||
- `tokio` - Async runtime
|
|
||||||
- `s3s` - S3 protocol implementation library
|
|
||||||
- `datafusion` - For S3 Select query processing
|
|
||||||
- `hyper`/`hyper-util` - HTTP client/server utilities
|
|
||||||
- `rustls` - TLS implementation
|
|
||||||
- `serde`/`serde_json` - Serialization
|
|
||||||
- `tracing` - Structured logging and observability
|
|
||||||
- `pprof` - Performance profiling with flamegraph support
|
|
||||||
- `tikv-jemallocator` - Memory allocator for Linux GNU builds
|
|
||||||
|
|
||||||
### Development Workflow
|
|
||||||
|
|
||||||
- Console resources are embedded during build via `rust-embed`
|
|
||||||
- Protocol buffers generated via custom `gproto` binary
|
|
||||||
- E2E tests in separate crate (`e2e_test`) with comprehensive KMS testing
|
|
||||||
- Shadow build for version/metadata embedding
|
|
||||||
- Support for both GNU and musl libc targets
|
|
||||||
- Development scripts in `scripts/` directory for common tasks
|
|
||||||
- Git hooks setup available via `make setup-hooks` or `just setup-hooks`
|
|
||||||
|
|
||||||
### Performance & Observability
|
|
||||||
|
|
||||||
- Performance profiling available with `pprof` integration (disabled on Windows)
|
|
||||||
- Profiling enabled via environment variables in production
|
|
||||||
- Built-in observability with OpenTelemetry integration
|
|
||||||
- Background services (scanner, heal) can be controlled via environment variables:
|
|
||||||
- `RUSTFS_ENABLE_SCANNER` (default: true)
|
|
||||||
- `RUSTFS_ENABLE_HEAL` (default: true)
|
|
||||||
|
|
||||||
### Service Architecture
|
|
||||||
|
|
||||||
- Service state management with graceful shutdown handling
|
|
||||||
- Parallel initialization of core systems (DNS, bucket metadata, IAM)
|
|
||||||
- Event notification system with MQTT and webhook support
|
|
||||||
- Auto-heal and data scanner for storage integrity
|
|
||||||
- Jemalloc allocator for Linux GNU targets for better performance
|
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
|
|
||||||
- `RUSTFS_ENABLE_SCANNER` - Enable/disable background data scanner (default: true)
|
|
||||||
- `RUSTFS_ENABLE_HEAL` - Enable/disable auto-heal functionality (default: true)
|
|
||||||
- Various profiling and observability controls
|
|
||||||
- Build-time variables for Docker builds (RELEASE, REGISTRY, etc.)
|
|
||||||
- Test environment configurations in `scripts/dev_rustfs.env`
|
|
||||||
|
|
||||||
### KMS Environment Variables
|
|
||||||
|
|
||||||
- `NO_PROXY=127.0.0.1,localhost` - Required for KMS E2E tests to bypass proxy
|
|
||||||
- `HTTP_PROXY=` `HTTPS_PROXY=` `http_proxy=` `https_proxy=` - Clear proxy settings for local KMS testing
|
|
||||||
|
|
||||||
## KMS (Key Management Service) Architecture
|
|
||||||
|
|
||||||
### KMS Implementation Status
|
|
||||||
|
|
||||||
- **Full KMS Integration:** Complete implementation with Local and Vault backends
|
|
||||||
- **Automatic Configuration:** KMS auto-configures on startup with `--kms-enable` flag
|
|
||||||
- **Encryption Support:** Full S3-compatible server-side encryption (SSE-S3, SSE-KMS, SSE-C)
|
|
||||||
- **Admin API:** Complete KMS management via HTTP admin endpoints
|
|
||||||
- **Production Ready:** Comprehensive testing including large files and key isolation
|
|
||||||
|
|
||||||
### KMS Configuration
|
|
||||||
|
|
||||||
- **Local Backend:** `--kms-backend local --kms-key-dir <path> --kms-default-key-id <id>`
|
|
||||||
- **Vault Backend:** `--kms-backend vault --kms-vault-endpoint <url> --kms-vault-key-name <name>`
|
|
||||||
- **Auto-startup:** KMS automatically initializes when `--kms-enable` is provided
|
|
||||||
- **Manual Configuration:** Also supports dynamic configuration via admin API
|
|
||||||
|
|
||||||
### S3 Encryption Support
|
|
||||||
|
|
||||||
- **SSE-S3:** Server-side encryption with S3-managed keys (`ServerSideEncryption: AES256`)
|
|
||||||
- **SSE-KMS:** Server-side encryption with KMS-managed keys (`ServerSideEncryption: aws:kms`)
|
|
||||||
- **SSE-C:** Server-side encryption with customer-provided keys
|
|
||||||
- **Response Headers:** All encryption types return correct `server_side_encryption` headers in PUT/GET responses
|
|
||||||
|
|
||||||
### KMS Testing Architecture
|
|
||||||
|
|
||||||
- **Comprehensive E2E Tests:** Located in `crates/e2e_test/src/kms/`
|
|
||||||
- **Test Environments:** Automated test environment setup with temporary directories
|
|
||||||
- **Encryption Coverage:** Tests all three encryption types (SSE-S3, SSE-KMS, SSE-C)
|
|
||||||
- **API Coverage:** Tests all KMS admin APIs (CreateKey, DescribeKey, ListKeys, etc.)
|
|
||||||
- **Edge Cases:** Key isolation, large file handling, error scenarios
|
|
||||||
|
|
||||||
### Key Files for KMS
|
|
||||||
|
|
||||||
- `crates/kms/` - Core KMS implementation with Local/Vault backends
|
|
||||||
- `rustfs/src/main.rs` - KMS auto-initialization in `init_kms_system()`
|
|
||||||
- `rustfs/src/storage/ecfs.rs` - SSE encryption/decryption in PUT/GET operations
|
|
||||||
- `rustfs/src/admin/handlers/kms*.rs` - KMS admin endpoints
|
|
||||||
- `crates/e2e_test/src/kms/` - Comprehensive KMS test suite
|
|
||||||
- `crates/rio/src/encrypt_reader.rs` - Streaming encryption for large files
|
|
||||||
|
|
||||||
## Code Style and Safety Requirements
|
|
||||||
|
|
||||||
- **Language Requirements:**
|
|
||||||
- Communicate with me in Chinese, but **only English can be used in code files**
|
|
||||||
- Code comments, function names, variable names, and all text in source files must be in English only
|
|
||||||
- No Chinese characters, emojis, or non-ASCII characters are allowed in any source code files
|
|
||||||
- This includes comments, strings, documentation, and any other text within code files
|
|
||||||
- **Safety-Critical Rules:**
|
|
||||||
- `unsafe_code = "deny"` enforced at workspace level
|
|
||||||
- Never use `unwrap()`, `expect()`, or panic-inducing code except in tests
|
|
||||||
- Avoid blocking I/O operations in async contexts
|
|
||||||
- Use proper error handling with `Result<T, E>` and `Option<T>`
|
|
||||||
- Follow Rust's ownership and borrowing rules strictly
|
|
||||||
- **Performance Guidelines:**
|
|
||||||
- Use `cargo clippy --all-targets --all-features -- -D warnings` to catch issues
|
|
||||||
- Prefer `anyhow` for error handling in applications, `thiserror` for libraries
|
|
||||||
- Use appropriate async runtimes and avoid blocking calls
|
|
||||||
- **Testing Standards:**
|
|
||||||
- All new features must include comprehensive tests
|
|
||||||
- Use `#[cfg(test)]` for test-only code that may use panic macros
|
|
||||||
- E2E tests should cover KMS integration scenarios
|
|
||||||
|
|
||||||
## Common Development Tasks
|
|
||||||
|
|
||||||
### Running KMS Tests Locally
|
|
||||||
|
|
||||||
1. **Clear proxy settings:** KMS tests require direct localhost connections
|
|
||||||
2. **Use serial execution:** `--test-threads=1` prevents port conflicts
|
|
||||||
3. **Enable output:** `--nocapture` shows detailed test logs
|
|
||||||
4. **Full command:**
|
|
||||||
`NO_PROXY=127.0.0.1,localhost HTTP_PROXY= HTTPS_PROXY= http_proxy= https_proxy= cargo test --package e2e_test test_local_kms_end_to_end -- --nocapture --test-threads=1`
|
|
||||||
|
|
||||||
### KMS Development Workflow
|
|
||||||
|
|
||||||
1. **Code changes:** Modify KMS-related code in `crates/kms/` or `rustfs/src/`
|
|
||||||
2. **Compile:** Always run `cargo build` after changes
|
|
||||||
3. **Test specific functionality:** Use targeted test commands for faster iteration
|
|
||||||
4. **Full validation:** Run complete end-to-end tests before commits
|
|
||||||
|
|
||||||
### Debugging KMS Issues
|
|
||||||
|
|
||||||
- **Server startup:** Check that KMS auto-initializes with debug logs
|
|
||||||
- **Encryption failures:** Verify SSE headers are correctly set in both PUT and GET responses
|
|
||||||
- **Test failures:** Use `--nocapture` to see detailed error messages
|
|
||||||
- **Key management:** Test admin API endpoints with proper authentication
|
|
||||||
|
|
||||||
## Important Reminders
|
|
||||||
|
|
||||||
- **Always compile after code changes:** Use `cargo build` to catch errors early
|
|
||||||
- **Don't bypass tests:** All functionality must be properly tested, not worked around
|
|
||||||
- **Use proper error handling:** Never use `unwrap()` or `expect()` in production code (except tests)
|
|
||||||
- **Follow S3 compatibility:** Ensure all encryption types return correct HTTP response headers
|
|
||||||
|
|
||||||
# important-instruction-reminders
|
|
||||||
|
|
||||||
Do what has been asked; nothing more, nothing less.
|
|
||||||
NEVER create files unless they're absolutely necessary for achieving your goal.
|
|
||||||
ALWAYS prefer editing an existing file to creating a new one.
|
|
||||||
NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly
|
|
||||||
requested by the User.
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
# Contributor Covenant Code of Conduct
|
|
||||||
|
|
||||||
## Our Pledge
|
|
||||||
|
|
||||||
We as members, contributors, and leaders pledge to make participation in our
|
|
||||||
community a harassment-free experience for everyone, regardless of age, body
|
|
||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
||||||
identity and expression, level of experience, education, socio-economic status,
|
|
||||||
nationality, personal appearance, race, religion, or sexual identity
|
|
||||||
and orientation.
|
|
||||||
|
|
||||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
||||||
diverse, inclusive, and healthy community.
|
|
||||||
|
|
||||||
## Our Standards
|
|
||||||
|
|
||||||
Examples of behavior that contributes to a positive environment for our
|
|
||||||
community include:
|
|
||||||
|
|
||||||
* Demonstrating empathy and kindness toward other people
|
|
||||||
* Being respectful of differing opinions, viewpoints, and experiences
|
|
||||||
* Giving and gracefully accepting constructive feedback
|
|
||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
||||||
and learning from the experience
|
|
||||||
* Focusing on what is best not just for us as individuals, but for the
|
|
||||||
overall community
|
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
|
||||||
|
|
||||||
* The use of sexualized language or imagery, and sexual attention or
|
|
||||||
advances of any kind
|
|
||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
||||||
* Public or private harassment
|
|
||||||
* Publishing others' private information, such as a physical or email
|
|
||||||
address, without their explicit permission
|
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
|
||||||
professional setting
|
|
||||||
|
|
||||||
## Enforcement Responsibilities
|
|
||||||
|
|
||||||
Community leaders are responsible for clarifying and enforcing our standards of
|
|
||||||
acceptable behavior and will take appropriate and fair corrective action in
|
|
||||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
||||||
or harmful.
|
|
||||||
|
|
||||||
Community leaders have the right and responsibility to remove, edit, or reject
|
|
||||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
||||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
||||||
decisions when appropriate.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This Code of Conduct applies within all community spaces, and also applies when
|
|
||||||
an individual is officially representing the community in public spaces.
|
|
||||||
Examples of representing our community include using an official e-mail address,
|
|
||||||
posting via an official social media account, or acting as an appointed
|
|
||||||
representative at an online or offline event.
|
|
||||||
|
|
||||||
## Enforcement
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
||||||
reported to the community leaders responsible for enforcement at
|
|
||||||
hello@rustfs.com.
|
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
|
||||||
reporter of any incident.
|
|
||||||
|
|
||||||
## Enforcement Guidelines
|
|
||||||
|
|
||||||
Community leaders will follow these Community Impact Guidelines in determining
|
|
||||||
the consequences for any action they deem in violation of this Code of Conduct:
|
|
||||||
|
|
||||||
### 1. Correction
|
|
||||||
|
|
||||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
||||||
unprofessional or unwelcome in the community.
|
|
||||||
|
|
||||||
**Consequence**: A private, written warning from community leaders, providing
|
|
||||||
clarity around the nature of the violation and an explanation of why the
|
|
||||||
behavior was inappropriate. A public apology may be requested.
|
|
||||||
|
|
||||||
### 2. Warning
|
|
||||||
|
|
||||||
**Community Impact**: A violation through a single incident or series
|
|
||||||
of actions.
|
|
||||||
|
|
||||||
**Consequence**: A warning with consequences for continued behavior. No
|
|
||||||
interaction with the people involved, including unsolicited interaction with
|
|
||||||
those enforcing the Code of Conduct, for a specified period of time. This
|
|
||||||
includes avoiding interactions in community spaces as well as external channels
|
|
||||||
like social media. Violating these terms may lead to a temporary or
|
|
||||||
permanent ban.
|
|
||||||
|
|
||||||
### 3. Temporary Ban
|
|
||||||
|
|
||||||
**Community Impact**: A serious violation of community standards, including
|
|
||||||
sustained inappropriate behavior.
|
|
||||||
|
|
||||||
**Consequence**: A temporary ban from any sort of interaction or public
|
|
||||||
communication with the community for a specified period of time. No public or
|
|
||||||
private interaction with the people involved, including unsolicited interaction
|
|
||||||
with those enforcing the Code of Conduct, is allowed during this period.
|
|
||||||
Violating these terms may lead to a permanent ban.
|
|
||||||
|
|
||||||
### 4. Permanent Ban
|
|
||||||
|
|
||||||
**Community Impact**: Demonstrating a pattern of violation of community
|
|
||||||
standards, including sustained inappropriate behavior, harassment of an
|
|
||||||
individual, or aggression toward or disparagement of classes of individuals.
|
|
||||||
|
|
||||||
**Consequence**: A permanent ban from any sort of public interaction within
|
|
||||||
the community.
|
|
||||||
|
|
||||||
## Attribution
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
||||||
version 2.0, available at
|
|
||||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
|
||||||
|
|
||||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
|
||||||
enforcement ladder](https://github.com/mozilla/diversity).
|
|
||||||
|
|
||||||
[homepage]: https://www.contributor-covenant.org
|
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see the FAQ at
|
|
||||||
https://www.contributor-covenant.org/faq. Translations are available at
|
|
||||||
https://www.contributor-covenant.org/translations.
|
|
||||||
9107
Cargo.lock
generated
9107
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
425
Cargo.toml
425
Cargo.toml
@@ -1,60 +1,33 @@
|
|||||||
# Copyright 2024 RustFS Team
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"rustfs", # Core file system implementation
|
"appauth", # Application authentication and authorization
|
||||||
"crates/appauth", # Application authentication and authorization
|
"cli/rustfs-gui", # Graphical user interface client
|
||||||
"crates/audit", # Audit target management system with multi-target fan-out
|
"common/common", # Shared utilities and data structures
|
||||||
"crates/common", # Shared utilities and data structures
|
"common/lock", # Distributed locking implementation
|
||||||
|
"common/protos", # Protocol buffer definitions
|
||||||
|
"common/workers", # Worker thread pools and task scheduling
|
||||||
"crates/config", # Configuration management
|
"crates/config", # Configuration management
|
||||||
"crates/crypto", # Cryptography and security features
|
"crates/event-notifier", # Event notification system
|
||||||
"crates/ecstore", # Erasure coding storage implementation
|
|
||||||
"crates/e2e_test", # End-to-end test suite
|
|
||||||
"crates/filemeta", # File metadata management
|
|
||||||
"crates/iam", # Identity and Access Management
|
|
||||||
"crates/lock", # Distributed locking implementation
|
|
||||||
"crates/madmin", # Management dashboard and admin API interface
|
|
||||||
"crates/notify", # Notification system for events
|
|
||||||
"crates/obs", # Observability utilities
|
"crates/obs", # Observability utilities
|
||||||
"crates/policy", # Policy management
|
|
||||||
"crates/protos", # Protocol buffer definitions
|
|
||||||
"crates/rio", # Rust I/O utilities and abstractions
|
|
||||||
"crates/targets", # Target-specific configurations and utilities
|
|
||||||
"crates/s3select-api", # S3 Select API interface
|
|
||||||
"crates/s3select-query", # S3 Select query engine
|
|
||||||
"crates/signer", # client signer
|
|
||||||
"crates/checksums", # client checksums
|
|
||||||
"crates/utils", # Utility functions and helpers
|
"crates/utils", # Utility functions and helpers
|
||||||
"crates/workers", # Worker thread pools and task scheduling
|
"crypto", # Cryptography and security features
|
||||||
"crates/zip", # ZIP file handling and compression
|
"ecstore", # Erasure coding storage implementation
|
||||||
"crates/ahm", # Asynchronous Hash Map for concurrent data structures
|
"e2e_test", # End-to-end test suite
|
||||||
"crates/mcp", # MCP server for S3 operations
|
"iam", # Identity and Access Management
|
||||||
"crates/kms", # Key Management Service
|
"madmin", # Management dashboard and admin API interface
|
||||||
|
"rustfs", # Core file system implementation
|
||||||
|
"s3select/api", # S3 Select API interface
|
||||||
|
"s3select/query", # S3 Select query engine
|
||||||
|
"crates/zip",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
repository = "https://github.com/rustfs/rustfs"
|
repository = "https://github.com/rustfs/rustfs"
|
||||||
rust-version = "1.85"
|
rust-version = "1.75"
|
||||||
version = "0.0.5"
|
version = "0.0.1"
|
||||||
homepage = "https://rustfs.com"
|
|
||||||
description = "RustFS is a high-performance distributed object storage software built using Rust, one of the most popular languages worldwide. "
|
|
||||||
keywords = ["RustFS", "Minio", "object-storage", "filesystem", "s3"]
|
|
||||||
categories = ["web-programming", "development-tools", "filesystem", "network-programming"]
|
|
||||||
|
|
||||||
[workspace.lints.rust]
|
[workspace.lints.rust]
|
||||||
unsafe_code = "deny"
|
unsafe_code = "deny"
|
||||||
@@ -63,224 +36,194 @@ unsafe_code = "deny"
|
|||||||
all = "warn"
|
all = "warn"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
# RustFS Internal Crates
|
api = { path = "./s3select/api", version = "0.0.1" }
|
||||||
rustfs = { path = "./rustfs", version = "0.0.5" }
|
appauth = { path = "./appauth", version = "0.0.1" }
|
||||||
rustfs-ahm = { path = "crates/ahm", version = "0.0.5" }
|
common = { path = "./common/common", version = "0.0.1" }
|
||||||
rustfs-appauth = { path = "crates/appauth", version = "0.0.5" }
|
crypto = { path = "./crypto", version = "0.0.1" }
|
||||||
rustfs-audit = { path = "crates/audit", version = "0.0.5" }
|
ecstore = { path = "./ecstore", version = "0.0.1" }
|
||||||
rustfs-checksums = { path = "crates/checksums", version = "0.0.5" }
|
iam = { path = "./iam", version = "0.0.1" }
|
||||||
rustfs-common = { path = "crates/common", version = "0.0.5" }
|
lock = { path = "./common/lock", version = "0.0.1" }
|
||||||
rustfs-config = { path = "./crates/config", version = "0.0.5" }
|
madmin = { path = "./madmin", version = "0.0.1" }
|
||||||
rustfs-crypto = { path = "crates/crypto", version = "0.0.5" }
|
policy = { path = "./policy", version = "0.0.1" }
|
||||||
rustfs-ecstore = { path = "crates/ecstore", version = "0.0.5" }
|
protos = { path = "./common/protos", version = "0.0.1" }
|
||||||
rustfs-filemeta = { path = "crates/filemeta", version = "0.0.5" }
|
query = { path = "./s3select/query", version = "0.0.1" }
|
||||||
rustfs-iam = { path = "crates/iam", version = "0.0.5" }
|
rustfs = { path = "./rustfs", version = "0.0.1" }
|
||||||
rustfs-kms = { path = "crates/kms", version = "0.0.5" }
|
rustfs-zip = { path = "./crates/zip", version = "0.0.1" }
|
||||||
rustfs-lock = { path = "crates/lock", version = "0.0.5" }
|
rustfs-config = { path = "./crates/config", version = "0.0.1" }
|
||||||
rustfs-madmin = { path = "crates/madmin", version = "0.0.5" }
|
rustfs-obs = { path = "crates/obs", version = "0.0.1" }
|
||||||
rustfs-mcp = { path = "crates/mcp", version = "0.0.5" }
|
rustfs-event-notifier = { path = "crates/event-notifier", version = "0.0.1" }
|
||||||
rustfs-notify = { path = "crates/notify", version = "0.0.5" }
|
rustfs-utils = { path = "crates/utils", version = "0.0.1" }
|
||||||
rustfs-obs = { path = "crates/obs", version = "0.0.5" }
|
workers = { path = "./common/workers", version = "0.0.1" }
|
||||||
rustfs-policy = { path = "crates/policy", version = "0.0.5" }
|
tokio-tar = "0.3.1"
|
||||||
rustfs-protos = { path = "crates/protos", version = "0.0.5" }
|
atoi = "2.0.0"
|
||||||
rustfs-rio = { path = "crates/rio", version = "0.0.5" }
|
|
||||||
rustfs-s3select-api = { path = "crates/s3select-api", version = "0.0.5" }
|
|
||||||
rustfs-s3select-query = { path = "crates/s3select-query", version = "0.0.5" }
|
|
||||||
rustfs-signer = { path = "crates/signer", version = "0.0.5" }
|
|
||||||
rustfs-targets = { path = "crates/targets", version = "0.0.5" }
|
|
||||||
rustfs-utils = { path = "crates/utils", version = "0.0.5" }
|
|
||||||
rustfs-workers = { path = "crates/workers", version = "0.0.5" }
|
|
||||||
rustfs-zip = { path = "./crates/zip", version = "0.0.5" }
|
|
||||||
|
|
||||||
# Async Runtime and Networking
|
|
||||||
async-channel = "2.5.0"
|
|
||||||
async-compression = { version = "0.4.19" }
|
|
||||||
async-recursion = "1.1.1"
|
async-recursion = "1.1.1"
|
||||||
async-trait = "0.1.89"
|
async-trait = "0.1.88"
|
||||||
axum = "0.8.7"
|
atomic_enum = "0.3.0"
|
||||||
axum-extra = "0.12.2"
|
aws-sdk-s3 = "1.29.0"
|
||||||
axum-server = { version = "0.8.0", features = ["tls-rustls-no-provider"], default-features = false }
|
axum = "0.8.4"
|
||||||
|
axum-extra = "0.10.1"
|
||||||
|
axum-server = { version = "0.7.2", features = ["tls-rustls"] }
|
||||||
|
backon = "1.5.1"
|
||||||
|
blake2 = "0.10.6"
|
||||||
|
bytes = "1.10.1"
|
||||||
|
bytesize = "2.0.1"
|
||||||
|
byteorder = "1.5.0"
|
||||||
|
chrono = { version = "0.4.41", features = ["serde"] }
|
||||||
|
clap = { version = "4.5.39", features = ["derive", "env"] }
|
||||||
|
config = "0.15.11"
|
||||||
|
const-str = { version = "0.6.2", features = ["std", "proc"] }
|
||||||
|
datafusion = "46.0.1"
|
||||||
|
derive_builder = "0.20.2"
|
||||||
|
dioxus = { version = "0.6.3", features = ["router"] }
|
||||||
|
dirs = "6.0.0"
|
||||||
|
flatbuffers = "25.2.10"
|
||||||
|
flexi_logger = { version = "0.30.2", features = ["trc"] }
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
futures-core = "0.3.31"
|
futures-core = "0.3.31"
|
||||||
futures-util = "0.3.31"
|
futures-util = "0.3.31"
|
||||||
hyper = { version = "1.8.1", features = ["http2", "http1", "server"] }
|
glob = "0.3.2"
|
||||||
hyper-rustls = { version = "0.27.7", default-features = false, features = ["native-tokio", "http1", "tls12", "logging", "http2", "ring", "webpki-roots"] }
|
hex = "0.4.3"
|
||||||
hyper-util = { version = "0.1.19", features = ["tokio", "server-auto", "server-graceful"] }
|
|
||||||
http = "1.4.0"
|
|
||||||
http-body = "1.0.1"
|
|
||||||
reqwest = { version = "0.12.25", default-features = false, features = ["rustls-tls-webpki-roots", "charset", "http2", "system-proxy", "stream", "json", "blocking"] }
|
|
||||||
socket2 = "0.6.1"
|
|
||||||
tokio = { version = "1.48.0", features = ["fs", "rt-multi-thread"] }
|
|
||||||
tokio-rustls = { version = "0.26.4", default-features = false, features = ["logging", "tls12", "ring"] }
|
|
||||||
tokio-stream = { version = "0.1.17" }
|
|
||||||
tokio-test = "0.4.4"
|
|
||||||
tokio-util = { version = "0.7.17", features = ["io", "compat"] }
|
|
||||||
tonic = { version = "0.14.2", features = ["gzip"] }
|
|
||||||
tonic-prost = { version = "0.14.2" }
|
|
||||||
tonic-prost-build = { version = "0.14.2" }
|
|
||||||
tower = { version = "0.5.2", features = ["timeout"] }
|
|
||||||
tower-http = { version = "0.6.8", features = ["cors"] }
|
|
||||||
|
|
||||||
# Serialization and Data Formats
|
|
||||||
bytes = { version = "1.11.0", features = ["serde"] }
|
|
||||||
bytesize = "2.3.1"
|
|
||||||
byteorder = "1.5.0"
|
|
||||||
flatbuffers = "25.9.23"
|
|
||||||
form_urlencoded = "1.2.2"
|
|
||||||
prost = "0.14.1"
|
|
||||||
quick-xml = "0.38.4"
|
|
||||||
rmcp = { version = "0.10.0" }
|
|
||||||
rmp = { version = "0.8.14" }
|
|
||||||
rmp-serde = { version = "1.3.0" }
|
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
|
||||||
serde_json = { version = "1.0.145", features = ["raw_value"] }
|
|
||||||
serde_urlencoded = "0.7.1"
|
|
||||||
schemars = "1.1.0"
|
|
||||||
|
|
||||||
# Cryptography and Security
|
|
||||||
aes-gcm = { version = "0.11.0-rc.2", features = ["rand_core"] }
|
|
||||||
argon2 = { version = "0.6.0-rc.3", features = ["std"] }
|
|
||||||
blake3 = { version = "1.8.2", features = ["rayon", "mmap"] }
|
|
||||||
chacha20poly1305 = { version = "0.11.0-rc.2" }
|
|
||||||
crc-fast = "1.6.0"
|
|
||||||
hmac = { version = "0.13.0-rc.3" }
|
|
||||||
jsonwebtoken = { version = "10.2.0", features = ["rust_crypto"] }
|
|
||||||
pbkdf2 = "0.13.0-rc.3"
|
|
||||||
rsa = { version = "0.10.0-rc.10" }
|
|
||||||
rustls = { version = "0.23.35", features = ["ring", "logging", "std", "tls12"], default-features = false }
|
|
||||||
rustls-pemfile = "2.2.0"
|
|
||||||
rustls-pki-types = "1.13.1"
|
|
||||||
sha1 = "0.11.0-rc.3"
|
|
||||||
sha2 = "0.11.0-rc.3"
|
|
||||||
subtle = "2.6"
|
|
||||||
zeroize = { version = "1.8.2", features = ["derive"] }
|
|
||||||
|
|
||||||
# Time and Date
|
|
||||||
chrono = { version = "0.4.42", features = ["serde"] }
|
|
||||||
humantime = "2.3.0"
|
|
||||||
time = { version = "0.3.44", features = ["std", "parsing", "formatting", "macros", "serde"] }
|
|
||||||
|
|
||||||
# Utilities and Tools
|
|
||||||
anyhow = "1.0.100"
|
|
||||||
arc-swap = "1.7.1"
|
|
||||||
astral-tokio-tar = "0.5.6"
|
|
||||||
atoi = "2.0.0"
|
|
||||||
atomic_enum = "0.3.0"
|
|
||||||
aws-config = { version = "1.8.11" }
|
|
||||||
aws-credential-types = { version = "1.2.10" }
|
|
||||||
aws-sdk-s3 = { version = "1.116.0", default-features = false, features = ["sigv4a", "rustls", "rt-tokio"] }
|
|
||||||
aws-smithy-types = { version = "1.3.4" }
|
|
||||||
base64 = "0.22.1"
|
|
||||||
base64-simd = "0.8.0"
|
|
||||||
brotli = "8.0.2"
|
|
||||||
cfg-if = "1.0.4"
|
|
||||||
clap = { version = "4.5.53", features = ["derive", "env"] }
|
|
||||||
const-str = { version = "0.7.0", features = ["std", "proc"] }
|
|
||||||
convert_case = "0.10.0"
|
|
||||||
criterion = { version = "0.8", features = ["html_reports"] }
|
|
||||||
crossbeam-queue = "0.3.12"
|
|
||||||
datafusion = "51.0.0"
|
|
||||||
derive_builder = "0.20.2"
|
|
||||||
enumset = "1.1.10"
|
|
||||||
faster-hex = "0.10.0"
|
|
||||||
flate2 = "1.1.5"
|
|
||||||
flexi_logger = { version = "0.31.7", features = ["trc", "dont_minimize_extra_stacks", "compress", "kv", "json"] }
|
|
||||||
glob = "0.3.3"
|
|
||||||
google-cloud-storage = "1.4.0"
|
|
||||||
google-cloud-auth = "1.2.0"
|
|
||||||
hashbrown = { version = "0.16.1", features = ["serde", "rayon"] }
|
|
||||||
heed = { version = "0.22.0" }
|
|
||||||
hex-simd = "0.8.0"
|
|
||||||
highway = { version = "1.3.0" }
|
highway = { version = "1.3.0" }
|
||||||
ipnetwork = { version = "0.21.1", features = ["serde"] }
|
hyper = "1.6.0"
|
||||||
|
hyper-util = { version = "0.1.14", features = [
|
||||||
|
"tokio",
|
||||||
|
"server-auto",
|
||||||
|
"server-graceful",
|
||||||
|
] }
|
||||||
|
http = "1.3.1"
|
||||||
|
http-body = "1.0.1"
|
||||||
|
humantime = "2.2.0"
|
||||||
|
include_dir = "0.7.4"
|
||||||
|
jsonwebtoken = "9.3.1"
|
||||||
|
keyring = { version = "3.6.2", features = [
|
||||||
|
"apple-native",
|
||||||
|
"windows-native",
|
||||||
|
"sync-secret-service",
|
||||||
|
] }
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
libc = "0.2.178"
|
libsystemd = { version = "0.7.2" }
|
||||||
libsystemd = "0.7.2"
|
local-ip-address = "0.6.5"
|
||||||
local-ip-address = "0.6.6"
|
matchit = "0.8.4"
|
||||||
lz4 = "1.28.1"
|
md-5 = "0.10.6"
|
||||||
matchit = "0.9.0"
|
mime = "0.3.17"
|
||||||
md-5 = "0.11.0-rc.3"
|
|
||||||
md5 = "0.8.0"
|
|
||||||
mime_guess = "2.0.5"
|
mime_guess = "2.0.5"
|
||||||
moka = { version = "0.12.11", features = ["future"] }
|
|
||||||
netif = "0.1.6"
|
netif = "0.1.6"
|
||||||
nix = { version = "0.30.1", features = ["fs"] }
|
nix = { version = "0.30.1", features = ["fs"] }
|
||||||
nu-ansi-term = "0.50.3"
|
nu-ansi-term = "0.50.1"
|
||||||
num_cpus = { version = "1.17.0" }
|
num_cpus = { version = "1.17.0" }
|
||||||
nvml-wrapper = "0.11.0"
|
nvml-wrapper = "0.11.0"
|
||||||
object_store = "0.12.4"
|
object_store = "0.11.2"
|
||||||
parking_lot = "0.12.5"
|
once_cell = "1.21.3"
|
||||||
path-absolutize = "3.1.1"
|
opentelemetry = { version = "0.30.0" }
|
||||||
path-clean = "1.0.1"
|
opentelemetry-appender-tracing = { version = "0.30.1", features = [
|
||||||
|
"experimental_use_tracing_span_context",
|
||||||
|
"experimental_metadata_attributes",
|
||||||
|
"spec_unstable_logs_enabled"
|
||||||
|
] }
|
||||||
|
opentelemetry_sdk = { version = "0.30.0" }
|
||||||
|
opentelemetry-stdout = { version = "0.30.0" }
|
||||||
|
opentelemetry-otlp = { version = "0.30.0", default-features = false, features = [
|
||||||
|
"grpc-tonic", "gzip-tonic", "trace", "metrics", "logs", "internal-logs"
|
||||||
|
] }
|
||||||
|
opentelemetry-semantic-conventions = { version = "0.30.0", features = [
|
||||||
|
"semconv_experimental",
|
||||||
|
] }
|
||||||
|
parking_lot = "0.12.4"
|
||||||
|
percent-encoding = "2.3.1"
|
||||||
pin-project-lite = "0.2.16"
|
pin-project-lite = "0.2.16"
|
||||||
pretty_assertions = "1.4.1"
|
# pin-utils = "0.1.0"
|
||||||
rand = { version = "0.10.0-rc.5", features = ["serde"] }
|
prost = "0.13.5"
|
||||||
rayon = "1.11.0"
|
prost-build = "0.13.5"
|
||||||
reed-solomon-simd = { version = "3.1.0" }
|
protobuf = "3.7"
|
||||||
regex = { version = "1.12.2" }
|
rand = "0.8.5"
|
||||||
rumqttc = { version = "0.25.1" }
|
rdkafka = { version = "0.37.0", features = ["tokio"] }
|
||||||
rust-embed = { version = "8.9.0" }
|
reed-solomon-erasure = { version = "6.0.0", features = ["simd-accel"] }
|
||||||
rustc-hash = { version = "2.1.1" }
|
regex = { version = "1.11.1" }
|
||||||
s3s = { version = "0.12.0-rc.4", features = ["minio"] }
|
reqwest = { version = "0.12.19", default-features = false, features = [
|
||||||
serial_test = "3.2.0"
|
"rustls-tls",
|
||||||
shadow-rs = { version = "1.4.0", default-features = false }
|
"charset",
|
||||||
siphasher = "1.0.1"
|
"http2",
|
||||||
smallvec = { version = "1.15.1", features = ["serde"] }
|
"system-proxy",
|
||||||
smartstring = "1.0.1"
|
"stream",
|
||||||
snafu = "0.8.9"
|
"json",
|
||||||
snap = "1.1.1"
|
"blocking",
|
||||||
starshard = { version = "0.6.0", features = ["rayon", "async", "serde"] }
|
] }
|
||||||
strum = { version = "0.27.2", features = ["derive"] }
|
rfd = { version = "0.15.3", default-features = false, features = [
|
||||||
sysctl = "0.7.1"
|
"xdg-portal",
|
||||||
sysinfo = "0.37.2"
|
"tokio",
|
||||||
temp-env = "0.3.6"
|
] }
|
||||||
tempfile = "3.23.0"
|
rmp = "0.8.14"
|
||||||
|
rmp-serde = "1.3.0"
|
||||||
|
rumqttc = { version = "0.24" }
|
||||||
|
rust-embed = { version = "8.7.2" }
|
||||||
|
rustfs-rsc = "2025.506.1"
|
||||||
|
rustls = { version = "0.23.27" }
|
||||||
|
rustls-pki-types = "1.12.0"
|
||||||
|
rustls-pemfile = "2.2.0"
|
||||||
|
s3s = { git = "https://github.com/Nugine/s3s.git", rev = "4733cdfb27b2713e832967232cbff413bb768c10" }
|
||||||
|
s3s-policy = { git = "https://github.com/Nugine/s3s.git", rev = "4733cdfb27b2713e832967232cbff413bb768c10" }
|
||||||
|
shadow-rs = { version = "1.1.1", default-features = false }
|
||||||
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
|
serde_json = "1.0.140"
|
||||||
|
serde_urlencoded = "0.7.1"
|
||||||
|
serde_with = "3.12.0"
|
||||||
|
sha2 = "0.10.9"
|
||||||
|
smallvec = { version = "1.15.0", features = ["serde"] }
|
||||||
|
snafu = "0.8.6"
|
||||||
|
socket2 = "0.5.10"
|
||||||
|
strum = { version = "0.27.1", features = ["derive"] }
|
||||||
|
sysinfo = "0.35.2"
|
||||||
|
tempfile = "3.20.0"
|
||||||
test-case = "3.3.1"
|
test-case = "3.3.1"
|
||||||
thiserror = "2.0.17"
|
thiserror = "2.0.12"
|
||||||
tracing = { version = "0.1.43" }
|
time = { version = "0.3.41", features = [
|
||||||
tracing-appender = "0.2.4"
|
"std",
|
||||||
|
"parsing",
|
||||||
|
"formatting",
|
||||||
|
"macros",
|
||||||
|
"serde",
|
||||||
|
] }
|
||||||
|
tokio = { version = "1.45.1", features = ["fs", "rt-multi-thread"] }
|
||||||
|
tonic = { version = "0.13.1", features = ["gzip"] }
|
||||||
|
tonic-build = { version = "0.13.1" }
|
||||||
|
tokio-rustls = { version = "0.26.2", default-features = false }
|
||||||
|
tokio-stream = { version = "0.1.17" }
|
||||||
|
tokio-util = { version = "0.7.15", features = ["io", "compat"] }
|
||||||
|
tower = { version = "0.5.2", features = ["timeout"] }
|
||||||
|
tower-http = { version = "0.6.6", features = ["cors"] }
|
||||||
|
tracing = "0.1.41"
|
||||||
|
tracing-core = "0.1.34"
|
||||||
tracing-error = "0.2.1"
|
tracing-error = "0.2.1"
|
||||||
tracing-opentelemetry = "0.32.0"
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter", "time"] }
|
||||||
tracing-subscriber = { version = "0.3.22", features = ["env-filter", "time"] }
|
tracing-appender = "0.2.3"
|
||||||
|
tracing-opentelemetry = "0.31.0"
|
||||||
transform-stream = "0.3.1"
|
transform-stream = "0.3.1"
|
||||||
url = "2.5.7"
|
url = "2.5.4"
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
uuid = { version = "1.19.0", features = ["v4", "fast-rng", "macro-diagnostics"] }
|
uuid = { version = "1.17.0", features = [
|
||||||
vaultrs = { version = "0.7.4" }
|
"v4",
|
||||||
walkdir = "2.5.0"
|
"fast-rng",
|
||||||
wildmatch = { version = "2.6.1", features = ["serde"] }
|
"macro-diagnostics",
|
||||||
|
] }
|
||||||
winapi = { version = "0.3.9" }
|
winapi = { version = "0.3.9" }
|
||||||
xxhash-rust = { version = "0.8.15", features = ["xxh64", "xxh3"] }
|
|
||||||
zip = "6.0.0"
|
|
||||||
zstd = "0.13.3"
|
|
||||||
|
|
||||||
# Observability and Metrics
|
|
||||||
metrics = "0.24.3"
|
|
||||||
opentelemetry = { version = "0.31.0" }
|
|
||||||
opentelemetry-appender-tracing = { version = "0.31.1", features = ["experimental_use_tracing_span_context", "experimental_metadata_attributes", "spec_unstable_logs_enabled"] }
|
|
||||||
opentelemetry-otlp = { version = "0.31.0", features = ["gzip-http", "reqwest-rustls"] }
|
|
||||||
opentelemetry_sdk = { version = "0.31.0" }
|
|
||||||
opentelemetry-semantic-conventions = { version = "0.31.0", features = ["semconv_experimental"] }
|
|
||||||
opentelemetry-stdout = { version = "0.31.0" }
|
|
||||||
|
|
||||||
# Performance Analysis and Memory Profiling
|
|
||||||
mimalloc = "0.1"
|
|
||||||
# Use tikv-jemallocator as memory allocator and enable performance analysis
|
|
||||||
tikv-jemallocator = { version = "0.6", features = ["profiling", "stats", "unprefixed_malloc_on_supported_platforms", "background_threads"] }
|
|
||||||
# Used to control and obtain statistics for jemalloc at runtime
|
|
||||||
tikv-jemalloc-ctl = { version = "0.6", features = ["use_std", "stats", "profiling"] }
|
|
||||||
# Used to generate pprof-compatible memory profiling data and support symbolization and flame graphs
|
|
||||||
jemalloc_pprof = { version = "0.8.1", features = ["symbolize", "flamegraph"] }
|
|
||||||
# Used to generate CPU performance analysis data and flame diagrams
|
|
||||||
pprof = { version = "0.15.0", features = ["flamegraph", "protobuf-codec"] }
|
|
||||||
|
|
||||||
|
|
||||||
|
[profile.wasm-dev]
|
||||||
|
inherits = "dev"
|
||||||
|
opt-level = 1
|
||||||
|
|
||||||
[workspace.metadata.cargo-shear]
|
[profile.server-dev]
|
||||||
ignored = ["rustfs", "rustfs-mcp", "tokio-test"]
|
inherits = "dev"
|
||||||
|
|
||||||
|
[profile.android-dev]
|
||||||
|
inherits = "dev"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
lto = "thin"
|
||||||
|
codegen-units = 1
|
||||||
|
panic = "abort" # Optional, remove the panic expansion code
|
||||||
|
strip = true # strip symbol information to reduce binary size
|
||||||
|
|
||||||
[profile.production]
|
[profile.production]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
|
|||||||
@@ -11,25 +11,21 @@
|
|||||||
Before every commit, you **MUST**:
|
Before every commit, you **MUST**:
|
||||||
|
|
||||||
1. **Format your code**:
|
1. **Format your code**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo fmt --all
|
cargo fmt --all
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Verify formatting**:
|
2. **Verify formatting**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo fmt --all --check
|
cargo fmt --all --check
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Pass clippy checks**:
|
3. **Pass clippy checks**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo clippy --all-targets --all-features -- -D warnings
|
cargo clippy --all-targets --all-features -- -D warnings
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Ensure compilation**:
|
4. **Ensure compilation**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo check --all-targets
|
cargo check --all-targets
|
||||||
```
|
```
|
||||||
@@ -140,7 +136,6 @@ Install the `rust-analyzer` extension and add to your `settings.json`:
|
|||||||
#### Other IDEs
|
#### Other IDEs
|
||||||
|
|
||||||
Configure your IDE to:
|
Configure your IDE to:
|
||||||
|
|
||||||
- Use the project's `rustfmt.toml` configuration
|
- Use the project's `rustfmt.toml` configuration
|
||||||
- Format on save
|
- Format on save
|
||||||
- Run clippy checks
|
- Run clippy checks
|
||||||
101
Dockerfile
101
Dockerfile
@@ -1,94 +1,17 @@
|
|||||||
FROM alpine:3.22 AS build
|
FROM alpine:latest
|
||||||
|
|
||||||
ARG TARGETARCH
|
# RUN apk add --no-cache <package-name>
|
||||||
ARG RELEASE=latest
|
|
||||||
|
|
||||||
RUN apk add --no-cache ca-certificates curl unzip
|
WORKDIR /app
|
||||||
WORKDIR /build
|
|
||||||
|
|
||||||
RUN set -eux; \
|
RUN mkdir -p /data/rustfs0 /data/rustfs1 /data/rustfs2 /data/rustfs3
|
||||||
case "$TARGETARCH" in \
|
|
||||||
amd64) ARCH_SUBSTR="x86_64-musl" ;; \
|
COPY ./target/x86_64-unknown-linux-musl/release/rustfs /app/rustfs
|
||||||
arm64) ARCH_SUBSTR="aarch64-musl" ;; \
|
|
||||||
*) echo "Unsupported TARGETARCH=$TARGETARCH" >&2; exit 1 ;; \
|
RUN chmod +x /app/rustfs
|
||||||
esac; \
|
|
||||||
if [ "$RELEASE" = "latest" ]; then \
|
EXPOSE 9000
|
||||||
TAG="$(curl -fsSL https://api.github.com/repos/rustfs/rustfs/releases \
|
EXPOSE 9001
|
||||||
| grep -o '"tag_name": "[^"]*"' | cut -d'"' -f4 | head -n 1)"; \
|
|
||||||
else \
|
|
||||||
TAG="$RELEASE"; \
|
|
||||||
fi; \
|
|
||||||
echo "Using tag: $TAG (arch pattern: $ARCH_SUBSTR)"; \
|
|
||||||
# Find download URL in assets list for this tag that contains arch substring and ends with .zip
|
|
||||||
URL="$(curl -fsSL "https://api.github.com/repos/rustfs/rustfs/releases/tags/$TAG" \
|
|
||||||
| grep -o "\"browser_download_url\": \"[^\"]*${ARCH_SUBSTR}[^\"]*\\.zip\"" \
|
|
||||||
| cut -d'"' -f4 | head -n 1)"; \
|
|
||||||
if [ -z "$URL" ]; then echo "Failed to locate release asset for $ARCH_SUBSTR at tag $TAG" >&2; exit 1; fi; \
|
|
||||||
echo "Downloading: $URL"; \
|
|
||||||
curl -fL "$URL" -o rustfs.zip; \
|
|
||||||
unzip -q rustfs.zip -d /build; \
|
|
||||||
# If binary is not in root directory, try to locate and move from zip to /build/rustfs
|
|
||||||
if [ ! -x /build/rustfs ]; then \
|
|
||||||
BIN_PATH="$(unzip -Z -1 rustfs.zip | grep -E '(^|/)rustfs$' | head -n 1 || true)"; \
|
|
||||||
if [ -n "$BIN_PATH" ]; then \
|
|
||||||
mkdir -p /build/.tmp && unzip -q rustfs.zip "$BIN_PATH" -d /build/.tmp && \
|
|
||||||
mv "/build/.tmp/$BIN_PATH" /build/rustfs; \
|
|
||||||
fi; \
|
|
||||||
fi; \
|
|
||||||
[ -x /build/rustfs ] || { echo "rustfs binary not found in asset" >&2; exit 1; }; \
|
|
||||||
chmod +x /build/rustfs; \
|
|
||||||
rm -rf rustfs.zip /build/.tmp || true
|
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3.22
|
CMD ["/app/rustfs"]
|
||||||
|
|
||||||
ARG RELEASE=latest
|
|
||||||
ARG BUILD_DATE
|
|
||||||
ARG VCS_REF
|
|
||||||
|
|
||||||
LABEL name="RustFS" \
|
|
||||||
vendor="RustFS Team" \
|
|
||||||
maintainer="RustFS Team <dev@rustfs.com>" \
|
|
||||||
version="v${RELEASE#v}" \
|
|
||||||
release="${RELEASE}" \
|
|
||||||
build-date="${BUILD_DATE}" \
|
|
||||||
vcs-ref="${VCS_REF}" \
|
|
||||||
summary="High-performance distributed object storage system compatible with S3 API" \
|
|
||||||
description="RustFS is a distributed object storage system written in Rust, supporting erasure coding, multi-tenant management, and observability." \
|
|
||||||
url="https://rustfs.com" \
|
|
||||||
license="Apache-2.0"
|
|
||||||
|
|
||||||
RUN apk add --no-cache ca-certificates coreutils curl
|
|
||||||
|
|
||||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
|
||||||
COPY --from=build /build/rustfs /usr/bin/rustfs
|
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
|
||||||
|
|
||||||
RUN chmod +x /usr/bin/rustfs /entrypoint.sh
|
|
||||||
|
|
||||||
RUN addgroup -g 10001 -S rustfs && \
|
|
||||||
adduser -u 10001 -G rustfs -S rustfs -D && \
|
|
||||||
mkdir -p /data /logs && \
|
|
||||||
chown -R rustfs:rustfs /data /logs && \
|
|
||||||
chmod 0750 /data /logs
|
|
||||||
|
|
||||||
ENV RUSTFS_ADDRESS=":9000" \
|
|
||||||
RUSTFS_CONSOLE_ADDRESS=":9001" \
|
|
||||||
RUSTFS_ACCESS_KEY="rustfsadmin" \
|
|
||||||
RUSTFS_SECRET_KEY="rustfsadmin" \
|
|
||||||
RUSTFS_CONSOLE_ENABLE="true" \
|
|
||||||
RUSTFS_EXTERNAL_ADDRESS="" \
|
|
||||||
RUSTFS_CORS_ALLOWED_ORIGINS="*" \
|
|
||||||
RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="*" \
|
|
||||||
RUSTFS_VOLUMES="/data" \
|
|
||||||
RUST_LOG="warn"
|
|
||||||
|
|
||||||
EXPOSE 9000 9001
|
|
||||||
|
|
||||||
VOLUME ["/data"]
|
|
||||||
|
|
||||||
USER rustfs
|
|
||||||
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
|
||||||
|
|
||||||
CMD ["rustfs"]
|
|
||||||
21
Dockerfile.obs
Normal file
21
Dockerfile.obs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
FROM ubuntu:latest
|
||||||
|
|
||||||
|
# RUN apk add --no-cache <package-name>
|
||||||
|
# 如果 rustfs 有依赖,可以在这里添加,例如:
|
||||||
|
# RUN apk add --no-cache openssl
|
||||||
|
# RUN apk add --no-cache bash # 安装 Bash
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 创建与 RUSTFS_VOLUMES 一致的目录
|
||||||
|
RUN mkdir -p /root/data/target/volume/test1 /root/data/target/volume/test2 /root/data/target/volume/test3 /root/data/target/volume/test4
|
||||||
|
|
||||||
|
# COPY ./target/x86_64-unknown-linux-musl/release/rustfs /app/rustfs
|
||||||
|
COPY ./target/x86_64-unknown-linux-gnu/release/rustfs /app/rustfs
|
||||||
|
|
||||||
|
RUN chmod +x /app/rustfs
|
||||||
|
|
||||||
|
EXPOSE 9000
|
||||||
|
EXPOSE 9002
|
||||||
|
|
||||||
|
CMD ["/app/rustfs"]
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
# syntax=docker/dockerfile:1.6
|
|
||||||
# Multi-stage Dockerfile for RustFS - LOCAL DEVELOPMENT ONLY
|
|
||||||
#
|
|
||||||
# IMPORTANT: This Dockerfile builds RustFS from source for local development and testing.
|
|
||||||
# CI/CD uses the production Dockerfile with prebuilt binaries instead.
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
# docker build -f Dockerfile.source -t rustfs:dev-local .
|
|
||||||
# docker run --rm -p 9000:9000 rustfs:dev-local
|
|
||||||
#
|
|
||||||
# Supports cross-compilation for amd64 and arm64 via TARGETPLATFORM.
|
|
||||||
|
|
||||||
ARG TARGETPLATFORM
|
|
||||||
ARG BUILDPLATFORM
|
|
||||||
|
|
||||||
# -----------------------------
|
|
||||||
# Build stage
|
|
||||||
# -----------------------------
|
|
||||||
FROM rust:1.88-bookworm AS builder
|
|
||||||
|
|
||||||
# Re-declare args after FROM
|
|
||||||
ARG TARGETPLATFORM
|
|
||||||
ARG BUILDPLATFORM
|
|
||||||
|
|
||||||
# Debug: print platforms
|
|
||||||
RUN echo "Build info -> BUILDPLATFORM=${BUILDPLATFORM}, TARGETPLATFORM=${TARGETPLATFORM}"
|
|
||||||
|
|
||||||
# Install build toolchain and headers
|
|
||||||
# Use distro packages for protoc/flatc to avoid host-arch mismatch
|
|
||||||
RUN set -eux; \
|
|
||||||
export DEBIAN_FRONTEND=noninteractive; \
|
|
||||||
apt-get update; \
|
|
||||||
apt-get install -y --no-install-recommends \
|
|
||||||
build-essential \
|
|
||||||
ca-certificates \
|
|
||||||
curl \
|
|
||||||
git \
|
|
||||||
pkg-config \
|
|
||||||
libssl-dev \
|
|
||||||
lld \
|
|
||||||
protobuf-compiler \
|
|
||||||
flatbuffers-compiler; \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Optional: cross toolchain for aarch64 (only when targeting linux/arm64)
|
|
||||||
RUN set -eux; \
|
|
||||||
if [ "${TARGETPLATFORM:-linux/amd64}" = "linux/arm64" ]; then \
|
|
||||||
export DEBIAN_FRONTEND=noninteractive; \
|
|
||||||
apt-get update; \
|
|
||||||
apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu; \
|
|
||||||
rm -rf /var/lib/apt/lists/*; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Add Rust targets based on TARGETPLATFORM
|
|
||||||
RUN set -eux; \
|
|
||||||
case "${TARGETPLATFORM:-linux/amd64}" in \
|
|
||||||
linux/amd64) rustup target add x86_64-unknown-linux-gnu ;; \
|
|
||||||
linux/arm64) rustup target add aarch64-unknown-linux-gnu ;; \
|
|
||||||
*) echo "Unsupported TARGETPLATFORM=${TARGETPLATFORM}" >&2; exit 1 ;; \
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Cross-compilation environment (used only when targeting aarch64)
|
|
||||||
ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc
|
|
||||||
ENV CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc
|
|
||||||
ENV CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++
|
|
||||||
|
|
||||||
WORKDIR /usr/src/rustfs
|
|
||||||
|
|
||||||
# Layered copy to maximize caching:
|
|
||||||
# 1) top-level manifests
|
|
||||||
COPY Cargo.toml Cargo.lock ./
|
|
||||||
# 2) workspace member manifests (adjust if workspace layout changes)
|
|
||||||
COPY rustfs/Cargo.toml rustfs/Cargo.toml
|
|
||||||
COPY crates/*/Cargo.toml crates/
|
|
||||||
COPY cli/rustfs-gui/Cargo.toml cli/rustfs-gui/Cargo.toml
|
|
||||||
|
|
||||||
# Pre-fetch dependencies for better caching
|
|
||||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
|
||||||
--mount=type=cache,target=/usr/local/cargo/git \
|
|
||||||
cargo fetch --locked || true
|
|
||||||
|
|
||||||
# 3) copy full sources (this is the main cache invalidation point)
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Cargo build configuration for lean release artifacts
|
|
||||||
ENV CARGO_NET_GIT_FETCH_WITH_CLI=true \
|
|
||||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse \
|
|
||||||
CARGO_INCREMENTAL=0 \
|
|
||||||
CARGO_PROFILE_RELEASE_DEBUG=false \
|
|
||||||
CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO=off \
|
|
||||||
CARGO_PROFILE_RELEASE_STRIP=symbols
|
|
||||||
|
|
||||||
# Generate protobuf/flatbuffers code (uses protoc/flatc from distro)
|
|
||||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
|
||||||
--mount=type=cache,target=/usr/local/cargo/git \
|
|
||||||
--mount=type=cache,target=/usr/src/rustfs/target \
|
|
||||||
cargo run --bin gproto
|
|
||||||
|
|
||||||
# Build RustFS (target depends on TARGETPLATFORM)
|
|
||||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
|
||||||
--mount=type=cache,target=/usr/local/cargo/git \
|
|
||||||
--mount=type=cache,target=/usr/src/rustfs/target \
|
|
||||||
set -eux; \
|
|
||||||
case "${TARGETPLATFORM:-linux/amd64}" in \
|
|
||||||
linux/amd64) \
|
|
||||||
echo "Building for x86_64-unknown-linux-gnu"; \
|
|
||||||
cargo build --release --locked --target x86_64-unknown-linux-gnu --bin rustfs -j "$(nproc)"; \
|
|
||||||
install -m 0755 target/x86_64-unknown-linux-gnu/release/rustfs /usr/local/bin/rustfs \
|
|
||||||
;; \
|
|
||||||
linux/arm64) \
|
|
||||||
echo "Building for aarch64-unknown-linux-gnu"; \
|
|
||||||
cargo build --release --locked --target aarch64-unknown-linux-gnu --bin rustfs -j "$(nproc)"; \
|
|
||||||
install -m 0755 target/aarch64-unknown-linux-gnu/release/rustfs /usr/local/bin/rustfs \
|
|
||||||
;; \
|
|
||||||
*) \
|
|
||||||
echo "Unsupported TARGETPLATFORM=${TARGETPLATFORM}" >&2; exit 1 \
|
|
||||||
;; \
|
|
||||||
esac
|
|
||||||
|
|
||||||
# -----------------------------
|
|
||||||
# Runtime stage (Ubuntu minimal)
|
|
||||||
# -----------------------------
|
|
||||||
FROM ubuntu:22.04
|
|
||||||
|
|
||||||
ARG BUILD_DATE
|
|
||||||
ARG VCS_REF
|
|
||||||
|
|
||||||
LABEL name="RustFS (dev-local)" \
|
|
||||||
maintainer="RustFS Team" \
|
|
||||||
build-date="${BUILD_DATE}" \
|
|
||||||
vcs-ref="${VCS_REF}" \
|
|
||||||
description="RustFS - local development image built from source (NOT for production)."
|
|
||||||
|
|
||||||
# Minimal runtime deps: certificates + tzdata + coreutils (for chroot --userspec)
|
|
||||||
RUN set -eux; \
|
|
||||||
export DEBIAN_FRONTEND=noninteractive; \
|
|
||||||
apt-get update; \
|
|
||||||
apt-get install -y --no-install-recommends \
|
|
||||||
ca-certificates \
|
|
||||||
tzdata \
|
|
||||||
coreutils; \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Create a conventional runtime user/group (final switch happens in entrypoint via chroot --userspec)
|
|
||||||
RUN set -eux; \
|
|
||||||
groupadd -g 1000 rustfs; \
|
|
||||||
useradd -u 1000 -g rustfs -M -s /usr/sbin/nologin rustfs
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Prepare data/log directories with sane defaults
|
|
||||||
RUN set -eux; \
|
|
||||||
mkdir -p /data /logs; \
|
|
||||||
chown -R rustfs:rustfs /data /logs /app; \
|
|
||||||
chmod 0750 /data /logs
|
|
||||||
|
|
||||||
# Copy the freshly built binary and the entrypoint
|
|
||||||
COPY --from=builder /usr/local/bin/rustfs /usr/bin/rustfs
|
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
|
||||||
RUN chmod +x /usr/bin/rustfs /entrypoint.sh
|
|
||||||
|
|
||||||
# Default environment (override in docker run/compose as needed)
|
|
||||||
ENV RUSTFS_ADDRESS=":9000" \
|
|
||||||
RUSTFS_ACCESS_KEY="rustfsadmin" \
|
|
||||||
RUSTFS_SECRET_KEY="rustfsadmin" \
|
|
||||||
RUSTFS_CONSOLE_ENABLE="true" \
|
|
||||||
RUSTFS_VOLUMES="/data" \
|
|
||||||
RUST_LOG="warn" \
|
|
||||||
RUSTFS_USERNAME="rustfs" \
|
|
||||||
RUSTFS_GROUPNAME="rustfs" \
|
|
||||||
RUSTFS_UID="1000" \
|
|
||||||
RUSTFS_GID="1000"
|
|
||||||
|
|
||||||
EXPOSE 9000
|
|
||||||
VOLUME ["/data"]
|
|
||||||
|
|
||||||
# Keep root here; entrypoint will drop privileges using chroot --userspec
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
|
||||||
CMD ["/usr/bin/rustfs"]
|
|
||||||
258
Justfile
258
Justfile
@@ -1,258 +0,0 @@
|
|||||||
DOCKER_CLI := env("DOCKER_CLI", "docker")
|
|
||||||
IMAGE_NAME := env("IMAGE_NAME", "rustfs:v1.0.0")
|
|
||||||
DOCKERFILE_SOURCE := env("DOCKERFILE_SOURCE", "Dockerfile.source")
|
|
||||||
DOCKERFILE_PRODUCTION := env("DOCKERFILE_PRODUCTION", "Dockerfile")
|
|
||||||
CONTAINER_NAME := env("CONTAINER_NAME", "rustfs-dev")
|
|
||||||
|
|
||||||
[group("📒 Help")]
|
|
||||||
[private]
|
|
||||||
default:
|
|
||||||
@just --list --list-heading $'🦀 RustFS justfile manual page:\n'
|
|
||||||
|
|
||||||
[doc("show help")]
|
|
||||||
[group("📒 Help")]
|
|
||||||
help: default
|
|
||||||
|
|
||||||
[doc("run `cargo fmt` to format codes")]
|
|
||||||
[group("👆 Code Quality")]
|
|
||||||
fmt:
|
|
||||||
@echo "🔧 Formatting code..."
|
|
||||||
cargo fmt --all
|
|
||||||
|
|
||||||
[doc("run `cargo fmt` in check mode")]
|
|
||||||
[group("👆 Code Quality")]
|
|
||||||
fmt-check:
|
|
||||||
@echo "📝 Checking code formatting..."
|
|
||||||
cargo fmt --all --check
|
|
||||||
|
|
||||||
[doc("run `cargo clippy`")]
|
|
||||||
[group("👆 Code Quality")]
|
|
||||||
clippy:
|
|
||||||
@echo "🔍 Running clippy checks..."
|
|
||||||
cargo clippy --all-targets --all-features --fix --allow-dirty -- -D warnings
|
|
||||||
|
|
||||||
[doc("run `cargo check`")]
|
|
||||||
[group("👆 Code Quality")]
|
|
||||||
check:
|
|
||||||
@echo "🔨 Running compilation check..."
|
|
||||||
cargo check --all-targets
|
|
||||||
|
|
||||||
[doc("run `cargo test`")]
|
|
||||||
[group("👆 Code Quality")]
|
|
||||||
test:
|
|
||||||
@echo "🧪 Running tests..."
|
|
||||||
cargo nextest run --all --exclude e2e_test
|
|
||||||
cargo test --all --doc
|
|
||||||
|
|
||||||
[doc("run `fmt` `clippy` `check` `test` at once")]
|
|
||||||
[group("👆 Code Quality")]
|
|
||||||
pre-commit: fmt clippy check test
|
|
||||||
@echo "✅ All pre-commit checks passed!"
|
|
||||||
|
|
||||||
[group("🤔 Git")]
|
|
||||||
setup-hooks:
|
|
||||||
@echo "🔧 Setting up git hooks..."
|
|
||||||
chmod +x .git/hooks/pre-commit
|
|
||||||
@echo "✅ Git hooks setup complete!"
|
|
||||||
|
|
||||||
[doc("use `release` mode for building")]
|
|
||||||
[group("🔨 Build")]
|
|
||||||
build:
|
|
||||||
@echo "🔨 Building RustFS using build-rustfs.sh script..."
|
|
||||||
./build-rustfs.sh
|
|
||||||
|
|
||||||
[doc("use `debug` mode for building")]
|
|
||||||
[group("🔨 Build")]
|
|
||||||
build-dev:
|
|
||||||
@echo "🔨 Building RustFS in development mode..."
|
|
||||||
./build-rustfs.sh --dev
|
|
||||||
|
|
||||||
[group("🔨 Build")]
|
|
||||||
[private]
|
|
||||||
build-target target:
|
|
||||||
@echo "🔨 Building rustfs for {{ target }}..."
|
|
||||||
@echo "💡 On macOS/Windows, use 'make build-docker' or 'make docker-dev' instead"
|
|
||||||
./build-rustfs.sh --platform {{ target }}
|
|
||||||
|
|
||||||
[doc("use `x86_64-unknown-linux-musl` target for building")]
|
|
||||||
[group("🔨 Build")]
|
|
||||||
build-musl: (build-target "x86_64-unknown-linux-musl")
|
|
||||||
|
|
||||||
[doc("use `x86_64-unknown-linux-gnu` target for building")]
|
|
||||||
[group("🔨 Build")]
|
|
||||||
build-gnu: (build-target "x86_64-unknown-linux-gnu")
|
|
||||||
|
|
||||||
[doc("use `aarch64-unknown-linux-musl` target for building")]
|
|
||||||
[group("🔨 Build")]
|
|
||||||
build-musl-arm64: (build-target "aarch64-unknown-linux-musl")
|
|
||||||
|
|
||||||
[doc("use `aarch64-unknown-linux-gnu` target for building")]
|
|
||||||
[group("🔨 Build")]
|
|
||||||
build-gnu-arm64: (build-target "aarch64-unknown-linux-gnu")
|
|
||||||
|
|
||||||
[doc("build and deploy to server")]
|
|
||||||
[group("🔨 Build")]
|
|
||||||
deploy-dev ip: build-musl
|
|
||||||
@echo "🚀 Deploying to dev server: {{ ip }}"
|
|
||||||
./scripts/dev_deploy.sh {{ ip }}
|
|
||||||
|
|
||||||
[group("🔨 Build")]
|
|
||||||
[private]
|
|
||||||
build-cross-all-pre:
|
|
||||||
@echo "🔧 Building all target architectures..."
|
|
||||||
@echo "💡 On macOS/Windows, use 'make docker-dev' for reliable multi-arch builds"
|
|
||||||
@echo "🔨 Generating protobuf code..."
|
|
||||||
-cargo run --bin gproto
|
|
||||||
|
|
||||||
[doc("build all targets at once")]
|
|
||||||
[group("🔨 Build")]
|
|
||||||
build-cross-all: build-cross-all-pre && build-gnu build-gnu-arm64 build-musl build-musl-arm64
|
|
||||||
|
|
||||||
# ========================================================================================
|
|
||||||
# Docker Multi-Architecture Builds (Primary Methods)
|
|
||||||
# ========================================================================================
|
|
||||||
|
|
||||||
[doc("build an image and run it")]
|
|
||||||
[group("🐳 Build Image")]
|
|
||||||
build-docker os="rockylinux9.3" cli=(DOCKER_CLI) dockerfile=(DOCKERFILE_SOURCE):
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
SOURCE_BUILD_IMAGE_NAME="rustfs/rustfs-{{ os }}:v1"
|
|
||||||
SOURCE_BUILD_CONTAINER_NAME="rustfs-{{ os }}-build"
|
|
||||||
BUILD_CMD="/root/.cargo/bin/cargo build --release --bin rustfs --target-dir /root/s3-rustfs/target/{{ os }}"
|
|
||||||
echo "🐳 Building RustFS using Docker ({{ os }})..."
|
|
||||||
{{ cli }} buildx build -t $SOURCE_BUILD_IMAGE_NAME -f {{ dockerfile }} .
|
|
||||||
{{ cli }} run --rm --name $SOURCE_BUILD_CONTAINER_NAME -v $(pwd):/root/s3-rustfs -it $SOURCE_BUILD_IMAGE_NAME $BUILD_CMD
|
|
||||||
|
|
||||||
[doc("build an image")]
|
|
||||||
[group("🐳 Build Image")]
|
|
||||||
docker-buildx:
|
|
||||||
@echo "🏗️ Building multi-architecture production Docker images with buildx..."
|
|
||||||
./docker-buildx.sh
|
|
||||||
|
|
||||||
[doc("build an image and push it")]
|
|
||||||
[group("🐳 Build Image")]
|
|
||||||
docker-buildx-push:
|
|
||||||
@echo "🚀 Building and pushing multi-architecture production Docker images with buildx..."
|
|
||||||
./docker-buildx.sh --push
|
|
||||||
|
|
||||||
[doc("build an image with a version")]
|
|
||||||
[group("🐳 Build Image")]
|
|
||||||
docker-buildx-version version:
|
|
||||||
@echo "🏗️ Building multi-architecture production Docker images (version: {{ version }}..."
|
|
||||||
./docker-buildx.sh --release {{ version }}
|
|
||||||
|
|
||||||
[doc("build an image with a version and push it")]
|
|
||||||
[group("🐳 Build Image")]
|
|
||||||
docker-buildx-push-version version:
|
|
||||||
@echo "🚀 Building and pushing multi-architecture production Docker images (version: {{ version }}..."
|
|
||||||
./docker-buildx.sh --release {{ version }} --push
|
|
||||||
|
|
||||||
[doc("build an image with a version and push it to registry")]
|
|
||||||
[group("🐳 Build Image")]
|
|
||||||
docker-dev-push registry cli=(DOCKER_CLI) source=(DOCKERFILE_SOURCE):
|
|
||||||
@echo "🚀 Building and pushing multi-architecture development Docker images..."
|
|
||||||
@echo "💡 push to registry: {{ registry }}"
|
|
||||||
{{ cli }} buildx build \
|
|
||||||
--platform linux/amd64,linux/arm64 \
|
|
||||||
--file {{ source }} \
|
|
||||||
--tag {{ registry }}/rustfs:source-latest \
|
|
||||||
--tag {{ registry }}/rustfs:dev-latest \
|
|
||||||
--push \
|
|
||||||
.
|
|
||||||
|
|
||||||
# Local production builds using direct buildx (alternative to docker-buildx.sh)
|
|
||||||
|
|
||||||
[group("🐳 Build Image")]
|
|
||||||
docker-buildx-production-local cli=(DOCKER_CLI) source=(DOCKERFILE_PRODUCTION):
|
|
||||||
@echo "🏗️ Building single-architecture production Docker image locally..."
|
|
||||||
@echo "💡 Alternative to docker-buildx.sh for local testing"
|
|
||||||
{{ cli }} buildx build \
|
|
||||||
--file {{ source }} \
|
|
||||||
--tag rustfs:production-latest \
|
|
||||||
--tag rustfs:latest \
|
|
||||||
--load \
|
|
||||||
--build-arg RELEASE=latest \
|
|
||||||
.
|
|
||||||
|
|
||||||
# Development/Source builds using direct buildx commands
|
|
||||||
|
|
||||||
[group("🐳 Build Image")]
|
|
||||||
docker-dev cli=(DOCKER_CLI) source=(DOCKERFILE_SOURCE):
|
|
||||||
@echo "🏗️ Building multi-architecture development Docker images with buildx..."
|
|
||||||
@echo "💡 This builds from source code and is intended for local development and testing"
|
|
||||||
@echo "⚠️ Multi-arch images cannot be loaded locally, use docker-dev-push to push to registry"
|
|
||||||
{{ cli }} buildx build \
|
|
||||||
--platform linux/amd64,linux/arm64 \
|
|
||||||
--file {{ source }} \
|
|
||||||
--tag rustfs:source-latest \
|
|
||||||
--tag rustfs:dev-latest \
|
|
||||||
.
|
|
||||||
|
|
||||||
[group("🐳 Build Image")]
|
|
||||||
docker-dev-local cli=(DOCKER_CLI) source=(DOCKERFILE_SOURCE):
|
|
||||||
@echo "🏗️ Building single-architecture development Docker image for local use..."
|
|
||||||
@echo "💡 This builds from source code for the current platform and loads locally"
|
|
||||||
{{ cli }} buildx build \
|
|
||||||
--file {{ source }} \
|
|
||||||
--tag rustfs:source-latest \
|
|
||||||
--tag rustfs:dev-latest \
|
|
||||||
--load \
|
|
||||||
.
|
|
||||||
|
|
||||||
# ========================================================================================
|
|
||||||
# Single Architecture Docker Builds (Traditional)
|
|
||||||
# ========================================================================================
|
|
||||||
|
|
||||||
[group("🐳 Build Image")]
|
|
||||||
docker-build-production cli=(DOCKER_CLI) source=(DOCKERFILE_PRODUCTION):
|
|
||||||
@echo "🏗️ Building single-architecture production Docker image..."
|
|
||||||
@echo "💡 Consider using 'make docker-buildx-production-local' for multi-arch support"
|
|
||||||
{{ cli }} build -f {{ source }} -t rustfs:latest .
|
|
||||||
|
|
||||||
[group("🐳 Build Image")]
|
|
||||||
docker-build-source cli=(DOCKER_CLI) source=(DOCKERFILE_SOURCE):
|
|
||||||
@echo "🏗️ Building single-architecture source Docker image..."
|
|
||||||
@echo "💡 Consider using 'make docker-dev-local' for multi-arch support"
|
|
||||||
{{ cli }} build -f {{ source }} -t rustfs:source .
|
|
||||||
|
|
||||||
# ========================================================================================
|
|
||||||
# Development Environment
|
|
||||||
# ========================================================================================
|
|
||||||
|
|
||||||
[group("🏃 Running")]
|
|
||||||
dev-env-start cli=(DOCKER_CLI) source=(DOCKERFILE_SOURCE) container=(CONTAINER_NAME):
|
|
||||||
@echo "🚀 Starting development environment..."
|
|
||||||
{{ cli }} buildx build \
|
|
||||||
--file {{ source }} \
|
|
||||||
--tag rustfs:dev \
|
|
||||||
--load \
|
|
||||||
.
|
|
||||||
-{{ cli }} stop {{ container }} 2>/dev/null
|
|
||||||
-{{ cli }} rm {{ container }} 2>/dev/null
|
|
||||||
{{ cli }} run -d --name {{ container }} \
|
|
||||||
-p 9010:9010 -p 9000:9000 \
|
|
||||||
-v {{ invocation_directory() }}:/workspace \
|
|
||||||
-it rustfs:dev
|
|
||||||
|
|
||||||
[group("🏃 Running")]
|
|
||||||
dev-env-stop cli=(DOCKER_CLI) container=(CONTAINER_NAME):
|
|
||||||
@echo "🛑 Stopping development environment..."
|
|
||||||
-{{ cli }} stop {{ container }} 2>/dev/null
|
|
||||||
-{{ cli }} rm {{ container }} 2>/dev/null
|
|
||||||
|
|
||||||
[group("🏃 Running")]
|
|
||||||
dev-env-restart: dev-env-stop dev-env-start
|
|
||||||
|
|
||||||
[group("👍 E2E")]
|
|
||||||
e2e-server:
|
|
||||||
sh scripts/run.sh
|
|
||||||
|
|
||||||
[group("👍 E2E")]
|
|
||||||
probe-e2e:
|
|
||||||
sh scripts/probe.sh
|
|
||||||
|
|
||||||
[doc("inspect one image")]
|
|
||||||
[group("🚚 Other")]
|
|
||||||
docker-inspect-multiarch image cli=(DOCKER_CLI):
|
|
||||||
@echo "🔍 Inspecting multi-architecture image: {{ image }}"
|
|
||||||
{{ cli }} buildx imagetools inspect {{ image }}
|
|
||||||
201
LICENSE
201
LICENSE
@@ -1,201 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright 2024 Beijing Henghesha Technology Co., Ltd.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|||||||
353
Makefile
353
Makefile
@@ -1,13 +1,11 @@
|
|||||||
###########
|
###########
|
||||||
# Remote development requires VSCode with Dev Containers, Remote SSH, Remote Explorer
|
# 远程开发,需要 VSCode 安装 Dev Containers, Remote SSH, Remote Explorer
|
||||||
# https://code.visualstudio.com/docs/remote/containers
|
# https://code.visualstudio.com/docs/remote/containers
|
||||||
###########
|
###########
|
||||||
DOCKER_CLI ?= docker
|
DOCKER_CLI ?= docker
|
||||||
IMAGE_NAME ?= rustfs:v1.0.0
|
IMAGE_NAME ?= rustfs:v1.0.0
|
||||||
CONTAINER_NAME ?= rustfs-dev
|
CONTAINER_NAME ?= rustfs-dev
|
||||||
# Docker build configurations
|
DOCKERFILE_PATH = $(shell pwd)/.docker
|
||||||
DOCKERFILE_PRODUCTION = Dockerfile
|
|
||||||
DOCKERFILE_SOURCE = Dockerfile.source
|
|
||||||
|
|
||||||
# Code quality and formatting targets
|
# Code quality and formatting targets
|
||||||
.PHONY: fmt
|
.PHONY: fmt
|
||||||
@@ -23,7 +21,6 @@ fmt-check:
|
|||||||
.PHONY: clippy
|
.PHONY: clippy
|
||||||
clippy:
|
clippy:
|
||||||
@echo "🔍 Running clippy checks..."
|
@echo "🔍 Running clippy checks..."
|
||||||
cargo clippy --fix --allow-dirty
|
|
||||||
cargo clippy --all-targets --all-features -- -D warnings
|
cargo clippy --all-targets --all-features -- -D warnings
|
||||||
|
|
||||||
.PHONY: check
|
.PHONY: check
|
||||||
@@ -34,13 +31,7 @@ check:
|
|||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test:
|
||||||
@echo "🧪 Running tests..."
|
@echo "🧪 Running tests..."
|
||||||
@if command -v cargo-nextest >/dev/null 2>&1; then \
|
cargo test --all --exclude e2e_test
|
||||||
cargo nextest run --all --exclude e2e_test; \
|
|
||||||
else \
|
|
||||||
echo "ℹ️ cargo-nextest not found; falling back to 'cargo test'"; \
|
|
||||||
cargo test --workspace --exclude e2e_test -- --nocapture; \
|
|
||||||
fi
|
|
||||||
cargo test --all --doc
|
|
||||||
|
|
||||||
.PHONY: pre-commit
|
.PHONY: pre-commit
|
||||||
pre-commit: fmt clippy check test
|
pre-commit: fmt clippy check test
|
||||||
@@ -52,6 +43,21 @@ setup-hooks:
|
|||||||
chmod +x .git/hooks/pre-commit
|
chmod +x .git/hooks/pre-commit
|
||||||
@echo "✅ Git hooks setup complete!"
|
@echo "✅ Git hooks setup complete!"
|
||||||
|
|
||||||
|
.PHONY: init-devenv
|
||||||
|
init-devenv:
|
||||||
|
$(DOCKER_CLI) build -t $(IMAGE_NAME) -f $(DOCKERFILE_PATH)/Dockerfile.devenv .
|
||||||
|
$(DOCKER_CLI) stop $(CONTAINER_NAME)
|
||||||
|
$(DOCKER_CLI) rm $(CONTAINER_NAME)
|
||||||
|
$(DOCKER_CLI) run -d --name $(CONTAINER_NAME) -p 9010:9010 -p 9000:9000 -v $(shell pwd):/root/s3-rustfs -it $(IMAGE_NAME)
|
||||||
|
|
||||||
|
.PHONY: start
|
||||||
|
start:
|
||||||
|
$(DOCKER_CLI) start $(CONTAINER_NAME)
|
||||||
|
|
||||||
|
.PHONY: stop
|
||||||
|
stop:
|
||||||
|
$(DOCKER_CLI) stop $(CONTAINER_NAME)
|
||||||
|
|
||||||
.PHONY: e2e-server
|
.PHONY: e2e-server
|
||||||
e2e-server:
|
e2e-server:
|
||||||
sh $(shell pwd)/scripts/run.sh
|
sh $(shell pwd)/scripts/run.sh
|
||||||
@@ -60,317 +66,16 @@ e2e-server:
|
|||||||
probe-e2e:
|
probe-e2e:
|
||||||
sh $(shell pwd)/scripts/probe.sh
|
sh $(shell pwd)/scripts/probe.sh
|
||||||
|
|
||||||
# Native build using build-rustfs.sh script
|
# make BUILD_OS=ubuntu22.04 build
|
||||||
.PHONY: build
|
# in target/ubuntu22.04/release/rustfs
|
||||||
build:
|
|
||||||
@echo "🔨 Building RustFS using build-rustfs.sh script..."
|
|
||||||
./build-rustfs.sh
|
|
||||||
|
|
||||||
.PHONY: build-dev
|
# make BUILD_OS=rockylinux9.3 build
|
||||||
build-dev:
|
# in target/rockylinux9.3/release/rustfs
|
||||||
@echo "🔨 Building RustFS in development mode..."
|
|
||||||
./build-rustfs.sh --dev
|
|
||||||
|
|
||||||
# Docker-based build (alternative approach)
|
|
||||||
# Usage: make BUILD_OS=ubuntu22.04 build-docker
|
|
||||||
# Output: target/ubuntu22.04/release/rustfs
|
|
||||||
BUILD_OS ?= rockylinux9.3
|
BUILD_OS ?= rockylinux9.3
|
||||||
.PHONY: build-docker
|
.PHONY: build
|
||||||
build-docker: SOURCE_BUILD_IMAGE_NAME = rustfs-$(BUILD_OS):v1
|
build: ROCKYLINUX_BUILD_IMAGE_NAME = rustfs-$(BUILD_OS):v1
|
||||||
build-docker: SOURCE_BUILD_CONTAINER_NAME = rustfs-$(BUILD_OS)-build
|
build: ROCKYLINUX_BUILD_CONTAINER_NAME = rustfs-$(BUILD_OS)-build
|
||||||
build-docker: BUILD_CMD = /root/.cargo/bin/cargo build --release --bin rustfs --target-dir /root/s3-rustfs/target/$(BUILD_OS)
|
build: BUILD_CMD = /root/.cargo/bin/cargo build --release --bin rustfs --target-dir /root/s3-rustfs/target/$(BUILD_OS)
|
||||||
build-docker:
|
build:
|
||||||
@echo "🐳 Building RustFS using Docker ($(BUILD_OS))..."
|
$(DOCKER_CLI) build -t $(ROCKYLINUX_BUILD_IMAGE_NAME) -f $(DOCKERFILE_PATH)/Dockerfile.$(BUILD_OS) .
|
||||||
$(DOCKER_CLI) buildx build -t $(SOURCE_BUILD_IMAGE_NAME) -f $(DOCKERFILE_SOURCE) .
|
$(DOCKER_CLI) run --rm --name $(ROCKYLINUX_BUILD_CONTAINER_NAME) -v $(shell pwd):/root/s3-rustfs -it $(ROCKYLINUX_BUILD_IMAGE_NAME) $(BUILD_CMD)
|
||||||
$(DOCKER_CLI) run --rm --name $(SOURCE_BUILD_CONTAINER_NAME) -v $(shell pwd):/root/s3-rustfs -it $(SOURCE_BUILD_IMAGE_NAME) $(BUILD_CMD)
|
|
||||||
|
|
||||||
.PHONY: build-musl
|
|
||||||
build-musl:
|
|
||||||
@echo "🔨 Building rustfs for x86_64-unknown-linux-musl..."
|
|
||||||
@echo "💡 On macOS/Windows, use 'make build-docker' or 'make docker-dev' instead"
|
|
||||||
./build-rustfs.sh --platform x86_64-unknown-linux-musl
|
|
||||||
|
|
||||||
.PHONY: build-gnu
|
|
||||||
build-gnu:
|
|
||||||
@echo "🔨 Building rustfs for x86_64-unknown-linux-gnu..."
|
|
||||||
@echo "💡 On macOS/Windows, use 'make build-docker' or 'make docker-dev' instead"
|
|
||||||
./build-rustfs.sh --platform x86_64-unknown-linux-gnu
|
|
||||||
|
|
||||||
.PHONY: build-musl-arm64
|
|
||||||
build-musl-arm64:
|
|
||||||
@echo "🔨 Building rustfs for aarch64-unknown-linux-musl..."
|
|
||||||
@echo "💡 On macOS/Windows, use 'make build-docker' or 'make docker-dev' instead"
|
|
||||||
./build-rustfs.sh --platform aarch64-unknown-linux-musl
|
|
||||||
|
|
||||||
.PHONY: build-gnu-arm64
|
|
||||||
build-gnu-arm64:
|
|
||||||
@echo "🔨 Building rustfs for aarch64-unknown-linux-gnu..."
|
|
||||||
@echo "💡 On macOS/Windows, use 'make build-docker' or 'make docker-dev' instead"
|
|
||||||
./build-rustfs.sh --platform aarch64-unknown-linux-gnu
|
|
||||||
|
|
||||||
.PHONY: deploy-dev
|
|
||||||
deploy-dev: build-musl
|
|
||||||
@echo "🚀 Deploying to dev server: $${IP}"
|
|
||||||
./scripts/dev_deploy.sh $${IP}
|
|
||||||
|
|
||||||
# ========================================================================================
|
|
||||||
# Docker Multi-Architecture Builds (Primary Methods)
|
|
||||||
# ========================================================================================
|
|
||||||
|
|
||||||
# Production builds using docker-buildx.sh (for CI/CD and production)
|
|
||||||
.PHONY: docker-buildx
|
|
||||||
docker-buildx:
|
|
||||||
@echo "🏗️ Building multi-architecture production Docker images with buildx..."
|
|
||||||
./docker-buildx.sh
|
|
||||||
|
|
||||||
.PHONY: docker-buildx-push
|
|
||||||
docker-buildx-push:
|
|
||||||
@echo "🚀 Building and pushing multi-architecture production Docker images with buildx..."
|
|
||||||
./docker-buildx.sh --push
|
|
||||||
|
|
||||||
.PHONY: docker-buildx-version
|
|
||||||
docker-buildx-version:
|
|
||||||
@if [ -z "$(VERSION)" ]; then \
|
|
||||||
echo "❌ Error: Please specify version, example: make docker-buildx-version VERSION=v1.0.0"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@echo "🏗️ Building multi-architecture production Docker images (version: $(VERSION))..."
|
|
||||||
./docker-buildx.sh --release $(VERSION)
|
|
||||||
|
|
||||||
.PHONY: docker-buildx-push-version
|
|
||||||
docker-buildx-push-version:
|
|
||||||
@if [ -z "$(VERSION)" ]; then \
|
|
||||||
echo "❌ Error: Please specify version, example: make docker-buildx-push-version VERSION=v1.0.0"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@echo "🚀 Building and pushing multi-architecture production Docker images (version: $(VERSION))..."
|
|
||||||
./docker-buildx.sh --release $(VERSION) --push
|
|
||||||
|
|
||||||
# Development/Source builds using direct buildx commands
|
|
||||||
.PHONY: docker-dev
|
|
||||||
docker-dev:
|
|
||||||
@echo "🏗️ Building multi-architecture development Docker images with buildx..."
|
|
||||||
@echo "💡 This builds from source code and is intended for local development and testing"
|
|
||||||
@echo "⚠️ Multi-arch images cannot be loaded locally, use docker-dev-push to push to registry"
|
|
||||||
$(DOCKER_CLI) buildx build \
|
|
||||||
--platform linux/amd64,linux/arm64 \
|
|
||||||
--file $(DOCKERFILE_SOURCE) \
|
|
||||||
--tag rustfs:source-latest \
|
|
||||||
--tag rustfs:dev-latest \
|
|
||||||
.
|
|
||||||
|
|
||||||
.PHONY: docker-dev-local
|
|
||||||
docker-dev-local:
|
|
||||||
@echo "🏗️ Building single-architecture development Docker image for local use..."
|
|
||||||
@echo "💡 This builds from source code for the current platform and loads locally"
|
|
||||||
$(DOCKER_CLI) buildx build \
|
|
||||||
--file $(DOCKERFILE_SOURCE) \
|
|
||||||
--tag rustfs:source-latest \
|
|
||||||
--tag rustfs:dev-latest \
|
|
||||||
--load \
|
|
||||||
.
|
|
||||||
|
|
||||||
.PHONY: docker-dev-push
|
|
||||||
docker-dev-push:
|
|
||||||
@if [ -z "$(REGISTRY)" ]; then \
|
|
||||||
echo "❌ Error: Please specify registry, example: make docker-dev-push REGISTRY=ghcr.io/username"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@echo "🚀 Building and pushing multi-architecture development Docker images..."
|
|
||||||
@echo "💡 Pushing to registry: $(REGISTRY)"
|
|
||||||
$(DOCKER_CLI) buildx build \
|
|
||||||
--platform linux/amd64,linux/arm64 \
|
|
||||||
--file $(DOCKERFILE_SOURCE) \
|
|
||||||
--tag $(REGISTRY)/rustfs:source-latest \
|
|
||||||
--tag $(REGISTRY)/rustfs:dev-latest \
|
|
||||||
--push \
|
|
||||||
.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Local production builds using direct buildx (alternative to docker-buildx.sh)
|
|
||||||
.PHONY: docker-buildx-production-local
|
|
||||||
docker-buildx-production-local:
|
|
||||||
@echo "🏗️ Building single-architecture production Docker image locally..."
|
|
||||||
@echo "💡 Alternative to docker-buildx.sh for local testing"
|
|
||||||
$(DOCKER_CLI) buildx build \
|
|
||||||
--file $(DOCKERFILE_PRODUCTION) \
|
|
||||||
--tag rustfs:production-latest \
|
|
||||||
--tag rustfs:latest \
|
|
||||||
--load \
|
|
||||||
--build-arg RELEASE=latest \
|
|
||||||
.
|
|
||||||
|
|
||||||
# ========================================================================================
|
|
||||||
# Single Architecture Docker Builds (Traditional)
|
|
||||||
# ========================================================================================
|
|
||||||
|
|
||||||
.PHONY: docker-build-production
|
|
||||||
docker-build-production:
|
|
||||||
@echo "🏗️ Building single-architecture production Docker image..."
|
|
||||||
@echo "💡 Consider using 'make docker-buildx-production-local' for multi-arch support"
|
|
||||||
$(DOCKER_CLI) build -f $(DOCKERFILE_PRODUCTION) -t rustfs:latest .
|
|
||||||
|
|
||||||
.PHONY: docker-build-source
|
|
||||||
docker-build-source:
|
|
||||||
@echo "🏗️ Building single-architecture source Docker image..."
|
|
||||||
@echo "💡 Consider using 'make docker-dev-local' for multi-arch support"
|
|
||||||
DOCKER_BUILDKIT=1 $(DOCKER_CLI) build \
|
|
||||||
--build-arg BUILDKIT_INLINE_CACHE=1 \
|
|
||||||
-f $(DOCKERFILE_SOURCE) -t rustfs:source .
|
|
||||||
|
|
||||||
# ========================================================================================
|
|
||||||
# Development Environment
|
|
||||||
# ========================================================================================
|
|
||||||
|
|
||||||
.PHONY: dev-env-start
|
|
||||||
dev-env-start:
|
|
||||||
@echo "🚀 Starting development environment..."
|
|
||||||
$(DOCKER_CLI) buildx build \
|
|
||||||
--file $(DOCKERFILE_SOURCE) \
|
|
||||||
--tag rustfs:dev \
|
|
||||||
--load \
|
|
||||||
.
|
|
||||||
$(DOCKER_CLI) stop $(CONTAINER_NAME) 2>/dev/null || true
|
|
||||||
$(DOCKER_CLI) rm $(CONTAINER_NAME) 2>/dev/null || true
|
|
||||||
$(DOCKER_CLI) run -d --name $(CONTAINER_NAME) \
|
|
||||||
-p 9010:9010 -p 9000:9000 \
|
|
||||||
-v $(shell pwd):/workspace \
|
|
||||||
-it rustfs:dev
|
|
||||||
|
|
||||||
.PHONY: dev-env-stop
|
|
||||||
dev-env-stop:
|
|
||||||
@echo "🛑 Stopping development environment..."
|
|
||||||
$(DOCKER_CLI) stop $(CONTAINER_NAME) 2>/dev/null || true
|
|
||||||
$(DOCKER_CLI) rm $(CONTAINER_NAME) 2>/dev/null || true
|
|
||||||
|
|
||||||
.PHONY: dev-env-restart
|
|
||||||
dev-env-restart: dev-env-stop dev-env-start
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ========================================================================================
|
|
||||||
# Build Utilities
|
|
||||||
# ========================================================================================
|
|
||||||
|
|
||||||
.PHONY: docker-inspect-multiarch
|
|
||||||
docker-inspect-multiarch:
|
|
||||||
@if [ -z "$(IMAGE)" ]; then \
|
|
||||||
echo "❌ Error: Please specify image, example: make docker-inspect-multiarch IMAGE=rustfs/rustfs:latest"; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
@echo "🔍 Inspecting multi-architecture image: $(IMAGE)"
|
|
||||||
docker buildx imagetools inspect $(IMAGE)
|
|
||||||
|
|
||||||
.PHONY: build-cross-all
|
|
||||||
build-cross-all:
|
|
||||||
@echo "🔧 Building all target architectures..."
|
|
||||||
@echo "💡 On macOS/Windows, use 'make docker-dev' for reliable multi-arch builds"
|
|
||||||
@echo "🔨 Generating protobuf code..."
|
|
||||||
cargo run --bin gproto || true
|
|
||||||
@echo "🔨 Building x86_64-unknown-linux-gnu..."
|
|
||||||
./build-rustfs.sh --platform x86_64-unknown-linux-gnu
|
|
||||||
@echo "🔨 Building aarch64-unknown-linux-gnu..."
|
|
||||||
./build-rustfs.sh --platform aarch64-unknown-linux-gnu
|
|
||||||
@echo "🔨 Building x86_64-unknown-linux-musl..."
|
|
||||||
./build-rustfs.sh --platform x86_64-unknown-linux-musl
|
|
||||||
@echo "🔨 Building aarch64-unknown-linux-musl..."
|
|
||||||
./build-rustfs.sh --platform aarch64-unknown-linux-musl
|
|
||||||
@echo "✅ All architectures built successfully!"
|
|
||||||
|
|
||||||
# ========================================================================================
|
|
||||||
# Help and Documentation
|
|
||||||
# ========================================================================================
|
|
||||||
|
|
||||||
.PHONY: help-build
|
|
||||||
help-build:
|
|
||||||
@echo "🔨 RustFS Build Help:"
|
|
||||||
@echo ""
|
|
||||||
@echo "🚀 Local Build (Recommended):"
|
|
||||||
@echo " make build # Build RustFS binary (includes console by default)"
|
|
||||||
@echo " make build-dev # Development mode build"
|
|
||||||
@echo " make build-musl # Build x86_64 musl version"
|
|
||||||
@echo " make build-gnu # Build x86_64 GNU version"
|
|
||||||
@echo " make build-musl-arm64 # Build aarch64 musl version"
|
|
||||||
@echo " make build-gnu-arm64 # Build aarch64 GNU version"
|
|
||||||
@echo ""
|
|
||||||
@echo "🐳 Docker Build:"
|
|
||||||
@echo " make build-docker # Build using Docker container"
|
|
||||||
@echo " make build-docker BUILD_OS=ubuntu22.04 # Specify build system"
|
|
||||||
@echo ""
|
|
||||||
@echo "🏗️ Cross-architecture Build:"
|
|
||||||
@echo " make build-cross-all # Build binaries for all architectures"
|
|
||||||
@echo ""
|
|
||||||
@echo "🔧 Direct usage of build-rustfs.sh script:"
|
|
||||||
@echo " ./build-rustfs.sh --help # View script help"
|
|
||||||
@echo " ./build-rustfs.sh --no-console # Build without console resources"
|
|
||||||
@echo " ./build-rustfs.sh --force-console-update # Force update console resources"
|
|
||||||
@echo " ./build-rustfs.sh --dev # Development mode build"
|
|
||||||
@echo " ./build-rustfs.sh --sign # Sign binary files"
|
|
||||||
@echo " ./build-rustfs.sh --platform x86_64-unknown-linux-gnu # Specify target platform"
|
|
||||||
@echo " ./build-rustfs.sh --skip-verification # Skip binary verification"
|
|
||||||
@echo ""
|
|
||||||
@echo "💡 build-rustfs.sh script provides more options, smart detection and binary verification"
|
|
||||||
|
|
||||||
.PHONY: help-docker
|
|
||||||
help-docker:
|
|
||||||
@echo "🐳 Docker Multi-architecture Build Help:"
|
|
||||||
@echo ""
|
|
||||||
@echo "🚀 Production Image Build (Recommended to use docker-buildx.sh):"
|
|
||||||
@echo " make docker-buildx # Build production multi-arch image (no push)"
|
|
||||||
@echo " make docker-buildx-push # Build and push production multi-arch image"
|
|
||||||
@echo " make docker-buildx-version VERSION=v1.0.0 # Build specific version"
|
|
||||||
@echo " make docker-buildx-push-version VERSION=v1.0.0 # Build and push specific version"
|
|
||||||
@echo ""
|
|
||||||
@echo "🔧 Development/Source Image Build (Local development testing):"
|
|
||||||
@echo " make docker-dev # Build dev multi-arch image (cannot load locally)"
|
|
||||||
@echo " make docker-dev-local # Build dev single-arch image (local load)"
|
|
||||||
@echo " make docker-dev-push REGISTRY=xxx # Build and push dev image"
|
|
||||||
@echo ""
|
|
||||||
@echo "🏗️ Local Production Image Build (Alternative):"
|
|
||||||
@echo " make docker-buildx-production-local # Build production single-arch image locally"
|
|
||||||
@echo ""
|
|
||||||
@echo "📦 Single-architecture Build (Traditional way):"
|
|
||||||
@echo " make docker-build-production # Build single-arch production image"
|
|
||||||
@echo " make docker-build-source # Build single-arch source image"
|
|
||||||
@echo ""
|
|
||||||
@echo "🚀 Development Environment Management:"
|
|
||||||
@echo " make dev-env-start # Start development container environment"
|
|
||||||
@echo " make dev-env-stop # Stop development container environment"
|
|
||||||
@echo " make dev-env-restart # Restart development container environment"
|
|
||||||
@echo ""
|
|
||||||
@echo "🔧 Auxiliary Tools:"
|
|
||||||
@echo " make build-cross-all # Build binaries for all architectures"
|
|
||||||
@echo " make docker-inspect-multiarch IMAGE=xxx # Check image architecture support"
|
|
||||||
@echo ""
|
|
||||||
@echo "📋 Environment Variables:"
|
|
||||||
@echo " REGISTRY Image registry address (required for push)"
|
|
||||||
@echo " DOCKERHUB_USERNAME Docker Hub username"
|
|
||||||
@echo " DOCKERHUB_TOKEN Docker Hub access token"
|
|
||||||
@echo " GITHUB_TOKEN GitHub access token"
|
|
||||||
@echo ""
|
|
||||||
@echo "💡 Suggestions:"
|
|
||||||
@echo " - Production use: Use docker-buildx* commands (based on precompiled binaries)"
|
|
||||||
@echo " - Local development: Use docker-dev* commands (build from source)"
|
|
||||||
@echo " - Development environment: Use dev-env-* commands to manage dev containers"
|
|
||||||
|
|
||||||
.PHONY: help
|
|
||||||
help:
|
|
||||||
@echo "🦀 RustFS Makefile Help:"
|
|
||||||
@echo ""
|
|
||||||
@echo "📋 Main Command Categories:"
|
|
||||||
@echo " make help-build # Show build-related help"
|
|
||||||
@echo " make help-docker # Show Docker-related help"
|
|
||||||
@echo ""
|
|
||||||
@echo "🔧 Code Quality:"
|
|
||||||
@echo " make fmt # Format code"
|
|
||||||
@echo " make clippy # Run clippy checks"
|
|
||||||
@echo " make test # Run tests"
|
|
||||||
@echo " make pre-commit # Run all pre-commit checks"
|
|
||||||
@echo ""
|
|
||||||
@echo "🚀 Quick Start:"
|
|
||||||
@echo " make build # Build RustFS binary"
|
|
||||||
@echo " make docker-dev-local # Build development Docker image (local)"
|
|
||||||
@echo " make dev-env-start # Start development environment"
|
|
||||||
@echo ""
|
|
||||||
@echo "💡 For more help use 'make help-build' or 'make help-docker'"
|
|
||||||
|
|||||||
266
README.md
266
README.md
@@ -1,216 +1,94 @@
|
|||||||
[](https://rustfs.com)
|
# RustFS
|
||||||
|
|
||||||
<p align="center">RustFS is a high-performance, distributed object storage system built in Rust.</p>
|
## English Documentation |[中文文档](README_ZH.md)
|
||||||
|
|
||||||
<p align="center">
|
### Prerequisites
|
||||||
<a href="https://github.com/rustfs/rustfs/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/rustfs/rustfs/actions/workflows/ci.yml/badge.svg" /></a>
|
|
||||||
<a href="https://github.com/rustfs/rustfs/actions/workflows/docker.yml"><img alt="Build and Push Docker Images" src="https://github.com/rustfs/rustfs/actions/workflows/docker.yml/badge.svg" /></a>
|
|
||||||
<img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/m/rustfs/rustfs"/>
|
|
||||||
<img alt="Github Last Commit" src="https://img.shields.io/github/last-commit/rustfs/rustfs"/>
|
|
||||||
<a href="https://hellogithub.com/repository/rustfs/rustfs" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=b95bcb72bdc340b68f16fdf6790b7d5b&claim_uid=MsbvjYeLDKAH457&theme=small" alt="Featured|HelloGitHub" /></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p align="center">
|
| Package | Version | Download Link |
|
||||||
<a href="https://docs.rustfs.com/installation/">Getting Started</a>
|
|---------|---------|----------------------------------------------------------------------------------------------------------------------------------|
|
||||||
· <a href="https://docs.rustfs.com/">Docs</a>
|
| Rust | 1.8.5+ | [rust-lang.org/tools/install](https://www.rust-lang.org/tools/install) |
|
||||||
· <a href="https://github.com/rustfs/rustfs/issues">Bug reports</a>
|
| protoc | 30.2+ | [protoc-30.2-linux-x86_64.zip](https://github.com/protocolbuffers/protobuf/releases/download/v30.2/protoc-30.2-linux-x86_64.zip) |
|
||||||
· <a href="https://github.com/rustfs/rustfs/discussions">Discussions</a>
|
| flatc | 24.0+ | [Linux.flatc.binary.g++-13.zip](https://github.com/google/flatbuffers/releases/download/v25.2.10/Linux.flatc.binary.g++-13.zip) |
|
||||||
</p>
|
|
||||||
|
|
||||||
<p align="center">
|
### Building RustFS
|
||||||
English | <a href="https://github.com/rustfs/rustfs/blob/main/README_ZH.md">简体中文</a> |
|
|
||||||
<a href="https://readme-i18n.com/rustfs/rustfs?lang=de">Deutsch</a> |
|
|
||||||
<a href="https://readme-i18n.com/rustfs/rustfs?lang=es">Español</a> |
|
|
||||||
<a href="https://readme-i18n.com/rustfs/rustfs?lang=fr">français</a> |
|
|
||||||
<a href="https://readme-i18n.com/rustfs/rustfs?lang=ja">日本語</a> |
|
|
||||||
<a href="https://readme-i18n.com/rustfs/rustfs?lang=ko">한국어</a> |
|
|
||||||
<a href="https://readme-i18n.com/rustfs/rustfs?lang=pt">Portuguese</a> |
|
|
||||||
<a href="https://readme-i18n.com/rustfs/rustfs?lang=ru">Русский</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
RustFS is a high-performance, distributed object storage system built in Rust—one of the most loved programming languages worldwide. RustFS combines the simplicity of MinIO with the memory safety and raw performance of Rust. It offers full S3 compatibility, is completely open-source, and is optimized for data lakes, AI, and big data workloads.
|
#### Generate Protobuf Code
|
||||||
|
|
||||||
Unlike other storage systems, RustFS is released under the permissible Apache 2.0 license, avoiding the restrictions of AGPL. With Rust as its foundation, RustFS delivers superior speed and secure distributed features for next-generation object storage.
|
|
||||||
|
|
||||||
## Feature & Status
|
|
||||||
|
|
||||||
- **High Performance**: Built with Rust to ensure maximum speed and resource efficiency.
|
|
||||||
- **Distributed Architecture**: Scalable and fault-tolerant design suitable for large-scale deployments.
|
|
||||||
- **S3 Compatibility**: Seamless integration with existing S3-compatible applications and tools.
|
|
||||||
- **Data Lake Support**: Optimized for high-throughput big data and AI workloads.
|
|
||||||
- **Open Source**: Licensed under Apache 2.0, encouraging unrestricted community contributions and commercial usage.
|
|
||||||
- **User-Friendly**: Designed with simplicity in mind for easy deployment and management.
|
|
||||||
|
|
||||||
| Feature | Status | Feature | Status |
|
|
||||||
| :--- | :--- | :--- | :--- |
|
|
||||||
| **S3 Core Features** | ✅ Available | **Bitrot Protection** | ✅ Available |
|
|
||||||
| **Upload / Download** | ✅ Available | **Single Node Mode** | ✅ Available |
|
|
||||||
| **Versioning** | ✅ Available | **Bucket Replication** | ⚠️ Partial Support |
|
|
||||||
| **Logging** | ✅ Available | **Lifecycle Management** | 🚧 Under Testing |
|
|
||||||
| **Event Notifications** | ✅ Available | **Distributed Mode** | 🚧 Under Testing |
|
|
||||||
| **K8s Helm Charts** | ✅ Available | **OPA (Open Policy Agent)** | 🚧 Under Testing |
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## RustFS vs MinIO Performance
|
|
||||||
|
|
||||||
**Stress Test Environment:**
|
|
||||||
|
|
||||||
| Type | Parameter | Remark |
|
|
||||||
|---------|-----------|----------------------------------------------------------|
|
|
||||||
| CPU | 2 Core | Intel Xeon (Sapphire Rapids) Platinum 8475B, 2.7/3.2 GHz |
|
|
||||||
| Memory | 4GB | |
|
|
||||||
| Network | 15Gbps | |
|
|
||||||
| Drive | 40GB x 4 | IOPS 3800 / Drive |
|
|
||||||
|
|
||||||
<https://github.com/user-attachments/assets/2e4979b5-260c-4f2c-ac12-c87fd558072a>
|
|
||||||
|
|
||||||
### RustFS vs Other Object Storage
|
|
||||||
|
|
||||||
| Feature | RustFS | Other Object Storage |
|
|
||||||
| :--- | :--- | :--- |
|
|
||||||
| **Console Experience** | **Powerful Console**<br>Comprehensive management interface. | **Basic / Limited Console**<br>Often overly simple or lacking critical features. |
|
|
||||||
| **Language & Safety** | **Rust-based**<br>Memory safety by design. | **Go or C-based**<br>Potential for memory GC pauses or leaks. |
|
|
||||||
| **Data Sovereignty** | **No Telemetry / Full Compliance**<br>Guards against unauthorized cross-border data egress. Compliant with GDPR (EU/UK), CCPA (US), and APPI (Japan). | **Potential Risk**<br>Possible legal exposure and unwanted data telemetry. |
|
|
||||||
| **Licensing** | **Permissive Apache 2.0**<br>Business-friendly, no "poison pill" clauses. | **Restrictive AGPL v3**<br>Risk of license traps and intellectual property pollution. |
|
|
||||||
| **Compatibility** | **100% S3 Compatible**<br>Works with any cloud provider or client, anywhere. | **Variable Compatibility**<br>May lack support for local cloud vendors or specific APIs. |
|
|
||||||
| **Edge & IoT** | **Strong Edge Support**<br>Ideal for secure, innovative edge devices. | **Weak Edge Support**<br>Often too heavy for edge gateways. |
|
|
||||||
| **Risk Profile** | **Enterprise Risk Mitigation**<br>Clear IP rights and safe for commercial use. | **Legal Risks**<br>Intellectual property ambiguity and usage restrictions. |
|
|
||||||
|
|
||||||
## Quickstart
|
|
||||||
|
|
||||||
To get started with RustFS, follow these steps:
|
|
||||||
|
|
||||||
### 1. One-click Installation (Option 1)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -O https://rustfs.com/install_rustfs.sh && bash install_rustfs.sh
|
|
||||||
````
|
|
||||||
|
|
||||||
### 2\. Docker Quick Start (Option 2)
|
|
||||||
|
|
||||||
The RustFS container runs as a non-root user `rustfs` (UID `10001`). If you run Docker with `-v` to mount a host directory, please ensure the host directory owner is set to `10001`, otherwise you will encounter permission denied errors.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Create data and logs directories
|
cargo run --bin gproto
|
||||||
mkdir -p data logs
|
|
||||||
|
|
||||||
# Change the owner of these directories
|
|
||||||
chown -R 10001:10001 data logs
|
|
||||||
|
|
||||||
# Using latest version
|
|
||||||
docker run -d -p 9000:9000 -p 9001:9001 -v $(pwd)/data:/data -v $(pwd)/logs:/logs rustfs/rustfs:latest
|
|
||||||
|
|
||||||
# Using specific version
|
|
||||||
docker run -d -p 9000:9000 -p 9001:9001 -v $(pwd)/data:/data -v $(pwd)/logs:/logs rustfs/rustfs:1.0.0.alpha.68
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also use Docker Compose. Using the `docker-compose.yml` file in the root directory:
|
#### Using Docker for Prerequisites
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: arduino/setup-protoc@v3
|
||||||
|
with:
|
||||||
|
version: "30.2"
|
||||||
|
|
||||||
|
- uses: Nugine/setup-flatc@v1
|
||||||
|
with:
|
||||||
|
version: "25.2.10"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Adding Console Web UI
|
||||||
|
|
||||||
|
1. Download the latest console UI:
|
||||||
|
```bash
|
||||||
|
wget https://dl.rustfs.com/artifacts/console/rustfs-console-latest.zip
|
||||||
|
```
|
||||||
|
2. Create the static directory:
|
||||||
|
```bash
|
||||||
|
mkdir -p ./rustfs/static
|
||||||
|
```
|
||||||
|
3. Extract and compile RustFS:
|
||||||
|
```bash
|
||||||
|
unzip rustfs-console-latest.zip -d ./rustfs/static
|
||||||
|
cargo build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running RustFS
|
||||||
|
|
||||||
|
#### Configuration
|
||||||
|
|
||||||
|
Set the required environment variables:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose --profile observability up -d
|
# Basic config
|
||||||
|
export RUSTFS_VOLUMES="./target/volume/test"
|
||||||
|
export RUSTFS_ADDRESS="0.0.0.0:9000"
|
||||||
|
export RUSTFS_CONSOLE_ENABLE=true
|
||||||
|
export RUSTFS_CONSOLE_ADDRESS="0.0.0.0:9001"
|
||||||
|
|
||||||
|
# Observability config
|
||||||
|
export RUSTFS_OBS_ENDPOINT="http://localhost:4317"
|
||||||
|
|
||||||
|
# Event message configuration
|
||||||
|
#export RUSTFS_EVENT_CONFIG="./deploy/config/event.toml"
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**NOTE**: We recommend reviewing the `docker-compose.yaml` file before running. It defines several services including Grafana, Prometheus, and Jaeger, which are helpful for RustFS observability. If you wish to start Redis or Nginx containers, you can specify the corresponding profiles.
|
#### Start the service
|
||||||
|
|
||||||
### 3\. Build from Source (Option 3) - Advanced Users
|
|
||||||
|
|
||||||
For developers who want to build RustFS Docker images from source with multi-architecture support:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build multi-architecture images locally
|
./rustfs /data/rustfs
|
||||||
./docker-buildx.sh --build-arg RELEASE=latest
|
|
||||||
|
|
||||||
# Build and push to registry
|
|
||||||
./docker-buildx.sh --push
|
|
||||||
|
|
||||||
# Build specific version
|
|
||||||
./docker-buildx.sh --release v1.0.0 --push
|
|
||||||
|
|
||||||
# Build for custom registry
|
|
||||||
./docker-buildx.sh --registry your-registry.com --namespace yourname --push
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The `docker-buildx.sh` script supports:
|
### Observability Stack
|
||||||
\- **Multi-architecture builds**: `linux/amd64`, `linux/arm64`
|
|
||||||
\- **Automatic version detection**: Uses git tags or commit hashes
|
|
||||||
\- **Registry flexibility**: Supports Docker Hub, GitHub Container Registry, etc.
|
|
||||||
\- **Build optimization**: Includes caching and parallel builds
|
|
||||||
|
|
||||||
You can also use Make targets for convenience:
|
#### Deployment
|
||||||
|
|
||||||
```bash
|
1. Navigate to the observability directory:
|
||||||
make docker-buildx # Build locally
|
```bash
|
||||||
make docker-buildx-push # Build and push
|
cd .docker/observability
|
||||||
make docker-buildx-version VERSION=v1.0.0 # Build specific version
|
```
|
||||||
make help-docker # Show all Docker-related commands
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Heads-up (macOS cross-compilation)**: macOS keeps the default `ulimit -n` at 256, so `cargo zigbuild` or `./build-rustfs.sh --platform ...` may fail with `ProcessFdQuotaExceeded` when targeting Linux. The build script attempts to raise the limit automatically, but if you still see the warning, run `ulimit -n 4096` (or higher) in your shell before building.
|
2. Start the observability stack:
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
### 4\. Build with Helm Chart (Option 4) - Cloud Native
|
#### Access Monitoring Dashboards
|
||||||
|
|
||||||
Follow the instructions in the [Helm Chart README](https://charts.rustfs.com/) to install RustFS on a Kubernetes cluster.
|
|
||||||
|
|
||||||
-----
|
|
||||||
|
|
||||||
### Accessing RustFS
|
|
||||||
|
|
||||||
5. **Access the Console**: Open your web browser and navigate to `http://localhost:9000` to access the RustFS console.
|
|
||||||
* Default credentials: `rustfsadmin` / `rustfsadmin`
|
|
||||||
6. **Create a Bucket**: Use the console to create a new bucket for your objects.
|
|
||||||
7. **Upload Objects**: You can upload files directly through the console or use S3-compatible APIs/clients to interact with your RustFS instance.
|
|
||||||
|
|
||||||
**NOTE**: To access the RustFS instance via `https`, please refer to the [TLS Configuration Docs](https://docs.rustfs.com/integration/tls-configured.html).
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
For detailed documentation, including configuration options, API references, and advanced usage, please visit our [Documentation](https://docs.rustfs.com).
|
|
||||||
|
|
||||||
## Getting Help
|
|
||||||
|
|
||||||
If you have any questions or need assistance:
|
|
||||||
|
|
||||||
- Check the [FAQ](https://github.com/rustfs/rustfs/discussions/categories/q-a) for common issues and solutions.
|
|
||||||
- Join our [GitHub Discussions](https://github.com/rustfs/rustfs/discussions) to ask questions and share your experiences.
|
|
||||||
- Open an issue on our [GitHub Issues](https://github.com/rustfs/rustfs/issues) page for bug reports or feature requests.
|
|
||||||
|
|
||||||
## Links
|
|
||||||
|
|
||||||
- [Documentation](https://docs.rustfs.com) - The manual you should read
|
|
||||||
- [Changelog](https://github.com/rustfs/rustfs/releases) - What we broke and fixed
|
|
||||||
- [GitHub Discussions](https://github.com/rustfs/rustfs/discussions) - Where the community lives
|
|
||||||
|
|
||||||
## Contact
|
|
||||||
|
|
||||||
- **Bugs**: [GitHub Issues](https://github.com/rustfs/rustfs/issues)
|
|
||||||
- **Business**: [hello@rustfs.com](mailto:hello@rustfs.com)
|
|
||||||
- **Jobs**: [jobs@rustfs.com](mailto:jobs@rustfs.com)
|
|
||||||
- **General Discussion**: [GitHub Discussions](https://github.com/rustfs/rustfs/discussions)
|
|
||||||
- **Contributing**: [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
||||||
|
|
||||||
## Contributors
|
|
||||||
|
|
||||||
RustFS is a community-driven project, and we appreciate all contributions. Check out the [Contributors](https://github.com/rustfs/rustfs/graphs/contributors) page to see the amazing people who have helped make RustFS better.
|
|
||||||
|
|
||||||
<a href="https://github.com/rustfs/rustfs/graphs/contributors">
|
|
||||||
<img src="https://opencollective.com/rustfs/contributors.svg?width=890&limit=500&button=false" alt="Contributors" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
## Github Trending Top
|
|
||||||
|
|
||||||
🚀 RustFS is beloved by open-source enthusiasts and enterprise users worldwide, often appearing on the GitHub Trending top charts.
|
|
||||||
|
|
||||||
<a href="https://trendshift.io/repositories/14181" target="_blank"><img src="https://raw.githubusercontent.com/rustfs/rustfs/refs/heads/main/docs/rustfs-trending.jpg" alt="rustfs%2Frustfs | Trendshift" /></a>
|
|
||||||
|
|
||||||
## Star History
|
|
||||||
|
|
||||||
[](https://www.star-history.com/#rustfs/rustfs&type=date&legend=top-left)
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
[Apache 2.0](https://opensource.org/licenses/Apache-2.0)
|
|
||||||
|
|
||||||
**RustFS** is a trademark of RustFS, Inc. All other trademarks are the property of their respective owners.
|
|
||||||
|
|
||||||
|
- Grafana: `http://localhost:3000` (credentials: `admin`/`admin`)
|
||||||
|
- Jaeger: `http://localhost:16686`
|
||||||
|
- Prometheus: `http://localhost:9090`
|
||||||
|
|||||||
278
README_ZH.md
278
README_ZH.md
@@ -1,219 +1,99 @@
|
|||||||
[](https://rustfs.com)
|
# RustFS
|
||||||
|
|
||||||
<p align="center">RustFS 是一个基于 Rust 构建的高性能分布式对象存储系统。</p>
|
## [English Documentation](README.md) |中文文档
|
||||||
|
|
||||||
<p align="center">
|
### 前置要求
|
||||||
<a href="https://github.com/rustfs/rustfs/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/rustfs/rustfs/actions/workflows/ci.yml/badge.svg" /></a>
|
|
||||||
<a href="https://github.com/rustfs/rustfs/actions/workflows/docker.yml"><img alt="构建并推送 Docker 镜像" src="https://github.com/rustfs/rustfs/actions/workflows/docker.yml/badge.svg" /></a>
|
|
||||||
<img alt="GitHub 提交活跃度" src="https://img.shields.io/github/commit-activity/m/rustfs/rustfs"/>
|
|
||||||
<img alt="Github 最新提交" src="https://img.shields.io/github/last-commit/rustfs/rustfs"/>
|
|
||||||
<a href="https://hellogithub.com/repository/rustfs/rustfs" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=b95bcb72bdc340b68f16fdf6790b7d5b&claim_uid=MsbvjYeLDKAH457&theme=small" alt="Featured|HelloGitHub" /></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p align="center">
|
| 软件包 | 版本 | 下载链接 |
|
||||||
<a href="https://docs.rustfs.com/installation/">快速开始</a>
|
|--------|--------|----------------------------------------------------------------------------------------------------------------------------------|
|
||||||
· <a href="https://docs.rustfs.com/">文档</a>
|
| Rust | 1.8.5+ | [rust-lang.org/tools/install](https://www.rust-lang.org/tools/install) |
|
||||||
· <a href="https://github.com/rustfs/rustfs/issues">报告 Bug</a>
|
| protoc | 30.2+ | [protoc-30.2-linux-x86_64.zip](https://github.com/protocolbuffers/protobuf/releases/download/v30.2/protoc-30.2-linux-x86_64.zip) |
|
||||||
· <a href="https://github.com/rustfs/rustfs/discussions">社区讨论</a>
|
| flatc | 24.0+ | [Linux.flatc.binary.g++-13.zip](https://github.com/google/flatbuffers/releases/download/v25.2.10/Linux.flatc.binary.g++-13.zip) |
|
||||||
</p>
|
|
||||||
|
|
||||||
<p align="center">
|
### 构建 RustFS
|
||||||
<a href="https://github.com/rustfs/rustfs/blob/main/README.md">English</a> | 简体中文 |
|
|
||||||
<a href="https://readme-i18n.com/rustfs/rustfs?lang=de">Deutsch</a> |
|
|
||||||
<a href="https://readme-i18n.com/rustfs/rustfs?lang=es">Español</a> |
|
|
||||||
<a href="https://readme-i18n.com/rustfs/rustfs?lang=fr">français</a> |
|
|
||||||
<a href="https://readme-i18n.com/rustfs/rustfs?lang=ja">日本語</a> |
|
|
||||||
<a href="https://readme-i18n.com/rustfs/rustfs?lang=ko">한국어</a> |
|
|
||||||
<a href="https://readme-i18n.com/rustfs/rustfs?lang=pt">Portuguese</a> |
|
|
||||||
<a href="https://readme-i18n.com/rustfs/rustfs?lang=ru">Русский</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
RustFS 是一个基于 Rust 构建的高性能分布式对象存储系统。Rust 是全球最受开发者喜爱的编程语言之一,RustFS 完美结合了 MinIO 的简洁性与 Rust 的内存安全及高性能优势。它提供完整的 S3 兼容性,完全开源,并专为数据湖、人工智能(AI)和大数据负载进行了优化。
|
#### 生成 Protobuf 代码
|
||||||
|
|
||||||
与其他存储系统不同,RustFS 采用更宽松、商业友好的 Apache 2.0 许可证,避免了 AGPL 协议的限制。以 Rust 为基石,RustFS 为下一代对象存储提供了更快的速度和更安全的分布式特性。
|
|
||||||
|
|
||||||
## 特征和功能状态
|
|
||||||
|
|
||||||
- **高性能**:基于 Rust 构建,确保极致的速度和资源效率。
|
|
||||||
- **分布式架构**:可扩展且容错的设计,适用于大规模部署。
|
|
||||||
- **S3 兼容性**:与现有的 S3 兼容应用和工具无缝集成。
|
|
||||||
- **数据湖支持**:专为高吞吐量的大数据和 AI 工作负载优化。
|
|
||||||
- **完全开源**:采用 Apache 2.0 许可证,鼓励社区贡献和商业使用。
|
|
||||||
- **简单易用**:设计简洁,易于部署和管理。
|
|
||||||
|
|
||||||
|
|
||||||
| 功能 | 状态 | 功能 | 状态 |
|
|
||||||
| :--- | :--- | :--- | :--- |
|
|
||||||
| **S3 核心功能** | ✅ 可用 | **Bitrot (防数据腐烂)** | ✅ 可用 |
|
|
||||||
| **上传 / 下载** | ✅ 可用 | **单机模式** | ✅ 可用 |
|
|
||||||
| **版本控制** | ✅ 可用 | **存储桶复制** | ⚠️ 部分可用 |
|
|
||||||
| **日志功能** | ✅ 可用 | **生命周期管理** | 🚧 测试中 |
|
|
||||||
| **事件通知** | ✅ 可用 | **分布式模式** | 🚧 测试中 |
|
|
||||||
| **K8s Helm Chart** | ✅ 可用 | **OPA (策略引擎)** | 🚧 测试中 |
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## RustFS vs MinIO 性能对比
|
|
||||||
|
|
||||||
**压力测试环境参数:**
|
|
||||||
|
|
||||||
| 类型 | 参数 | 备注 |
|
|
||||||
|---------|-----------|----------------------------------------------------------|
|
|
||||||
| CPU | 2 核 | Intel Xeon (Sapphire Rapids) Platinum 8475B , 2.7/3.2 GHz |
|
|
||||||
| 内存 | 4GB | |
|
|
||||||
| 网络 | 15Gbps | |
|
|
||||||
| 硬盘 | 40GB x 4 | IOPS 3800 / Drive |
|
|
||||||
|
|
||||||
<https://github.com/user-attachments/assets/2e4979b5-260c-4f2c-ac12-c87fd558072a>
|
|
||||||
|
|
||||||
### RustFS vs 其他对象存储
|
|
||||||
|
|
||||||
| 特性 | RustFS | 其他对象存储 |
|
|
||||||
| :--- | :--- | :--- |
|
|
||||||
| **控制台体验** | **功能强大的控制台**<br>提供全面的管理界面。 | **基础/简陋的控制台**<br>通常功能过于简单或缺失关键特性。 |
|
|
||||||
| **语言与安全** | **基于 Rust 开发**<br>天生的内存安全。 | **基于 Go 或 C 开发**<br>存在内存 GC 停顿或内存泄漏的潜在风险。 |
|
|
||||||
| **数据主权** | **无遥测 / 完全合规**<br>防止未经授权的数据跨境传输。完全符合 GDPR (欧盟/英国)、CCPA (美国) 和 APPI (日本) 等法规。 | **潜在风险**<br>可能存在法律风险和隐蔽的数据遥测(Telemetry)。 |
|
|
||||||
| **开源协议** | **宽松的 Apache 2.0**<br>商业友好,无“毒丸”条款。 | **受限的 AGPL v3**<br>存在许可证陷阱和知识产权污染的风险。 |
|
|
||||||
| **兼容性** | **100% S3 兼容**<br>适用于任何云提供商和客户端,随处运行。 | **兼容性不一**<br>虽然支持 S3,但可能缺乏对本地云厂商或特定 API 的支持。 |
|
|
||||||
| **边缘与 IoT** | **强大的边缘支持**<br>非常适合安全、创新的边缘设备。 | **边缘支持较弱**<br>对于边缘网关来说通常过于沉重。 |
|
|
||||||
| **成本** | **稳定且免费**<br>免费社区支持,稳定的商业定价。 | **高昂成本**<br>1PiB 的成本可能高达 250,000 美元。 |
|
|
||||||
| **风险控制** | **企业级风险规避**<br>清晰的知识产权,商业使用安全无忧。 | **法律风险**<br>知识产权归属模糊及使用限制风险。 |
|
|
||||||
|
|
||||||
## 快速开始
|
|
||||||
|
|
||||||
请按照以下步骤快速上手 RustFS:
|
|
||||||
|
|
||||||
### 1. 一键安装脚本 (选项 1)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -O https://rustfs.com/install_rustfs.sh && bash install_rustfs.sh
|
|
||||||
````
|
|
||||||
|
|
||||||
### 2\. Docker 快速启动 (选项 2)
|
|
||||||
|
|
||||||
RustFS 容器以非 root 用户 `rustfs` (UID `10001`) 运行。如果您使用 Docker 的 `-v` 参数挂载宿主机目录,请务必确保宿主机目录的所有者已更改为 `1000`,否则会遇到权限拒绝错误。
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 创建数据和日志目录
|
cargo run --bin gproto
|
||||||
mkdir -p data logs
|
|
||||||
|
|
||||||
# 更改这两个目录的所有者
|
|
||||||
chown -R 10001:10001 data logs
|
|
||||||
|
|
||||||
# 使用最新版本运行
|
|
||||||
docker run -d -p 9000:9000 -p 9001:9001 -v $(pwd)/data:/data -v $(pwd)/logs:/logs rustfs/rustfs:latest
|
|
||||||
|
|
||||||
# 使用指定版本运行
|
|
||||||
docker run -d -p 9000:9000 -p 9001:9001 -v $(pwd)/data:/data -v $(pwd)/logs:/logs rustfs/rustfs:1.0.0.alpha.68
|
|
||||||
```
|
```
|
||||||
|
|
||||||
您也可以使用 Docker Compose。使用根目录下的 `docker-compose.yml` 文件:
|
#### 使用 Docker 安装依赖
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: arduino/setup-protoc@v3
|
||||||
|
with:
|
||||||
|
version: "30.2"
|
||||||
|
|
||||||
|
- uses: Nugine/setup-flatc@v1
|
||||||
|
with:
|
||||||
|
version: "25.2.10"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 添加控制台 Web UI
|
||||||
|
|
||||||
|
1. 下载最新的控制台 UI:
|
||||||
|
```bash
|
||||||
|
wget https://dl.rustfs.com/artifacts/console/rustfs-console-latest.zip
|
||||||
|
```
|
||||||
|
2. 创建静态资源目录:
|
||||||
|
```bash
|
||||||
|
mkdir -p ./rustfs/static
|
||||||
|
```
|
||||||
|
3. 解压并编译 RustFS:
|
||||||
|
```bash
|
||||||
|
unzip rustfs-console-latest.zip -d ./rustfs/static
|
||||||
|
cargo build
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行 RustFS
|
||||||
|
|
||||||
|
#### 配置
|
||||||
|
|
||||||
|
设置必要的环境变量:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose --profile observability up -d
|
# 基础配置
|
||||||
|
export RUSTFS_VOLUMES="./target/volume/test"
|
||||||
|
export RUSTFS_ADDRESS="0.0.0.0:9000"
|
||||||
|
export RUSTFS_CONSOLE_ENABLE=true
|
||||||
|
export RUSTFS_CONSOLE_ADDRESS="0.0.0.0:9001"
|
||||||
|
|
||||||
|
# 可观测性配置
|
||||||
|
export RUSTFS_OBS_ENDPOINT="http://localhost:4317"
|
||||||
|
|
||||||
|
# 事件消息配置
|
||||||
|
#export RUSTFS_EVENT_CONFIG="./deploy/config/event.toml"
|
||||||
```
|
```
|
||||||
|
|
||||||
**注意**: 我们建议您在运行前查看 `docker-compose.yaml` 文件。该文件定义了包括 Grafana、Prometheus 和 Jaeger 在内的多个服务,有助于 RustFS 的可观测性监控。如果您还想启动 Redis 或 Nginx 容器,可以指定相应的 profile。
|
#### 启动服务
|
||||||
|
|
||||||
### 3\. 源码编译 (选项 3) - 进阶用户
|
|
||||||
|
|
||||||
适用于希望从源码构建支持多架构 RustFS Docker 镜像的开发者:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 在本地构建多架构镜像
|
./rustfs /data/rustfs
|
||||||
./docker-buildx.sh --build-arg RELEASE=latest
|
|
||||||
|
|
||||||
# 构建并推送到仓库
|
|
||||||
./docker-buildx.sh --push
|
|
||||||
|
|
||||||
# 构建指定版本
|
|
||||||
./docker-buildx.sh --release v1.0.0 --push
|
|
||||||
|
|
||||||
# 构建并推送到自定义仓库
|
|
||||||
./docker-buildx.sh --registry your-registry.com --namespace yourname --push
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`docker-buildx.sh` 脚本支持:
|
### 可观测性系统
|
||||||
\- **多架构构建**: `linux/amd64`, `linux/arm64`
|
|
||||||
\- **自动版本检测**: 使用 git tags 或 commit hash
|
|
||||||
\- **灵活的仓库支持**: 支持 Docker Hub, GitHub Container Registry 等
|
|
||||||
\- **构建优化**: 包含缓存和并行构建
|
|
||||||
|
|
||||||
为了方便起见,您也可以使用 Make 命令:
|
#### 部署
|
||||||
|
|
||||||
|
1. 进入可观测性目录:
|
||||||
|
```bash
|
||||||
|
cd .docker/observability
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 启动可观测性系统:
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 访问监控面板
|
||||||
|
|
||||||
|
- Grafana: `http://localhost:3000` (默认账号/密码:`admin`/`admin`)
|
||||||
|
- Jaeger: `http://localhost:16686`
|
||||||
|
- Prometheus: `http://localhost:9090`
|
||||||
|
|
||||||
|
#### 配置可观测性
|
||||||
|
|
||||||
```bash
|
|
||||||
make docker-buildx # 本地构建
|
|
||||||
make docker-buildx-push # 构建并推送
|
|
||||||
make docker-buildx-version VERSION=v1.0.0 # 构建指定版本
|
|
||||||
make help-docker # 显示所有 Docker 相关命令
|
|
||||||
```
|
```
|
||||||
|
OpenTelemetry Collector 地址(endpoint): http://localhost:4317
|
||||||
> **注意 (macOS 交叉编译)**: macOS 默认的 `ulimit -n` 限制为 256,因此在使用 `cargo zigbuild` 或 `./build-rustfs.sh --platform ...` 交叉编译 Linux 版本时,可能会因 `ProcessFdQuotaExceeded` 失败。构建脚本会尝试自动提高限制,但如果您仍然看到警告,请在构建前在终端运行 `ulimit -n 4096` (或更高)。
|
```
|
||||||
|
|
||||||
### 4\. 使用 Helm Chart 安装 (选项 4) - 云原生环境
|
|
||||||
|
|
||||||
请按照 [Helm Chart README](https://charts.rustfs.com) 上的说明在 Kubernetes 集群上安装 RustFS。
|
|
||||||
|
|
||||||
-----
|
|
||||||
|
|
||||||
### 访问 RustFS
|
|
||||||
|
|
||||||
5. **访问控制台**: 打开浏览器并访问 `http://localhost:9000` 进入 RustFS 控制台。
|
|
||||||
* 默认账号/密码: `rustfsadmin` / `rustfsadmin`
|
|
||||||
6. **创建存储桶**: 使用控制台为您的对象创建一个新的存储桶 (Bucket)。
|
|
||||||
7. **上传对象**: 您可以直接通过控制台上传文件,或使用 S3 兼容的 API/客户端与您的 RustFS 实例进行交互。
|
|
||||||
|
|
||||||
**注意**: 如果您希望通过 `https` 访问 RustFS 实例,请参考 [TLS 配置文档](https://docs.rustfs.com/integration/tls-configured.html)。
|
|
||||||
|
|
||||||
## 文档
|
|
||||||
|
|
||||||
有关详细文档,包括配置选项、API 参考和高级用法,请访问我们的 [官方文档](https://docs.rustfs.com)。
|
|
||||||
|
|
||||||
## 获取帮助
|
|
||||||
|
|
||||||
如果您有任何问题或需要帮助:
|
|
||||||
|
|
||||||
- 查看 [FAQ](https://github.com/rustfs/rustfs/discussions/categories/q-a) 寻找常见问题和解决方案。
|
|
||||||
- 加入我们的 [GitHub Discussions](https://github.com/rustfs/rustfs/discussions) 提问并分享您的经验。
|
|
||||||
- 在我们的 [GitHub Issues](https://github.com/rustfs/rustfs/issues) 页面提交 Bug 报告或功能请求。
|
|
||||||
|
|
||||||
## 链接
|
|
||||||
|
|
||||||
- [官方文档](https://docs.rustfs.com) - 必读手册
|
|
||||||
- [更新日志](https://github.com/rustfs/rustfs/releases) - 版本变更记录
|
|
||||||
- [社区讨论](https://github.com/rustfs/rustfs/discussions) - 社区交流地
|
|
||||||
|
|
||||||
## 联系方式
|
|
||||||
|
|
||||||
- **Bug 反馈**: [GitHub Issues](https://github.com/rustfs/rustfs/issues)
|
|
||||||
- **商务合作**: [hello@rustfs.com](mailto:hello@rustfs.com)
|
|
||||||
- **工作机会**: [jobs@rustfs.com](mailto:jobs@rustfs.com)
|
|
||||||
- **一般讨论**: [GitHub Discussions](https://github.com/rustfs/rustfs/discussions)
|
|
||||||
- **贡献指南**: [CONTRIBUTING.md](https://www.google.com/search?q=CONTRIBUTING.md)
|
|
||||||
|
|
||||||
## 贡献者
|
|
||||||
|
|
||||||
RustFS 是一个社区驱动的项目,我们感谢所有的贡献。请查看 [贡献者](https://github.com/rustfs/rustfs/graphs/contributors) 页面,看看那些让 RustFS 变得更好的了不起的人们。
|
|
||||||
|
|
||||||
<a href="https://github.com/rustfs/rustfs/graphs/contributors">
|
|
||||||
<img src="https://opencollective.com/rustfs/contributors.svg?width=890&limit=500&button=false" alt="Contributors" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
## Github Trending Top
|
|
||||||
|
|
||||||
🚀 RustFS 深受全球开源爱好者和企业用户的喜爱,经常荣登 GitHub Trending 榜单。
|
|
||||||
|
|
||||||
<a href="https://trendshift.io/repositories/14181" target="_blank"><img src="https://raw.githubusercontent.com/rustfs/rustfs/refs/heads/main/docs/rustfs-trending.jpg" alt="rustfs%2Frustfs | Trendshift" /></a>
|
|
||||||
|
|
||||||
## Star 历史
|
|
||||||
|
|
||||||
[](https://www.star-history.com/#rustfs/rustfs&type=date&legend=top-left)
|
|
||||||
|
|
||||||
|
|
||||||
## 许可证
|
|
||||||
|
|
||||||
[Apache 2.0](https://opensource.org/licenses/Apache-2.0)
|
|
||||||
|
|
||||||
**RustFS** 是 RustFS, Inc. 的商标。所有其他商标均为其各自所有者的财产。
|
|
||||||
|
|
||||||
18
SECURITY.md
18
SECURITY.md
@@ -1,18 +0,0 @@
|
|||||||
# Security Policy
|
|
||||||
|
|
||||||
## Supported Versions
|
|
||||||
|
|
||||||
Use this section to tell people about which versions of your project are
|
|
||||||
currently being supported with security updates.
|
|
||||||
|
|
||||||
| Version | Supported |
|
|
||||||
| ------- | ------------------ |
|
|
||||||
| 1.x.x | :white_check_mark: |
|
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
|
||||||
|
|
||||||
Use this section to tell people how to report a vulnerability.
|
|
||||||
|
|
||||||
Tell them where to go, how often they can expect to get an update on a
|
|
||||||
reported vulnerability, what to expect if the vulnerability is accepted or
|
|
||||||
declined, etc.
|
|
||||||
154
SSE_KMS_IMPROVEMENTS.md
Normal file
154
SSE_KMS_IMPROVEMENTS.md
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
# RustFS SSE-KMS 改进实现总结
|
||||||
|
|
||||||
|
本次改进针对 RustFS 的 SSE-KMS 系统进行了四个主要增强,使其更符合 MinIO 标准并支持动态配置管理。
|
||||||
|
|
||||||
|
## 实现的改进
|
||||||
|
|
||||||
|
### 1. 创建 KMS 配置子系统 ✅
|
||||||
|
|
||||||
|
**实现位置**: `crates/config/src/lib.rs`
|
||||||
|
|
||||||
|
- 创建了统一的配置管理器 `ConfigManager`
|
||||||
|
- 支持动态 KMS 配置的读取、设置和持久化
|
||||||
|
- 提供线程安全的全局配置访问
|
||||||
|
- 支持配置验证和错误处理
|
||||||
|
|
||||||
|
**主要功能**:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// 全局配置管理器
|
||||||
|
ConfigManager::global().get_kms_config("vault").await
|
||||||
|
ConfigManager::global().set_kms_config("vault", config).await
|
||||||
|
ConfigManager::global().validate_all_configs().await
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. KMS 配置查找和验证 ✅
|
||||||
|
|
||||||
|
**实现位置**: `ecstore/src/config/kms.rs`
|
||||||
|
|
||||||
|
- 实现了完整的 KMS 配置结构 `Config`
|
||||||
|
- 支持环境变量和配置文件双重配置源
|
||||||
|
- 提供配置验证和连接测试功能
|
||||||
|
- 兼容 MinIO 的配置参数命名
|
||||||
|
|
||||||
|
**主要特性**:
|
||||||
|
|
||||||
|
- 支持 Vault 端点、密钥名称、认证 token 等配置
|
||||||
|
- 自动验证配置完整性和有效性
|
||||||
|
- 支持 TLS 配置和证书验证
|
||||||
|
- 提供连接测试功能
|
||||||
|
|
||||||
|
### 3. S3 标准元数据格式支持 ✅
|
||||||
|
|
||||||
|
**实现位置**: `crypto/src/sse_kms.rs`
|
||||||
|
|
||||||
|
- 实现了 MinIO 兼容的元数据格式
|
||||||
|
- 支持标准 S3 SSE-KMS HTTP 头部
|
||||||
|
- 提供元数据与 HTTP 头部的双向转换
|
||||||
|
- 支持分片加密的元数据管理
|
||||||
|
|
||||||
|
**标准头部支持**:
|
||||||
|
|
||||||
|
```
|
||||||
|
x-amz-server-side-encryption: aws:kms
|
||||||
|
x-amz-server-side-encryption-aws-kms-key-id: key-id
|
||||||
|
x-amz-server-side-encryption-context: context
|
||||||
|
x-amz-meta-sse-kms-encrypted-key: encrypted-data-key
|
||||||
|
x-amz-meta-sse-kms-iv: initialization-vector
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 管理 API 支持动态配置 ✅
|
||||||
|
|
||||||
|
**实现位置**: `rustfs/src/admin/handlers.rs` 和 `rustfs/src/admin/mod.rs`
|
||||||
|
|
||||||
|
- 添加了 KMS 配置管理的 REST API 端点
|
||||||
|
- 支持 MinIO 兼容的配置管理路径
|
||||||
|
- 提供获取和设置配置的 HTTP 接口
|
||||||
|
- 支持实时配置更新和验证
|
||||||
|
|
||||||
|
**API 端点**:
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /minio/admin/v3/config # 获取所有配置
|
||||||
|
POST /minio/admin/v3/config/kms_vault/{target} # 设置KMS配置
|
||||||
|
GET /rustfs/admin/v3/config # RustFS原生配置API
|
||||||
|
POST /rustfs/admin/v3/config/kms_vault/{target} # RustFS原生设置API
|
||||||
|
```
|
||||||
|
|
||||||
|
## 技术特性
|
||||||
|
|
||||||
|
### 兼容性
|
||||||
|
|
||||||
|
- ✅ 完全兼容 MinIO 的 SSE-KMS 配置格式
|
||||||
|
- ✅ 支持标准 S3 SSE-KMS HTTP 头部
|
||||||
|
- ✅ 兼容 MinIO Admin API 配置管理接口
|
||||||
|
|
||||||
|
### 安全性
|
||||||
|
|
||||||
|
- ✅ 支持 RustyVault KMS 集成
|
||||||
|
- ✅ 数据密钥的安全生成和加密存储
|
||||||
|
- ✅ 支持 TLS 连接和证书验证
|
||||||
|
- ✅ 敏感配置信息的安全处理
|
||||||
|
|
||||||
|
### 性能
|
||||||
|
|
||||||
|
- ✅ 异步配置操作
|
||||||
|
- ✅ 线程安全的全局配置缓存
|
||||||
|
- ✅ 高效的元数据序列化/反序列化
|
||||||
|
- ✅ 支持分片并行加密
|
||||||
|
|
||||||
|
### 可维护性
|
||||||
|
|
||||||
|
- ✅ 模块化设计,职责分离
|
||||||
|
- ✅ 完整的错误处理和日志记录
|
||||||
|
- ✅ 丰富的单元测试覆盖
|
||||||
|
- ✅ 详细的文档和注释
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
### 配置 KMS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 通过环境变量配置
|
||||||
|
export RUSTFS_KMS_ENABLED=true
|
||||||
|
export RUSTFS_KMS_VAULT_ENDPOINT=http://vault:8200
|
||||||
|
export RUSTFS_KMS_VAULT_KEY_NAME=rustfs-key
|
||||||
|
export RUSTFS_KMS_VAULT_TOKEN=vault-token
|
||||||
|
|
||||||
|
# 通过API配置
|
||||||
|
curl -X POST "http://rustfs:9000/minio/admin/v3/config/kms_vault/default" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"endpoint": "http://vault:8200",
|
||||||
|
"key_name": "rustfs-encryption-key",
|
||||||
|
"token": "vault-token",
|
||||||
|
"enabled": true
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用 SSE-KMS 上传对象
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用aws-cli上传加密对象
|
||||||
|
aws s3 cp file.txt s3://bucket/file.txt \
|
||||||
|
--server-side-encryption aws:kms \
|
||||||
|
--ssekms-key-id rustfs-encryption-key
|
||||||
|
```
|
||||||
|
|
||||||
|
## 部署注意事项
|
||||||
|
|
||||||
|
1. **RustyVault 集成**: 确保 RustyVault 服务可访问且已正确配置 transit 引擎
|
||||||
|
2. **网络安全**: 建议在生产环境中使用 TLS 连接到 Vault
|
||||||
|
3. **权限管理**: 确保 RustFS 具有访问 Vault 密钥的适当权限
|
||||||
|
4. **监控**: 建议监控 KMS 连接状态和加密操作性能
|
||||||
|
|
||||||
|
## 后续发展
|
||||||
|
|
||||||
|
这次实现为 RustFS 的企业级加密功能奠定了坚实基础。未来可以考虑:
|
||||||
|
|
||||||
|
- 支持多个 KMS 提供商(AWS KMS, Azure Key Vault 等)
|
||||||
|
- 实现密钥轮换功能
|
||||||
|
- 添加加密性能监控和优化
|
||||||
|
- 支持更复杂的访问控制策略
|
||||||
|
|
||||||
|
通过这些改进,RustFS 现在具备了与 MinIO 相当的 SSE-KMS 功能,可以满足企业级数据加密需求。
|
||||||
68
TODO.md
Normal file
68
TODO.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# TODO LIST
|
||||||
|
|
||||||
|
## 基础存储
|
||||||
|
|
||||||
|
- [x] EC 可用读写数量判断 Read/WriteQuorum
|
||||||
|
- [ ] 优化后台并发执行,可中断,传引用?
|
||||||
|
- [x] 小文件存储到 metafile, inlinedata
|
||||||
|
- [x] 完善 bucketmeta
|
||||||
|
- [x] 对象锁
|
||||||
|
- [x] 边读写边 hash,实现 reader 嵌套
|
||||||
|
- [x] 远程 rpc
|
||||||
|
- [x] 错误类型判断,程序中判断错误类型,如何统一错误
|
||||||
|
- [x] 优化 xlmeta, 自定义 msg 数据结构
|
||||||
|
- [ ] 优化 io.reader 参考 GetObjectNInfo 方便 io copy 如果 异步写,再平衡
|
||||||
|
- [ ] 代码优化 使用范型?
|
||||||
|
- [ ] 抽象出 metafile 存储
|
||||||
|
|
||||||
|
## 基础功能
|
||||||
|
|
||||||
|
- [ ] 桶操作
|
||||||
|
- [x] 创建 CreateBucket
|
||||||
|
- [x] 列表 ListBuckets
|
||||||
|
- [ ] 桶下面的文件列表 ListObjects
|
||||||
|
- [x] 简单实现功能
|
||||||
|
- [ ] 优化并发读取
|
||||||
|
- [ ] 删除
|
||||||
|
- [x] 详情 HeadBucket
|
||||||
|
- [ ] 文件操作
|
||||||
|
- [x] 上传 PutObject
|
||||||
|
- [x] 大文件上传
|
||||||
|
- [x] 创建分片上传 CreateMultipartUpload
|
||||||
|
- [x] 上传分片 PubObjectPart
|
||||||
|
- [x] 提交完成 CompleteMultipartUpload
|
||||||
|
- [x] 取消上传 AbortMultipartUpload
|
||||||
|
- [x] 下载 GetObject
|
||||||
|
- [x] 删除 DeleteObjects
|
||||||
|
- [ ] 版本控制
|
||||||
|
- [ ] 对象锁
|
||||||
|
- [ ] 复制 CopyObject
|
||||||
|
- [ ] 详情 HeadObject
|
||||||
|
- [ ] 对象预先签名(get、put、head、post)
|
||||||
|
|
||||||
|
## 扩展功能
|
||||||
|
|
||||||
|
- [ ] 用户管理
|
||||||
|
- [ ] Policy 管理
|
||||||
|
- [ ] AK/SK分配管理
|
||||||
|
- [ ] data scanner 统计和对象修复
|
||||||
|
- [ ] 桶配额
|
||||||
|
- [ ] 桶只读
|
||||||
|
- [ ] 桶复制
|
||||||
|
- [ ] 桶事件通知
|
||||||
|
- [ ] 桶公开、桶私有
|
||||||
|
- [ ] 对象生命周期管理
|
||||||
|
- [ ] prometheus 对接
|
||||||
|
- [ ] 日志收集和日志外发
|
||||||
|
- [ ] 对象压缩
|
||||||
|
- [ ] STS
|
||||||
|
- [ ] 分层(阿里云、腾讯云、S3 远程对接)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 性能优化
|
||||||
|
- [ ] bitrot impl AsyncRead/AsyncWrite
|
||||||
|
- [ ] erasure 并发读写
|
||||||
|
- [x] 完善删除逻辑,并发处理,先移动到回收站,
|
||||||
|
- [ ] 空间不足时清空回收站
|
||||||
|
- [ ] list_object 使用 reader 传输
|
||||||
41
_typos.toml
41
_typos.toml
@@ -1,41 +0,0 @@
|
|||||||
[default]
|
|
||||||
# # Ignore specific spell checking patterns
|
|
||||||
# extend-ignore-identifiers-re = [
|
|
||||||
# # Ignore common patterns in base64 encoding and hash values
|
|
||||||
# "[A-Za-z0-9+/]{8,}={0,2}", # base64 encoding
|
|
||||||
# "[A-Fa-f0-9]{8,}", # hexadecimal hash
|
|
||||||
# "[A-Za-z0-9_-]{20,}", # long random strings
|
|
||||||
# ]
|
|
||||||
|
|
||||||
# # Ignore specific regex patterns in content
|
|
||||||
# extend-ignore-re = [
|
|
||||||
# # Ignore hash values and encoded strings (base64 patterns)
|
|
||||||
# "(?i)[A-Za-z0-9+/]{8,}={0,2}",
|
|
||||||
# # Ignore long strings in quotes (usually hash or base64)
|
|
||||||
# '"[A-Za-z0-9+/=_-]{8,}"',
|
|
||||||
# # Ignore IV values and similar cryptographic strings
|
|
||||||
# '"[A-Za-z0-9+/=]{12,}"',
|
|
||||||
# # Ignore cryptographic signatures and keys (including partial strings)
|
|
||||||
# "[A-Za-z0-9+/]{6,}[A-Za-z0-9+/=]*",
|
|
||||||
# # Ignore base64-like strings in comments (common in examples)
|
|
||||||
# "//.*[A-Za-z0-9+/]{8,}[A-Za-z0-9+/=]*",
|
|
||||||
# ]
|
|
||||||
extend-ignore-re = [
|
|
||||||
# Ignore long strings in quotes (usually hash or base64)
|
|
||||||
'"[A-Za-z0-9+/=_-]{32,}"',
|
|
||||||
# Ignore IV values and similar cryptographic strings
|
|
||||||
'"[A-Za-z0-9+/=]{12,}"',
|
|
||||||
# Ignore cryptographic signatures and keys (including partial strings)
|
|
||||||
"[A-Za-z0-9+/]{16,}[A-Za-z0-9+/=]*",
|
|
||||||
]
|
|
||||||
|
|
||||||
[default.extend-words]
|
|
||||||
bui = "bui"
|
|
||||||
typ = "typ"
|
|
||||||
clen = "clen"
|
|
||||||
datas = "datas"
|
|
||||||
bre = "bre"
|
|
||||||
abd = "abd"
|
|
||||||
|
|
||||||
[files]
|
|
||||||
extend-exclude = []
|
|
||||||
19
appauth/Cargo.toml
Normal file
19
appauth/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "appauth"
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
base64-simd = "0.8.0"
|
||||||
|
common.workspace = true
|
||||||
|
hex-simd = "0.8.0"
|
||||||
|
rand.workspace = true
|
||||||
|
rsa = "0.9.8"
|
||||||
|
serde.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
1
appauth/src/lib.rs
Normal file
1
appauth/src/lib.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod token;
|
||||||
110
appauth/src/token.rs
Normal file
110
appauth/src/token.rs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
use common::error::Result;
|
||||||
|
use rsa::Pkcs1v15Encrypt;
|
||||||
|
use rsa::{
|
||||||
|
pkcs8::{DecodePrivateKey, DecodePublicKey},
|
||||||
|
RsaPrivateKey, RsaPublicKey,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||||
|
pub struct Token {
|
||||||
|
pub name: String, // 应用 ID
|
||||||
|
pub expired: u64, // 到期时间 (UNIX 时间戳)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 公钥生成 Token
|
||||||
|
// [token] Token 对象
|
||||||
|
// [key] 公钥字符串
|
||||||
|
// 返回 base64 处理的加密字符串
|
||||||
|
pub fn gencode(token: &Token, key: &str) -> Result<String> {
|
||||||
|
let data = serde_json::to_vec(token)?;
|
||||||
|
let public_key = RsaPublicKey::from_public_key_pem(key)?;
|
||||||
|
let encrypted_data = public_key.encrypt(&mut rand::thread_rng(), Pkcs1v15Encrypt, &data)?;
|
||||||
|
Ok(base64_simd::URL_SAFE_NO_PAD.encode_to_string(&encrypted_data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 私钥解析 Token
|
||||||
|
// [token] base64 处理的加密字符串
|
||||||
|
// [key] 私钥字符串
|
||||||
|
// 返回 Token 对象
|
||||||
|
pub fn parse(token: &str, key: &str) -> Result<Token> {
|
||||||
|
let encrypted_data = base64_simd::URL_SAFE_NO_PAD.decode_to_vec(token.as_bytes())?;
|
||||||
|
let private_key = RsaPrivateKey::from_pkcs8_pem(key)?;
|
||||||
|
let decrypted_data = private_key.decrypt(Pkcs1v15Encrypt, &encrypted_data)?;
|
||||||
|
let res: Token = serde_json::from_slice(&decrypted_data)?;
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_license(license: &str) -> Result<Token> {
|
||||||
|
parse(license, TEST_PRIVATE_KEY)
|
||||||
|
// match parse(license, TEST_PRIVATE_KEY) {
|
||||||
|
// Ok(token) => {
|
||||||
|
// if token.expired > SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() {
|
||||||
|
// Ok(token)
|
||||||
|
// } else {
|
||||||
|
// Err("Token expired".into())
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// Err(e) => Err(e),
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
static TEST_PRIVATE_KEY:&str ="-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCj86SrJIuxSxR6\nBJ/dlJEUIj6NeBRnhLQlCDdovuz61+7kJXVcxaR66w4m8W7SLEUP+IlPtnn6vmiG\n7XMhGNHIr7r1JsEVVLhZmL3tKI66DEZl786ZhG81BWqUlmcooIPS8UEPZNqJXLuz\nVGhxNyVGbj/tV7QC2pSISnKaixc+nrhxvo7w56p5qrm9tik0PjTgfZsUePkoBsSN\npoRkAauS14MAzK6HGB75CzG3dZqXUNWSWVocoWtQbZUwFGXyzU01ammsHQDvc2xu\nK1RQpd1qYH5bOWZ0N0aPFwT0r59HztFXg9sbjsnuhO1A7OiUOkc6iGVuJ0wm/9nA\nwZIBqzgjAgMBAAECggEAPMpeSEbotPhNw2BrllE76ec4omPfzPJbiU+em+wPGoNu\nRJHPDnMKJbl6Kd5jZPKdOOrCnxfd6qcnQsBQa/kz7+GYxMV12l7ra+1Cnujm4v0i\nLTHZvPpp8ZLsjeOmpF3AAzsJEJgon74OqtOlVjVIUPEYKvzV9ijt4gsYq0zfdYv0\nhrTMzyrGM4/UvKLsFIBROAfCeWfA7sXLGH8JhrRAyDrtCPzGtyyAmzoHKHtHafcB\nuyPFw/IP8otAgpDk5iiQPNkH0WwzAQIm12oHuNUa66NwUK4WEjXTnDg8KeWLHHNv\nIfN8vdbZchMUpMIvvkr7is315d8f2cHCB5gEO+GWAQKBgQDR/0xNll+FYaiUKCPZ\nvkOCAd3l5mRhsqnjPQ/6Ul1lAyYWpoJSFMrGGn/WKTa/FVFJRTGbBjwP+Mx10bfb\ngUg2GILDTISUh54fp4zngvTi9w4MWGKXrb7I1jPkM3vbJfC/v2fraQ/r7qHPpO2L\nf6ZbGxasIlSvr37KeGoelwcAQQKBgQDH3hmOTS2Hl6D4EXdq5meHKrfeoicGN7m8\noQK7u8iwn1R9zK5nh6IXxBhKYNXNwdCQtBZVRvFjjZ56SZJb7lKqa1BcTsgJfZCy\nnI3Uu4UykrECAH8AVCVqBXUDJmeA2yE+gDAtYEjvhSDHpUfWxoGHr0B/Oqk2Lxc/\npRy1qV5fYwKBgBWSL/hYVf+RhIuTg/s9/BlCr9SJ0g3nGGRrRVTlWQqjRCpXeFOO\nJzYqSq9pFGKUggEQxoOyJEFPwVDo9gXqRcyov+Xn2kaXl7qQr3yoixc1YZALFDWY\nd1ySBEqQr0xXnV9U/gvEgwotPRnjSzNlLWV2ZuHPtPtG/7M0o1H5GZMBAoGAKr3N\nW0gX53o+my4pCnxRQW+aOIsWq1a5aqRIEFudFGBOUkS2Oz+fI1P1GdrRfhnnfzpz\n2DK+plp/vIkFOpGhrf4bBlJ2psjqa7fdANRFLMaAAfyXLDvScHTQTCcnVUAHQPVq\n2BlSH56pnugyj7SNuLV6pnql+wdhAmRN2m9o1h8CgYAbX2juSr4ioXwnYjOUdrIY\n4+ERvHcXdjoJmmPcAm4y5NbSqLXyU0FQmplNMt2A5LlniWVJ9KNdjAQUt60FZw/+\nr76LdxXaHNZghyx0BOs7mtq5unSQXamZ8KixasfhE9uz3ij1jXjG6hafWkS8/68I\nuWbaZqgvy7a9oPHYlKH7Jg==\n-----END PRIVATE KEY-----\n";
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use rsa::{
|
||||||
|
pkcs8::{EncodePrivateKey, EncodePublicKey, LineEnding},
|
||||||
|
RsaPrivateKey,
|
||||||
|
};
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
#[test]
|
||||||
|
fn test_gencode_and_parse() {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let bits = 2048;
|
||||||
|
let private_key = RsaPrivateKey::new(&mut rng, bits).expect("Failed to generate private key");
|
||||||
|
let public_key = RsaPublicKey::from(&private_key);
|
||||||
|
|
||||||
|
let private_key_pem = private_key.to_pkcs8_pem(LineEnding::LF).unwrap();
|
||||||
|
let public_key_pem = public_key.to_public_key_pem(LineEnding::LF).unwrap();
|
||||||
|
|
||||||
|
let token = Token {
|
||||||
|
name: "test_app".to_string(),
|
||||||
|
expired: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() + 3600, // 1 hour from now
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded = gencode(&token, &public_key_pem).expect("Failed to encode token");
|
||||||
|
|
||||||
|
let decoded = parse(&encoded, &private_key_pem).expect("Failed to decode token");
|
||||||
|
|
||||||
|
assert_eq!(token.name, decoded.name);
|
||||||
|
assert_eq!(token.expired, decoded.expired);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_invalid_token() {
|
||||||
|
let private_key_pem = RsaPrivateKey::new(&mut rand::thread_rng(), 2048)
|
||||||
|
.expect("Failed to generate private key")
|
||||||
|
.to_pkcs8_pem(LineEnding::LF)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let invalid_token = "invalid_base64_token";
|
||||||
|
let result = parse(invalid_token, &private_key_pem);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gencode_with_invalid_key() {
|
||||||
|
let token = Token {
|
||||||
|
name: "test_app".to_string(),
|
||||||
|
expired: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() + 3600, // 1 hour from now
|
||||||
|
};
|
||||||
|
|
||||||
|
let invalid_key = "invalid_public_key";
|
||||||
|
let result = gencode(&token, invalid_key);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
45
bucket_replicate_test.md
Normal file
45
bucket_replicate_test.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
启动两个rustfs
|
||||||
|
rustfs --address 0.0.0.0:9000 /rustfs-data9000
|
||||||
|
rustfs --address 0.0.0.0:9001 /rustfs-data9001
|
||||||
|
|
||||||
|
|
||||||
|
### 使用 minio mc 设置 alias 分别为 rustfs 和 rustfs2
|
||||||
|
|
||||||
|
|
||||||
|
### 创建 bucket
|
||||||
|
mc mb rustfs/srcbucket
|
||||||
|
|
||||||
|
### 创建 desc bucket
|
||||||
|
|
||||||
|
mc mb rustfs2/destbucket
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 开启版本控制
|
||||||
|
|
||||||
|
mc version enable rustfs/srcbucket
|
||||||
|
mc version enable rustfs2/destbucket
|
||||||
|
|
||||||
|
#### 使用修改过的 mc 才能 add bucket replication
|
||||||
|
|
||||||
|
./mc replication add rustfs/srcbucket --remote-bucket rustfs2/destbucket
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###### 复制一个小文件;
|
||||||
|
mc cp ./1.txt rustfs/srcbucket
|
||||||
|
|
||||||
|
###### 查看是否成功
|
||||||
|
mc ls --versions rustfs/srcbucket/1.txt
|
||||||
|
mc ls --versions rustfs/destbucket/1.txt
|
||||||
|
|
||||||
|
|
||||||
|
##### 复制一个大文件
|
||||||
|
1 创建一个大文件
|
||||||
|
dd if=/dev/zero of=./dd.out bs=4096000 count=1000
|
||||||
|
|
||||||
|
mc cp ./dd.out rustfs/srcbucket/
|
||||||
|
|
||||||
|
##### 查看是否成功
|
||||||
|
mc ls --versions rustfs/srcbucket/dd.out
|
||||||
|
mc ls --versions rustfs2/destbucket/dd.out
|
||||||
609
build-rustfs.sh
609
build-rustfs.sh
@@ -1,609 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# RustFS Binary Build Script
|
|
||||||
# This script compiles RustFS binaries for different platforms and architectures
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Colors for output
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# Auto-detect current platform
|
|
||||||
detect_platform() {
|
|
||||||
local arch=$(uname -m)
|
|
||||||
local os=$(uname -s | tr '[:upper:]' '[:lower:]')
|
|
||||||
|
|
||||||
case "$os" in
|
|
||||||
"linux")
|
|
||||||
case "$arch" in
|
|
||||||
"x86_64")
|
|
||||||
# Default to GNU for better compatibility
|
|
||||||
echo "x86_64-unknown-linux-gnu"
|
|
||||||
;;
|
|
||||||
"aarch64"|"arm64")
|
|
||||||
echo "aarch64-unknown-linux-gnu"
|
|
||||||
;;
|
|
||||||
"armv7l")
|
|
||||||
echo "armv7-unknown-linux-gnueabihf"
|
|
||||||
;;
|
|
||||||
"loongarch64")
|
|
||||||
echo "loongarch64-unknown-linux-musl"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "unknown-platform"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
"darwin")
|
|
||||||
case "$arch" in
|
|
||||||
"x86_64")
|
|
||||||
echo "x86_64-apple-darwin"
|
|
||||||
;;
|
|
||||||
"arm64"|"aarch64")
|
|
||||||
echo "aarch64-apple-darwin"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "unknown-platform"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "unknown-platform"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# Cross-platform SHA256 checksum generation
|
|
||||||
generate_sha256() {
|
|
||||||
local file="$1"
|
|
||||||
local output_file="$2"
|
|
||||||
local os=$(uname -s | tr '[:upper:]' '[:lower:]')
|
|
||||||
|
|
||||||
case "$os" in
|
|
||||||
"linux")
|
|
||||||
if command -v sha256sum &> /dev/null; then
|
|
||||||
sha256sum "$file" > "$output_file"
|
|
||||||
elif command -v shasum &> /dev/null; then
|
|
||||||
shasum -a 256 "$file" > "$output_file"
|
|
||||||
else
|
|
||||||
print_message $RED "❌ No SHA256 command found (sha256sum or shasum)"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
"darwin")
|
|
||||||
if command -v shasum &> /dev/null; then
|
|
||||||
shasum -a 256 "$file" > "$output_file"
|
|
||||||
elif command -v sha256sum &> /dev/null; then
|
|
||||||
sha256sum "$file" > "$output_file"
|
|
||||||
else
|
|
||||||
print_message $RED "❌ No SHA256 command found (shasum or sha256sum)"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
# Try common commands in order
|
|
||||||
if command -v sha256sum &> /dev/null; then
|
|
||||||
sha256sum "$file" > "$output_file"
|
|
||||||
elif command -v shasum &> /dev/null; then
|
|
||||||
shasum -a 256 "$file" > "$output_file"
|
|
||||||
else
|
|
||||||
print_message $RED "❌ No SHA256 command found"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# Default values
|
|
||||||
OUTPUT_DIR="target/release"
|
|
||||||
PLATFORM=$(detect_platform) # Auto-detect current platform
|
|
||||||
BINARY_NAME="rustfs"
|
|
||||||
BUILD_TYPE="release"
|
|
||||||
SIGN=false
|
|
||||||
WITH_CONSOLE=true
|
|
||||||
FORCE_CONSOLE_UPDATE=false
|
|
||||||
CONSOLE_VERSION="latest"
|
|
||||||
SKIP_VERIFICATION=false
|
|
||||||
CUSTOM_PLATFORM=""
|
|
||||||
|
|
||||||
# Print usage
|
|
||||||
usage() {
|
|
||||||
echo "Usage: $0 [OPTIONS]"
|
|
||||||
echo ""
|
|
||||||
echo "Description:"
|
|
||||||
echo " Build RustFS binary for the current platform. Designed for CI/CD pipelines"
|
|
||||||
echo " where different runners build platform-specific binaries natively."
|
|
||||||
echo " Includes automatic verification to ensure the built binary is functional."
|
|
||||||
echo ""
|
|
||||||
echo "Options:"
|
|
||||||
echo " -o, --output-dir DIR Output directory (default: target/release)"
|
|
||||||
echo " -b, --binary-name NAME Binary name (default: rustfs)"
|
|
||||||
echo " -p, --platform TARGET Target platform (default: auto-detect)"
|
|
||||||
echo " Supported platforms:"
|
|
||||||
echo " x86_64-unknown-linux-gnu"
|
|
||||||
echo " aarch64-unknown-linux-gnu"
|
|
||||||
echo " armv7-unknown-linux-gnueabihf"
|
|
||||||
echo " x86_64-unknown-linux-musl"
|
|
||||||
echo " aarch64-unknown-linux-musl"
|
|
||||||
echo " armv7-unknown-linux-musleabihf"
|
|
||||||
echo " x86_64-apple-darwin"
|
|
||||||
echo " aarch64-apple-darwin"
|
|
||||||
echo " x86_64-pc-windows-msvc"
|
|
||||||
echo " aarch64-pc-windows-msvc"
|
|
||||||
echo " --dev Build in dev mode"
|
|
||||||
echo " --sign Sign binaries after build"
|
|
||||||
echo " --with-console Download console static assets (default)"
|
|
||||||
echo " --no-console Skip console static assets"
|
|
||||||
echo " --force-console-update Force update console assets even if they exist"
|
|
||||||
echo " --console-version VERSION Console version to download (default: latest)"
|
|
||||||
echo " --skip-verification Skip binary verification after build"
|
|
||||||
echo " -h, --help Show this help message"
|
|
||||||
echo ""
|
|
||||||
echo "Examples:"
|
|
||||||
echo " $0 # Build for current platform (includes console assets)"
|
|
||||||
echo " $0 --dev # Development build"
|
|
||||||
echo " $0 --sign # Build and sign binary (release CI)"
|
|
||||||
echo " $0 --no-console # Build without console static assets"
|
|
||||||
echo " $0 --force-console-update # Force update console assets"
|
|
||||||
echo " $0 --platform x86_64-unknown-linux-musl # Build for specific platform"
|
|
||||||
echo " $0 --skip-verification # Skip binary verification (for cross-compilation)"
|
|
||||||
echo ""
|
|
||||||
echo "Detected platform: $(detect_platform)"
|
|
||||||
echo "CI Usage: Run this script on each platform's runner to build native binaries"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Print colored message
|
|
||||||
print_message() {
|
|
||||||
local color=$1
|
|
||||||
local message=$2
|
|
||||||
echo -e "${color}${message}${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Prevent zig/ld from hitting macOS file descriptor defaults during linking
|
|
||||||
ensure_file_descriptor_limit() {
|
|
||||||
local required_limit=4096
|
|
||||||
local current_limit
|
|
||||||
current_limit=$(ulimit -Sn 2>/dev/null || echo "")
|
|
||||||
|
|
||||||
if [ -z "$current_limit" ] || [ "$current_limit" = "unlimited" ]; then
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
if (( current_limit >= required_limit )); then
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
local hard_limit target_limit
|
|
||||||
hard_limit=$(ulimit -Hn 2>/dev/null || echo "")
|
|
||||||
target_limit=$required_limit
|
|
||||||
|
|
||||||
if [ -n "$hard_limit" ] && [ "$hard_limit" != "unlimited" ] && (( hard_limit < required_limit )); then
|
|
||||||
target_limit=$hard_limit
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ulimit -Sn "$target_limit" 2>/dev/null; then
|
|
||||||
print_message $YELLOW "🔧 Increased open file limit from $current_limit to $target_limit to avoid ProcessFdQuotaExceeded"
|
|
||||||
else
|
|
||||||
print_message $YELLOW "⚠️ Unable to raise ulimit -n automatically (current: $current_limit, needed: $required_limit). Please run 'ulimit -n $required_limit' manually before building."
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get version from git
|
|
||||||
get_version() {
|
|
||||||
if git describe --abbrev=0 --tags >/dev/null 2>&1; then
|
|
||||||
git describe --abbrev=0 --tags
|
|
||||||
else
|
|
||||||
git rev-parse --short HEAD
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Setup rust environment
|
|
||||||
setup_rust_environment() {
|
|
||||||
print_message $BLUE "🔧 Setting up Rust environment..."
|
|
||||||
|
|
||||||
# Install required target for current platform
|
|
||||||
print_message $YELLOW "Installing target: $PLATFORM"
|
|
||||||
rustup target add "$PLATFORM"
|
|
||||||
|
|
||||||
# Set up environment variables for musl targets
|
|
||||||
if [[ "$PLATFORM" == *"musl"* ]]; then
|
|
||||||
print_message $YELLOW "Setting up environment for musl target..."
|
|
||||||
export RUSTFLAGS="-C target-feature=-crt-static"
|
|
||||||
|
|
||||||
# For cargo-zigbuild, set up additional environment variables
|
|
||||||
if command -v cargo-zigbuild &> /dev/null; then
|
|
||||||
print_message $YELLOW "Configuring cargo-zigbuild for musl target..."
|
|
||||||
|
|
||||||
# Set environment variables for better musl support
|
|
||||||
export CC_x86_64_unknown_linux_musl="zig cc -target x86_64-linux-musl"
|
|
||||||
export CXX_x86_64_unknown_linux_musl="zig c++ -target x86_64-linux-musl"
|
|
||||||
export AR_x86_64_unknown_linux_musl="zig ar"
|
|
||||||
export CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER="zig cc -target x86_64-linux-musl"
|
|
||||||
|
|
||||||
export CC_aarch64_unknown_linux_musl="zig cc -target aarch64-linux-musl"
|
|
||||||
export CXX_aarch64_unknown_linux_musl="zig c++ -target aarch64-linux-musl"
|
|
||||||
export AR_aarch64_unknown_linux_musl="zig ar"
|
|
||||||
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER="zig cc -target aarch64-linux-musl"
|
|
||||||
|
|
||||||
# Set environment variables for zstd-sys to avoid target parsing issues
|
|
||||||
export ZSTD_SYS_USE_PKG_CONFIG=1
|
|
||||||
export PKG_CONFIG_ALLOW_CROSS=1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Install required tools
|
|
||||||
if [ "$SIGN" = true ]; then
|
|
||||||
if ! command -v minisign &> /dev/null; then
|
|
||||||
print_message $YELLOW "Installing minisign for binary signing..."
|
|
||||||
cargo install minisign
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Download console static assets
|
|
||||||
download_console_assets() {
|
|
||||||
local static_dir="rustfs/static"
|
|
||||||
local console_exists=false
|
|
||||||
|
|
||||||
# Check if console assets already exist
|
|
||||||
if [ -d "$static_dir" ] && [ -f "$static_dir/index.html" ]; then
|
|
||||||
console_exists=true
|
|
||||||
local static_size=$(du -sh "$static_dir" 2>/dev/null | cut -f1 || echo "unknown")
|
|
||||||
print_message $YELLOW "Console static assets already exist ($static_size)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Determine if we need to download
|
|
||||||
local should_download=false
|
|
||||||
if [ "$WITH_CONSOLE" = true ]; then
|
|
||||||
if [ "$console_exists" = false ]; then
|
|
||||||
print_message $BLUE "🎨 Console assets not found, downloading..."
|
|
||||||
should_download=true
|
|
||||||
elif [ "$FORCE_CONSOLE_UPDATE" = true ]; then
|
|
||||||
print_message $BLUE "🎨 Force updating console assets..."
|
|
||||||
should_download=true
|
|
||||||
else
|
|
||||||
print_message $GREEN "✅ Console assets already available, skipping download"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if [ "$console_exists" = true ]; then
|
|
||||||
print_message $GREEN "✅ Using existing console assets"
|
|
||||||
else
|
|
||||||
print_message $YELLOW "⚠️ Console assets not found. Use --download-console to download them."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$should_download" = true ]; then
|
|
||||||
print_message $BLUE "📥 Downloading console static assets..."
|
|
||||||
|
|
||||||
# Create static directory
|
|
||||||
mkdir -p "$static_dir"
|
|
||||||
|
|
||||||
# Download from GitHub Releases (consistent with Docker build)
|
|
||||||
local download_url
|
|
||||||
if [ "$CONSOLE_VERSION" = "latest" ]; then
|
|
||||||
print_message $YELLOW "Getting latest console release info..."
|
|
||||||
# For now, use dl.rustfs.com as fallback until GitHub Releases includes console assets
|
|
||||||
download_url="https://dl.rustfs.com/artifacts/console/rustfs-console-latest.zip"
|
|
||||||
else
|
|
||||||
download_url="https://dl.rustfs.com/artifacts/console/rustfs-console-${CONSOLE_VERSION}.zip"
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_message $YELLOW "Downloading from: $download_url"
|
|
||||||
|
|
||||||
# Download with retries
|
|
||||||
local temp_file="console-assets-temp.zip"
|
|
||||||
local download_success=false
|
|
||||||
|
|
||||||
for i in {1..3}; do
|
|
||||||
if curl -L "$download_url" -o "$temp_file" --retry 3 --retry-delay 5 --max-time 300; then
|
|
||||||
download_success=true
|
|
||||||
break
|
|
||||||
else
|
|
||||||
print_message $YELLOW "Download attempt $i failed, retrying..."
|
|
||||||
sleep 2
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$download_success" = true ]; then
|
|
||||||
# Verify the downloaded file
|
|
||||||
if [ -f "$temp_file" ] && [ -s "$temp_file" ]; then
|
|
||||||
print_message $BLUE "📦 Extracting console assets..."
|
|
||||||
|
|
||||||
# Extract to static directory
|
|
||||||
if unzip -o "$temp_file" -d "$static_dir"; then
|
|
||||||
rm "$temp_file"
|
|
||||||
local final_size=$(du -sh "$static_dir" 2>/dev/null | cut -f1 || echo "unknown")
|
|
||||||
print_message $GREEN "✅ Console assets downloaded successfully ($final_size)"
|
|
||||||
else
|
|
||||||
print_message $RED "❌ Failed to extract console assets"
|
|
||||||
rm -f "$temp_file"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
print_message $RED "❌ Downloaded file is empty or invalid"
|
|
||||||
rm -f "$temp_file"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
print_message $RED "❌ Failed to download console assets after 3 attempts"
|
|
||||||
print_message $YELLOW "💡 Console assets are optional. Build will continue without them."
|
|
||||||
rm -f "$temp_file"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Verify binary functionality
|
|
||||||
verify_binary() {
|
|
||||||
local binary_path="$1"
|
|
||||||
|
|
||||||
# Check if binary exists
|
|
||||||
if [ ! -f "$binary_path" ]; then
|
|
||||||
print_message $RED "❌ Binary file not found: $binary_path"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if binary is executable
|
|
||||||
if [ ! -x "$binary_path" ]; then
|
|
||||||
print_message $RED "❌ Binary is not executable: $binary_path"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check basic functionality - try to run help command
|
|
||||||
print_message $YELLOW " Testing --help command..."
|
|
||||||
if ! "$binary_path" --help >/dev/null 2>&1; then
|
|
||||||
print_message $RED "❌ Binary failed to run --help command"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check version command
|
|
||||||
print_message $YELLOW " Testing --version command..."
|
|
||||||
if ! "$binary_path" --version >/dev/null 2>&1; then
|
|
||||||
print_message $YELLOW "⚠️ Binary does not support --version command (this is optional)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Try to get some basic info about the binary
|
|
||||||
local file_info=$(file "$binary_path" 2>/dev/null || echo "unknown")
|
|
||||||
print_message $YELLOW " Binary info: $file_info"
|
|
||||||
|
|
||||||
# Check if it's a valid ELF/Mach-O binary
|
|
||||||
if command -v readelf >/dev/null 2>&1; then
|
|
||||||
if readelf -h "$binary_path" >/dev/null 2>&1; then
|
|
||||||
print_message $YELLOW " ELF binary structure: valid"
|
|
||||||
fi
|
|
||||||
elif command -v otool >/dev/null 2>&1; then
|
|
||||||
if otool -h "$binary_path" >/dev/null 2>&1; then
|
|
||||||
print_message $YELLOW " Mach-O binary structure: valid"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# Build binary for current platform
|
|
||||||
build_binary() {
|
|
||||||
local version=$(get_version)
|
|
||||||
local output_file="${OUTPUT_DIR}/${PLATFORM}/${BINARY_NAME}"
|
|
||||||
|
|
||||||
print_message $BLUE "🏗️ Building for platform: $PLATFORM"
|
|
||||||
print_message $YELLOW " Version: $version"
|
|
||||||
print_message $YELLOW " Output: $output_file"
|
|
||||||
|
|
||||||
# Create output directory
|
|
||||||
mkdir -p "${OUTPUT_DIR}/${PLATFORM}"
|
|
||||||
|
|
||||||
# Simple build logic matching the working version (4fb4b353)
|
|
||||||
# Force rebuild by touching build.rs
|
|
||||||
touch rustfs/build.rs
|
|
||||||
|
|
||||||
# Determine build command based on platform and cross-compilation needs
|
|
||||||
local build_cmd=""
|
|
||||||
local current_platform=$(detect_platform)
|
|
||||||
|
|
||||||
print_message $BLUE "📦 Using working version build logic..."
|
|
||||||
|
|
||||||
# Check if we need cross-compilation
|
|
||||||
if [ "$PLATFORM" != "$current_platform" ]; then
|
|
||||||
# Cross-compilation needed
|
|
||||||
if [[ "$PLATFORM" == *"apple-darwin"* ]]; then
|
|
||||||
print_message $RED "❌ macOS cross-compilation not supported"
|
|
||||||
print_message $YELLOW "💡 macOS targets must be built natively on macOS runners"
|
|
||||||
return 1
|
|
||||||
elif [[ "$PLATFORM" == *"windows"* ]]; then
|
|
||||||
# Use cross for Windows ARM64
|
|
||||||
if ! command -v cross &> /dev/null; then
|
|
||||||
print_message $YELLOW "📦 Installing cross tool..."
|
|
||||||
cargo install cross --git https://github.com/cross-rs/cross
|
|
||||||
fi
|
|
||||||
build_cmd="cross build"
|
|
||||||
else
|
|
||||||
# Use zigbuild for Linux ARM64 (matches working version)
|
|
||||||
if ! command -v cargo-zigbuild &> /dev/null; then
|
|
||||||
print_message $RED "❌ cargo-zigbuild not found. Please install it first."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
build_cmd="cargo zigbuild"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
# Native compilation
|
|
||||||
build_cmd="RUSTFLAGS=-Clink-arg=-lm cargo build"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$BUILD_TYPE" = "release" ]; then
|
|
||||||
build_cmd+=" --release"
|
|
||||||
fi
|
|
||||||
|
|
||||||
build_cmd+=" --target $PLATFORM"
|
|
||||||
build_cmd+=" -p rustfs --bins"
|
|
||||||
|
|
||||||
print_message $BLUE "📦 Executing: $build_cmd"
|
|
||||||
|
|
||||||
# Execute build (this matches exactly what the working version does)
|
|
||||||
if eval $build_cmd; then
|
|
||||||
print_message $GREEN "✅ Successfully built for $PLATFORM"
|
|
||||||
|
|
||||||
# Copy binary to output directory
|
|
||||||
cp "target/${PLATFORM}/${BUILD_TYPE}/${BINARY_NAME}" "$output_file"
|
|
||||||
|
|
||||||
# Generate checksums
|
|
||||||
print_message $BLUE "🔐 Generating checksums..."
|
|
||||||
(cd "${OUTPUT_DIR}/${PLATFORM}" && generate_sha256 "${BINARY_NAME}" "${BINARY_NAME}.sha256sum")
|
|
||||||
|
|
||||||
# Verify binary functionality (if not skipped)
|
|
||||||
if [ "$SKIP_VERIFICATION" = false ]; then
|
|
||||||
print_message $BLUE "🔍 Verifying binary functionality..."
|
|
||||||
if verify_binary "$output_file"; then
|
|
||||||
print_message $GREEN "✅ Binary verification passed"
|
|
||||||
else
|
|
||||||
print_message $RED "❌ Binary verification failed"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
print_message $YELLOW "⚠️ Binary verification skipped by user request"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Sign binary if requested
|
|
||||||
if [ "$SIGN" = true ]; then
|
|
||||||
print_message $BLUE "✍️ Signing binary..."
|
|
||||||
(cd "${OUTPUT_DIR}/${PLATFORM}" && minisign -S -m "${BINARY_NAME}" -s ~/.minisign/minisign.key)
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_message $GREEN "✅ Build completed successfully"
|
|
||||||
else
|
|
||||||
print_message $RED "❌ Failed to build for $PLATFORM"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Main build function
|
|
||||||
build_rustfs() {
|
|
||||||
local version=$(get_version)
|
|
||||||
|
|
||||||
print_message $BLUE "🚀 Starting RustFS binary build process..."
|
|
||||||
print_message $YELLOW " Version: $version"
|
|
||||||
print_message $YELLOW " Platform: $PLATFORM"
|
|
||||||
print_message $YELLOW " Output Directory: $OUTPUT_DIR"
|
|
||||||
print_message $YELLOW " Build Type: $BUILD_TYPE"
|
|
||||||
print_message $YELLOW " Sign: $SIGN"
|
|
||||||
print_message $YELLOW " With Console: $WITH_CONSOLE"
|
|
||||||
if [ "$WITH_CONSOLE" = true ]; then
|
|
||||||
print_message $YELLOW " Console Version: $CONSOLE_VERSION"
|
|
||||||
print_message $YELLOW " Force Console Update: $FORCE_CONSOLE_UPDATE"
|
|
||||||
fi
|
|
||||||
print_message $YELLOW " Skip Verification: $SKIP_VERIFICATION"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Setup environment
|
|
||||||
setup_rust_environment
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Download console assets if requested
|
|
||||||
download_console_assets
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Build binary
|
|
||||||
build_binary
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
print_message $GREEN "🎉 Build process completed successfully!"
|
|
||||||
|
|
||||||
# Show built binary
|
|
||||||
local binary_file="${OUTPUT_DIR}/${PLATFORM}/${BINARY_NAME}"
|
|
||||||
if [ -f "$binary_file" ]; then
|
|
||||||
local size=$(ls -lh "$binary_file" | awk '{print $5}')
|
|
||||||
print_message $BLUE "📋 Built binary: $binary_file ($size)"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Parse command line arguments
|
|
||||||
while [[ $# -gt 0 ]]; do
|
|
||||||
case $1 in
|
|
||||||
-o|--output-dir)
|
|
||||||
OUTPUT_DIR="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
-b|--binary-name)
|
|
||||||
BINARY_NAME="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
-p|--platform)
|
|
||||||
CUSTOM_PLATFORM="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
--dev)
|
|
||||||
BUILD_TYPE="debug"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--sign)
|
|
||||||
SIGN=true
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--with-console)
|
|
||||||
WITH_CONSOLE=true
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--no-console)
|
|
||||||
WITH_CONSOLE=false
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--force-console-update)
|
|
||||||
FORCE_CONSOLE_UPDATE=true
|
|
||||||
WITH_CONSOLE=true # Auto-enable download when forcing update
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--console-version)
|
|
||||||
CONSOLE_VERSION="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
--skip-verification)
|
|
||||||
SKIP_VERIFICATION=true
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
-h|--help)
|
|
||||||
usage
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
print_message $RED "❌ Unknown option: $1"
|
|
||||||
usage
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# Main execution
|
|
||||||
main() {
|
|
||||||
print_message $BLUE "🦀 RustFS Binary Build Script"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Check if we're in a Rust project
|
|
||||||
if [ ! -f "Cargo.toml" ]; then
|
|
||||||
print_message $RED "❌ No Cargo.toml found. Are you in a Rust project directory?"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Override platform if specified
|
|
||||||
if [ -n "$CUSTOM_PLATFORM" ]; then
|
|
||||||
PLATFORM="$CUSTOM_PLATFORM"
|
|
||||||
print_message $YELLOW "🎯 Using specified platform: $PLATFORM"
|
|
||||||
|
|
||||||
# Auto-enable skip verification for cross-compilation
|
|
||||||
if [ "$PLATFORM" != "$(detect_platform)" ]; then
|
|
||||||
SKIP_VERIFICATION=true
|
|
||||||
print_message $YELLOW "⚠️ Cross-compilation detected, enabling --skip-verification"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
ensure_file_descriptor_limit
|
|
||||||
|
|
||||||
# Start build process
|
|
||||||
build_rustfs
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run main function
|
|
||||||
main
|
|
||||||
21
build_rustfs.sh
Executable file
21
build_rustfs.sh
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
clear
|
||||||
|
|
||||||
|
# 获取当前平台架构
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
|
||||||
|
# 根据架构设置 target 目录
|
||||||
|
if [ "$ARCH" == "x86_64" ]; then
|
||||||
|
TARGET_DIR="target/x86_64"
|
||||||
|
elif [ "$ARCH" == "aarch64" ]; then
|
||||||
|
TARGET_DIR="target/arm64"
|
||||||
|
else
|
||||||
|
TARGET_DIR="target/unknown"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 设置 CARGO_TARGET_DIR 并构建项目
|
||||||
|
CARGO_TARGET_DIR=$TARGET_DIR RUSTFLAGS="-C link-arg=-fuse-ld=mold" cargo build --package rustfs
|
||||||
|
|
||||||
|
echo -e "\a"
|
||||||
|
echo -e "\a"
|
||||||
|
echo -e "\a"
|
||||||
32
cli/rustfs-gui/Cargo.toml
Normal file
32
cli/rustfs-gui/Cargo.toml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
[package]
|
||||||
|
name = "rustfs-gui"
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
chrono = { workspace = true }
|
||||||
|
dioxus = { workspace = true, features = ["router"] }
|
||||||
|
dirs = { workspace = true }
|
||||||
|
hex = { workspace = true }
|
||||||
|
keyring = { workspace = true }
|
||||||
|
lazy_static = { workspace = true }
|
||||||
|
rfd = { workspace = true }
|
||||||
|
rust-embed = { workspace = true, features = ["interpolate-folder-path"] }
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
sha2 = { workspace = true }
|
||||||
|
tokio = { workspace = true, features = ["io-util", "net", "process", "sync"] }
|
||||||
|
tracing-subscriber = { workspace = true, features = ["fmt", "env-filter", "tracing-log", "time", "local-time", "json"] }
|
||||||
|
tracing-appender = { workspace = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["desktop"]
|
||||||
|
web = ["dioxus/web"]
|
||||||
|
desktop = ["dioxus/desktop"]
|
||||||
|
mobile = ["dioxus/mobile"]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
50
cli/rustfs-gui/Dioxus.toml
Normal file
50
cli/rustfs-gui/Dioxus.toml
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
[application]
|
||||||
|
|
||||||
|
# App (Project) Name
|
||||||
|
name = "rustfs-gui"
|
||||||
|
|
||||||
|
# The static resource path
|
||||||
|
asset_dir = "public"
|
||||||
|
|
||||||
|
[web.app]
|
||||||
|
|
||||||
|
# HTML title tag content
|
||||||
|
title = "rustfs-gui"
|
||||||
|
|
||||||
|
# include `assets` in web platform
|
||||||
|
[web.resource]
|
||||||
|
|
||||||
|
# Additional CSS style files
|
||||||
|
style = []
|
||||||
|
|
||||||
|
# Additional JavaScript files
|
||||||
|
script = []
|
||||||
|
|
||||||
|
[web.resource.dev]
|
||||||
|
|
||||||
|
# Javascript code file
|
||||||
|
# serve: [dev-server] only
|
||||||
|
script = []
|
||||||
|
|
||||||
|
[bundle]
|
||||||
|
identifier = "com.rustfs.cli.gui"
|
||||||
|
|
||||||
|
publisher = "RustFsGUI"
|
||||||
|
|
||||||
|
category = "Utility"
|
||||||
|
|
||||||
|
copyright = "Copyright 2025 rustfs.com"
|
||||||
|
|
||||||
|
icon = [
|
||||||
|
"assets/icons/icon.icns",
|
||||||
|
"assets/icons/icon.ico"
|
||||||
|
]
|
||||||
|
#[bundle.macos]
|
||||||
|
#provider_short_name = "RustFs"
|
||||||
|
[bundle.windows]
|
||||||
|
tsp = true
|
||||||
|
icon_path = "assets/icons/icon.ico"
|
||||||
|
allow_downgrades = true
|
||||||
|
[bundle.windows.webview_install_mode]
|
||||||
|
[bundle.windows.webview_install_mode.EmbedBootstrapper]
|
||||||
|
silent = true
|
||||||
34
cli/rustfs-gui/README.md
Normal file
34
cli/rustfs-gui/README.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
## Rustfs GUI
|
||||||
|
|
||||||
|
### Tailwind
|
||||||
|
|
||||||
|
1. Install npm: https://docs.npmjs.com/downloading-and-installing-node-js-and-npm
|
||||||
|
2. Install the Tailwind CSS CLI: https://tailwindcss.com/docs/installation
|
||||||
|
3. Run the following command in the root of the project to start the Tailwind CSS compiler:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx tailwindcss -i ./input.css -o ./assets/tailwind.css --watch
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dioxus CLI
|
||||||
|
|
||||||
|
#### Install the stable version (recommended)
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cargo install dioxus-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
### Serving Your App
|
||||||
|
|
||||||
|
Run the following command in the root of your project to start developing with the default platform:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dx serve
|
||||||
|
```
|
||||||
|
|
||||||
|
To run for a different platform, use the `--platform platform` flag. E.g.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dx serve --platform desktop
|
||||||
|
```
|
||||||
|
|
||||||
BIN
cli/rustfs-gui/assets/favicon.ico
Normal file
BIN
cli/rustfs-gui/assets/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
BIN
cli/rustfs-gui/assets/icons/icon-all.icns
Normal file
BIN
cli/rustfs-gui/assets/icons/icon-all.icns
Normal file
Binary file not shown.
BIN
cli/rustfs-gui/assets/icons/icon-all.ico
Normal file
BIN
cli/rustfs-gui/assets/icons/icon-all.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
BIN
cli/rustfs-gui/assets/icons/icon.icns
Normal file
BIN
cli/rustfs-gui/assets/icons/icon.icns
Normal file
Binary file not shown.
BIN
cli/rustfs-gui/assets/icons/icon.ico
Normal file
BIN
cli/rustfs-gui/assets/icons/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
32
cli/rustfs-gui/assets/js/sts.js
Normal file
32
cli/rustfs-gui/assets/js/sts.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
window.switchTab = function (tabId) {
|
||||||
|
// Hide everything
|
||||||
|
document.querySelectorAll('.tab-content').forEach(content => {
|
||||||
|
content.classList.add('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset all label styles
|
||||||
|
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||||
|
btn.classList.remove('border-b-2', 'border-black');
|
||||||
|
btn.classList.add('text-gray-500');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Displays the selected content
|
||||||
|
const activeContent = document.getElementById(tabId);
|
||||||
|
if (activeContent) {
|
||||||
|
activeContent.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates the selected label style
|
||||||
|
const activeBtn = document.querySelector(`[data-tab="${tabId}"]`);
|
||||||
|
if (activeBtn) {
|
||||||
|
activeBtn.classList.add('border-b-2', 'border-black');
|
||||||
|
activeBtn.classList.remove('text-gray-500');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.togglePassword = function (button) {
|
||||||
|
const input = button.parentElement.querySelector('input[type="password"], input[type="text"]');
|
||||||
|
if (input) {
|
||||||
|
input.type = input.type === 'password' ? 'text' : 'password';
|
||||||
|
}
|
||||||
|
};
|
||||||
BIN
cli/rustfs-gui/assets/rustfs-logo-square.png
Normal file
BIN
cli/rustfs-gui/assets/rustfs-logo-square.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
20
cli/rustfs-gui/assets/rustfs-logo.svg
Normal file
20
cli/rustfs-gui/assets/rustfs-logo.svg
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<svg width="1558" height="260" viewBox="0 0 1558 260" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_0_3)">
|
||||||
|
<path d="M1288.5 112.905H1159.75V58.4404H1262L1270 0L1074 0V260H1159.75V162.997H1296.95L1288.5 112.905Z"
|
||||||
|
fill="#0196D0"/>
|
||||||
|
<path d="M1058.62 58.4404V0H789V58.4404H881.133V260H966.885V58.4404H1058.62Z" fill="#0196D0"/>
|
||||||
|
<path d="M521 179.102V0L454.973 15V161C454.973 181.124 452.084 193.146 443.5 202C434.916 211.257 419.318 214.5 400.5 214.5C381.022 214.5 366.744 210.854 357.5 202C348.916 193.548 346.357 175.721 346.357 156V0L280 15V175.48C280 208.08 290.234 229.412 309.712 241.486C329.19 253.56 358.903 260 400.5 260C440.447 260 470.159 253.56 490.297 241.486C510.766 229.412 521 208.483 521 179.102Z"
|
||||||
|
fill="#0196D0"/>
|
||||||
|
<path d="M172.84 84.2813C172.84 97.7982 168.249 107.737 158.41 113.303C149.883 118.471 137.092 121.254 120.693 122.049V162.997C129.876 163.792 138.076 166.177 144.307 176.514L184.647 260H265L225.316 180.489C213.181 155.046 201.374 149.48 178.744 143.517C212.197 138.349 241.386 118.471 241.386 73.1499C241.386 53.2722 233.843 30.2141 218.756 17.8899C203.998 5.56575 183.991 0 159.394 0H120.693V48.5015H127.58C142.23 48.5015 153.6 51.4169 161.689 57.2477C169.233 62.8135 172.84 71.5596 172.84 84.2813ZM120.693 122.049C119.163 122.049 117.741 122.049 116.43 122.049H68.5457V48.5015H120.693V0H0V260H70.5137V162.997H110.526C113.806 162.997 117.741 162.997 120.693 162.997V122.049Z"
|
||||||
|
fill="#0196D0"/>
|
||||||
|
<path d="M774 179.297C774 160.829 766.671 144.669 752.013 131.972C738.127 119.66 712.025 110.169 673.708 103.5C662.136 101.191 651.722 99.6523 643.235 97.3437C586.532 84.6467 594.632 52.7118 650.564 52.7118C680.651 52.7118 709.582 61.946 738.127 66.9478C742.37 67.7174 743.913 68.1021 744.298 68.1021L750.47 12.697C720.383 3.46282 684.895 0 654.036 0C616.619 0 587.689 6.54088 567.245 19.2379C546.801 31.9349 536 57.7137 536 82.3382C536 103.5 543.715 119.66 559.916 131.972C575.731 143.515 604.276 152.749 645.55 160.059C658.279 162.368 668.694 163.907 676.794 166.215C685.023 168.524 691.066 170.704 694.924 172.756C702.253 176.604 706.11 182.375 706.11 188.531C706.11 196.611 701.481 202.767 692.224 207C664.836 220.081 587.689 212.001 556.83 198.15L543.715 247.784C547.186 248.169 552.972 249.323 559.916 250.477C616.619 259.327 690.681 270.869 741.212 238.935C762.814 225.468 774 206.23 774 179.297Z"
|
||||||
|
fill="#0196D0"/>
|
||||||
|
<path d="M1558 179.568C1558 160.383 1550.42 144.268 1535.67 131.99C1521.32 119.968 1494.34 110.631 1454.74 103.981C1442.38 101.679 1432.01 99.3764 1422.84 97.8416C1422.44 97.8416 1422.04 97.8416 1422.04 97.4579V112.422L1361.04 75.2038L1422.04 38.3692V52.9496C1424.7 52.9496 1427.49 52.9496 1430.41 52.9496C1461.51 52.9496 1491.42 62.5419 1521.32 67.5299C1525.31 67.9136 1526.9 67.9136 1527.3 67.9136L1533.68 12.6619C1502.98 3.83692 1465.9 0 1434 0C1395.33 0 1365.43 6.52277 1345.09 19.5683C1323.16 32.6139 1312 57.9376 1312 82.8776C1312 103.981 1320.37 120.096 1336.72 131.607C1353.46 143.885 1382.97 153.093 1425.23 160.383C1434 161.535 1441.18 162.686 1447.56 164.22L1448.36 150.791L1507.36 190.312L1445.57 224.844L1445.96 212.949C1409.68 215.635 1357.45 209.112 1333.53 197.985L1320.37 247.482C1323.56 248.249 1329.54 248.633 1336.72 250.551C1395.33 259.376 1471.88 270.887 1524.11 238.657C1546.84 225.611 1558 205.659 1558 179.568Z"
|
||||||
|
fill="#0196D0"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_0_3">
|
||||||
|
<rect width="1558" height="260" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.5 KiB |
17
cli/rustfs-gui/assets/styling/navbar.css
Normal file
17
cli/rustfs-gui/assets/styling/navbar.css
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#navbar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar a {
|
||||||
|
color: #ffffff;
|
||||||
|
margin-right: 20px;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar a:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #ffffff;
|
||||||
|
/ / #91a4d2;
|
||||||
|
}
|
||||||
956
cli/rustfs-gui/assets/tailwind.css
Normal file
956
cli/rustfs-gui/assets/tailwind.css
Normal file
@@ -0,0 +1,956 @@
|
|||||||
|
*, ::before, ::after {
|
||||||
|
--tw-border-spacing-x: 0;
|
||||||
|
--tw-border-spacing-y: 0;
|
||||||
|
--tw-translate-x: 0;
|
||||||
|
--tw-translate-y: 0;
|
||||||
|
--tw-rotate: 0;
|
||||||
|
--tw-skew-x: 0;
|
||||||
|
--tw-skew-y: 0;
|
||||||
|
--tw-scale-x: 1;
|
||||||
|
--tw-scale-y: 1;
|
||||||
|
--tw-pan-x: ;
|
||||||
|
--tw-pan-y: ;
|
||||||
|
--tw-pinch-zoom: ;
|
||||||
|
--tw-scroll-snap-strictness: proximity;
|
||||||
|
--tw-gradient-from-position: ;
|
||||||
|
--tw-gradient-via-position: ;
|
||||||
|
--tw-gradient-to-position: ;
|
||||||
|
--tw-ordinal: ;
|
||||||
|
--tw-slashed-zero: ;
|
||||||
|
--tw-numeric-figure: ;
|
||||||
|
--tw-numeric-spacing: ;
|
||||||
|
--tw-numeric-fraction: ;
|
||||||
|
--tw-ring-inset: ;
|
||||||
|
--tw-ring-offset-width: 0px;
|
||||||
|
--tw-ring-offset-color: #fff;
|
||||||
|
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||||
|
--tw-ring-offset-shadow: 0 0 #0000;
|
||||||
|
--tw-ring-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow-colored: 0 0 #0000;
|
||||||
|
--tw-blur: ;
|
||||||
|
--tw-brightness: ;
|
||||||
|
--tw-contrast: ;
|
||||||
|
--tw-grayscale: ;
|
||||||
|
--tw-hue-rotate: ;
|
||||||
|
--tw-invert: ;
|
||||||
|
--tw-saturate: ;
|
||||||
|
--tw-sepia: ;
|
||||||
|
--tw-drop-shadow: ;
|
||||||
|
--tw-backdrop-blur: ;
|
||||||
|
--tw-backdrop-brightness: ;
|
||||||
|
--tw-backdrop-contrast: ;
|
||||||
|
--tw-backdrop-grayscale: ;
|
||||||
|
--tw-backdrop-hue-rotate: ;
|
||||||
|
--tw-backdrop-invert: ;
|
||||||
|
--tw-backdrop-opacity: ;
|
||||||
|
--tw-backdrop-saturate: ;
|
||||||
|
--tw-backdrop-sepia: ;
|
||||||
|
--tw-contain-size: ;
|
||||||
|
--tw-contain-layout: ;
|
||||||
|
--tw-contain-paint: ;
|
||||||
|
--tw-contain-style: ;
|
||||||
|
}
|
||||||
|
|
||||||
|
::backdrop {
|
||||||
|
--tw-border-spacing-x: 0;
|
||||||
|
--tw-border-spacing-y: 0;
|
||||||
|
--tw-translate-x: 0;
|
||||||
|
--tw-translate-y: 0;
|
||||||
|
--tw-rotate: 0;
|
||||||
|
--tw-skew-x: 0;
|
||||||
|
--tw-skew-y: 0;
|
||||||
|
--tw-scale-x: 1;
|
||||||
|
--tw-scale-y: 1;
|
||||||
|
--tw-pan-x: ;
|
||||||
|
--tw-pan-y: ;
|
||||||
|
--tw-pinch-zoom: ;
|
||||||
|
--tw-scroll-snap-strictness: proximity;
|
||||||
|
--tw-gradient-from-position: ;
|
||||||
|
--tw-gradient-via-position: ;
|
||||||
|
--tw-gradient-to-position: ;
|
||||||
|
--tw-ordinal: ;
|
||||||
|
--tw-slashed-zero: ;
|
||||||
|
--tw-numeric-figure: ;
|
||||||
|
--tw-numeric-spacing: ;
|
||||||
|
--tw-numeric-fraction: ;
|
||||||
|
--tw-ring-inset: ;
|
||||||
|
--tw-ring-offset-width: 0px;
|
||||||
|
--tw-ring-offset-color: #fff;
|
||||||
|
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||||
|
--tw-ring-offset-shadow: 0 0 #0000;
|
||||||
|
--tw-ring-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow-colored: 0 0 #0000;
|
||||||
|
--tw-blur: ;
|
||||||
|
--tw-brightness: ;
|
||||||
|
--tw-contrast: ;
|
||||||
|
--tw-grayscale: ;
|
||||||
|
--tw-hue-rotate: ;
|
||||||
|
--tw-invert: ;
|
||||||
|
--tw-saturate: ;
|
||||||
|
--tw-sepia: ;
|
||||||
|
--tw-drop-shadow: ;
|
||||||
|
--tw-backdrop-blur: ;
|
||||||
|
--tw-backdrop-brightness: ;
|
||||||
|
--tw-backdrop-contrast: ;
|
||||||
|
--tw-backdrop-grayscale: ;
|
||||||
|
--tw-backdrop-hue-rotate: ;
|
||||||
|
--tw-backdrop-invert: ;
|
||||||
|
--tw-backdrop-opacity: ;
|
||||||
|
--tw-backdrop-saturate: ;
|
||||||
|
--tw-backdrop-sepia: ;
|
||||||
|
--tw-contain-size: ;
|
||||||
|
--tw-contain-layout: ;
|
||||||
|
--tw-contain-paint: ;
|
||||||
|
--tw-contain-style: ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
||||||
|
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
|
||||||
|
*/
|
||||||
|
|
||||||
|
*,
|
||||||
|
::before,
|
||||||
|
::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* 1 */
|
||||||
|
border-width: 0;
|
||||||
|
/* 2 */
|
||||||
|
border-style: solid;
|
||||||
|
/* 2 */
|
||||||
|
border-color: #e5e7eb;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
::before,
|
||||||
|
::after {
|
||||||
|
--tw-content: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Use a consistent sensible line-height in all browsers.
|
||||||
|
2. Prevent adjustments of font size after orientation changes in iOS.
|
||||||
|
3. Use a more readable tab size.
|
||||||
|
4. Use the user's configured `sans` font-family by default.
|
||||||
|
5. Use the user's configured `sans` font-feature-settings by default.
|
||||||
|
6. Use the user's configured `sans` font-variation-settings by default.
|
||||||
|
7. Disable tap highlights on iOS
|
||||||
|
*/
|
||||||
|
|
||||||
|
html,
|
||||||
|
:host {
|
||||||
|
line-height: 1.5;
|
||||||
|
/* 1 */
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
/* 2 */
|
||||||
|
-moz-tab-size: 4;
|
||||||
|
/* 3 */
|
||||||
|
-o-tab-size: 4;
|
||||||
|
tab-size: 4;
|
||||||
|
/* 3 */
|
||||||
|
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
/* 4 */
|
||||||
|
font-feature-settings: normal;
|
||||||
|
/* 5 */
|
||||||
|
font-variation-settings: normal;
|
||||||
|
/* 6 */
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
/* 7 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Remove the margin in all browsers.
|
||||||
|
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
/* 1 */
|
||||||
|
line-height: inherit;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Add the correct height in Firefox.
|
||||||
|
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
||||||
|
3. Ensure horizontal rules are visible by default.
|
||||||
|
*/
|
||||||
|
|
||||||
|
hr {
|
||||||
|
height: 0;
|
||||||
|
/* 1 */
|
||||||
|
color: inherit;
|
||||||
|
/* 2 */
|
||||||
|
border-top-width: 1px;
|
||||||
|
/* 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct text decoration in Chrome, Edge, and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
abbr:where([title]) {
|
||||||
|
-webkit-text-decoration: underline dotted;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the default font size and weight for headings.
|
||||||
|
*/
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reset links to optimize for opt-in styling instead of opt-out.
|
||||||
|
*/
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct font weight in Edge and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Use the user's configured `mono` font-family by default.
|
||||||
|
2. Use the user's configured `mono` font-feature-settings by default.
|
||||||
|
3. Use the user's configured `mono` font-variation-settings by default.
|
||||||
|
4. Correct the odd `em` font sizing in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp,
|
||||||
|
pre {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
/* 1 */
|
||||||
|
font-feature-settings: normal;
|
||||||
|
/* 2 */
|
||||||
|
font-variation-settings: normal;
|
||||||
|
/* 3 */
|
||||||
|
font-size: 1em;
|
||||||
|
/* 4 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct font size in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
||||||
|
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
||||||
|
3. Remove gaps between table borders by default.
|
||||||
|
*/
|
||||||
|
|
||||||
|
table {
|
||||||
|
text-indent: 0;
|
||||||
|
/* 1 */
|
||||||
|
border-color: inherit;
|
||||||
|
/* 2 */
|
||||||
|
border-collapse: collapse;
|
||||||
|
/* 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Change the font styles in all browsers.
|
||||||
|
2. Remove the margin in Firefox and Safari.
|
||||||
|
3. Remove default padding in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
optgroup,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
/* 1 */
|
||||||
|
font-feature-settings: inherit;
|
||||||
|
/* 1 */
|
||||||
|
font-variation-settings: inherit;
|
||||||
|
/* 1 */
|
||||||
|
font-size: 100%;
|
||||||
|
/* 1 */
|
||||||
|
font-weight: inherit;
|
||||||
|
/* 1 */
|
||||||
|
line-height: inherit;
|
||||||
|
/* 1 */
|
||||||
|
letter-spacing: inherit;
|
||||||
|
/* 1 */
|
||||||
|
color: inherit;
|
||||||
|
/* 1 */
|
||||||
|
margin: 0;
|
||||||
|
/* 2 */
|
||||||
|
padding: 0;
|
||||||
|
/* 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the inheritance of text transform in Edge and Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Correct the inability to style clickable types in iOS and Safari.
|
||||||
|
2. Remove default button styles.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
input:where([type='button']),
|
||||||
|
input:where([type='reset']),
|
||||||
|
input:where([type='submit']) {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
/* 1 */
|
||||||
|
background-color: transparent;
|
||||||
|
/* 2 */
|
||||||
|
background-image: none;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Use the modern Firefox focus style for all focusable elements.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:-moz-focusring {
|
||||||
|
outline: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
|
||||||
|
*/
|
||||||
|
|
||||||
|
:-moz-ui-invalid {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct vertical alignment in Chrome and Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Correct the cursor style of increment and decrement buttons in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-inner-spin-button,
|
||||||
|
::-webkit-outer-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Correct the odd appearance in Chrome and Safari.
|
||||||
|
2. Correct the outline style in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[type='search'] {
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
/* 1 */
|
||||||
|
outline-offset: -2px;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the inner padding in Chrome and Safari on macOS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Correct the inability to style clickable types in iOS and Safari.
|
||||||
|
2. Change font properties to `inherit` in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
/* 1 */
|
||||||
|
font: inherit;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct display in Chrome and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Removes the default spacing and border for appropriate elements.
|
||||||
|
*/
|
||||||
|
|
||||||
|
blockquote,
|
||||||
|
dl,
|
||||||
|
dd,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
hr,
|
||||||
|
figure,
|
||||||
|
p,
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
menu {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reset default styling for dialogs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
dialog {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Prevent resizing textareas horizontally by default.
|
||||||
|
*/
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
|
||||||
|
2. Set the default placeholder color to the user's configured gray 400 color.
|
||||||
|
*/
|
||||||
|
|
||||||
|
input::-moz-placeholder, textarea::-moz-placeholder {
|
||||||
|
opacity: 1;
|
||||||
|
/* 1 */
|
||||||
|
color: #9ca3af;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
input::placeholder,
|
||||||
|
textarea::placeholder {
|
||||||
|
opacity: 1;
|
||||||
|
/* 1 */
|
||||||
|
color: #9ca3af;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the default cursor for buttons.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
[role="button"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Make sure disabled buttons don't get the pointer cursor.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||||
|
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
||||||
|
This can trigger a poorly considered lint error in some tools but is included by design.
|
||||||
|
*/
|
||||||
|
|
||||||
|
img,
|
||||||
|
svg,
|
||||||
|
video,
|
||||||
|
canvas,
|
||||||
|
audio,
|
||||||
|
iframe,
|
||||||
|
embed,
|
||||||
|
object {
|
||||||
|
display: block;
|
||||||
|
/* 1 */
|
||||||
|
vertical-align: middle;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||||
|
*/
|
||||||
|
|
||||||
|
img,
|
||||||
|
video {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make elements with the HTML hidden attribute stay hidden by default */
|
||||||
|
|
||||||
|
[hidden]:where(:not([hidden="until-found"])) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.static {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
|
||||||
|
.absolute {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.relative {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-2 {
|
||||||
|
right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-6 {
|
||||||
|
right: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-1\/2 {
|
||||||
|
top: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-4 {
|
||||||
|
top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.z-10 {
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-2 {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-4 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-6 {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-8 {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ml-2 {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-16 {
|
||||||
|
height: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-24 {
|
||||||
|
height: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-4 {
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-5 {
|
||||||
|
height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-6 {
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.min-h-screen {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-16 {
|
||||||
|
width: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-20 {
|
||||||
|
width: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-24 {
|
||||||
|
width: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-4 {
|
||||||
|
width: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-48 {
|
||||||
|
width: 12rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-5 {
|
||||||
|
width: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-6 {
|
||||||
|
width: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-1 {
|
||||||
|
flex: 1 1 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.-translate-y-1\/2 {
|
||||||
|
--tw-translate-y: -50%;
|
||||||
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
|
}
|
||||||
|
|
||||||
|
.transform {
|
||||||
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-spin {
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-col {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-center {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-x-2 > :not([hidden]) ~ :not([hidden]) {
|
||||||
|
--tw-space-x-reverse: 0;
|
||||||
|
margin-right: calc(0.5rem * var(--tw-space-x-reverse));
|
||||||
|
margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
|
||||||
|
--tw-space-x-reverse: 0;
|
||||||
|
margin-right: calc(1rem * var(--tw-space-x-reverse));
|
||||||
|
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-x-8 > :not([hidden]) ~ :not([hidden]) {
|
||||||
|
--tw-space-x-reverse: 0;
|
||||||
|
margin-right: calc(2rem * var(--tw-space-x-reverse));
|
||||||
|
margin-left: calc(2rem * calc(1 - var(--tw-space-x-reverse)));
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-y-4 > :not([hidden]) ~ :not([hidden]) {
|
||||||
|
--tw-space-y-reverse: 0;
|
||||||
|
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
|
||||||
|
margin-bottom: calc(1rem * var(--tw-space-y-reverse));
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-y-6 > :not([hidden]) ~ :not([hidden]) {
|
||||||
|
--tw-space-y-reverse: 0;
|
||||||
|
margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
|
||||||
|
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
|
||||||
|
}
|
||||||
|
|
||||||
|
.rounded {
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rounded-full {
|
||||||
|
border-radius: 9999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rounded-lg {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rounded-md {
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border {
|
||||||
|
border-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-b {
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-b-2 {
|
||||||
|
border-bottom-width: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-black {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(0 0 0 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-gray-200 {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(229 231 235 / var(--tw-border-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-\[\#111827\] {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(17 24 39 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-gray-100 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-gray-900 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(17 24 39 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-red-500 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(239 68 68 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-white {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-2 {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-4 {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-8 {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.px-1 {
|
||||||
|
padding-left: 0.25rem;
|
||||||
|
padding-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.px-3 {
|
||||||
|
padding-left: 0.75rem;
|
||||||
|
padding-right: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.px-4 {
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-0\.5 {
|
||||||
|
padding-top: 0.125rem;
|
||||||
|
padding-bottom: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-2 {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-4 {
|
||||||
|
padding-top: 1rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-6 {
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
padding-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pr-10 {
|
||||||
|
padding-right: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-2xl {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-base {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-sm {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-medium {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-semibold {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-blue-500 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(59 130 246 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-blue-600 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(37 99 235 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-gray-400 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(156 163 175 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-gray-500 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(107 114 128 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-gray-600 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(75 85 99 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-white {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.opacity-25 {
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opacity-75 {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter {
|
||||||
|
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover\:bg-\[\#1f2937\]:hover {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(31 41 55 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover\:bg-gray-100:hover {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover\:bg-red-600:hover {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(220 38 38 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover\:text-gray-700:hover {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(55 65 81 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover\:text-gray-900:hover {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(17 24 39 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.focus\:outline-none:focus {
|
||||||
|
outline: 2px solid transparent;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.focus\:ring-2:focus {
|
||||||
|
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||||
|
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||||
|
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||||
|
}
|
||||||
|
|
||||||
|
.focus\:ring-blue-500:focus {
|
||||||
|
--tw-ring-opacity: 1;
|
||||||
|
--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1));
|
||||||
|
}
|
||||||
1
cli/rustfs-gui/embedded-rustfs/README.md
Normal file
1
cli/rustfs-gui/embedded-rustfs/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
rustfs bin path, do not delete
|
||||||
3
cli/rustfs-gui/input.css
Normal file
3
cli/rustfs-gui/input.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
315
cli/rustfs-gui/src/components/home.rs
Normal file
315
cli/rustfs-gui/src/components/home.rs
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
use crate::components::navbar::LoadingSpinner;
|
||||||
|
use crate::route::Route;
|
||||||
|
use crate::utils::{RustFSConfig, ServiceManager};
|
||||||
|
use chrono::Datelike;
|
||||||
|
use dioxus::logger::tracing::debug;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
const HEADER_LOGO: Asset = asset!("/assets/rustfs-logo.svg");
|
||||||
|
const TAILWIND_CSS: Asset = asset!("/assets/tailwind.css");
|
||||||
|
|
||||||
|
/// Define the state of the service
|
||||||
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
|
enum ServiceState {
|
||||||
|
Start,
|
||||||
|
Stop,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Define the Home component
|
||||||
|
/// The Home component is the main component of the application
|
||||||
|
/// It is responsible for starting and stopping the service
|
||||||
|
/// It also displays the service status and provides a button to toggle the service
|
||||||
|
/// The Home component also displays the footer of the application
|
||||||
|
/// The footer contains links to the official site, documentation, GitHub, and license
|
||||||
|
/// The footer also displays the version of the application
|
||||||
|
/// The Home component also contains a button to change the theme of the application
|
||||||
|
/// The Home component also contains a button to go to the settings page
|
||||||
|
#[component]
|
||||||
|
pub fn Home() -> Element {
|
||||||
|
#[allow(clippy::redundant_closure)]
|
||||||
|
let service = use_signal(|| ServiceManager::new());
|
||||||
|
let conf = RustFSConfig::load().unwrap_or_else(|e| {
|
||||||
|
ServiceManager::show_error(&format!("加载配置失败:{}", e));
|
||||||
|
RustFSConfig::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
debug!("loaded configurations:{:?}", conf);
|
||||||
|
let config = use_signal(|| conf.clone());
|
||||||
|
|
||||||
|
use dioxus_router::prelude::Link;
|
||||||
|
use document::{Meta, Stylesheet, Title};
|
||||||
|
let mut service_state = use_signal(|| ServiceState::Start);
|
||||||
|
// Create a periodic check on the effect of the service status
|
||||||
|
use_effect(move || {
|
||||||
|
spawn(async move {
|
||||||
|
loop {
|
||||||
|
if let Some(pid) = ServiceManager::check_service_status().await {
|
||||||
|
debug!("service_running true pid: {:?}", pid);
|
||||||
|
service_state.set(ServiceState::Stop);
|
||||||
|
} else {
|
||||||
|
debug!("service_running true pid: 0");
|
||||||
|
service_state.set(ServiceState::Start);
|
||||||
|
}
|
||||||
|
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
debug!("project start service_state: {:?}", service_state.read());
|
||||||
|
// Use 'use_signal' to manage service status
|
||||||
|
let mut loading = use_signal(|| false);
|
||||||
|
let mut start_service = move |_| {
|
||||||
|
let service = service;
|
||||||
|
let config = config.read().clone();
|
||||||
|
let mut service_state = service_state;
|
||||||
|
// set the loading status
|
||||||
|
loading.set(true);
|
||||||
|
debug!("stop loading_state: {:?}", loading.read());
|
||||||
|
spawn(async move {
|
||||||
|
match service.read().start(config).await {
|
||||||
|
Ok(result) => {
|
||||||
|
if result.success {
|
||||||
|
let duration = result.end_time - result.start_time;
|
||||||
|
debug!("The service starts successfully and takes a long time:{}ms", duration.num_milliseconds());
|
||||||
|
service_state.set(ServiceState::Stop);
|
||||||
|
} else {
|
||||||
|
ServiceManager::show_error(&result.message);
|
||||||
|
service_state.set(ServiceState::Start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
ServiceManager::show_error(&format!("服务启动失败:{}", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Only set loading to false when it's actually done
|
||||||
|
loading.set(false);
|
||||||
|
debug!("start loading_state: {:?}", loading.read());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut stop_service = move |_| {
|
||||||
|
let service = service;
|
||||||
|
let mut service_state = service_state;
|
||||||
|
// set the loading status
|
||||||
|
loading.set(true);
|
||||||
|
spawn(async move {
|
||||||
|
match service.read().stop().await {
|
||||||
|
Ok(result) => {
|
||||||
|
if result.success {
|
||||||
|
let duration = result.end_time - result.start_time;
|
||||||
|
debug!("The service stops successfully and takes a long time:{}ms", duration.num_milliseconds());
|
||||||
|
service_state.set(ServiceState::Start);
|
||||||
|
} else {
|
||||||
|
ServiceManager::show_error(&result.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
ServiceManager::show_error(&format!("服务停止失败:{}", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug!("service_state: {:?}", service_state.read());
|
||||||
|
// Only set loading to false when it's actually done
|
||||||
|
loading.set(false);
|
||||||
|
debug!("stop loading_state: {:?}", loading.read());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Toggle the state when the button is clicked
|
||||||
|
let toggle_service = {
|
||||||
|
let mut service_state = service_state;
|
||||||
|
debug!("toggle_service service_state: {:?}", service_state.read());
|
||||||
|
move |_| {
|
||||||
|
if service_state.read().eq(&ServiceState::Stop) {
|
||||||
|
// If the service status is started, you need to run a command to stop the service
|
||||||
|
stop_service(());
|
||||||
|
service_state.set(ServiceState::Start);
|
||||||
|
} else {
|
||||||
|
start_service(());
|
||||||
|
service_state.set(ServiceState::Stop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define dynamic styles based on state
|
||||||
|
let button_class = if service_state.read().eq(&ServiceState::Start) {
|
||||||
|
"bg-[#111827] hover:bg-[#1f2937] text-white px-4 py-2 rounded-md flex items-center space-x-2"
|
||||||
|
} else {
|
||||||
|
"bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md flex items-center space-x-2"
|
||||||
|
};
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
// The Stylesheet component inserts a style link into the head of the document
|
||||||
|
Stylesheet {href: TAILWIND_CSS,}
|
||||||
|
Title { "RustFS APP" }
|
||||||
|
Meta {
|
||||||
|
name: "description",
|
||||||
|
content: "RustFS RustFS 用热门安全的 Rust 语言开发,兼容 S3 协议。适用于 AI/ML 及海量数据存储、大数据、互联网、工业和保密存储等全部场景。近乎免费使用。遵循 Apache 2 协议,支持国产保密设备和系统。",
|
||||||
|
}
|
||||||
|
div { class: "min-h-screen flex flex-col items-center bg-white",
|
||||||
|
div { class: "absolute top-4 right-6 flex space-x-2",
|
||||||
|
// change theme
|
||||||
|
button { class: "p-2 hover:bg-gray-100 rounded-lg", ChangeThemeButton {} }
|
||||||
|
// setting button
|
||||||
|
Link {
|
||||||
|
class: "p-2 hover:bg-gray-100 rounded-lg",
|
||||||
|
to: Route::SettingViews {},
|
||||||
|
SettingButton {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main { class: "flex-1 flex flex-col items-center justify-center space-y-6 p-4",
|
||||||
|
div { class: "w-24 h-24 bg-gray-900 rounded-full flex items-center justify-center",
|
||||||
|
img { alt: "Logo", class: "w-16 h-16", src: HEADER_LOGO }
|
||||||
|
}
|
||||||
|
div { class: "text-gray-600",
|
||||||
|
"Service is running on "
|
||||||
|
span { class: "text-blue-600", " 127.0.0.1:9000 " }
|
||||||
|
}
|
||||||
|
LoadingSpinner {
|
||||||
|
loading: loading.read().to_owned(),
|
||||||
|
text: "服务处理中...",
|
||||||
|
}
|
||||||
|
button { class: button_class, onclick: toggle_service,
|
||||||
|
svg {
|
||||||
|
class: "h-4 w-4",
|
||||||
|
fill: "none",
|
||||||
|
stroke: "currentColor",
|
||||||
|
view_box: "0 0 24 24",
|
||||||
|
xmlns: "http://www.w3.org/2000/svg",
|
||||||
|
if service_state.read().eq(&ServiceState::Start) {
|
||||||
|
path {
|
||||||
|
d: "M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z",
|
||||||
|
stroke_linecap: "round",
|
||||||
|
stroke_linejoin: "round",
|
||||||
|
stroke_width: "2",
|
||||||
|
}
|
||||||
|
path {
|
||||||
|
d: "M21 12a9 9 0 11-18 0 9 9 0 0118 0z",
|
||||||
|
stroke_linecap: "round",
|
||||||
|
stroke_linejoin: "round",
|
||||||
|
stroke_width: "2",
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
path {
|
||||||
|
stroke_linecap: "round",
|
||||||
|
stroke_linejoin: "round",
|
||||||
|
stroke_width: "2",
|
||||||
|
d: "M21 12a9 9 0 11-18 0 9 9 0 0118 0z",
|
||||||
|
}
|
||||||
|
path {
|
||||||
|
stroke_linecap: "round",
|
||||||
|
stroke_linejoin: "round",
|
||||||
|
stroke_width: "2",
|
||||||
|
d: "M9 10h6v4H9z",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span { id: "serviceStatus",
|
||||||
|
if service_state.read().eq(&ServiceState::Start) {
|
||||||
|
"Start service"
|
||||||
|
} else {
|
||||||
|
"Stop service"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Footer { version: "v1.0.0".to_string() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn Footer(version: String) -> Element {
|
||||||
|
let now = chrono::Local::now();
|
||||||
|
let year = now.naive_local().year();
|
||||||
|
rsx! {
|
||||||
|
footer { class: "w-full py-6 flex flex-col items-center space-y-4 mb-6",
|
||||||
|
nav { class: "flex space-x-4 text-gray-600",
|
||||||
|
a { class: "hover:text-gray-900", href: "https://rustfs.com", "Official Site" }
|
||||||
|
a {
|
||||||
|
class: "hover:text-gray-900",
|
||||||
|
href: "https://rustfs.com/docs",
|
||||||
|
"Documentation"
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
class: "hover:text-gray-900",
|
||||||
|
href: "https://github.com/rustfs/rustfs",
|
||||||
|
"GitHub"
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
class: "hover:text-gray-900",
|
||||||
|
href: "https://rustfs.com/docs/license/",
|
||||||
|
"License"
|
||||||
|
}
|
||||||
|
a { class: "hover:text-gray-900", href: "#", "Sponsors" }
|
||||||
|
}
|
||||||
|
div { class: "text-gray-500 text-sm", " © rustfs.com {year}, All rights reserved." }
|
||||||
|
div { class: "text-gray-400 text-sm mb-8", " version {version} " }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn GoBackButtons() -> Element {
|
||||||
|
rsx! {
|
||||||
|
button {
|
||||||
|
class: "p-2 hover:bg-gray-100 rounded-lg",
|
||||||
|
"onclick": "window.history.back()",
|
||||||
|
"Back to the Past"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn GoForwardButtons() -> Element {
|
||||||
|
rsx! {
|
||||||
|
button {
|
||||||
|
class: "p-2 hover:bg-gray-100 rounded-lg",
|
||||||
|
"onclick": "window.history.forward()",
|
||||||
|
"Back to the Future"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn ChangeThemeButton() -> Element {
|
||||||
|
rsx! {
|
||||||
|
svg {
|
||||||
|
class: "h-6 w-6 text-gray-600",
|
||||||
|
fill: "none",
|
||||||
|
stroke: "currentColor",
|
||||||
|
view_box: "0 0 24 24",
|
||||||
|
xmlns: "http://www.w3.org/2000/svg",
|
||||||
|
path {
|
||||||
|
d: "M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z",
|
||||||
|
stroke_linecap: "round",
|
||||||
|
stroke_linejoin: "round",
|
||||||
|
stroke_width: "2",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn SettingButton() -> Element {
|
||||||
|
rsx! {
|
||||||
|
svg {
|
||||||
|
class: "h-6 w-6 text-gray-600",
|
||||||
|
fill: "none",
|
||||||
|
stroke: "currentColor",
|
||||||
|
view_box: "0 0 24 24",
|
||||||
|
xmlns: "http://www.w3.org/2000/svg",
|
||||||
|
path {
|
||||||
|
d: "M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z",
|
||||||
|
stroke_linecap: "round",
|
||||||
|
stroke_linejoin: "round",
|
||||||
|
stroke_width: "2",
|
||||||
|
}
|
||||||
|
path {
|
||||||
|
d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z",
|
||||||
|
stroke_linecap: "round",
|
||||||
|
stroke_linejoin: "round",
|
||||||
|
stroke_width: "2",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
cli/rustfs-gui/src/components/mod.rs
Normal file
6
cli/rustfs-gui/src/components/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
mod home;
|
||||||
|
pub use home::Home;
|
||||||
|
mod navbar;
|
||||||
|
pub use navbar::Navbar;
|
||||||
|
mod setting;
|
||||||
|
pub use setting::Setting;
|
||||||
60
cli/rustfs-gui/src/components/navbar.rs
Normal file
60
cli/rustfs-gui/src/components/navbar.rs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
use crate::route::Route;
|
||||||
|
use dioxus::logger::tracing::debug;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
const NAVBAR_CSS: Asset = asset!("/assets/styling/navbar.css");
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn Navbar() -> Element {
|
||||||
|
rsx! {
|
||||||
|
document::Link { rel: "stylesheet", href: NAVBAR_CSS }
|
||||||
|
|
||||||
|
div { id: "navbar", class: "hidden", style: "display: none;",
|
||||||
|
Link { to: Route::HomeViews {}, "Home" }
|
||||||
|
Link { to: Route::SettingViews {}, "Setting" }
|
||||||
|
}
|
||||||
|
|
||||||
|
Outlet::<Route> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Props, PartialEq, Debug, Clone)]
|
||||||
|
pub struct LoadingSpinnerProps {
|
||||||
|
#[props(default = true)]
|
||||||
|
loading: bool,
|
||||||
|
#[props(default = "正在处理中...")]
|
||||||
|
text: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn LoadingSpinner(props: LoadingSpinnerProps) -> Element {
|
||||||
|
debug!("loading: {}", props.loading);
|
||||||
|
if !props.loading {
|
||||||
|
debug!("LoadingSpinner false loading: {}", props.loading);
|
||||||
|
return rsx! {};
|
||||||
|
}
|
||||||
|
rsx! {
|
||||||
|
div { class: "flex items-center justify-center z-10",
|
||||||
|
svg {
|
||||||
|
class: "animate-spin h-5 w-5 text-blue-500",
|
||||||
|
xmlns: "http://www.w3.org/2000/svg",
|
||||||
|
fill: "none",
|
||||||
|
view_box: "0 0 24 24",
|
||||||
|
circle {
|
||||||
|
class: "opacity-25",
|
||||||
|
cx: "12",
|
||||||
|
cy: "12",
|
||||||
|
r: "10",
|
||||||
|
stroke: "currentColor",
|
||||||
|
stroke_width: "4",
|
||||||
|
}
|
||||||
|
path {
|
||||||
|
class: "opacity-75",
|
||||||
|
fill: "currentColor",
|
||||||
|
d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span { class: "ml-2 text-gray-600", "{props.text}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
202
cli/rustfs-gui/src/components/setting.rs
Normal file
202
cli/rustfs-gui/src/components/setting.rs
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
use crate::components::navbar::LoadingSpinner;
|
||||||
|
use dioxus::logger::tracing::{debug, error};
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
const SETTINGS_JS: Asset = asset!("/assets/js/sts.js");
|
||||||
|
const TAILWIND_CSS: Asset = asset!("/assets/tailwind.css");
|
||||||
|
#[component]
|
||||||
|
pub fn Setting() -> Element {
|
||||||
|
use crate::utils::{RustFSConfig, ServiceManager};
|
||||||
|
use document::{Meta, Script, Stylesheet, Title};
|
||||||
|
|
||||||
|
#[allow(clippy::redundant_closure)]
|
||||||
|
let service = use_signal(|| ServiceManager::new());
|
||||||
|
let conf = RustFSConfig::load().unwrap_or_else(|e| {
|
||||||
|
error!("load config error: {}", e);
|
||||||
|
RustFSConfig::default_config()
|
||||||
|
});
|
||||||
|
debug!("conf address: {:?}", conf.clone().address);
|
||||||
|
|
||||||
|
let config = use_signal(|| conf.clone());
|
||||||
|
let address_state = use_signal(|| conf.address.to_string());
|
||||||
|
let mut host_state = use_signal(|| conf.host.to_string());
|
||||||
|
let mut port_state = use_signal(|| conf.port.to_string());
|
||||||
|
let mut access_key_state = use_signal(|| conf.access_key.to_string());
|
||||||
|
let mut secret_key_state = use_signal(|| conf.secret_key.to_string());
|
||||||
|
let mut volume_name_state = use_signal(|| conf.volume_name.to_string());
|
||||||
|
let loading = use_signal(|| false);
|
||||||
|
|
||||||
|
let save_and_restart = {
|
||||||
|
let host_state = host_state;
|
||||||
|
let port_state = port_state;
|
||||||
|
let access_key_state = access_key_state;
|
||||||
|
let secret_key_state = secret_key_state;
|
||||||
|
let volume_name_state = volume_name_state;
|
||||||
|
let mut loading = loading;
|
||||||
|
debug!("save_and_restart access_key:{}", access_key_state.read());
|
||||||
|
move |_| {
|
||||||
|
// set the loading status
|
||||||
|
loading.set(true);
|
||||||
|
let mut config = config;
|
||||||
|
config.write().address = format!("{}:{}", host_state.read(), port_state.read());
|
||||||
|
config.write().host = host_state.read().to_string();
|
||||||
|
config.write().port = port_state.read().to_string();
|
||||||
|
config.write().access_key = access_key_state.read().to_string();
|
||||||
|
config.write().secret_key = secret_key_state.read().to_string();
|
||||||
|
config.write().volume_name = volume_name_state.read().to_string();
|
||||||
|
// restart service
|
||||||
|
let service = service;
|
||||||
|
let config = config.read().clone();
|
||||||
|
spawn(async move {
|
||||||
|
if let Err(e) = service.read().restart(config).await {
|
||||||
|
ServiceManager::show_error(&format!("发送重启命令失败:{}", e));
|
||||||
|
}
|
||||||
|
// reset the status when you're done
|
||||||
|
loading.set(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
Title { "Settings - RustFS App" }
|
||||||
|
Meta { name: "description", content: "Settings - RustFS App." }
|
||||||
|
// The Stylesheet component inserts a style link into the head of the document
|
||||||
|
Stylesheet { href: TAILWIND_CSS }
|
||||||
|
Script { src: SETTINGS_JS }
|
||||||
|
div { class: "bg-white p-8",
|
||||||
|
h1 { class: "text-2xl font-semibold mb-6", "Settings" }
|
||||||
|
div { class: "border-b border-gray-200 mb-6",
|
||||||
|
nav { class: "flex space-x-8",
|
||||||
|
button {
|
||||||
|
class: "tab-btn px-1 py-4 text-sm font-medium border-b-2 border-black",
|
||||||
|
"data-tab": "service",
|
||||||
|
"onclick": "switchTab('service')",
|
||||||
|
"Service "
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
class: "tab-btn px-1 py-4 text-sm font-medium text-gray-500 hover:text-gray-700",
|
||||||
|
"data-tab": "user",
|
||||||
|
"onclick": "switchTab('user')",
|
||||||
|
"User "
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
class: "tab-btn px-1 py-4 text-sm font-medium text-gray-500 hover:text-gray-700 hidden",
|
||||||
|
"data-tab": "logs",
|
||||||
|
"onclick": "switchTab('logs')",
|
||||||
|
"Logs "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div { id: "tabContent",
|
||||||
|
div { class: "tab-content", id: "service",
|
||||||
|
div { class: "mb-8",
|
||||||
|
h2 { class: "text-base font-medium mb-2", "Service address" }
|
||||||
|
p { class: "text-gray-600 mb-4",
|
||||||
|
" The service address is the IP address and port number of the service. the default address is "
|
||||||
|
code { class: "bg-gray-100 px-1 py-0.5 rounded", {address_state} }
|
||||||
|
". "
|
||||||
|
}
|
||||||
|
div { class: "flex space-x-2",
|
||||||
|
input {
|
||||||
|
class: "border rounded px-3 py-2 w-48 focus:outline-none focus:ring-2 focus:ring-blue-500",
|
||||||
|
r#type: "text",
|
||||||
|
value: host_state,
|
||||||
|
oninput: move |evt| host_state.set(evt.value().clone()),
|
||||||
|
}
|
||||||
|
span { class: "flex items-center", ":" }
|
||||||
|
input {
|
||||||
|
class: "border rounded px-3 py-2 w-20 focus:outline-none focus:ring-2 focus:ring-blue-500",
|
||||||
|
r#type: "text",
|
||||||
|
value: port_state,
|
||||||
|
oninput: move |evt| port_state.set(evt.value().clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div { class: "mb-8",
|
||||||
|
h2 { class: "text-base font-medium mb-2", "Storage path" }
|
||||||
|
p { class: "text-gray-600 mb-4",
|
||||||
|
"Update the storage path of the service. the default path is {volume_name_state}."
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
class: "border rounded px-3 py-2 w-full focus:outline-none focus:ring-2 focus:ring-blue-500",
|
||||||
|
r#type: "text",
|
||||||
|
value: volume_name_state,
|
||||||
|
oninput: move |evt| volume_name_state.set(evt.value().clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div { class: "tab-content hidden", id: "user",
|
||||||
|
div { class: "mb-8",
|
||||||
|
h2 { class: "text-base font-medium mb-2", "User" }
|
||||||
|
p { class: "text-gray-600 mb-4",
|
||||||
|
"The user is the owner of the service. the default user is "
|
||||||
|
code { class: "bg-gray-100 px-1 py-0.5 rounded", {access_key_state} }
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
class: "border rounded px-3 py-2 w-full focus:outline-none focus:ring-2 focus:ring-blue-500",
|
||||||
|
r#type: "text",
|
||||||
|
value: access_key_state,
|
||||||
|
oninput: move |evt| access_key_state.set(evt.value().clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div { class: "mb-8",
|
||||||
|
h2 { class: "text-base font-medium mb-2", "Password" }
|
||||||
|
p { class: "text-gray-600 mb-4",
|
||||||
|
"The password is the password of the user. the default password is "
|
||||||
|
code { class: "bg-gray-100 px-1 py-0.5 rounded", {secret_key_state} }
|
||||||
|
}
|
||||||
|
div { class: "relative",
|
||||||
|
input {
|
||||||
|
class: "border rounded px-3 py-2 w-full pr-10 focus:outline-none focus:ring-2 focus:ring-blue-500",
|
||||||
|
r#type: "password",
|
||||||
|
value: secret_key_state,
|
||||||
|
oninput: move |evt| secret_key_state.set(evt.value().clone()),
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
class: "absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700",
|
||||||
|
"onclick": "togglePassword(this)",
|
||||||
|
svg {
|
||||||
|
class: "h-5 w-5",
|
||||||
|
fill: "currentColor",
|
||||||
|
view_box: "0 0 20 20",
|
||||||
|
xmlns: "http://www.w3.org/2000/svg",
|
||||||
|
path { d: "M10 12a2 2 0 100-4 2 2 0 000 4z" }
|
||||||
|
path {
|
||||||
|
clip_rule: "evenodd",
|
||||||
|
d: "M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z",
|
||||||
|
fill_rule: "evenodd",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div { class: "tab-content hidden", id: "logs",
|
||||||
|
div { class: "mb-8",
|
||||||
|
h2 { class: "text-base font-medium mb-2", "Logs storage path" }
|
||||||
|
p { class: "text-gray-600 mb-4",
|
||||||
|
"The logs storage path is the path where the logs are stored. the default path is /var/log/rustfs. "
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
class: "border rounded px-3 py-2 w-full focus:outline-none focus:ring-2 focus:ring-blue-500",
|
||||||
|
r#type: "text",
|
||||||
|
value: "/var/logs/rustfs",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div { class: "flex space-x-4",
|
||||||
|
button {
|
||||||
|
class: "bg-[#111827] text-white px-4 py-2 rounded hover:bg-[#1f2937]",
|
||||||
|
onclick: save_and_restart,
|
||||||
|
" Save and restart "
|
||||||
|
}
|
||||||
|
GoBackButton { "Back" }
|
||||||
|
}
|
||||||
|
LoadingSpinner {
|
||||||
|
loading: loading.read().to_owned(),
|
||||||
|
text: "服务处理中...",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
cli/rustfs-gui/src/main.rs
Normal file
9
cli/rustfs-gui/src/main.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
mod components;
|
||||||
|
mod route;
|
||||||
|
mod utils;
|
||||||
|
mod views;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let _worker_guard = utils::init_logger();
|
||||||
|
dioxus::launch(views::App);
|
||||||
|
}
|
||||||
3
cli/rustfs-gui/src/route/mod.rs
Normal file
3
cli/rustfs-gui/src/route/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
mod router;
|
||||||
|
|
||||||
|
pub use router::Route;
|
||||||
14
cli/rustfs-gui/src/route/router.rs
Normal file
14
cli/rustfs-gui/src/route/router.rs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
use crate::components::Navbar;
|
||||||
|
use crate::views::{HomeViews, SettingViews};
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
/// The router for the application
|
||||||
|
#[derive(Debug, Clone, Routable, PartialEq)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub enum Route {
|
||||||
|
#[layout(Navbar)]
|
||||||
|
#[route("/")]
|
||||||
|
HomeViews {},
|
||||||
|
#[route("/settings")]
|
||||||
|
SettingViews {},
|
||||||
|
}
|
||||||
550
cli/rustfs-gui/src/utils/config.rs
Normal file
550
cli/rustfs-gui/src/utils/config.rs
Normal file
@@ -0,0 +1,550 @@
|
|||||||
|
use keyring::Entry;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
/// Configuration for the RustFS service
|
||||||
|
///
|
||||||
|
/// # Fields
|
||||||
|
/// * `address` - The address of the RustFS service
|
||||||
|
/// * `host` - The host of the RustFS service
|
||||||
|
/// * `port` - The port of the RustFS service
|
||||||
|
/// * `access_key` - The access key of the RustFS service
|
||||||
|
/// * `secret_key` - The secret key of the RustFS service
|
||||||
|
/// * `domain_name` - The domain name of the RustFS service
|
||||||
|
/// * `volume_name` - The volume name of the RustFS service
|
||||||
|
/// * `console_address` - The console address of the RustFS service
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// let config = RustFSConfig {
|
||||||
|
/// address: "127.0.0.1:9000".to_string(),
|
||||||
|
/// host: "127.0.0.1".to_string(),
|
||||||
|
/// port: "9000".to_string(),
|
||||||
|
/// access_key: "rustfsadmin".to_string(),
|
||||||
|
/// secret_key: "rustfsadmin".to_string(),
|
||||||
|
/// domain_name: "demo.rustfs.com".to_string(),
|
||||||
|
/// volume_name: "data".to_string(),
|
||||||
|
/// console_address: "127.0.0.1:9001".to_string(),
|
||||||
|
/// };
|
||||||
|
/// println!("{:?}", config);
|
||||||
|
/// assert_eq!(config.address, "127.0.0.1:9000");
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Default, Deserialize, Serialize, Ord, PartialOrd, Eq, PartialEq)]
|
||||||
|
pub struct RustFSConfig {
|
||||||
|
pub address: String,
|
||||||
|
pub host: String,
|
||||||
|
pub port: String,
|
||||||
|
pub access_key: String,
|
||||||
|
pub secret_key: String,
|
||||||
|
pub domain_name: String,
|
||||||
|
pub volume_name: String,
|
||||||
|
pub console_address: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RustFSConfig {
|
||||||
|
/// keyring the name of the service
|
||||||
|
const SERVICE_NAME: &'static str = "rustfs-service";
|
||||||
|
/// keyring the key of the service
|
||||||
|
const SERVICE_KEY: &'static str = "rustfs_key";
|
||||||
|
/// default domain name
|
||||||
|
const DEFAULT_DOMAIN_NAME_VALUE: &'static str = "demo.rustfs.com";
|
||||||
|
/// default address value
|
||||||
|
const DEFAULT_ADDRESS_VALUE: &'static str = "127.0.0.1:9000";
|
||||||
|
/// default port value
|
||||||
|
const DEFAULT_PORT_VALUE: &'static str = "9000";
|
||||||
|
/// default host value
|
||||||
|
const DEFAULT_HOST_VALUE: &'static str = "127.0.0.1";
|
||||||
|
/// default access key value
|
||||||
|
const DEFAULT_ACCESS_KEY_VALUE: &'static str = "rustfsadmin";
|
||||||
|
/// default secret key value
|
||||||
|
const DEFAULT_SECRET_KEY_VALUE: &'static str = "rustfsadmin";
|
||||||
|
/// default console address value
|
||||||
|
const DEFAULT_CONSOLE_ADDRESS_VALUE: &'static str = "127.0.0.1:9001";
|
||||||
|
|
||||||
|
/// get the default volume_name
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * The default volume name
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// let volume_name = RustFSConfig::default_volume_name();
|
||||||
|
/// ```
|
||||||
|
pub fn default_volume_name() -> String {
|
||||||
|
dirs::home_dir()
|
||||||
|
.map(|home| home.join("rustfs").join("data"))
|
||||||
|
.and_then(|path| path.to_str().map(String::from))
|
||||||
|
.unwrap_or_else(|| "data".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// create a default configuration
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * The default configuration
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// let config = RustFSConfig::default_config();
|
||||||
|
/// println!("{:?}", config);
|
||||||
|
/// assert_eq!(config.address, "127.0.0.1:9000");
|
||||||
|
/// ```
|
||||||
|
pub fn default_config() -> Self {
|
||||||
|
Self {
|
||||||
|
address: Self::DEFAULT_ADDRESS_VALUE.to_string(),
|
||||||
|
host: Self::DEFAULT_HOST_VALUE.to_string(),
|
||||||
|
port: Self::DEFAULT_PORT_VALUE.to_string(),
|
||||||
|
access_key: Self::DEFAULT_ACCESS_KEY_VALUE.to_string(),
|
||||||
|
secret_key: Self::DEFAULT_SECRET_KEY_VALUE.to_string(),
|
||||||
|
domain_name: Self::DEFAULT_DOMAIN_NAME_VALUE.to_string(),
|
||||||
|
volume_name: Self::default_volume_name(),
|
||||||
|
console_address: Self::DEFAULT_CONSOLE_ADDRESS_VALUE.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load the configuration from the keyring
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// * If the configuration cannot be loaded from the keyring
|
||||||
|
/// * If the configuration cannot be deserialized
|
||||||
|
/// * If the address cannot be extracted from the configuration
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// let config = RustFSConfig::load().unwrap();
|
||||||
|
/// println!("{:?}", config);
|
||||||
|
/// assert_eq!(config.address, "127.0.0.1:9000");
|
||||||
|
/// ```
|
||||||
|
pub fn load() -> Result<Self, Box<dyn Error>> {
|
||||||
|
let mut config = Self::default_config();
|
||||||
|
|
||||||
|
// Try to get the configuration of the storage from the keyring
|
||||||
|
let entry = Entry::new(Self::SERVICE_NAME, Self::SERVICE_KEY)?;
|
||||||
|
if let Ok(stored_json) = entry.get_password() {
|
||||||
|
if let Ok(stored_config) = serde_json::from_str::<RustFSConfig>(&stored_json) {
|
||||||
|
// update fields that are not empty and non default
|
||||||
|
if !stored_config.address.is_empty() && stored_config.address != Self::DEFAULT_ADDRESS_VALUE {
|
||||||
|
config.address = stored_config.address;
|
||||||
|
let (host, port) = Self::extract_host_port(config.address.as_str())
|
||||||
|
.ok_or_else(|| format!("无法从地址 '{}' 中提取主机和端口", config.address))?;
|
||||||
|
config.host = host.to_string();
|
||||||
|
config.port = port.to_string();
|
||||||
|
}
|
||||||
|
if !stored_config.access_key.is_empty() && stored_config.access_key != Self::DEFAULT_ACCESS_KEY_VALUE {
|
||||||
|
config.access_key = stored_config.access_key;
|
||||||
|
}
|
||||||
|
if !stored_config.secret_key.is_empty() && stored_config.secret_key != Self::DEFAULT_SECRET_KEY_VALUE {
|
||||||
|
config.secret_key = stored_config.secret_key;
|
||||||
|
}
|
||||||
|
if !stored_config.domain_name.is_empty() && stored_config.domain_name != Self::DEFAULT_DOMAIN_NAME_VALUE {
|
||||||
|
config.domain_name = stored_config.domain_name;
|
||||||
|
}
|
||||||
|
// The stored volume_name is updated only if it is not empty and different from the default
|
||||||
|
if !stored_config.volume_name.is_empty() && stored_config.volume_name != Self::default_volume_name() {
|
||||||
|
config.volume_name = stored_config.volume_name;
|
||||||
|
}
|
||||||
|
if !stored_config.console_address.is_empty()
|
||||||
|
&& stored_config.console_address != Self::DEFAULT_CONSOLE_ADDRESS_VALUE
|
||||||
|
{
|
||||||
|
config.console_address = stored_config.console_address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Auxiliary method: Extract the host and port from the address string
|
||||||
|
/// # Arguments
|
||||||
|
/// * `address` - The address string
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * `Some((host, port))` - The host and port
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// * If the address is not in the form 'host:port'
|
||||||
|
/// * If the port is not a valid u16
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// let (host, port) = RustFSConfig::extract_host_port("127.0.0.1:9000").unwrap();
|
||||||
|
/// assert_eq!(host, "127.0.0.1");
|
||||||
|
/// assert_eq!(port, 9000);
|
||||||
|
/// ```
|
||||||
|
pub fn extract_host_port(address: &str) -> Option<(&str, u16)> {
|
||||||
|
let parts: Vec<&str> = address.split(':').collect();
|
||||||
|
if parts.len() == 2 {
|
||||||
|
if let Ok(port) = parts[1].parse::<u16>() {
|
||||||
|
return Some((parts[0], port));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// save the configuration to keyring
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// * If the configuration cannot be serialized
|
||||||
|
/// * If the configuration cannot be saved to the keyring
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// let config = RustFSConfig::default_config();
|
||||||
|
/// config.save().unwrap();
|
||||||
|
/// ```
|
||||||
|
pub fn save(&self) -> Result<(), Box<dyn Error>> {
|
||||||
|
let entry = Entry::new(Self::SERVICE_NAME, Self::SERVICE_KEY)?;
|
||||||
|
let json = serde_json::to_string(self)?;
|
||||||
|
entry.set_password(&json)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear the stored configuration from the system keyring
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// Returns `Ok(())` if the configuration was successfully cleared, or an error if the operation failed.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// RustFSConfig::clear().unwrap();
|
||||||
|
/// ```
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn clear() -> Result<(), Box<dyn Error>> {
|
||||||
|
let entry = Entry::new(Self::SERVICE_NAME, Self::SERVICE_KEY)?;
|
||||||
|
entry.delete_credential()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rustfs_config_default() {
|
||||||
|
let config = RustFSConfig::default();
|
||||||
|
assert!(config.address.is_empty());
|
||||||
|
assert!(config.host.is_empty());
|
||||||
|
assert!(config.port.is_empty());
|
||||||
|
assert!(config.access_key.is_empty());
|
||||||
|
assert!(config.secret_key.is_empty());
|
||||||
|
assert!(config.domain_name.is_empty());
|
||||||
|
assert!(config.volume_name.is_empty());
|
||||||
|
assert!(config.console_address.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rustfs_config_creation() {
|
||||||
|
let config = RustFSConfig {
|
||||||
|
address: "192.168.1.100:9000".to_string(),
|
||||||
|
host: "192.168.1.100".to_string(),
|
||||||
|
port: "9000".to_string(),
|
||||||
|
access_key: "testuser".to_string(),
|
||||||
|
secret_key: "testpass".to_string(),
|
||||||
|
domain_name: "test.rustfs.com".to_string(),
|
||||||
|
volume_name: "/data/rustfs".to_string(),
|
||||||
|
console_address: "192.168.1.100:9001".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(config.address, "192.168.1.100:9000");
|
||||||
|
assert_eq!(config.host, "192.168.1.100");
|
||||||
|
assert_eq!(config.port, "9000");
|
||||||
|
assert_eq!(config.access_key, "testuser");
|
||||||
|
assert_eq!(config.secret_key, "testpass");
|
||||||
|
assert_eq!(config.domain_name, "test.rustfs.com");
|
||||||
|
assert_eq!(config.volume_name, "/data/rustfs");
|
||||||
|
assert_eq!(config.console_address, "192.168.1.100:9001");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default_volume_name() {
|
||||||
|
let volume_name = RustFSConfig::default_volume_name();
|
||||||
|
assert!(!volume_name.is_empty());
|
||||||
|
// Should either be the home directory path or fallback to "data"
|
||||||
|
assert!(volume_name.contains("rustfs") || volume_name == "data");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default_config() {
|
||||||
|
let config = RustFSConfig::default_config();
|
||||||
|
assert_eq!(config.address, RustFSConfig::DEFAULT_ADDRESS_VALUE);
|
||||||
|
assert_eq!(config.host, RustFSConfig::DEFAULT_HOST_VALUE);
|
||||||
|
assert_eq!(config.port, RustFSConfig::DEFAULT_PORT_VALUE);
|
||||||
|
assert_eq!(config.access_key, RustFSConfig::DEFAULT_ACCESS_KEY_VALUE);
|
||||||
|
assert_eq!(config.secret_key, RustFSConfig::DEFAULT_SECRET_KEY_VALUE);
|
||||||
|
assert_eq!(config.domain_name, RustFSConfig::DEFAULT_DOMAIN_NAME_VALUE);
|
||||||
|
assert_eq!(config.console_address, RustFSConfig::DEFAULT_CONSOLE_ADDRESS_VALUE);
|
||||||
|
assert!(!config.volume_name.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_host_port_valid() {
|
||||||
|
let test_cases = vec![
|
||||||
|
("127.0.0.1:9000", Some(("127.0.0.1", 9000))),
|
||||||
|
("localhost:8080", Some(("localhost", 8080))),
|
||||||
|
("192.168.1.100:3000", Some(("192.168.1.100", 3000))),
|
||||||
|
("0.0.0.0:80", Some(("0.0.0.0", 80))),
|
||||||
|
("example.com:443", Some(("example.com", 443))),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (input, expected) in test_cases {
|
||||||
|
let result = RustFSConfig::extract_host_port(input);
|
||||||
|
assert_eq!(result, expected, "Failed for input: {}", input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_host_port_invalid() {
|
||||||
|
let invalid_cases = vec![
|
||||||
|
"127.0.0.1", // Missing port
|
||||||
|
"127.0.0.1:", // Empty port
|
||||||
|
"127.0.0.1:abc", // Invalid port
|
||||||
|
"127.0.0.1:99999", // Port out of range
|
||||||
|
"", // Empty string
|
||||||
|
"127.0.0.1:9000:extra", // Too many parts
|
||||||
|
"invalid", // No colon
|
||||||
|
];
|
||||||
|
|
||||||
|
for input in invalid_cases {
|
||||||
|
let result = RustFSConfig::extract_host_port(input);
|
||||||
|
assert_eq!(result, None, "Should be None for input: {}", input);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case: empty host but valid port should still work
|
||||||
|
let result = RustFSConfig::extract_host_port(":9000");
|
||||||
|
assert_eq!(result, Some(("", 9000)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_host_port_edge_cases() {
|
||||||
|
// Test edge cases for port numbers
|
||||||
|
assert_eq!(RustFSConfig::extract_host_port("host:0"), Some(("host", 0)));
|
||||||
|
assert_eq!(RustFSConfig::extract_host_port("host:65535"), Some(("host", 65535)));
|
||||||
|
assert_eq!(RustFSConfig::extract_host_port("host:65536"), None); // Out of range
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serialization() {
|
||||||
|
let config = RustFSConfig {
|
||||||
|
address: "127.0.0.1:9000".to_string(),
|
||||||
|
host: "127.0.0.1".to_string(),
|
||||||
|
port: "9000".to_string(),
|
||||||
|
access_key: "admin".to_string(),
|
||||||
|
secret_key: "password".to_string(),
|
||||||
|
domain_name: "test.com".to_string(),
|
||||||
|
volume_name: "/data".to_string(),
|
||||||
|
console_address: "127.0.0.1:9001".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let json = serde_json::to_string(&config).unwrap();
|
||||||
|
assert!(json.contains("127.0.0.1:9000"));
|
||||||
|
assert!(json.contains("admin"));
|
||||||
|
assert!(json.contains("test.com"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deserialization() {
|
||||||
|
let json = r#"{
|
||||||
|
"address": "192.168.1.100:9000",
|
||||||
|
"host": "192.168.1.100",
|
||||||
|
"port": "9000",
|
||||||
|
"access_key": "testuser",
|
||||||
|
"secret_key": "testpass",
|
||||||
|
"domain_name": "example.com",
|
||||||
|
"volume_name": "/opt/data",
|
||||||
|
"console_address": "192.168.1.100:9001"
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let config: RustFSConfig = serde_json::from_str(json).unwrap();
|
||||||
|
assert_eq!(config.address, "192.168.1.100:9000");
|
||||||
|
assert_eq!(config.host, "192.168.1.100");
|
||||||
|
assert_eq!(config.port, "9000");
|
||||||
|
assert_eq!(config.access_key, "testuser");
|
||||||
|
assert_eq!(config.secret_key, "testpass");
|
||||||
|
assert_eq!(config.domain_name, "example.com");
|
||||||
|
assert_eq!(config.volume_name, "/opt/data");
|
||||||
|
assert_eq!(config.console_address, "192.168.1.100:9001");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serialization_deserialization_roundtrip() {
|
||||||
|
let original_config = RustFSConfig {
|
||||||
|
address: "10.0.0.1:8080".to_string(),
|
||||||
|
host: "10.0.0.1".to_string(),
|
||||||
|
port: "8080".to_string(),
|
||||||
|
access_key: "roundtrip_user".to_string(),
|
||||||
|
secret_key: "roundtrip_pass".to_string(),
|
||||||
|
domain_name: "roundtrip.test".to_string(),
|
||||||
|
volume_name: "/tmp/roundtrip".to_string(),
|
||||||
|
console_address: "10.0.0.1:8081".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let json = serde_json::to_string(&original_config).unwrap();
|
||||||
|
let deserialized_config: RustFSConfig = serde_json::from_str(&json).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(original_config, deserialized_config);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_config_ordering() {
|
||||||
|
let config1 = RustFSConfig {
|
||||||
|
address: "127.0.0.1:9000".to_string(),
|
||||||
|
host: "127.0.0.1".to_string(),
|
||||||
|
port: "9000".to_string(),
|
||||||
|
access_key: "admin".to_string(),
|
||||||
|
secret_key: "password".to_string(),
|
||||||
|
domain_name: "test.com".to_string(),
|
||||||
|
volume_name: "/data".to_string(),
|
||||||
|
console_address: "127.0.0.1:9001".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let config2 = RustFSConfig {
|
||||||
|
address: "127.0.0.1:9000".to_string(),
|
||||||
|
host: "127.0.0.1".to_string(),
|
||||||
|
port: "9000".to_string(),
|
||||||
|
access_key: "admin".to_string(),
|
||||||
|
secret_key: "password".to_string(),
|
||||||
|
domain_name: "test.com".to_string(),
|
||||||
|
volume_name: "/data".to_string(),
|
||||||
|
console_address: "127.0.0.1:9001".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let config3 = RustFSConfig {
|
||||||
|
address: "127.0.0.1:9001".to_string(), // Different port
|
||||||
|
host: "127.0.0.1".to_string(),
|
||||||
|
port: "9001".to_string(),
|
||||||
|
access_key: "admin".to_string(),
|
||||||
|
secret_key: "password".to_string(),
|
||||||
|
domain_name: "test.com".to_string(),
|
||||||
|
volume_name: "/data".to_string(),
|
||||||
|
console_address: "127.0.0.1:9002".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(config1, config2);
|
||||||
|
assert_ne!(config1, config3);
|
||||||
|
assert!(config1 < config3); // Lexicographic ordering
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clone() {
|
||||||
|
let original = RustFSConfig::default_config();
|
||||||
|
let cloned = original.clone();
|
||||||
|
|
||||||
|
assert_eq!(original, cloned);
|
||||||
|
assert_eq!(original.address, cloned.address);
|
||||||
|
assert_eq!(original.access_key, cloned.access_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_debug_format() {
|
||||||
|
let config = RustFSConfig::default_config();
|
||||||
|
let debug_str = format!("{:?}", config);
|
||||||
|
|
||||||
|
assert!(debug_str.contains("RustFSConfig"));
|
||||||
|
assert!(debug_str.contains("address"));
|
||||||
|
assert!(debug_str.contains("127.0.0.1:9000"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_constants() {
|
||||||
|
assert_eq!(RustFSConfig::SERVICE_NAME, "rustfs-service");
|
||||||
|
assert_eq!(RustFSConfig::SERVICE_KEY, "rustfs_key");
|
||||||
|
assert_eq!(RustFSConfig::DEFAULT_DOMAIN_NAME_VALUE, "demo.rustfs.com");
|
||||||
|
assert_eq!(RustFSConfig::DEFAULT_ADDRESS_VALUE, "127.0.0.1:9000");
|
||||||
|
assert_eq!(RustFSConfig::DEFAULT_PORT_VALUE, "9000");
|
||||||
|
assert_eq!(RustFSConfig::DEFAULT_HOST_VALUE, "127.0.0.1");
|
||||||
|
assert_eq!(RustFSConfig::DEFAULT_ACCESS_KEY_VALUE, "rustfsadmin");
|
||||||
|
assert_eq!(RustFSConfig::DEFAULT_SECRET_KEY_VALUE, "rustfsadmin");
|
||||||
|
assert_eq!(RustFSConfig::DEFAULT_CONSOLE_ADDRESS_VALUE, "127.0.0.1:9001");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_strings() {
|
||||||
|
let config = RustFSConfig {
|
||||||
|
address: "".to_string(),
|
||||||
|
host: "".to_string(),
|
||||||
|
port: "".to_string(),
|
||||||
|
access_key: "".to_string(),
|
||||||
|
secret_key: "".to_string(),
|
||||||
|
domain_name: "".to_string(),
|
||||||
|
volume_name: "".to_string(),
|
||||||
|
console_address: "".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(config.address.is_empty());
|
||||||
|
assert!(config.host.is_empty());
|
||||||
|
assert!(config.port.is_empty());
|
||||||
|
assert!(config.access_key.is_empty());
|
||||||
|
assert!(config.secret_key.is_empty());
|
||||||
|
assert!(config.domain_name.is_empty());
|
||||||
|
assert!(config.volume_name.is_empty());
|
||||||
|
assert!(config.console_address.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_very_long_strings() {
|
||||||
|
let long_string = "a".repeat(1000);
|
||||||
|
let config = RustFSConfig {
|
||||||
|
address: format!("{}:9000", long_string),
|
||||||
|
host: long_string.clone(),
|
||||||
|
port: "9000".to_string(),
|
||||||
|
access_key: long_string.clone(),
|
||||||
|
secret_key: long_string.clone(),
|
||||||
|
domain_name: format!("{}.com", long_string),
|
||||||
|
volume_name: format!("/data/{}", long_string),
|
||||||
|
console_address: format!("{}:9001", long_string),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(config.host.len(), 1000);
|
||||||
|
assert_eq!(config.access_key.len(), 1000);
|
||||||
|
assert_eq!(config.secret_key.len(), 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_special_characters() {
|
||||||
|
let config = RustFSConfig {
|
||||||
|
address: "127.0.0.1:9000".to_string(),
|
||||||
|
host: "127.0.0.1".to_string(),
|
||||||
|
port: "9000".to_string(),
|
||||||
|
access_key: "user@domain.com".to_string(),
|
||||||
|
secret_key: "p@ssw0rd!#$%".to_string(),
|
||||||
|
domain_name: "test-domain.example.com".to_string(),
|
||||||
|
volume_name: "/data/rust-fs/storage".to_string(),
|
||||||
|
console_address: "127.0.0.1:9001".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(config.access_key.contains("@"));
|
||||||
|
assert!(config.secret_key.contains("!#$%"));
|
||||||
|
assert!(config.domain_name.contains("-"));
|
||||||
|
assert!(config.volume_name.contains("/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unicode_strings() {
|
||||||
|
let config = RustFSConfig {
|
||||||
|
address: "127.0.0.1:9000".to_string(),
|
||||||
|
host: "127.0.0.1".to_string(),
|
||||||
|
port: "9000".to_string(),
|
||||||
|
access_key: "用户名".to_string(),
|
||||||
|
secret_key: "密码123".to_string(),
|
||||||
|
domain_name: "测试.com".to_string(),
|
||||||
|
volume_name: "/数据/存储".to_string(),
|
||||||
|
console_address: "127.0.0.1:9001".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(config.access_key, "用户名");
|
||||||
|
assert_eq!(config.secret_key, "密码123");
|
||||||
|
assert_eq!(config.domain_name, "测试.com");
|
||||||
|
assert_eq!(config.volume_name, "/数据/存储");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_memory_efficiency() {
|
||||||
|
// Test that the structure doesn't use excessive memory
|
||||||
|
assert!(std::mem::size_of::<RustFSConfig>() < 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Keyring-related tests (load, save, clear) are not included here
|
||||||
|
// because they require actual keyring access and would be integration tests
|
||||||
|
// rather than unit tests. They should be tested separately in an integration
|
||||||
|
// test environment where keyring access can be properly mocked or controlled.
|
||||||
|
}
|
||||||
887
cli/rustfs-gui/src/utils/helper.rs
Normal file
887
cli/rustfs-gui/src/utils/helper.rs
Normal file
@@ -0,0 +1,887 @@
|
|||||||
|
use crate::utils::RustFSConfig;
|
||||||
|
use dioxus::logger::tracing::{debug, error, info};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use rust_embed::RustEmbed;
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
use std::error::Error;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::Command as StdCommand;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::fs;
|
||||||
|
use tokio::fs::File;
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
use tokio::sync::{mpsc, Mutex};
|
||||||
|
|
||||||
|
#[derive(RustEmbed)]
|
||||||
|
#[folder = "$CARGO_MANIFEST_DIR/embedded-rustfs/"]
|
||||||
|
struct Asset;
|
||||||
|
|
||||||
|
// Use `lazy_static` to cache the checksum of embedded resources
|
||||||
|
lazy_static! {
|
||||||
|
static ref RUSTFS_HASH: Mutex<String> = {
|
||||||
|
let rustfs_file = if cfg!(windows) { "rustfs.exe" } else { "rustfs" };
|
||||||
|
let rustfs_data = Asset::get(rustfs_file).expect("RustFs binary not embedded");
|
||||||
|
let hash = hex::encode(Sha256::digest(&rustfs_data.data));
|
||||||
|
Mutex::new(hash)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Service command
|
||||||
|
/// This enum represents the commands that can be sent to the service manager
|
||||||
|
/// to start, stop, or restart the service
|
||||||
|
/// The `Start` variant contains the configuration for the service
|
||||||
|
/// The `Restart` variant contains the configuration for the service
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// let config = RustFSConfig {
|
||||||
|
/// address: "127.0.0.1:9000".to_string(),
|
||||||
|
/// host: "127.0.0.1".to_string(),
|
||||||
|
/// port: "9000".to_string(),
|
||||||
|
/// access_key: "rustfsadmin".to_string(),
|
||||||
|
/// secret_key: "rustfsadmin".to_string(),
|
||||||
|
/// domain_name: "demo.rustfs.com".to_string(),
|
||||||
|
/// volume_name: "data".to_string(),
|
||||||
|
/// console_address: "127.0.0.1:9001".to_string(),
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// let command = ServiceCommand::Start(config);
|
||||||
|
/// println!("{:?}", command);
|
||||||
|
///
|
||||||
|
/// assert_eq!(command, ServiceCommand::Start(config));
|
||||||
|
/// ```
|
||||||
|
pub enum ServiceCommand {
|
||||||
|
Start(RustFSConfig),
|
||||||
|
Stop,
|
||||||
|
Restart(RustFSConfig),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Service operation result
|
||||||
|
/// This struct represents the result of a service operation
|
||||||
|
/// It contains information about the success of the operation,
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// use chrono::Local;
|
||||||
|
///
|
||||||
|
/// let result = ServiceOperationResult {
|
||||||
|
/// success: true,
|
||||||
|
/// start_time: chrono::Local::now(),
|
||||||
|
/// end_time: chrono::Local::now(),
|
||||||
|
/// message: "服务启动成功".to_string(),
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// println!("{:?}", result);
|
||||||
|
/// assert_eq!(result.success, true);
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ServiceOperationResult {
|
||||||
|
pub success: bool,
|
||||||
|
pub start_time: chrono::DateTime<chrono::Local>,
|
||||||
|
pub end_time: chrono::DateTime<chrono::Local>,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Service manager
|
||||||
|
/// This struct represents a service manager that can be used to start, stop, or restart a service
|
||||||
|
/// It contains a command sender that can be used to send commands to the service manager
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// let service_manager = ServiceManager::new();
|
||||||
|
/// println!("{:?}", service_manager);
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ServiceManager {
|
||||||
|
command_tx: mpsc::Sender<ServiceCommand>,
|
||||||
|
// process: Arc<Mutex<Option<Child>>>,
|
||||||
|
// pid: Arc<Mutex<Option<u32>>>, // Add PID storage
|
||||||
|
// current_config: Arc<Mutex<Option<RustFSConfig>>>, // Add configuration storage
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServiceManager {
|
||||||
|
/// check if the service is running and return a pid
|
||||||
|
/// This function is platform dependent
|
||||||
|
/// On Unix systems, it uses the `ps` command to check for the service
|
||||||
|
/// On Windows systems, it uses the `wmic` command to check for the service
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// let pid = check_service_status().await;
|
||||||
|
/// println!("{:?}", pid);
|
||||||
|
/// ```
|
||||||
|
pub async fn check_service_status() -> Option<u32> {
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
// use the ps command on a unix system
|
||||||
|
if let Ok(output) = StdCommand::new("ps").arg("-ef").output() {
|
||||||
|
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||||
|
for line in output_str.lines() {
|
||||||
|
// match contains `rustfs/bin/rustfs` of the line
|
||||||
|
if line.contains("rustfs/bin/rustfs") && !line.contains("grep") {
|
||||||
|
if let Some(pid_str) = line.split_whitespace().nth(1) {
|
||||||
|
if let Ok(pid) = pid_str.parse::<u32>() {
|
||||||
|
return Some(pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
if let Ok(output) = StdCommand::new("wmic")
|
||||||
|
.arg("process")
|
||||||
|
.arg("where")
|
||||||
|
.arg("caption='rustfs.exe'")
|
||||||
|
.arg("get")
|
||||||
|
.arg("processid")
|
||||||
|
.output()
|
||||||
|
{
|
||||||
|
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||||
|
for line in output_str.lines() {
|
||||||
|
if let Ok(pid) = line.trim().parse::<u32>() {
|
||||||
|
return Some(pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepare the service
|
||||||
|
/// This function downloads the service executable if it doesn't exist
|
||||||
|
/// It also creates the necessary directories for the service
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// let executable_path = prepare_service().await;
|
||||||
|
/// println!("{:?}", executable_path);
|
||||||
|
/// ```
|
||||||
|
async fn prepare_service() -> Result<PathBuf, Box<dyn Error>> {
|
||||||
|
// get the user directory
|
||||||
|
let home_dir = dirs::home_dir().ok_or("无法获取用户目录")?;
|
||||||
|
let rustfs_dir = home_dir.join("rustfs");
|
||||||
|
let bin_dir = rustfs_dir.join("bin");
|
||||||
|
let data_dir = rustfs_dir.join("data");
|
||||||
|
let logs_dir = rustfs_dir.join("logs");
|
||||||
|
|
||||||
|
// create the necessary directories
|
||||||
|
for dir in [&bin_dir, &data_dir, &logs_dir] {
|
||||||
|
if !dir.exists() {
|
||||||
|
tokio::fs::create_dir_all(dir).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let rustfs_file = if cfg!(windows) { "rustfs.exe" } else { "rustfs" };
|
||||||
|
let executable_path = bin_dir.join(rustfs_file);
|
||||||
|
let hash_path = bin_dir.join("embedded_rustfs.sha256");
|
||||||
|
|
||||||
|
if executable_path.exists() && hash_path.exists() {
|
||||||
|
let cached_hash = fs::read_to_string(&hash_path).await?;
|
||||||
|
let expected_hash = RUSTFS_HASH.lock().await;
|
||||||
|
if cached_hash == *expected_hash {
|
||||||
|
println!("Use cached rustfs: {:?}", executable_path);
|
||||||
|
return Ok(executable_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract and write files
|
||||||
|
let rustfs_data = Asset::get(rustfs_file).expect("RustFS binary not embedded");
|
||||||
|
let mut file = File::create(&executable_path).await?;
|
||||||
|
file.write_all(&rustfs_data.data).await?;
|
||||||
|
let expected_hash = hex::encode(Sha256::digest(&rustfs_data.data));
|
||||||
|
fs::write(&hash_path, expected_hash).await?;
|
||||||
|
|
||||||
|
// set execution permissions on unix systems
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
let mut perms = std::fs::metadata(&executable_path)?.permissions();
|
||||||
|
perms.set_mode(0o755);
|
||||||
|
std::fs::set_permissions(&executable_path, perms)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(executable_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function: Extracts the port from the address string
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// let address = "127.0.0.1:9000";
|
||||||
|
/// let port = extract_port(address);
|
||||||
|
/// println!("{:?}", port);
|
||||||
|
/// ```
|
||||||
|
fn extract_port(address: &str) -> Option<u16> {
|
||||||
|
address.split(':').nth(1)?.parse().ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new instance of the service manager
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// let service_manager = ServiceManager::new();
|
||||||
|
/// println!("{:?}", service_manager);
|
||||||
|
/// ```
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
let (command_tx, mut command_rx) = mpsc::channel(10);
|
||||||
|
// Start the control loop
|
||||||
|
tokio::spawn(async move {
|
||||||
|
while let Some(cmd) = command_rx.recv().await {
|
||||||
|
match cmd {
|
||||||
|
ServiceCommand::Start(config) => {
|
||||||
|
if let Err(e) = Self::start_service(&config).await {
|
||||||
|
Self::show_error(&format!("启动服务失败:{}", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ServiceCommand::Stop => {
|
||||||
|
if let Err(e) = Self::stop_service().await {
|
||||||
|
Self::show_error(&format!("停止服务失败:{}", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ServiceCommand::Restart(config) => {
|
||||||
|
if Self::check_service_status().await.is_some() {
|
||||||
|
if let Err(e) = Self::stop_service().await {
|
||||||
|
Self::show_error(&format!("重启服务失败:{}", e));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Err(e) = Self::start_service(&config).await {
|
||||||
|
Self::show_error(&format!("重启服务失败:{}", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ServiceManager { command_tx }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start the service
|
||||||
|
/// This function starts the service with the given configuration
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// let config = RustFSConfig {
|
||||||
|
/// address: "127.0.0.1:9000".to_string(),
|
||||||
|
/// host: "127.0.0.1".to_string(),
|
||||||
|
/// port: "9000".to_string(),
|
||||||
|
/// access_key: "rustfsadmin".to_string(),
|
||||||
|
/// secret_key: "rustfsadmin".to_string(),
|
||||||
|
/// domain_name: "demo.rustfs.com".to_string(),
|
||||||
|
/// volume_name: "data".to_string(),
|
||||||
|
/// console_address: "127.0.0.1:9001".to_string(),
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// let result = start_service(&config).await;
|
||||||
|
/// println!("{:?}", result);
|
||||||
|
/// ```
|
||||||
|
async fn start_service(config: &RustFSConfig) -> Result<(), Box<dyn Error>> {
|
||||||
|
// Check if the service is already running
|
||||||
|
if let Some(existing_pid) = Self::check_service_status().await {
|
||||||
|
return Err(format!("服务已经在运行,PID: {}", existing_pid).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the service program
|
||||||
|
let executable_path = Self::prepare_service().await?;
|
||||||
|
// Check the data catalog
|
||||||
|
let volume_name_path = Path::new(&config.volume_name);
|
||||||
|
if !volume_name_path.exists() {
|
||||||
|
tokio::fs::create_dir_all(&config.volume_name).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the port from the configuration
|
||||||
|
let main_port = Self::extract_port(&config.address).ok_or("无法解析主服务端口")?;
|
||||||
|
let console_port = Self::extract_port(&config.console_address).ok_or("无法解析控制台端口")?;
|
||||||
|
|
||||||
|
let host = config.address.split(':').next().ok_or("无法解析主机地址")?;
|
||||||
|
|
||||||
|
// Check the port
|
||||||
|
let ports = vec![main_port, console_port];
|
||||||
|
for port in ports {
|
||||||
|
if Self::is_port_in_use(host, port).await {
|
||||||
|
return Err(format!("端口 {} 已被占用", port).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the service
|
||||||
|
let mut child = tokio::process::Command::new(executable_path)
|
||||||
|
.arg("--address")
|
||||||
|
.arg(&config.address)
|
||||||
|
.arg("--access-key")
|
||||||
|
.arg(&config.access_key)
|
||||||
|
.arg("--secret-key")
|
||||||
|
.arg(&config.secret_key)
|
||||||
|
.arg("--console-address")
|
||||||
|
.arg(&config.console_address)
|
||||||
|
.arg(config.volume_name.clone())
|
||||||
|
.spawn()?;
|
||||||
|
|
||||||
|
let process_pid = child.id().unwrap();
|
||||||
|
// Wait for the service to start
|
||||||
|
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||||
|
|
||||||
|
// Check if the service started successfully
|
||||||
|
if Self::is_port_in_use(host, main_port).await {
|
||||||
|
Self::show_info(&format!("服务启动成功!进程 ID: {}", process_pid));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
child.kill().await?;
|
||||||
|
Err("服务启动失败".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stop the service
|
||||||
|
/// This function stops the service
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// let result = stop_service().await;
|
||||||
|
/// println!("{:?}", result);
|
||||||
|
/// ```
|
||||||
|
async fn stop_service() -> Result<(), Box<dyn Error>> {
|
||||||
|
let existing_pid = Self::check_service_status().await;
|
||||||
|
debug!("existing_pid: {:?}", existing_pid);
|
||||||
|
if let Some(service_pid) = existing_pid {
|
||||||
|
// An attempt was made to terminate the process
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
StdCommand::new("kill").arg("-9").arg(service_pid.to_string()).output()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
StdCommand::new("taskkill")
|
||||||
|
.arg("/F")
|
||||||
|
.arg("/PID")
|
||||||
|
.arg(&service_pid.to_string())
|
||||||
|
.output()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the service is indeed stopped
|
||||||
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
|
if Self::check_service_status().await.is_some() {
|
||||||
|
return Err("服务停止失败".into());
|
||||||
|
}
|
||||||
|
Self::show_info("服务已成功停止");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("服务未运行".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the port is in use
|
||||||
|
/// This function checks if the given port is in use on the given host
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// let host = "127.0.0.1";
|
||||||
|
/// let port = 9000;
|
||||||
|
/// let result = is_port_in_use(host, port).await;
|
||||||
|
/// println!("{:?}", result);
|
||||||
|
/// ```
|
||||||
|
async fn is_port_in_use(host: &str, port: u16) -> bool {
|
||||||
|
TcpStream::connect(format!("{}:{}", host, port)).await.is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show an error message
|
||||||
|
/// This function shows an error message dialog
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// show_error("This is an error message");
|
||||||
|
/// ```
|
||||||
|
pub(crate) fn show_error(message: &str) {
|
||||||
|
rfd::MessageDialog::new()
|
||||||
|
.set_title("错误")
|
||||||
|
.set_description(message)
|
||||||
|
.set_level(rfd::MessageLevel::Error)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show an information message
|
||||||
|
/// This function shows an information message dialog
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// show_info("This is an information message");
|
||||||
|
/// ```
|
||||||
|
pub(crate) fn show_info(message: &str) {
|
||||||
|
rfd::MessageDialog::new()
|
||||||
|
.set_title("成功")
|
||||||
|
.set_description(message)
|
||||||
|
.set_level(rfd::MessageLevel::Info)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start the service
|
||||||
|
/// This function sends a `Start` command to the service manager
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// let config = RustFSConfig {
|
||||||
|
/// address: "127.0.0.1:9000".to_string(),
|
||||||
|
/// host: "127.0.0.1".to_string(),
|
||||||
|
/// port: "9000".to_string(),
|
||||||
|
/// access_key: "rustfsadmin".to_string(),
|
||||||
|
/// secret_key: "rustfsadmin".to_string(),
|
||||||
|
/// domain_name: "demo.rustfs.com".to_string(),
|
||||||
|
/// volume_name: "data".to_string(),
|
||||||
|
/// console_address: "127.0.0.1:9001".to_string(),
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// let service_manager = ServiceManager::new();
|
||||||
|
/// let result = service_manager.start(config).await;
|
||||||
|
/// println!("{:?}", result);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// This function returns an error if the service fails to start
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// This function panics if the port number is invalid
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// This function is not marked as unsafe
|
||||||
|
///
|
||||||
|
/// # Performance
|
||||||
|
/// This function is not optimized for performance
|
||||||
|
///
|
||||||
|
/// # Design
|
||||||
|
/// This function is designed to be simple and easy to use
|
||||||
|
///
|
||||||
|
/// # Security
|
||||||
|
/// This function does not have any security implications
|
||||||
|
pub async fn start(&self, config: RustFSConfig) -> Result<ServiceOperationResult, Box<dyn Error>> {
|
||||||
|
let start_time = chrono::Local::now();
|
||||||
|
self.command_tx.send(ServiceCommand::Start(config.clone())).await?;
|
||||||
|
|
||||||
|
let host = &config.host;
|
||||||
|
let port = config.port.parse::<u16>().expect("无效的端口号");
|
||||||
|
// wait for the service to actually start
|
||||||
|
let mut retries = 0;
|
||||||
|
while retries < 30 {
|
||||||
|
// wait up to 30 seconds
|
||||||
|
if Self::check_service_status().await.is_some() && Self::is_port_in_use(host, port).await {
|
||||||
|
let end_time = chrono::Local::now();
|
||||||
|
return Ok(ServiceOperationResult {
|
||||||
|
success: true,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
message: "服务启动成功".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
|
retries += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Err("服务启动超时".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stop the service
|
||||||
|
/// This function sends a `Stop` command to the service manager
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// let service_manager = ServiceManager::new();
|
||||||
|
/// let result = service_manager.stop().await;
|
||||||
|
/// println!("{:?}", result);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// This function returns an error if the service fails to stop
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// This function panics if the port number is invalid
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// This function is not marked as unsafe
|
||||||
|
///
|
||||||
|
/// # Performance
|
||||||
|
/// This function is not optimized for performance
|
||||||
|
///
|
||||||
|
/// # Design
|
||||||
|
/// This function is designed to be simple and easy to use
|
||||||
|
///
|
||||||
|
/// # Security
|
||||||
|
/// This function does not have any security implications
|
||||||
|
pub async fn stop(&self) -> Result<ServiceOperationResult, Box<dyn Error>> {
|
||||||
|
let start_time = chrono::Local::now();
|
||||||
|
self.command_tx.send(ServiceCommand::Stop).await?;
|
||||||
|
|
||||||
|
// Wait for the service to actually stop
|
||||||
|
let mut retries = 0;
|
||||||
|
while retries < 15 {
|
||||||
|
// Wait up to 15 seconds
|
||||||
|
if Self::check_service_status().await.is_none() {
|
||||||
|
let end_time = chrono::Local::now();
|
||||||
|
return Ok(ServiceOperationResult {
|
||||||
|
success: true,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
message: "服务停止成功".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
|
retries += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Err("服务停止超时".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restart the service
|
||||||
|
/// This function sends a `Restart` command to the service manager
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// let config = RustFSConfig {
|
||||||
|
/// address: "127.0.0.1:9000".to_string(),
|
||||||
|
/// host: "127.0.0.1".to_string(),
|
||||||
|
/// port: "9000".to_string(),
|
||||||
|
/// access_key: "rustfsadmin".to_string(),
|
||||||
|
/// secret_key: "rustfsadmin".to_string(),
|
||||||
|
/// domain_name: "demo.rustfs.com".to_string(),
|
||||||
|
/// volume_name: "data".to_string(),
|
||||||
|
/// console_address: "127.0.0.1:9001".to_string(),
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// let service_manager = ServiceManager::new();
|
||||||
|
/// let result = service_manager.restart(config).await;
|
||||||
|
/// println!("{:?}", result);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// This function returns an error if the service fails to restart
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// This function panics if the port number is invalid
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// This function is not marked as unsafe
|
||||||
|
///
|
||||||
|
/// # Performance
|
||||||
|
/// This function is not optimized for performance
|
||||||
|
///
|
||||||
|
/// # Design
|
||||||
|
/// This function is designed to be simple and easy to use
|
||||||
|
///
|
||||||
|
/// # Security
|
||||||
|
/// This function does not have any security implications
|
||||||
|
pub async fn restart(&self, config: RustFSConfig) -> Result<ServiceOperationResult, Box<dyn Error>> {
|
||||||
|
let start_time = chrono::Local::now();
|
||||||
|
self.command_tx.send(ServiceCommand::Restart(config.clone())).await?;
|
||||||
|
|
||||||
|
let host = &config.host;
|
||||||
|
let port = config.port.parse::<u16>().expect("无效的端口号");
|
||||||
|
|
||||||
|
// wait for the service to restart
|
||||||
|
let mut retries = 0;
|
||||||
|
while retries < 45 {
|
||||||
|
// Longer waiting time is given as both the stop and start processes are involved
|
||||||
|
if Self::check_service_status().await.is_some() && Self::is_port_in_use(host, port).await {
|
||||||
|
match config.save() {
|
||||||
|
Ok(_) => info!("save config success"),
|
||||||
|
Err(e) => {
|
||||||
|
error!("save config error: {}", e);
|
||||||
|
self.command_tx.send(ServiceCommand::Stop).await?;
|
||||||
|
Self::show_error("保存配置失败");
|
||||||
|
return Err("保存配置失败".into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let end_time = chrono::Local::now();
|
||||||
|
return Ok(ServiceOperationResult {
|
||||||
|
success: true,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
message: "服务重启成功".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
|
retries += 1;
|
||||||
|
}
|
||||||
|
Err("服务重启超时".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_service_command_creation() {
|
||||||
|
let config = RustFSConfig::default_config();
|
||||||
|
|
||||||
|
let start_cmd = ServiceCommand::Start(config.clone());
|
||||||
|
let stop_cmd = ServiceCommand::Stop;
|
||||||
|
let restart_cmd = ServiceCommand::Restart(config);
|
||||||
|
|
||||||
|
// Test that commands can be created
|
||||||
|
match start_cmd {
|
||||||
|
ServiceCommand::Start(_) => {}
|
||||||
|
_ => panic!("Expected Start command"),
|
||||||
|
}
|
||||||
|
|
||||||
|
match stop_cmd {
|
||||||
|
ServiceCommand::Stop => {}
|
||||||
|
_ => panic!("Expected Stop command"),
|
||||||
|
}
|
||||||
|
|
||||||
|
match restart_cmd {
|
||||||
|
ServiceCommand::Restart(_) => {}
|
||||||
|
_ => panic!("Expected Restart command"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_service_operation_result_creation() {
|
||||||
|
let start_time = chrono::Local::now();
|
||||||
|
let end_time = chrono::Local::now();
|
||||||
|
|
||||||
|
let success_result = ServiceOperationResult {
|
||||||
|
success: true,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
message: "Operation successful".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let failure_result = ServiceOperationResult {
|
||||||
|
success: false,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
message: "Operation failed".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(success_result.success);
|
||||||
|
assert_eq!(success_result.message, "Operation successful");
|
||||||
|
|
||||||
|
assert!(!failure_result.success);
|
||||||
|
assert_eq!(failure_result.message, "Operation failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_service_operation_result_debug() {
|
||||||
|
let result = ServiceOperationResult {
|
||||||
|
success: true,
|
||||||
|
start_time: chrono::Local::now(),
|
||||||
|
end_time: chrono::Local::now(),
|
||||||
|
message: "Test message".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let debug_str = format!("{:?}", result);
|
||||||
|
assert!(debug_str.contains("ServiceOperationResult"));
|
||||||
|
assert!(debug_str.contains("success: true"));
|
||||||
|
assert!(debug_str.contains("Test message"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_service_manager_creation() {
|
||||||
|
// Test ServiceManager creation in a tokio runtime
|
||||||
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
|
rt.block_on(async {
|
||||||
|
let service_manager = ServiceManager::new();
|
||||||
|
|
||||||
|
// Test that ServiceManager can be created and cloned
|
||||||
|
let cloned_manager = service_manager.clone();
|
||||||
|
|
||||||
|
// Both should be valid (we can't test much more without async runtime)
|
||||||
|
assert!(format!("{:?}", service_manager).contains("ServiceManager"));
|
||||||
|
assert!(format!("{:?}", cloned_manager).contains("ServiceManager"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_port_valid() {
|
||||||
|
let test_cases = vec![
|
||||||
|
("127.0.0.1:9000", Some(9000)),
|
||||||
|
("localhost:8080", Some(8080)),
|
||||||
|
("192.168.1.100:3000", Some(3000)),
|
||||||
|
("0.0.0.0:80", Some(80)),
|
||||||
|
("example.com:443", Some(443)),
|
||||||
|
("host:65535", Some(65535)),
|
||||||
|
("host:1", Some(1)),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (input, expected) in test_cases {
|
||||||
|
let result = ServiceManager::extract_port(input);
|
||||||
|
assert_eq!(result, expected, "Failed for input: {}", input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_port_invalid() {
|
||||||
|
let invalid_cases = vec![
|
||||||
|
"127.0.0.1", // Missing port
|
||||||
|
"127.0.0.1:", // Empty port
|
||||||
|
"127.0.0.1:abc", // Invalid port
|
||||||
|
"127.0.0.1:99999", // Port out of range
|
||||||
|
"", // Empty string
|
||||||
|
"invalid", // No colon
|
||||||
|
"host:-1", // Negative port
|
||||||
|
"host:0.5", // Decimal port
|
||||||
|
];
|
||||||
|
|
||||||
|
for input in invalid_cases {
|
||||||
|
let result = ServiceManager::extract_port(input);
|
||||||
|
assert_eq!(result, None, "Should be None for input: {}", input);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case: empty host but valid port should still work
|
||||||
|
assert_eq!(ServiceManager::extract_port(":9000"), Some(9000));
|
||||||
|
|
||||||
|
// Special case: multiple colons - extract_port takes the second part
|
||||||
|
// For "127.0.0.1:9000:extra", it takes "9000" which is valid
|
||||||
|
assert_eq!(ServiceManager::extract_port("127.0.0.1:9000:extra"), Some(9000));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_port_edge_cases() {
|
||||||
|
// Test edge cases for port numbers
|
||||||
|
assert_eq!(ServiceManager::extract_port("host:0"), Some(0));
|
||||||
|
assert_eq!(ServiceManager::extract_port("host:65535"), Some(65535));
|
||||||
|
assert_eq!(ServiceManager::extract_port("host:65536"), None); // Out of range
|
||||||
|
// IPv6-like address - extract_port takes the second part after split(':')
|
||||||
|
// For "::1:8080", split(':') gives ["", "", "1", "8080"], nth(1) gives ""
|
||||||
|
assert_eq!(ServiceManager::extract_port("::1:8080"), None); // Second part is empty
|
||||||
|
// For "[::1]:8080", split(':') gives ["[", "", "1]", "8080"], nth(1) gives ""
|
||||||
|
assert_eq!(ServiceManager::extract_port("[::1]:8080"), None); // Second part is empty
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_show_error() {
|
||||||
|
// Test that show_error function exists and can be called
|
||||||
|
// We can't actually test the dialog in a test environment
|
||||||
|
// so we just verify the function signature
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_show_info() {
|
||||||
|
// Test that show_info function exists and can be called
|
||||||
|
// We can't actually test the dialog in a test environment
|
||||||
|
// so we just verify the function signature
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_service_operation_result_timing() {
|
||||||
|
let start_time = chrono::Local::now();
|
||||||
|
std::thread::sleep(Duration::from_millis(10)); // Small delay
|
||||||
|
let end_time = chrono::Local::now();
|
||||||
|
|
||||||
|
let result = ServiceOperationResult {
|
||||||
|
success: true,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
message: "Timing test".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// End time should be after start time
|
||||||
|
assert!(result.end_time >= result.start_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_service_operation_result_with_unicode() {
|
||||||
|
let result = ServiceOperationResult {
|
||||||
|
success: true,
|
||||||
|
start_time: chrono::Local::now(),
|
||||||
|
end_time: chrono::Local::now(),
|
||||||
|
message: "操作成功 🎉".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(result.message, "操作成功 🎉");
|
||||||
|
assert!(result.success);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_service_operation_result_with_long_message() {
|
||||||
|
let long_message = "A".repeat(10000);
|
||||||
|
let result = ServiceOperationResult {
|
||||||
|
success: false,
|
||||||
|
start_time: chrono::Local::now(),
|
||||||
|
end_time: chrono::Local::now(),
|
||||||
|
message: long_message.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(result.message.len(), 10000);
|
||||||
|
assert_eq!(result.message, long_message);
|
||||||
|
assert!(!result.success);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_service_command_with_different_configs() {
|
||||||
|
let config1 = RustFSConfig {
|
||||||
|
address: "127.0.0.1:9000".to_string(),
|
||||||
|
host: "127.0.0.1".to_string(),
|
||||||
|
port: "9000".to_string(),
|
||||||
|
access_key: "admin1".to_string(),
|
||||||
|
secret_key: "pass1".to_string(),
|
||||||
|
domain_name: "test1.com".to_string(),
|
||||||
|
volume_name: "/data1".to_string(),
|
||||||
|
console_address: "127.0.0.1:9001".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let config2 = RustFSConfig {
|
||||||
|
address: "192.168.1.100:8080".to_string(),
|
||||||
|
host: "192.168.1.100".to_string(),
|
||||||
|
port: "8080".to_string(),
|
||||||
|
access_key: "admin2".to_string(),
|
||||||
|
secret_key: "pass2".to_string(),
|
||||||
|
domain_name: "test2.com".to_string(),
|
||||||
|
volume_name: "/data2".to_string(),
|
||||||
|
console_address: "192.168.1.100:8081".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let start_cmd1 = ServiceCommand::Start(config1);
|
||||||
|
let restart_cmd2 = ServiceCommand::Restart(config2);
|
||||||
|
|
||||||
|
// Test that different configs can be used
|
||||||
|
match start_cmd1 {
|
||||||
|
ServiceCommand::Start(config) => {
|
||||||
|
assert_eq!(config.address, "127.0.0.1:9000");
|
||||||
|
assert_eq!(config.access_key, "admin1");
|
||||||
|
}
|
||||||
|
_ => panic!("Expected Start command"),
|
||||||
|
}
|
||||||
|
|
||||||
|
match restart_cmd2 {
|
||||||
|
ServiceCommand::Restart(config) => {
|
||||||
|
assert_eq!(config.address, "192.168.1.100:8080");
|
||||||
|
assert_eq!(config.access_key, "admin2");
|
||||||
|
}
|
||||||
|
_ => panic!("Expected Restart command"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_memory_efficiency() {
|
||||||
|
// Test that structures don't use excessive memory
|
||||||
|
assert!(std::mem::size_of::<ServiceCommand>() < 2000);
|
||||||
|
assert!(std::mem::size_of::<ServiceOperationResult>() < 1000);
|
||||||
|
assert!(std::mem::size_of::<ServiceManager>() < 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: The following methods are not tested here because they require:
|
||||||
|
// - Async runtime (tokio)
|
||||||
|
// - File system access
|
||||||
|
// - Network access
|
||||||
|
// - Process management
|
||||||
|
// - External dependencies (embedded assets)
|
||||||
|
//
|
||||||
|
// These should be tested in integration tests:
|
||||||
|
// - check_service_status()
|
||||||
|
// - prepare_service()
|
||||||
|
// - start_service()
|
||||||
|
// - stop_service()
|
||||||
|
// - is_port_in_use()
|
||||||
|
// - ServiceManager::start()
|
||||||
|
// - ServiceManager::stop()
|
||||||
|
// - ServiceManager::restart()
|
||||||
|
//
|
||||||
|
// The RUSTFS_HASH lazy_static is also not tested here as it depends
|
||||||
|
// on embedded assets that may not be available in unit test environment.
|
||||||
|
}
|
||||||
286
cli/rustfs-gui/src/utils/logger.rs
Normal file
286
cli/rustfs-gui/src/utils/logger.rs
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
use dioxus::logger::tracing::debug;
|
||||||
|
use tracing_appender::non_blocking::WorkerGuard;
|
||||||
|
use tracing_appender::rolling::{RollingFileAppender, Rotation};
|
||||||
|
use tracing_subscriber::fmt;
|
||||||
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
|
use tracing_subscriber::util::SubscriberInitExt;
|
||||||
|
|
||||||
|
/// Initialize the logger with a rolling file appender
|
||||||
|
/// that rotates log files daily
|
||||||
|
pub fn init_logger() -> WorkerGuard {
|
||||||
|
// configuring rolling logs rolling by day
|
||||||
|
let home_dir = dirs::home_dir().expect("无法获取用户目录");
|
||||||
|
let rustfs_dir = home_dir.join("rustfs");
|
||||||
|
let logs_dir = rustfs_dir.join("logs");
|
||||||
|
let file_appender = RollingFileAppender::builder()
|
||||||
|
.rotation(Rotation::DAILY) // rotate log files once every hour
|
||||||
|
.filename_prefix("rustfs-cli") // log file names will be prefixed with `myapp.`
|
||||||
|
.filename_suffix("log") // log file names will be suffixed with `.log`
|
||||||
|
.build(logs_dir) // try to build an appender that stores log files in `/ var/ log`
|
||||||
|
.expect("initializing rolling file appender failed");
|
||||||
|
// non-blocking writer for improved performance
|
||||||
|
let (non_blocking_file, worker_guard) = tracing_appender::non_blocking(file_appender);
|
||||||
|
|
||||||
|
// console output layer
|
||||||
|
let console_layer = fmt::layer()
|
||||||
|
.with_writer(std::io::stdout)
|
||||||
|
.with_ansi(true)
|
||||||
|
.with_line_number(true); // enable colors in the console
|
||||||
|
|
||||||
|
// file output layer
|
||||||
|
let file_layer = fmt::layer()
|
||||||
|
.with_writer(non_blocking_file)
|
||||||
|
.with_ansi(false)
|
||||||
|
.with_thread_names(true)
|
||||||
|
.with_target(true)
|
||||||
|
.with_thread_ids(true)
|
||||||
|
.with_level(true)
|
||||||
|
.with_line_number(true); // disable colors in the file
|
||||||
|
|
||||||
|
// Combine all tiers and initialize global subscribers
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(console_layer)
|
||||||
|
.with(file_layer)
|
||||||
|
.with(tracing_subscriber::EnvFilter::new("info")) // filter the log level by environment variables
|
||||||
|
.init();
|
||||||
|
debug!("Logger initialized");
|
||||||
|
worker_guard
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::sync::Once;
|
||||||
|
|
||||||
|
static INIT: Once = Once::new();
|
||||||
|
|
||||||
|
// Helper function to ensure logger is only initialized once in tests
|
||||||
|
fn ensure_logger_init() {
|
||||||
|
INIT.call_once(|| {
|
||||||
|
// Initialize a simple test logger to avoid conflicts
|
||||||
|
let _ = tracing_subscriber::fmt().with_test_writer().try_init();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_logger_initialization_components() {
|
||||||
|
ensure_logger_init();
|
||||||
|
|
||||||
|
// Test that we can create the components used in init_logger
|
||||||
|
// without actually initializing the global logger again
|
||||||
|
|
||||||
|
// Test home directory access
|
||||||
|
let home_dir_result = dirs::home_dir();
|
||||||
|
assert!(home_dir_result.is_some(), "Should be able to get home directory");
|
||||||
|
|
||||||
|
let home_dir = home_dir_result.unwrap();
|
||||||
|
let rustfs_dir = home_dir.join("rustfs");
|
||||||
|
let logs_dir = rustfs_dir.join("logs");
|
||||||
|
|
||||||
|
// Test path construction
|
||||||
|
assert!(rustfs_dir.to_string_lossy().contains("rustfs"));
|
||||||
|
assert!(logs_dir.to_string_lossy().contains("logs"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rolling_file_appender_builder() {
|
||||||
|
ensure_logger_init();
|
||||||
|
|
||||||
|
// Test that we can create a RollingFileAppender builder
|
||||||
|
let builder = RollingFileAppender::builder()
|
||||||
|
.rotation(Rotation::DAILY)
|
||||||
|
.filename_prefix("test-rustfs-cli")
|
||||||
|
.filename_suffix("log");
|
||||||
|
|
||||||
|
// We can't actually build it without creating directories,
|
||||||
|
// but we can verify the builder pattern works
|
||||||
|
let debug_str = format!("{:?}", builder);
|
||||||
|
// The actual debug format might be different, so just check it's not empty
|
||||||
|
assert!(!debug_str.is_empty());
|
||||||
|
// Check that it contains some expected parts
|
||||||
|
assert!(debug_str.contains("Builder") || debug_str.contains("builder") || debug_str.contains("RollingFileAppender"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rotation_types() {
|
||||||
|
ensure_logger_init();
|
||||||
|
|
||||||
|
// Test different rotation types
|
||||||
|
let daily = Rotation::DAILY;
|
||||||
|
let hourly = Rotation::HOURLY;
|
||||||
|
let minutely = Rotation::MINUTELY;
|
||||||
|
let never = Rotation::NEVER;
|
||||||
|
|
||||||
|
// Test that rotation types can be created and formatted
|
||||||
|
assert!(!format!("{:?}", daily).is_empty());
|
||||||
|
assert!(!format!("{:?}", hourly).is_empty());
|
||||||
|
assert!(!format!("{:?}", minutely).is_empty());
|
||||||
|
assert!(!format!("{:?}", never).is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fmt_layer_configuration() {
|
||||||
|
ensure_logger_init();
|
||||||
|
|
||||||
|
// Test that we can create fmt layers with different configurations
|
||||||
|
// We can't actually test the layers directly due to type complexity,
|
||||||
|
// but we can test that the configuration values are correct
|
||||||
|
|
||||||
|
// Test console layer settings
|
||||||
|
let console_ansi = true;
|
||||||
|
let console_line_number = true;
|
||||||
|
assert!(console_ansi);
|
||||||
|
assert!(console_line_number);
|
||||||
|
|
||||||
|
// Test file layer settings
|
||||||
|
let file_ansi = false;
|
||||||
|
let file_thread_names = true;
|
||||||
|
let file_target = true;
|
||||||
|
let file_thread_ids = true;
|
||||||
|
let file_level = true;
|
||||||
|
let file_line_number = true;
|
||||||
|
|
||||||
|
assert!(!file_ansi);
|
||||||
|
assert!(file_thread_names);
|
||||||
|
assert!(file_target);
|
||||||
|
assert!(file_thread_ids);
|
||||||
|
assert!(file_level);
|
||||||
|
assert!(file_line_number);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_env_filter_creation() {
|
||||||
|
ensure_logger_init();
|
||||||
|
|
||||||
|
// Test that EnvFilter can be created with different levels
|
||||||
|
let info_filter = tracing_subscriber::EnvFilter::new("info");
|
||||||
|
let debug_filter = tracing_subscriber::EnvFilter::new("debug");
|
||||||
|
let warn_filter = tracing_subscriber::EnvFilter::new("warn");
|
||||||
|
let error_filter = tracing_subscriber::EnvFilter::new("error");
|
||||||
|
|
||||||
|
// Test that filters can be created
|
||||||
|
assert!(!format!("{:?}", info_filter).is_empty());
|
||||||
|
assert!(!format!("{:?}", debug_filter).is_empty());
|
||||||
|
assert!(!format!("{:?}", warn_filter).is_empty());
|
||||||
|
assert!(!format!("{:?}", error_filter).is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_path_construction() {
|
||||||
|
ensure_logger_init();
|
||||||
|
|
||||||
|
// Test path construction logic used in init_logger
|
||||||
|
if let Some(home_dir) = dirs::home_dir() {
|
||||||
|
let rustfs_dir = home_dir.join("rustfs");
|
||||||
|
let logs_dir = rustfs_dir.join("logs");
|
||||||
|
|
||||||
|
// Test that paths are constructed correctly
|
||||||
|
assert!(rustfs_dir.ends_with("rustfs"));
|
||||||
|
assert!(logs_dir.ends_with("logs"));
|
||||||
|
assert!(logs_dir.parent().unwrap().ends_with("rustfs"));
|
||||||
|
|
||||||
|
// Test path string representation
|
||||||
|
let rustfs_str = rustfs_dir.to_string_lossy();
|
||||||
|
let logs_str = logs_dir.to_string_lossy();
|
||||||
|
|
||||||
|
assert!(rustfs_str.contains("rustfs"));
|
||||||
|
assert!(logs_str.contains("rustfs"));
|
||||||
|
assert!(logs_str.contains("logs"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_filename_patterns() {
|
||||||
|
ensure_logger_init();
|
||||||
|
|
||||||
|
// Test the filename patterns used in the logger
|
||||||
|
let prefix = "rustfs-cli";
|
||||||
|
let suffix = "log";
|
||||||
|
|
||||||
|
assert_eq!(prefix, "rustfs-cli");
|
||||||
|
assert_eq!(suffix, "log");
|
||||||
|
|
||||||
|
// Test that these would create valid filenames
|
||||||
|
let sample_filename = format!("{}.2024-01-01.{}", prefix, suffix);
|
||||||
|
assert_eq!(sample_filename, "rustfs-cli.2024-01-01.log");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_worker_guard_type() {
|
||||||
|
ensure_logger_init();
|
||||||
|
|
||||||
|
// Test that WorkerGuard type exists and can be referenced
|
||||||
|
// We can't actually create one without the full setup, but we can test the type
|
||||||
|
let guard_size = std::mem::size_of::<WorkerGuard>();
|
||||||
|
assert!(guard_size > 0, "WorkerGuard should have non-zero size");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_logger_configuration_constants() {
|
||||||
|
ensure_logger_init();
|
||||||
|
|
||||||
|
// Test the configuration values used in the logger
|
||||||
|
let default_log_level = "info";
|
||||||
|
let filename_prefix = "rustfs-cli";
|
||||||
|
let filename_suffix = "log";
|
||||||
|
let rotation = Rotation::DAILY;
|
||||||
|
|
||||||
|
assert_eq!(default_log_level, "info");
|
||||||
|
assert_eq!(filename_prefix, "rustfs-cli");
|
||||||
|
assert_eq!(filename_suffix, "log");
|
||||||
|
assert!(matches!(rotation, Rotation::DAILY));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_directory_names() {
|
||||||
|
ensure_logger_init();
|
||||||
|
|
||||||
|
// Test the directory names used in the logger setup
|
||||||
|
let rustfs_dir_name = "rustfs";
|
||||||
|
let logs_dir_name = "logs";
|
||||||
|
|
||||||
|
assert_eq!(rustfs_dir_name, "rustfs");
|
||||||
|
assert_eq!(logs_dir_name, "logs");
|
||||||
|
|
||||||
|
// Test path joining
|
||||||
|
let combined = format!("{}/{}", rustfs_dir_name, logs_dir_name);
|
||||||
|
assert_eq!(combined, "rustfs/logs");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_layer_settings() {
|
||||||
|
ensure_logger_init();
|
||||||
|
|
||||||
|
// Test the boolean settings used in layer configuration
|
||||||
|
let console_ansi = true;
|
||||||
|
let console_line_number = true;
|
||||||
|
let file_ansi = false;
|
||||||
|
let file_thread_names = true;
|
||||||
|
let file_target = true;
|
||||||
|
let file_thread_ids = true;
|
||||||
|
let file_level = true;
|
||||||
|
let file_line_number = true;
|
||||||
|
|
||||||
|
// Verify the settings
|
||||||
|
assert!(console_ansi);
|
||||||
|
assert!(console_line_number);
|
||||||
|
assert!(!file_ansi);
|
||||||
|
assert!(file_thread_names);
|
||||||
|
assert!(file_target);
|
||||||
|
assert!(file_thread_ids);
|
||||||
|
assert!(file_level);
|
||||||
|
assert!(file_line_number);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: The actual init_logger() function is not tested here because:
|
||||||
|
// 1. It initializes a global tracing subscriber which can only be done once
|
||||||
|
// 2. It requires file system access to create directories
|
||||||
|
// 3. It has side effects that would interfere with other tests
|
||||||
|
// 4. It returns a WorkerGuard that needs to be kept alive
|
||||||
|
//
|
||||||
|
// This function should be tested in integration tests where:
|
||||||
|
// - File system access can be properly controlled
|
||||||
|
// - The global state can be managed
|
||||||
|
// - The actual logging behavior can be verified
|
||||||
|
// - The WorkerGuard lifecycle can be properly managed
|
||||||
|
}
|
||||||
7
cli/rustfs-gui/src/utils/mod.rs
Normal file
7
cli/rustfs-gui/src/utils/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
mod config;
|
||||||
|
mod helper;
|
||||||
|
mod logger;
|
||||||
|
|
||||||
|
pub use config::RustFSConfig;
|
||||||
|
pub use helper::ServiceManager;
|
||||||
|
pub use logger::init_logger;
|
||||||
24
cli/rustfs-gui/src/views/app.rs
Normal file
24
cli/rustfs-gui/src/views/app.rs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
use crate::route::Route;
|
||||||
|
use dioxus::logger::tracing::info;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
const FAVICON: Asset = asset!("/assets/favicon.ico");
|
||||||
|
const TAILWIND_CSS: Asset = asset!("/assets/tailwind.css");
|
||||||
|
|
||||||
|
/// The main application component
|
||||||
|
/// This is the root component of the application
|
||||||
|
/// It contains the global resources and the router
|
||||||
|
/// for the application
|
||||||
|
#[component]
|
||||||
|
pub fn App() -> Element {
|
||||||
|
// Build cool things ✌️
|
||||||
|
use document::{Link, Title};
|
||||||
|
info!("App rendered");
|
||||||
|
rsx! {
|
||||||
|
// Global app resources
|
||||||
|
Link { rel: "icon", href: FAVICON }
|
||||||
|
Link { rel: "stylesheet", href: TAILWIND_CSS }
|
||||||
|
Title { "RustFS" }
|
||||||
|
Router::<Route> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
cli/rustfs-gui/src/views/home.rs
Normal file
9
cli/rustfs-gui/src/views/home.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use crate::components::Home;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn HomeViews() -> Element {
|
||||||
|
rsx! {
|
||||||
|
Home {}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
cli/rustfs-gui/src/views/mod.rs
Normal file
7
cli/rustfs-gui/src/views/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
mod app;
|
||||||
|
mod home;
|
||||||
|
mod setting;
|
||||||
|
|
||||||
|
pub use app::App;
|
||||||
|
pub use home::HomeViews;
|
||||||
|
pub use setting::SettingViews;
|
||||||
9
cli/rustfs-gui/src/views/setting.rs
Normal file
9
cli/rustfs-gui/src/views/setting.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use crate::components::Setting;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn SettingViews() -> Element {
|
||||||
|
rsx! {
|
||||||
|
Setting {}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user