mirror of
https://github.com/pgsty/minio.git
synced 2026-10-01 23:35:59 +03:00
Compare commits
4335 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f00f6762f | |||
| af2b1794d3 | |||
| d371f77dcc | |||
| 022722a7a7 | |||
| 03027727d1 | |||
| 4fcdf37ce6 | |||
| 9ebe81c1b3 | |||
| e5f5c9e7f6 | |||
| 7b4cacc392 | |||
| a0dd7dae9b | |||
| 709d50a916 | |||
| dff81f293b | |||
| f653a6ea03 | |||
| 47d239f84f | |||
| e069fe9d92 | |||
| d848fb52b5 | |||
| 3ce8319251 | |||
| 9df0f4abaf | |||
| 4d0693cb8c | |||
| bf59e3f222 | |||
| 41aa846097 | |||
| 4093fa0d78 | |||
| 13bf126ebd | |||
| 1cf529ce8a | |||
| 9b76a21675 | |||
| 89637554d6 | |||
| 2dd1e00da4 | |||
| 5d955b5b74 | |||
| f7808a172c | |||
| acc9b514f5 | |||
| 31ba01c5d5 | |||
| 48ec10312f | |||
| 114dc10529 | |||
| 461e9a7210 | |||
| fcbb93e895 | |||
| 62cf066ff5 | |||
| 1ee64a8d89 | |||
| 01aaef2b50 | |||
| bcc62afe3d | |||
| 5c57658163 | |||
| f175e98c34 | |||
| 12f631b502 | |||
| ccb676e60c | |||
| 51d41345f7 | |||
| e59a3d938e | |||
| b32f2d9dd0 | |||
| d63c92e393 | |||
| 68127c5a63 | |||
| c4b5e1cb45 | |||
| 9a303f5096 | |||
| 87d8b5967f | |||
| f760046c44 | |||
| 93e7ef4bcc | |||
| a4229b366f | |||
| 420340bc14 | |||
| e7e87402ed | |||
| a164e1dda1 | |||
| 2f61325d4a | |||
| a6145e1d2e | |||
| 5232546690 | |||
| 0c46cb641b | |||
| 1233254309 | |||
| 8a2fe9b7a0 | |||
| f26ee6bf0a | |||
| d69c4ccfe4 | |||
| 086e619505 | |||
| 48e1846525 | |||
| bcc8871b1d | |||
| 25cb3511c9 | |||
| cfefc049c1 | |||
| af56d17630 | |||
| d1105bbb3d | |||
| 66fe61ff65 | |||
| a1141a43f2 | |||
| 702f113f51 | |||
| 63aace4099 | |||
| 9d7094b770 | |||
| 450dcb8484 | |||
| 4074d00b96 | |||
| 75ba0ce402 | |||
| da142327f2 | |||
| a3df317ae0 | |||
| 2c50d11f72 | |||
| 5ac33e1583 | |||
| d57c4e8407 | |||
| 374de0fa32 | |||
| 80e23dc9f2 | |||
| 2cc0e3c6ed | |||
| f9da3b919d | |||
| 1309853f57 | |||
| 39b8e6c30a | |||
| 9a6e1477f4 | |||
| 49375ed2d3 | |||
| f817b5261c | |||
| 885bd2c20a | |||
| 89b75e7913 | |||
| 079ebb1926 | |||
| 04c29aac11 | |||
| 95e7a190b3 | |||
| 8e2392e48f | |||
| 3abe0d95a5 | |||
| 9c6c9805de | |||
| 5cb900bfad | |||
| 0af5d22286 | |||
| f1687f402b | |||
| ce606df2c4 | |||
| fd44dc4e9b | |||
| 479745e764 | |||
| cc1c54475f | |||
| e7654d470c | |||
| 4b25f7e819 | |||
| 711b092f86 | |||
| 2bc103b80c | |||
| ad873c7357 | |||
| 0af0907eff | |||
| e27ba2bc14 | |||
| 236e163c0b | |||
| 7220210e8d | |||
| 34cbca97ea | |||
| 109d824e5f | |||
| 87746913fc | |||
| 7935c84f9a | |||
| c185635b43 | |||
| c201148738 | |||
| 53adb21c52 | |||
| 58b0ee36ca | |||
| 8a1f594add | |||
| 5b9959617e | |||
| da19d91b64 | |||
| 40bee4b7ba | |||
| 6a9b5d6763 | |||
| 0720ed477e | |||
| 46e82eb54d | |||
| f8ca4a8656 | |||
| e7d0e62f16 | |||
| aee290fc34 | |||
| 62cce2b152 | |||
| 65d4806a7b | |||
| 765757473a | |||
| 425bd7fff1 | |||
| e12e739a53 | |||
| 8b736dee34 | |||
| 32e75c27bf | |||
| 885ca604a1 | |||
| d10382d0dc | |||
| 0db4bf3b00 | |||
| 87c621965d | |||
| b2dca43fda | |||
| 33a91d972f | |||
| 2cbd48a3c3 | |||
| b5409ca112 | |||
| 35bd75948a | |||
| 0c8d74205b | |||
| fcc4d77895 | |||
| c52acc1a5d | |||
| 5703426b3c | |||
| f0bd164b92 | |||
| 9936a69d89 | |||
| ce2326c946 | |||
| 4c164907f5 | |||
| 9b11dc9469 | |||
| 1eccc6908b | |||
| 202371afbd | |||
| 41ef4411d9 | |||
| 035aa6c201 | |||
| edf36bcbfa | |||
| 3598c4305d | |||
| 94fbb6df6d | |||
| f4c1286c9d | |||
| 6e112d1856 | |||
| e62dc6e023 | |||
| 84e1580a47 | |||
| ebac0ca73b | |||
| 3f9c79e919 | |||
| ec2979ca48 | |||
| 21646eebd2 | |||
| 5711996231 | |||
| 0079723d35 | |||
| bc3b35f975 | |||
| 00d864ed0e | |||
| 8f2a30d9af | |||
| d9766d7378 | |||
| 5594d284fc | |||
| 632eb4729a | |||
| 3b5de82f5a | |||
| 76195f1c68 | |||
| 6586fbfd0d | |||
| 53b09f4e25 | |||
| 59812446ad | |||
| 1c2f59604d | |||
| b3a6d5dbf8 | |||
| 11de51ec26 | |||
| 10f3a8590b | |||
| 55c22abec1 | |||
| 32a1b81e4c | |||
| 62d8c649fe | |||
| 312397739e | |||
| 7ade0c045b | |||
| f9f9fa6c90 | |||
| 0a9c777795 | |||
| d2d47a41fb | |||
| dee2c3a02b | |||
| c4140609b7 | |||
| 75a6734e49 | |||
| f8b598f1d3 | |||
| fe9dd90b22 | |||
| 04b097fd9f | |||
| ab3ae99ca3 | |||
| 5db7be4ee4 | |||
| ff44527a3c | |||
| c9ad746732 | |||
| f3438b2602 | |||
| 938603458d | |||
| d5e763b072 | |||
| 74c97d005d | |||
| 1be684ba29 | |||
| 1367364c40 | |||
| 5f44fe369c | |||
| c6cc136833 | |||
| 97e31af331 | |||
| 0507c3d56a | |||
| 87b9895bc0 | |||
| ca1f11b708 | |||
| 514a041f4a | |||
| f8106cd693 | |||
| b1ba685ac5 | |||
| bc7658f035 | |||
| f377b5c95b | |||
| 45381222b7 | |||
| 4d6e1ea8ea | |||
| 20aaefa89c | |||
| 82509ddc0a | |||
| f2ba439022 | |||
| 2ba1f38503 | |||
| 8d58343263 | |||
| 2b2e0d2a5d | |||
| e407dc58eb | |||
| 32b2aa49f1 | |||
| 8448512a1f | |||
| 150e7b5f9e | |||
| 21870fa2e7 | |||
| d4c8da162b | |||
| fc7bf7b295 | |||
| d28885d0e5 | |||
| c4fd97d0bf | |||
| b73581b05d | |||
| 474cd5801e | |||
| ee9252a608 | |||
| fb406fdc94 | |||
| dd3bdb8086 | |||
| 7c103389f5 | |||
| 3861f33cba | |||
| 07d92db523 | |||
| 5732930102 | |||
| 2aea7fe9c4 | |||
| ffb70eb373 | |||
| e73436c99d | |||
| 0b0ae2423a | |||
| 7e079ff05c | |||
| 229fe2b3c3 | |||
| 38ed9d1e1f | |||
| 47cd7807d3 | |||
| 04d3d316d2 | |||
| 4bb8c813ac | |||
| b6ef7e430c | |||
| 91d9091758 | |||
| 0eebc928f7 | |||
| 724f8703d8 | |||
| e4e3007da6 | |||
| 13e6458d90 | |||
| 590aeaa7d1 | |||
| 5d152416de | |||
| edc8be6ed1 | |||
| 49c8aeac40 | |||
| e9c5340be9 | |||
| 2e2377d1c6 | |||
| 58735ee382 | |||
| a96116b128 | |||
| c4b9d38d8a | |||
| 8d76a255c4 | |||
| c5bc57b7a3 | |||
| 3814818537 | |||
| 7a49a7a3da | |||
| ff3395d3c6 | |||
| ce4525632f | |||
| 1c9a2431fe | |||
| f2520f3346 | |||
| 05df6e70d7 | |||
| c0e7159771 | |||
| 56c67dacf1 | |||
| eee05a17c3 | |||
| 45eb2e423d | |||
| 6b0998157c | |||
| 7fea6d5a5f | |||
| 68eeb002f6 | |||
| c565987b9c | |||
| f1ba683582 | |||
| 43f4bb7ed4 | |||
| 7a060cab1e | |||
| d014a12cff | |||
| 100e2e57a7 | |||
| 6e20e74774 | |||
| 3be10fcc1a | |||
| b14ea22aa8 | |||
| 86a7782900 | |||
| 4679314556 | |||
| 062a91beed | |||
| 16b78eb4e5 | |||
| 9462cce16e | |||
| b6d47b739c | |||
| 4c34d23099 | |||
| 2ff594f4bb | |||
| 219670d317 | |||
| 6bd9cf77ef | |||
| a6d6d9b028 | |||
| 05be686b84 | |||
| b57275be34 | |||
| 6740e6978f | |||
| 62717d7bf6 | |||
| f1c77d5a2b | |||
| c47733abc4 | |||
| c46b16ec62 | |||
| fd2ca1c6d2 | |||
| 6613c2a3cb | |||
| bd8df51665 | |||
| e071bb77e4 | |||
| 30749911bd | |||
| 15ab10833b | |||
| 77bdc4c0cd | |||
| 15def34dce | |||
| d88f46ccee | |||
| 021110b451 | |||
| aa51393694 | |||
| e064b5555f | |||
| 2ca4971d91 | |||
| 4c185d5a66 | |||
| ca674a6967 | |||
| 3b8a55deef | |||
| 11d79fddc3 | |||
| 475236c79c | |||
| 1814ae52f4 | |||
| 632ade111b | |||
| 32863c8523 | |||
| cf7df097b2 | |||
| 10c7670b80 | |||
| 9c799f42d5 | |||
| 8eae745ab2 | |||
| b42ee4e8ac | |||
| 5f4513fd40 | |||
| dfe6698627 | |||
| 2602177ef6 | |||
| 9dd1dc172d | |||
| 0c14d81510 | |||
| 162ded3438 | |||
| fe6dc47804 | |||
| 744a9dcd71 | |||
| 2f55347f78 | |||
| 97b7d28040 | |||
| 22c1e41fd2 | |||
| 38366f6543 | |||
| 1af351a702 | |||
| b6f70ab085 | |||
| 80e8eaa423 | |||
| ca7baa670d | |||
| a36fd8fffb | |||
| 8069a32ac8 | |||
| 89d346bf51 | |||
| 9247179269 | |||
| 3e14733f15 | |||
| c8590413fd | |||
| 3f192f3f0c | |||
| 15fcc3c8ac | |||
| c1aec0518a | |||
| 7babc0c390 | |||
| b7f52ca433 | |||
| 4dfc27ce32 | |||
| ce01ccbdc1 | |||
| d495d30d57 | |||
| 3e61b1d3a5 | |||
| df627ff896 | |||
| 73ac524724 | |||
| fd69c89d05 | |||
| 5e40665acd | |||
| 65795ee1f4 | |||
| f48dbe777d | |||
| f44110890b | |||
| 9e10f6d9a0 | |||
| 18b712d49a | |||
| db4c0fd5e3 | |||
| efb6e5b00b | |||
| f444b6f37e | |||
| 3252d5b7f3 | |||
| 56fa63bfd1 | |||
| 3b950f8fa8 | |||
| d24f449e08 | |||
| e4fa063942 | |||
| ff58df9499 | |||
| 1869bd30b8 | |||
| 68e0ba9971 | |||
| ce1c537eb1 | |||
| ee55e5391a | |||
| f2f9a40dce | |||
| 377fc616d9 | |||
| 5abd9a80f6 | |||
| 00f3cf74fc | |||
| 68521b37f2 | |||
| 8630937e7d | |||
| d4cd4b4337 | |||
| 27742d4694 | |||
| 58659f26f4 | |||
| 3a0cc6c86e | |||
| 10b0a234d2 | |||
| 18f97e70b1 | |||
| 52eee5a2f1 | |||
| c6d3aac5c4 | |||
| fa18589d1c | |||
| 05e569960a | |||
| 9e49d5e7a6 | |||
| c1a49490c7 | |||
| 334c313da4 | |||
| 1b8ac0af9f | |||
| ba3c0fd1c7 | |||
| d51a4a4ff6 | |||
| 62383dfbfe | |||
| bde0d5a291 | |||
| 534f4a9fb1 | |||
| b8631cf531 | |||
| 456d9462e5 | |||
| 756f3c8142 | |||
| 7a80ec1cce | |||
| ae71d76901 | |||
| 07c3a429bf | |||
| 0cde982902 | |||
| d0f50cdd9b | |||
| da532ab93d | |||
| 558fc1c09c | |||
| 9fdbf6fe83 | |||
| 5c87d4ae87 | |||
| f0b91e5504 | |||
| 3b7cb6512c | |||
| 4ea6f3b06b | |||
| 86d9d9b55e | |||
| 5a35585acd | |||
| 0848e69602 | |||
| 02ba581ecf | |||
| b44b2a090c | |||
| c7d6a9722d | |||
| a8abdc797e | |||
| 0638ccc5f3 | |||
| b1a34fd63f | |||
| ffcfa36b13 | |||
| 376fbd11a7 | |||
| c76f209ccc | |||
| 7a6a2256b1 | |||
| d002beaee3 | |||
| 71f293d9ab | |||
| e3d183b6a4 | |||
| 752abc2e2c | |||
| b9f0e8c712 | |||
| 7ced9663e6 | |||
| 50fcf9b670 | |||
| 64f5c6103f | |||
| e909be6380 | |||
| 83b2ad418b | |||
| 7a64bb9766 | |||
| 34679befef | |||
| 4021d8c8e2 | |||
| de234b888c | |||
| 2718d9a430 | |||
| a65292cab1 | |||
| e0c79be251 | |||
| a6c538c5a1 | |||
| e1fcaebc77 | |||
| 21409f112d | |||
| 417c8648f0 | |||
| e2245a0b12 | |||
| b4b3d208dd | |||
| 0a36d41dcd | |||
| ea77bcfc98 | |||
| 9f24ca5d66 | |||
| 816666a4c6 | |||
| 2c7fe094d1 | |||
| 9ebe168782 | |||
| ee2028cde6 | |||
| ecde75f911 | |||
| 12a6ea89cc | |||
| 63e102c049 | |||
| 160f8a901b | |||
| ef9b03fbf5 | |||
| 1d50cae43d | |||
| c0a33952c6 | |||
| 8cad40a483 | |||
| 6d18dba9a2 | |||
| 9ea14c88d8 | |||
| 30a1261c22 | |||
| 0e017ab071 | |||
| f14198e3dc | |||
| 93c389dbc9 | |||
| ddd9a84cd7 | |||
| b7540169a2 | |||
| f01374950f | |||
| 18aceae620 | |||
| 427826abc5 | |||
| 2780778c10 | |||
| 2d8ba15b9e | |||
| bd6dd55e7f | |||
| 0d7408fc99 | |||
| 864f80e226 | |||
| 0379d6a37f | |||
| 43aa8e4259 | |||
| e2ed696619 | |||
| fb3f67a597 | |||
| 7ee75368e0 | |||
| 1d6478b8ae | |||
| 0581001b6f | |||
| 479303e7e9 | |||
| 89aec6804b | |||
| eb33bc6bf5 | |||
| 3310f740f0 | |||
| 4595293ca0 | |||
| 02a67cbd2a | |||
| 2b34e5b9ae | |||
| a6258668a6 | |||
| d0cada583f | |||
| 0bd8f06b62 | |||
| 6640be3bed | |||
| eafeb27e90 | |||
| f2c9eb0f79 | |||
| f2619d1f62 | |||
| 8c70975283 | |||
| 01447d2438 | |||
| 07f31e574c | |||
| 8d223e07fb | |||
| 4041a8727c | |||
| 5f243fde9a | |||
| a0e3f1cc18 | |||
| b1bc641105 | |||
| e0c8738230 | |||
| 9aa24b1920 | |||
| 53d40e41bc | |||
| e88d494775 | |||
| b67f0cf721 | |||
| 46922c71b7 | |||
| 670edb4fcf | |||
| 42d4ab2a0a | |||
| 5e2eb372bf | |||
| cccb37a5ac | |||
| dbf31af6cb | |||
| 93e40c3ab4 | |||
| 8aa0e9ff7c | |||
| bbd6f18afb | |||
| 2a3acc4f24 | |||
| 11507d46da | |||
| f9c62dea55 | |||
| 8c2c92f7af | |||
| 4c71f1b4ec | |||
| 6cd8a372cb | |||
| 953a3e2bbd | |||
| 7cc0c69228 | |||
| f129fd48f2 | |||
| bc4008ced4 | |||
| 526053339b | |||
| 62a35b3e77 | |||
| 39df134204 | |||
| ad4cbce22d | |||
| 90f5e1e5f6 | |||
| aeabac9181 | |||
| b312f13473 | |||
| 727a803bc0 | |||
| d0e443172d | |||
| 60446e7ac0 | |||
| b8544266e5 | |||
| 437dd4e32a | |||
| 447054b841 | |||
| 9bf43e54cd | |||
| 60f8423157 | |||
| 4355ea3c3f | |||
| e30f1ad7bd | |||
| f00c8c4cce | |||
| 703f51164d | |||
| b8dde47d4e | |||
| 7fa3e39f85 | |||
| 4df7a3aa8f | |||
| 64a8f2e554 | |||
| f4fd4ea66d | |||
| 712fe1a8df | |||
| 4a319bedc9 | |||
| bdb3db6dad | |||
| abb385af41 | |||
| 4ee62606e4 | |||
| 079d64c801 | |||
| dcc000ae2c | |||
| c5d19ecebb | |||
| ed29a525b3 | |||
| 020c46cd3c | |||
| 827004cd6d | |||
| 779ec8f0d4 | |||
| 3d0f513ee2 | |||
| 4b6eadbd80 | |||
| 6f47414b23 | |||
| 224a27992a | |||
| 232544e1d8 | |||
| dbcb71828d | |||
| b9196757fd | |||
| b4ac53d157 | |||
| 4952bdb770 | |||
| 00b2ef2932 | |||
| 4536ecfaa4 | |||
| 43a7402968 | |||
| 330dca9a35 | |||
| ddd137d317 | |||
| 06ddd8770e | |||
| 16f8cf1c52 | |||
| 01e520eb23 | |||
| 02f770a0c0 | |||
| 2f4c79bc0f | |||
| 969ee7dfbe | |||
| 5f0b086b05 | |||
| 68b004a48f | |||
| 54ecce66f0 | |||
| 2b008c598b | |||
| 86d02b17cf | |||
| c1a95a70ac | |||
| f246c9053f | |||
| 9cdd204ae4 | |||
| 7b3eb9f7f8 | |||
| d56ef8dbe1 | |||
| a248ed5ff5 | |||
| 5bb31e4883 | |||
| aff2a76d80 | |||
| eddbe6bca2 | |||
| 734d1e320a | |||
| b8dab7b1a9 | |||
| abd6bf060d | |||
| f0d4ef604c | |||
| 2712f75762 | |||
| 4c46668da8 | |||
| 02e93fd6ba | |||
| 366876e98b | |||
| d202fdd022 | |||
| c07e5b49d4 | |||
| 9a39f8ad4d | |||
| 7e0c1c9413 | |||
| 485d833cd7 | |||
| e8a476ef5a | |||
| 267f0ecea2 | |||
| 4ee3434854 | |||
| 0e9854372e | |||
| b5177993b3 | |||
| 55f5c18fd9 | |||
| 8ce101c174 | |||
| 4972735507 | |||
| e6ca6de194 | |||
| cefc43e4da | |||
| 25e34fda5f | |||
| 4208d7af5a | |||
| 8d42f37e4b | |||
| 7cb4b5c636 | |||
| 1615920f48 | |||
| 7ee42b3ff5 | |||
| a6f1e727fb | |||
| c1fc7779ca | |||
| b3ab7546ee | |||
| ad88a81e3d | |||
| c4239ced22 | |||
| f85c28e960 | |||
| f7e176d4ca | |||
| 72a0d14195 | |||
| 6abe4128d7 | |||
| ed5ed7e490 | |||
| 51410c9023 | |||
| 96ca402dcd | |||
| 3da7c9cce3 | |||
| a14e19ec54 | |||
| e091dde041 | |||
| d10bb7e1b6 | |||
| 7ebceacac6 | |||
| 1593cb615d | |||
| 86a41d1631 | |||
| d4157b819c | |||
| e0aceca1b7 | |||
| 87804624fe | |||
| e029f8a9d7 | |||
| 1bc6681176 | |||
| 28322124e2 | |||
| cbfe9de3e7 | |||
| dc86b8d9d4 | |||
| ba70118e2b | |||
| cb1d3e50f7 | |||
| ded0b19d97 | |||
| d0bb3dd136 | |||
| ab7714b01e | |||
| e5b18df6db | |||
| 0abfd1bcb1 | |||
| 6186d11761 | |||
| e8b457e8a6 | |||
| afea40cc0f | |||
| 402b798f1b | |||
| 4759532e90 | |||
| 7f1e1713ab | |||
| b2c5819dbc | |||
| 2b0156b1fc | |||
| f6f0807c86 | |||
| 0c53d86017 | |||
| 6a6ee46d76 | |||
| 974cbb3bb7 | |||
| 03e996320e | |||
| 78fcb76294 | |||
| 3d152015eb | |||
| ade8925155 | |||
| 05a6c170bf | |||
| e1c2344591 | |||
| 48a591e9b4 | |||
| fa5d9c02ef | |||
| 5bd27346ac | |||
| 3c82cf9327 | |||
| 70d40083e9 | |||
| 8a30967542 | |||
| 229f04ab79 | |||
| 1123dc3676 | |||
| 5bf41aff17 | |||
| e47d787adb | |||
| 398ffb1136 | |||
| 5862582cd7 | |||
| e36b1146d6 | |||
| c28a4beeb7 | |||
| 15ab0808b3 | |||
| 3bae73fb42 | |||
| bc527eceda | |||
| b963f36e1e | |||
| cdd7512a2e | |||
| a6d5287310 | |||
| 22822f4151 | |||
| 0b7aa6af87 | |||
| 8c9ab85cfa | |||
| b1c849bedc | |||
| fb24bcfee0 | |||
| 8268c12cfb | |||
| 3f39da48ea | |||
| 9d5cdaa2e3 | |||
| 84e122c5c3 | |||
| 261111e728 | |||
| 0f1e8db4c5 | |||
| 64e803b136 | |||
| a0f9e9f661 | |||
| b6b7cddc9c | |||
| 241be9709c | |||
| 85f08d7752 | |||
| 6be88a2b99 | |||
| 060276932d | |||
| 6224849fd1 | |||
| 9b79eec29e | |||
| c2e318dd40 | |||
| 69258d5945 | |||
| d7ef6315ae | |||
| aaf4fb1184 | |||
| f05641c3c6 | |||
| 7a34c88d73 | |||
| 6c746843ac | |||
| bb07df7e7b | |||
| 1cb824039e | |||
| 504e52b45e | |||
| 38c0840834 | |||
| c65e67c357 | |||
| fb2360ff88 | |||
| 1a2de1bdde | |||
| 993b97f1db | |||
| 1c4d28d7af | |||
| af55f37b27 | |||
| 2d67c26794 | |||
| 006cacfefb | |||
| c28f09d4a7 | |||
| 73992d2b9f | |||
| a8f143298f | |||
| 2d44c161c7 | |||
| 9511056f44 | |||
| fb4ad000b6 | |||
| a8ff12bc72 | |||
| 1e1bd3afd9 | |||
| 7b239ae154 | |||
| 8a11282522 | |||
| 85c3db3a93 | |||
| 37383ecd09 | |||
| 6378ca10a4 | |||
| 9e81ccd2d9 | |||
| 72cff79c8a | |||
| a5702f978e | |||
| 4687c4616f | |||
| d8dfb57d5c | |||
| b07c58aa05 | |||
| cc0c41d216 | |||
| f1302c40fe | |||
| 3b1aa40372 | |||
| d96798ae7b | |||
| b508264ac4 | |||
| db78431b1d | |||
| 743ddb196a | |||
| 3ffeabdfcb | |||
| 51b1f41518 | |||
| e7a56f35b9 | |||
| 516af01a12 | |||
| acdb355070 | |||
| 37c02a5f7b | |||
| 04be352ae9 | |||
| 53eb7656de | |||
| d8f0e0ea6e | |||
| 2e0fd2cba9 | |||
| 909b169593 | |||
| 4e67a4027e | |||
| 49055658a9 | |||
| 89c58ce87d | |||
| 14876a4df1 | |||
| 2681219039 | |||
| 5da7f0100a | |||
| dea9abed29 | |||
| e3eb5c1328 | |||
| fb9364f1fb | |||
| 6efb56851c | |||
| db2c7ed1d1 | |||
| 74c047cb03 | |||
| 50a5ad48fc | |||
| 292fccff6e | |||
| a9dc061d84 | |||
| 01a8c09920 | |||
| 4c8562bcec | |||
| f13c04629b | |||
| 80ff907d08 | |||
| 673df6d517 | |||
| 2d40433bc1 | |||
| 3bc39db34e | |||
| a17f14f73a | |||
| 6651c655cb | |||
| 3ae104edae | |||
| c87a489514 | |||
| a60267501d | |||
| 641a56da0d | |||
| 59788e25c7 | |||
| a16193bb50 | |||
| 132e7413ba | |||
| 1966668066 | |||
| 064f36ca5a | |||
| 15b609ecea | |||
| 4a1edfd9aa | |||
| b7f319b62a | |||
| 33c101544d | |||
| 21cf29330e | |||
| 3b21bb5be8 | |||
| 6fe2b3f901 | |||
| b368d4cc13 | |||
| 0680af7414 | |||
| 91805bcab6 | |||
| c0e2886e37 | |||
| 4f5dded4d4 | |||
| b3a94c4e85 | |||
| 8e618d45fc | |||
| 3ef59d2821 | |||
| 23db4958f5 | |||
| d9ee668b6d | |||
| 2e5d792f0c | |||
| b276651eaa | |||
| 3535197f99 | |||
| 95f076340a | |||
| 698bb93a46 | |||
| 2584430141 | |||
| ded373e600 | |||
| e8c54c3d6c | |||
| f944a42886 | |||
| eff0ea43aa | |||
| 3b602bb532 | |||
| 459985f0fa | |||
| d0080046c2 | |||
| 7fcb428622 | |||
| 4ea6f94ed8 | |||
| 83adc2eebf | |||
| 989c318a28 | |||
| ef802f2b2c | |||
| f5d2fbc84c | |||
| e139673969 | |||
| a8c6465f22 | |||
| 27538e2d22 | |||
| 6c6f0987dc | |||
| 5f64658faa | |||
| ce183cb2b4 | |||
| b3bac73c0f | |||
| e726d8ff0f | |||
| f4230777b3 | |||
| 380233d646 | |||
| d592bc0c1c | |||
| 0d0b0aa599 | |||
| b433bf14ba | |||
| cf371da346 | |||
| 107d951893 | |||
| 22c53b1c70 | |||
| 88926ad8e9 | |||
| 32d04091a2 | |||
| b6d4a77b94 | |||
| be84a4fd68 | |||
| 2ec1f404ac | |||
| 2040559f71 | |||
| ca0ce4c6ef | |||
| 757cf413cb | |||
| b35acb3dbc | |||
| e404abf103 | |||
| f7ff19cb18 | |||
| f736702da8 | |||
| 91faaa1387 | |||
| 68a9f521d5 | |||
| f365a98029 | |||
| 47bbc272df | |||
| aebac90013 | |||
| 7ca4ba77c4 | |||
| 13512170b5 | |||
| 154fcaeb56 | |||
| 722118386d | |||
| 709612cb37 | |||
| b35d083872 | |||
| 5e7b243bde | |||
| f8f9fc77ac | |||
| 499531f0b5 | |||
| 3c2141513f | |||
| 602f6a9ad0 | |||
| 22c5a5b91b | |||
| 41f508765d | |||
| 7dccd1f589 | |||
| 55ff598b23 | |||
| a22ce4550c | |||
| 168ae81b1f | |||
| 5f6a25cdd0 | |||
| be97ae4c5d | |||
| 4d7d008741 | |||
| 2d7a3d1516 | |||
| dfab400d43 | |||
| 70078eab10 | |||
| 3415c4dd1e | |||
| e200808ab7 | |||
| fae563b85d | |||
| 3e6dc02f8f | |||
| 95e4cbbfde | |||
| 2825294b7b | |||
| bce93b5cfa | |||
| 7a4b250c8b | |||
| e5335450a4 | |||
| a6ffdf1dd4 | |||
| 69e41f87ef | |||
| ee48f9f206 | |||
| 9ba39d7fad | |||
| d2fb371f80 | |||
| 2f9018f03b | |||
| eb990f64a9 | |||
| bbb64eaade | |||
| 7bd1d899bc | |||
| c91d1ec2e3 | |||
| c50b64027d | |||
| 20960b6a2d | |||
| 3bd3470d0b | |||
| ba39ed9af7 | |||
| 62e6dc950d | |||
| 5a5046ce45 | |||
| ad04afe381 | |||
| ba9f0f2480 | |||
| d06b63d056 | |||
| 7ce28c3b1d | |||
| e3ac4035b9 | |||
| d21b6daa49 | |||
| 76ebb16688 | |||
| 55aa431578 | |||
| 614981e566 | |||
| b8b956a05d | |||
| d2eed44c78 | |||
| 789cbc6fb2 | |||
| 0662c90b5c | |||
| a2cab02554 | |||
| 6c7a21df6b | |||
| f933b0b708 | |||
| 9f305273a7 | |||
| cbd9efcb43 | |||
| 29a25a538f | |||
| 2dd8faaedc | |||
| f00187033d | |||
| c5141d65ac | |||
| 069c4015cd | |||
| 2f6e03fb60 | |||
| 0fbb945e13 | |||
| b94dd835c9 | |||
| 44fc707423 | |||
| 5aaef9790f | |||
| 7edc352d23 | |||
| 850a84b08a | |||
| 4148754ce0 | |||
| 2107722829 | |||
| d326ba52e9 | |||
| 91e1487de4 | |||
| 5ffb2a9605 | |||
| 17fe91d6d1 | |||
| 90a9f2dd70 | |||
| d5e48cfd65 | |||
| d274566463 | |||
| 39ac720826 | |||
| 21b6204692 | |||
| d98faeb26a | |||
| 0a63dc199c | |||
| 3ba857dfa1 | |||
| a8554c4022 | |||
| ba54b39c02 | |||
| 2a75225569 | |||
| e72429c79c | |||
| c5b3f5553f | |||
| d3ae0aaad3 | |||
| d67bccf861 | |||
| 1277ad69a6 | |||
| 8f93e81afb | |||
| 4af31e654b | |||
| aad50579ba | |||
| 38d059b0ae | |||
| bd4eeb4522 | |||
| 03e3493288 | |||
| 64baedf5a4 | |||
| 2f64d5f77e | |||
| f79a4ef4d0 | |||
| 2d53854b19 | |||
| e5c83535af | |||
| c904ef966e | |||
| 8f266e0772 | |||
| e0fe7cc391 | |||
| 9d20dec56a | |||
| 597a785253 | |||
| 7d75b1e758 | |||
| 5f78691fcf | |||
| a591e06ae5 | |||
| 443c93c634 | |||
| 5659cddc84 | |||
| 2a03a34bde | |||
| 1654a9b7e6 | |||
| 673a521711 | |||
| 2e23076688 | |||
| b92ac55250 | |||
| 7981509cc8 | |||
| 6d5bc045bc | |||
| d38e020b29 | |||
| 7d29030292 | |||
| 7c7650b7c3 | |||
| ca80eced24 | |||
| d0e0b81d8e | |||
| 391baa1c9a | |||
| ae14681c3e | |||
| 4d698841f4 | |||
| 9906b3ade9 | |||
| bf1769d3e0 | |||
| 63e1ad9f29 | |||
| 2c7bcee53f | |||
| 1fd90c93ff | |||
| e947a844c9 | |||
| 4e2d39293a | |||
| 1228d6bf1a | |||
| fc4561c64c | |||
| 3b7747b42b | |||
| e432e79324 | |||
| 08d74819b6 | |||
| aa3fde1784 | |||
| 0b3eb7f218 | |||
| 69c9496c71 | |||
| b792b36495 | |||
| d3db7d31a3 | |||
| c05ca63158 | |||
| 6d3e0c7db6 | |||
| 0e59e50b39 | |||
| d4b391de1b | |||
| de4d3dac00 | |||
| 534e7161df | |||
| 9b219cd646 | |||
| 3bab4822f3 | |||
| 3c5f2d8916 | |||
| 5808190398 | |||
| b2a82248b1 | |||
| 4e5fcca8b9 | |||
| c36eaedb93 | |||
| 7752b03add | |||
| 01bfc78535 | |||
| 074d70112d | |||
| e8d14c0d90 | |||
| 60d7e8143a | |||
| 9667a170de | |||
| abae30f9e1 | |||
| f9311bc9d1 | |||
| b598402738 | |||
| bd026b913f | |||
| 72ff69d9bb | |||
| f30417d9a8 | |||
| 47a4ad3cd7 | |||
| 2f7a10ab31 | |||
| b534dc69ab | |||
| 7b7d2ea7d4 | |||
| e00de1c302 | |||
| 3549e583a6 | |||
| f5e3eedf34 | |||
| 519dbfebf6 | |||
| 9a267f9270 | |||
| 67bd71b7a5 | |||
| ec49fff583 | |||
| 8b660e18f2 | |||
| 981497799a | |||
| b9bdc17465 | |||
| b413ff9fdb | |||
| 6a15580817 | |||
| 39633a5581 | |||
| 1e83f15e2f | |||
| 888d2bb1d8 | |||
| 847ee5ac45 | |||
| 9a9a49aa84 | |||
| a03ca80269 | |||
| 523bd769f1 | |||
| 8ff70ea5a9 | |||
| da3e7747ca | |||
| 4afb59e63f | |||
| 1526e7ece3 | |||
| 6c07bfee8a | |||
| 446c760820 | |||
| 04f92f1291 | |||
| 4a60a7794d | |||
| e5b16adb1c | |||
| 402a3ac719 | |||
| f3d61c51fc | |||
| 0cde17ae5d | |||
| 8c1bba681b | |||
| dbfb5e797b | |||
| 08ff702434 | |||
| 0e2148264a | |||
| a75f42344b | |||
| 7926401cbd | |||
| 8161411c5d | |||
| f64dea2aac | |||
| 6579304d8c | |||
| 6bb10a81a6 | |||
| 3cf8a7c888 | |||
| 2e38bb5175 | |||
| a372c6a377 | |||
| 93b2f8a0c5 | |||
| 1a6568a25d | |||
| 9e95703efc | |||
| d8e05aca81 | |||
| 410a1ac040 | |||
| 4caa3422bd | |||
| a658b976f5 | |||
| 135874ebdc | |||
| f4f1c42cba | |||
| e7aa26dc29 | |||
| c54ffde568 | |||
| 9a3c992d7a | |||
| 0c855638de | |||
| 943d815783 | |||
| 4c0acba62d | |||
| 62c3cdee75 | |||
| 3212d0c8cd | |||
| 1d03bea965 | |||
| fbfeb59658 | |||
| 701da1282a | |||
| df93ff92ba | |||
| 77d5331e85 | |||
| 14cdadfb56 | |||
| f3a52cc195 | |||
| 7640cd24c9 | |||
| f7b665347e | |||
| 9693c382a8 | |||
| ee1047bd52 | |||
| 5ea5ab162b | |||
| b5a09ff96b | |||
| 95c65f4e8f | |||
| 6bfff7532e | |||
| 1aa8896ad6 | |||
| 3e32ceb39f | |||
| ca1350b092 | |||
| 9205434ed3 | |||
| cd50e9b4bc | |||
| ec816f3840 | |||
| 5f774951b1 | |||
| 2ca9befd2a | |||
| 72f5cb577e | |||
| 928c0181bf | |||
| 03767d26da | |||
| 108e6f92d4 | |||
| d653a59fc0 | |||
| 01bfdf949a | |||
| 98f7821eb3 | |||
| 2d3898e0d5 | |||
| ae46ce9937 | |||
| dfc112c06b | |||
| ca5fab8656 | |||
| 6df76ca73c | |||
| f65dd3e5a2 | |||
| a8d601b64a | |||
| 73b4794cf7 | |||
| e2709ea129 | |||
| 740ec80819 | |||
| d95e054282 | |||
| 7c1f9667d1 | |||
| 9246990496 | |||
| 0cf3d93360 | |||
| cb06aee5ac | |||
| 1c70e9ed1b | |||
| f3d6a2dd37 | |||
| d1c58fc2eb | |||
| b8f05b1471 | |||
| e7baf78ee8 | |||
| 87299eba10 | |||
| d3a07c29ba | |||
| 8d39b715dc | |||
| 7e3166475d | |||
| 5206c0e883 | |||
| 41ec038523 | |||
| 08d3d06a06 | |||
| 074febd9e1 | |||
| aa8d25797b | |||
| 8d7d4adb91 | |||
| ffa91f9794 | |||
| 0c31e61343 | |||
| 9b926f7dbe | |||
| 35d8728990 | |||
| f7ed9a75ba | |||
| 9496c17e13 | |||
| ed64e91f06 | |||
| a481825ae1 | |||
| 7bb0f32332 | |||
| c6f8dc431e | |||
| 78f177b8ee | |||
| 787c44c39d | |||
| f06fee0364 | |||
| c957e0d426 | |||
| 04101d472f | |||
| 51fc145161 | |||
| 9d63bb1b41 | |||
| 8ff2a7a2b9 | |||
| 91f91d8f47 | |||
| a207bd6790 | |||
| 96d226c0b1 | |||
| a86d98826d | |||
| 1bb670ecba | |||
| c9e9a8e2b9 | |||
| 272367ccd2 | |||
| 95bf4a57b6 | |||
| 2228eb61cb | |||
| 5f07eb2d17 | |||
| d96d696841 | |||
| e18c0ab9bf | |||
| faeb2b7e79 | |||
| 97ce11cb6b | |||
| d7daae4762 | |||
| 3d86ae12bc | |||
| ba46ee5dfa | |||
| 912bbb2f1d | |||
| 4f660a8eb7 | |||
| ae4fb1b72e | |||
| b435806d91 | |||
| 06929258bc | |||
| cb577835d9 | |||
| 7f35f74f14 | |||
| 3d6194e93c | |||
| 72c7845f7e | |||
| 1c99597a06 | |||
| feb9d8480b | |||
| 4e670458b8 | |||
| 48deccdc40 | |||
| 2eee744e34 | |||
| 3f72439b8a | |||
| 468a9fae83 | |||
| d87f91720b | |||
| aa0eec16ab | |||
| d63e603040 | |||
| 8222a640ac | |||
| 7e45d84ace | |||
| 139a606f0a | |||
| 289223b6de | |||
| c61dd16a1e | |||
| 3e38fa54a5 | |||
| 4a02189ba0 | |||
| 3d4fc28ec9 | |||
| ec3a3bb10d | |||
| 364d3a0ac9 | |||
| cb536a73eb | |||
| 428155add9 | |||
| 8bce123bba | |||
| 0a56dbde2f | |||
| 7ff4164d65 | |||
| 53a14c7301 | |||
| dc45a5010d | |||
| 4b9192034c | |||
| deeadd1a37 | |||
| 1fc4203c19 | |||
| 15b930be1f | |||
| 7fd76dbbb7 | |||
| da81c6cc27 | |||
| a03dac41eb | |||
| b657ffa496 | |||
| 55778ae278 | |||
| d990661d1f | |||
| 280526caf7 | |||
| 1173b26fc8 | |||
| 383489d5d9 | |||
| 9370b11684 | |||
| 999bbd3a14 | |||
| 235edd88aa | |||
| b5e074e54c | |||
| 4d7068931a | |||
| d7fb6fddf6 | |||
| 7213bd7131 | |||
| d4aac7cd72 | |||
| 741de4cf94 | |||
| f168ef9989 | |||
| a0de56abb6 | |||
| c201d8bda9 | |||
| d2373d5d6c | |||
| 93fb7d62d8 | |||
| 485298b680 | |||
| 062f0cffad | |||
| ce1c640ce0 | |||
| 5c32058ff3 | |||
| 24b4f9d748 | |||
| 81d7531f1f | |||
| b4a23f720e | |||
| a2f6252b2f | |||
| 6c964fede5 | |||
| a25a8312d8 | |||
| b2c5b75efa | |||
| 2dfa9adc5d | |||
| 88a89213ff | |||
| 8e2238ea09 | |||
| 2007dd26ae | |||
| 31e8f7c525 | |||
| 51f62a8da3 | |||
| 650efc2e96 | |||
| 1787bcfc91 | |||
| 2cc4997d24 | |||
| 934f6cabf6 | |||
| 233cc3905a | |||
| 68dd74c5ab | |||
| 48b590e14b | |||
| 8ab3dac4f2 | |||
| 3fb0cbc030 | |||
| 837a2a3d4b | |||
| e91a4a414c | |||
| 74ccee6619 | |||
| c26b8d4eb8 | |||
| dae9dc4847 | |||
| 89f759566c | |||
| 5dc1ef0b87 | |||
| cd7551031b | |||
| df57bfcd6c | |||
| dfb1f39b57 | |||
| e3e3d92241 | |||
| 1b5f28e99b | |||
| b69bcdcdc4 | |||
| e385f54185 | |||
| 9a4d003ac7 | |||
| d5656eeb65 | |||
| 8edc67b0a9 | |||
| 18b0b7299a | |||
| 09b0e7133d | |||
| 6d08af61a0 | |||
| a7577da768 | |||
| 325fd80687 | |||
| 09626d78ff | |||
| 8f03c6e0db | |||
| 2c2f5d871c | |||
| c599c11e70 | |||
| ef06644799 | |||
| 6769d4dd54 | |||
| f3e7c42425 | |||
| f46bee242c | |||
| d7520f0ae6 | |||
| 44b70eb646 | |||
| 828d4df6f0 | |||
| 467714f33b | |||
| f8696cc8f6 | |||
| 9a7c7ab2d0 | |||
| 40fb3371fa | |||
| 51874a5776 | |||
| 62ce52c8fd | |||
| 2bdb9511bd | |||
| 9a012a53ef | |||
| 0aae0180fb | |||
| 1dd8ef09a6 | |||
| 95032e4710 | |||
| b1351e2dee | |||
| 30c2596512 | |||
| 2b5e4b853c | |||
| 85bcb5874a | |||
| 92788e4cf4 | |||
| 8a698fef71 | |||
| b49ce1713f | |||
| c2b54d92f6 | |||
| f965434022 | |||
| a3ac62596c | |||
| 2faba02d6b | |||
| ee158e1610 | |||
| fa68efb1e7 | |||
| 8c53a4405a | |||
| c32f699105 | |||
| 53aa8f5650 | |||
| 56887f3208 | |||
| 92180bc793 | |||
| 22aa16ab12 | |||
| 526b829a09 | |||
| c44f311c4f | |||
| 9ea5d08ecd | |||
| 35deb1a8e2 | |||
| c7f7c47388 | |||
| cb7dab17cb | |||
| cd419a35fe | |||
| e06168596f | |||
| 4c8197a119 | |||
| 23c10350f3 | |||
| b6e98aed01 | |||
| 00dcba9ddd | |||
| 607cafadbc | |||
| 68dde2359f | |||
| f9dbf41e27 | |||
| 7405760f44 | |||
| 7e4a6b4bcd | |||
| b5791e6f28 | |||
| 00cb58eaf3 | |||
| f961ec4aaf | |||
| 134db72bb7 | |||
| 6fd0b434e2 | |||
| effe21f3eb | |||
| 1118b285d3 | |||
| 912a0031b7 | |||
| a14e192376 | |||
| f8e15e7d09 | |||
| 7b9f9e0628 | |||
| ac8e9ce04f | |||
| cfd8645843 | |||
| 0c068b15c7 | |||
| 30a466aa71 | |||
| 4d94609c44 | |||
| 6b63123ca9 | |||
| 0cc9fb73e1 | |||
| eac4e4b279 | |||
| 6d381f7c0a | |||
| 4fa06aefc6 | |||
| 0e177a44e0 | |||
| afd19de5a9 | |||
| e3fbac9e24 | |||
| a9cf32811c | |||
| 53997ecc79 | |||
| 8e69f3cb89 | |||
| 997ba3a574 | |||
| 8e68ff9321 | |||
| 62761a23e6 | |||
| 404d8b3084 | |||
| 6005ad3d48 | |||
| 035a3ea4ae | |||
| 7ec43bd177 | |||
| a29c66ed74 | |||
| e104b183d8 | |||
| 7e082f232e | |||
| d28bf71f25 | |||
| 5b1a74b6b2 | |||
| eead4db1d2 | |||
| 980fb5e2ab | |||
| 9bcc46d93d | |||
| 22687c1f50 | |||
| ebc6c9b498 | |||
| 630963fa6b | |||
| f674168b8b | |||
| 7e023f2d50 | |||
| 27d02ea6f7 | |||
| 794a7993cb | |||
| 6f16d1cb2c | |||
| 7aa00bff89 | |||
| e046eb1d17 | |||
| ba975ca320 | |||
| fec13b0ec1 | |||
| 100c35c281 | |||
| 8414aff424 | |||
| f225ca3312 | |||
| 8b68e0bfdc | |||
| 6ae97aedc9 | |||
| 960d604013 | |||
| 63bf5f42a1 | |||
| ff80cfd83d | |||
| 99fde2ba85 | |||
| ce0cb913bc | |||
| d99d16e8c3 | |||
| 31743789dc | |||
| 6fd63e920a | |||
| 59cc3e93d6 | |||
| 61a4bb38cd | |||
| b192bc348c | |||
| 6440d0fbf3 | |||
| ee0055b929 | |||
| 24ecc44bac | |||
| 0ae4915a93 | |||
| 4cd777a5e0 | |||
| 65028d4a35 | |||
| caac9d216e | |||
| 057192913c | |||
| f25cbdf43c | |||
| 6da4a9c7bb | |||
| 80ca120088 | |||
| a669946357 | |||
| 7ffc162ea8 | |||
| bcfd7fbbcf | |||
| 486e2e48ea | |||
| 2ddf2ca934 | |||
| 403ec7cf21 | |||
| 29b1a29044 | |||
| b4ab8e095a | |||
| ff4f4d4649 | |||
| 9987ff570b | |||
| cff8235068 | |||
| 9ef132c33b | |||
| ff8269575a | |||
| 7743d952dc | |||
| 944f3c1477 | |||
| 1d3bd02089 | |||
| 38de8e6936 | |||
| 6347fb6636 | |||
| 32e668eb94 | |||
| c51f9ef940 | |||
| 1a91edecae | |||
| c88308cf0e | |||
| 88837fb753 | |||
| d0283ff354 | |||
| f449a7ae2c | |||
| a113b2c394 | |||
| 74851834c0 | |||
| e377bb949a | |||
| c905d3fe21 | |||
| b6e9d235fe | |||
| 6968f7237a | |||
| 4a6c97463f | |||
| 6c912ac960 | |||
| 708cebe7f0 | |||
| 152023e837 | |||
| 0f16e19239 | |||
| 2c38e44e48 | |||
| 82739574b5 | |||
| f78d677ab6 | |||
| e39e2306d6 | |||
| 52229a21cb | |||
| 961f7dea82 | |||
| feeeef71f1 | |||
| 65c4d550cb | |||
| f9b4a8d6e8 | |||
| e11d851aee | |||
| ac81f0248c | |||
| 83bf15a703 | |||
| cc960adbee | |||
| c66c5828ea | |||
| 19387cafab | |||
| 7c0673279b | |||
| 7ce0d71a96 | |||
| dd2542e96c | |||
| 21d60eab7c | |||
| 4d2320ba8b | |||
| a4a74e9844 | |||
| 9588978028 | |||
| 479940b7d0 | |||
| 8cd967803c | |||
| a0e1163fb6 | |||
| 8ccd1ee34a | |||
| ca258c04cb | |||
| 30bd5e2669 | |||
| 38637897ba | |||
| c727c8b684 | |||
| 993d96feef | |||
| b2b26d9c95 | |||
| cba3dd276b | |||
| a47fc75c26 | |||
| 42cfdf246f | |||
| e5c8794b8b | |||
| ac90a873eb | |||
| 5ce68ad7fd | |||
| 099e88516d | |||
| 82a6ad2c10 | |||
| c1a78224cf | |||
| 39f9350697 | |||
| e31081d79d | |||
| f02d282754 | |||
| a89e0bab7d | |||
| 3a90af0bcd | |||
| 53ceb0791f | |||
| 2cd98a0d21 | |||
| a0b10c05e5 | |||
| 04135fa6cd | |||
| 42dc6329e6 | |||
| 9b8ba97f9f | |||
| 7705605b5a | |||
| 414bcb0c73 | |||
| f4710948c4 | |||
| 3f4488c589 | |||
| 9434fff215 | |||
| 695962fae8 | |||
| 8f13c8c3bf | |||
| c1cae51fb5 | |||
| 31d16f6cc2 | |||
| a50ea92c64 | |||
| 5b2ced0119 | |||
| 8a0ba093dd | |||
| fbd8dfe60f | |||
| 60aff22931 | |||
| 5fc7da345d | |||
| fd2c38fbef | |||
| ba245c6c46 | |||
| 496027b589 | |||
| 8bd4f6568b | |||
| 9d7660b409 | |||
| da55499db0 | |||
| 22f8e39b58 | |||
| eba23bbac4 | |||
| 4550535cbb | |||
| 8432fd5ac2 | |||
| 7c948adf88 | |||
| 56b7045c20 | |||
| b1a109a611 | |||
| 7a311a3b66 | |||
| d55b6b9909 | |||
| f4389fb322 | |||
| 331208bec1 | |||
| 7680e5f81d | |||
| 6acf038a84 | |||
| bdf4e386cf | |||
| ad8a34858f | |||
| 162eced7d2 | |||
| bec1f7c26a | |||
| 8771617199 | |||
| 54bc995f0a | |||
| 6c89a81af4 | |||
| 8fa2898ff1 | |||
| 10ca0a6936 | |||
| b3314e97a6 | |||
| 3b9a948045 | |||
| 3781a0f9ad | |||
| e79b289325 | |||
| 6d4c1156d6 | |||
| 3f72c7fcc7 | |||
| d521c84d55 | |||
| 946b070744 | |||
| 4a21dce2b5 | |||
| 5fe7f9fa93 | |||
| 65f34cd823 | |||
| 196e7e072b | |||
| 6f97663174 | |||
| aed7a1818a | |||
| b50d90183e | |||
| 6b06da76cb | |||
| 6ca6788bb7 | |||
| 2e23e61a45 | |||
| 9cdf490bc5 | |||
| cfed671ea3 | |||
| 53ce92b9ca | |||
| 7350a29fec | |||
| 5cc2c62c66 | |||
| 4bc5ed6c76 | |||
| e99a597899 | |||
| 73dde66dbe | |||
| e30c0e7ca3 | |||
| 8fc200c0cc | |||
| 708296ae1b | |||
| fbb5e75e01 | |||
| f327b21557 | |||
| 45b7253f39 | |||
| 05bb655efc | |||
| 8fdfcfb562 | |||
| e7c144eeac | |||
| e98172d72d | |||
| f2d063e7b9 | |||
| a50f26b7f5 | |||
| 69294cf98a | |||
| c397fb6c7a | |||
| 961b0b524e | |||
| 860fc200b0 | |||
| 109a9e3f35 | |||
| 5f971fea6e | |||
| 0d7abe3b9f | |||
| 94fbcd8ebe | |||
| 879d5dd236 | |||
| 34187e047d | |||
| 0ee722f8c3 | |||
| b7d11141e1 | |||
| e9babf3dac | |||
| 0bb81f2e9c | |||
| bea0b050cd | |||
| ce62980d4e | |||
| dc88865908 | |||
| 9fbd931058 | |||
| b0264bdb90 | |||
| 95d6f43cc8 | |||
| 9cb94eb4a9 | |||
| bd0819330d | |||
| 8d9e83fd99 | |||
| be02333529 | |||
| 506f121576 | |||
| ca488cce87 | |||
| 11dc723324 | |||
| dd6ea18901 | |||
| 3369eeb920 | |||
| 9032f49f25 | |||
| fbc6f3f6e8 | |||
| fba883839d | |||
| a93214ea63 | |||
| e6b0fc465b | |||
| 70fbcfee4a | |||
| 0b074d0fae | |||
| d67e4d5b17 | |||
| 891c60d83d | |||
| fe3e49c4eb | |||
| 58306a9d34 | |||
| 41091d9472 | |||
| a4cfb5e1ed | |||
| 51aa59a737 | |||
| 8bedb419a9 | |||
| f56a182b71 | |||
| 317b40ef90 | |||
| e938ece492 | |||
| 02331a612c | |||
| 8317557f70 | |||
| 1bb7a2a295 | |||
| 215ca58d6a | |||
| 12f570a307 | |||
| e4b619ce1a | |||
| 0a286153bb | |||
| 22d59e757d | |||
| 0daa2dbf59 | |||
| 96c2304ae8 | |||
| 343dd2f491 | |||
| 38f35463b7 | |||
| 5573986e8e | |||
| f3367a1b20 | |||
| a3c2f7b0e8 | |||
| 8fbec30998 | |||
| a7466eeb0e | |||
| 8b1e819bf3 | |||
| fe63664164 | |||
| 4598827dcb | |||
| 9afdb05bf4 | |||
| 9569a85cee | |||
| 54721b7c7b | |||
| 91d8bddbd1 | |||
| 80adc87a14 | |||
| 117ad1b65b | |||
| 2229509362 | |||
| 6ef8e87492 | |||
| 0a25083fdb | |||
| 15137d0327 | |||
| 8c9974bc0f | |||
| 079b6c2b50 | |||
| 0924b34a17 | |||
| 754f7a8a39 | |||
| 64bafe1dfe | |||
| c3e456e7e6 | |||
| 57aaeafd2f | |||
| 3c2e1a87e2 | |||
| da95a2d13f | |||
| cc5e05fdeb | |||
| a79c390cca | |||
| 8a56af439c | |||
| f6e581ce54 | |||
| 8953f88780 | |||
| 4b4a98d5e5 | |||
| 7472818d94 | |||
| ad44fe8d3e | |||
| 55e713db0a | |||
| 33322e6638 | |||
| a1792ca0d1 | |||
| ac8c43fe9c | |||
| 4d40ee00e9 | |||
| 06f59ad631 | |||
| 877e0cac03 | |||
| ef67c39910 | |||
| 508710f4d1 | |||
| 3aa3d9cf14 | |||
| c2fedb4c3f | |||
| 03dc65e12d | |||
| b8d62a8068 | |||
| dbc2368a7b | |||
| 54aed421b8 | |||
| d5e8dac1cf | |||
| 96ec8fcba1 | |||
| 0663eb69ed | |||
| 0594d37230 | |||
| 3cc30bcc18 | |||
| 99c1a642a4 | |||
| c60f54e5be | |||
| 483389f2e2 | |||
| c0f2f84285 | |||
| 069d118329 | |||
| a7b1834772 | |||
| 6415dec37a | |||
| 74253e1ddc | |||
| 01b3fb91e5 | |||
| 2dc917e87f | |||
| 0a284a1a10 | |||
| 5c8339e1e8 | |||
| fd37418da2 | |||
| bbfea29c2b | |||
| aa703dc903 | |||
| 8cd80fec8c | |||
| 780882efcf | |||
| c5636143c6 | |||
| ba6218b354 | |||
| 8e32de3ba9 | |||
| e37508fb8f | |||
| b46a717425 | |||
| 7926df0b80 | |||
| 557df666fd | |||
| f91b257f50 | |||
| 28a2d1eb3d | |||
| a0ae1489e5 | |||
| edfb310a59 | |||
| a2312028b9 | |||
| 78f1f69d57 | |||
| e1e33077e8 | |||
| b3e7de010d | |||
| f5b04865f4 | |||
| bf1c6edb76 | |||
| 2ac7fee017 | |||
| 128256e3ab | |||
| a66a7f3e97 | |||
| 20b79f8945 | |||
| 9a877734b2 | |||
| 409c391850 | |||
| 763ff085a6 | |||
| 5b9656374c | |||
| b32014549c | |||
| 9476d212bc | |||
| 000928d34e | |||
| 6829ae5b13 | |||
| f09756443d | |||
| 5512016885 | |||
| 21ecb941fe | |||
| 77e94087cf | |||
| 9ab1f25a47 | |||
| aaab7aefbe | |||
| 5b8599e52d | |||
| 74e0c9ab9b | |||
| dcce83b288 | |||
| f731e7ea36 | |||
| ec30bb89a4 | |||
| 7cd08594f6 | |||
| 2b4531f069 | |||
| 11544a62aa | |||
| 18550387d5 | |||
| efb03e19e6 | |||
| c27d0583d4 | |||
| 0de2b9a1b2 | |||
| 9dc29d7687 | |||
| 72871dbb9a | |||
| 4bda4e4e2b | |||
| 1971c54a50 | |||
| bb77b89da0 | |||
| b336e9a79f | |||
| a2ab21e91c | |||
| 603437e70f | |||
| db3a9a5990 | |||
| 24c7e73b4e | |||
| c053e57068 | |||
| 6d20ec3bea | |||
| c50627ee3e | |||
| b3cd893f93 | |||
| 22d2dbc4e6 | |||
| 2b5d9428b1 | |||
| d6446cb096 | |||
| c34bdc33fb | |||
| dd8547e51c | |||
| aec023f537 | |||
| e101eeeda9 | |||
| f29522269d | |||
| 3c470a6b8b | |||
| 6bc7d711b3 | |||
| 10d5dd3a67 | |||
| d9f1df01eb | |||
| cdeab19673 | |||
| 22ee678136 | |||
| 50a8f13e85 | |||
| 6dec60b6e6 | |||
| ac3a19138a | |||
| 21e8e071d7 | |||
| 57f84a8b4c | |||
| 8a672e70a7 | |||
| 22041bbcc4 | |||
| 5afb459113 | |||
| 91ebac0a00 | |||
| 5fcb1cfd31 | |||
| 3a90fb108c | |||
| 4eeb48f8e0 | |||
| 373d48c8a3 | |||
| 1472875670 | |||
| 74cfb207c1 | |||
| 6a096e7dc7 | |||
| 9788d85ea3 | |||
| 69c0e18685 | |||
| 3cac927348 | |||
| 9081346c40 | |||
| fcfadb0e51 | |||
| 2add57cfed | |||
| c5279ec630 | |||
| b73699fad8 | |||
| b8ebe54e53 | |||
| c3d70e0795 | |||
| 8c4561b8da | |||
| fd421ddd6f | |||
| 9947c01c8e | |||
| a00db4267c | |||
| 36385010f5 | |||
| fa6d082bfd | |||
| 9fab91852a | |||
| b733e6e83c | |||
| ce05bb69dc | |||
| 37aa5934a1 | |||
| 1647fc7edc | |||
| 7b92687397 | |||
| 419e5baf16 | |||
| dc48cd841a | |||
| b0e1776d6d | |||
| 7a7068ee47 | |||
| cbc0ef459b | |||
| a2aabfabd9 | |||
| 822cbd4b43 | |||
| 3c19a9308d | |||
| 32890342ce | |||
| ed2c2a285f | |||
| 18e23bafd9 | |||
| 8b8be2695f | |||
| 96fbf18201 | |||
| c8a57a8fa2 | |||
| b1c2dacab3 | |||
| 65939913b4 | |||
| 08b3a466e8 | |||
| 5aa7c38035 | |||
| 1df5e31706 | |||
| 9f7044aed0 | |||
| 41de53996b | |||
| 9878031cfd | |||
| e3fbcaeb72 | |||
| ca6dd8be5e | |||
| fba0924b1d | |||
| 703ed46d79 | |||
| f7ca6c63c2 | |||
| ad69b9907f | |||
| b9269151a4 | |||
| bfddbb8b40 | |||
| 13a2dc8485 | |||
| 1e51424e8a | |||
| 5b114b43f7 | |||
| 812f5a02d7 | |||
| 19f70dbfbf | |||
| 1c99fb106c | |||
| 71c32e9b48 | |||
| 380a59520b | |||
| 3995355150 | |||
| 8208bcb896 | |||
| d665e855de | |||
| 18b3655c99 | |||
| 6a8d8f34a5 | |||
| b1c1f02132 | |||
| ea93643e6a | |||
| e47e625f73 | |||
| b13fcaf666 | |||
| 9458485e43 | |||
| 0ce9e00ffa | |||
| c778c381b5 | |||
| 0d1fbef751 | |||
| b48bbe08b2 | |||
| cce90cb2b7 | |||
| 07b1281046 | |||
| 3515b99671 | |||
| 6a67c277eb | |||
| 1067dd3011 | |||
| 7cafdc0512 | |||
| 8a57b6bced | |||
| 6f0ed2a091 | |||
| 53abd25116 | |||
| 1ea7826c0e | |||
| 97f4cf48f8 | |||
| 0cde37be50 | |||
| 6aeca54ece | |||
| 124e28578c | |||
| 62c9e500de | |||
| 02cc18ff29 | |||
| ba4566e86d | |||
| 87cb0081ec | |||
| 4a6af93c83 | |||
| a2f0771fd3 | |||
| af564b8ba0 | |||
| adb8be069e | |||
| 7c8746732b | |||
| f506117edb | |||
| 1c5af7c31a | |||
| 3a0125fa1f | |||
| 328cb0a076 | |||
| c3c8441a1d | |||
| fa2a8d7209 | |||
| e3ea97c964 | |||
| 7219ae530e | |||
| 8f8f8854f0 | |||
| 4c6869cd9a | |||
| bc7c0d8624 | |||
| 11dfc817f3 | |||
| dde1a12819 | |||
| 065fd094d1 | |||
| d09351bb10 | |||
| 25d38e030b | |||
| 9ebd10d3f4 | |||
| 8a9b886011 | |||
| 21f0d6b549 | |||
| 3ba927edae | |||
| c4ca0a5a57 | |||
| 406ea4f281 | |||
| 64aa7feabd | |||
| 875f4076ec | |||
| 4643efe6be | |||
| b760137e1d | |||
| 5f56f441bf | |||
| 96a22bfcbb | |||
| 6c59b33fb1 | |||
| dfaf735073 | |||
| 0d2b7bf94d | |||
| 7fcfde7f07 | |||
| b1391d1991 | |||
| 49c8e16410 | |||
| 0e93681589 | |||
| eb55034dfe | |||
| c45bc32d98 | |||
| 6e860b6dc5 | |||
| b732a673dc | |||
| b6b6d6e8d8 | |||
| 23e4895dfc | |||
| 8666c55ca6 | |||
| a3f00c5d5e | |||
| 26c23b30f4 | |||
| a436fd513b | |||
| 3bc34ffd94 | |||
| 533cd8d6df | |||
| cb089dcb52 | |||
| e0329cfdbb | |||
| 239ccc9c40 | |||
| b762fbaf21 | |||
| 0285df5a02 | |||
| 45fb375c41 | |||
| 4a4950fe41 | |||
| 1664fd8bb1 | |||
| 21cdd2bf5d | |||
| 0153f96a20 | |||
| a7a7533190 | |||
| 311380f8cb | |||
| b0f0e53bba | |||
| 004f1e2f66 | |||
| 2fa561f22e | |||
| 81be718674 | |||
| 8162fd1e20 | |||
| 49a1e2f98e | |||
| 684c46369c | |||
| 715c9e3ca9 | |||
| 73edd5b8fd | |||
| 5e5bdf5432 | |||
| 48a3e9bc82 | |||
| f13cfcb83e | |||
| 9c0e8cd15b | |||
| ad2a70ba06 | |||
| 731e03fe5a | |||
| f9d029c8fa | |||
| 7057d00a28 | |||
| 114fab4c70 | |||
| c2edbfae55 | |||
| a92cb66468 | |||
| 535f97ba61 | |||
| 14ebd82dbd | |||
| aea7b08a47 | |||
| 47dcfcbdd4 | |||
| bf3901342c | |||
| e1731d9403 | |||
| b28bcad11b | |||
| a7c71e4c6b | |||
| 1a42693d68 | |||
| e7b60c4d65 | |||
| f95129894d | |||
| c32c71c836 | |||
| 14e1ace552 | |||
| a7fb3a3853 | |||
| 2da4bd5f1a | |||
| 7e76d66184 | |||
| 7764f4a8e3 | |||
| e1094dde08 | |||
| 4894c67196 | |||
| d004c45386 | |||
| 6624f970c0 | |||
| de684dc122 | |||
| 331bdc2245 | |||
| e12ab486a2 | |||
| 9eeee92d36 | |||
| 756d6aa729 | |||
| bddd53d6d2 | |||
| c0a5bdaed9 | |||
| a99cd825ab | |||
| 6426b74770 | |||
| 4f257bf1e6 | |||
| 73a056999c | |||
| 0120ff93bc | |||
| 49638fa533 | |||
| 76510dac8a | |||
| 7a3a7b19e5 | |||
| 24e86d0c59 | |||
| 9b5c2c386a | |||
| d118031ed6 | |||
| 341a89c00d | |||
| df29d25e6b | |||
| 3e196fa7b3 | |||
| 04c792476f | |||
| 005a4a275a | |||
| bdddf597f6 | |||
| bb6921bf9c | |||
| bb63375f1b | |||
| 4f89e5bba9 | |||
| 183428db03 | |||
| fc6d873758 | |||
| 5e2f8d7a42 | |||
| 9b9871cfbb | |||
| f80b6926d3 | |||
| 6dc55fe5ed | |||
| 2d1cda2061 | |||
| a566bcf613 | |||
| f6040dffaf | |||
| 9885a0a6af | |||
| f64d62b01d | |||
| 82075e8e3a | |||
| 5b7c83341b | |||
| 8522905d97 | |||
| 524ed7ccd0 | |||
| fb49aead9b | |||
| 85f5700e4e | |||
| 43b3c093ef | |||
| bd6842d917 | |||
| e8c98c3246 | |||
| dfd7cca0d2 | |||
| af3d99e35f | |||
| f6186965c3 | |||
| 90c2129f44 | |||
| 69e131ee69 | |||
| 28a01f0320 | |||
| 6d0bc5ab1e | |||
| 7af78af1f0 | |||
| 45a717a142 | |||
| cb1ec0a0d9 | |||
| abb1f22057 | |||
| 73efe436a5 | |||
| f41edb23e2 | |||
| 6335a48a53 | |||
| e20aab25ec | |||
| 66bea3942a | |||
| 08acd9c43d | |||
| ff5988f4e0 | |||
| 1bf23374a3 | |||
| 899b429094 | |||
| c47ff44f5e | |||
| 8af0773baf | |||
| 37cbd114de | |||
| 2dbb1cff4a | |||
| 6efcf9c982 | |||
| 0bc34952eb | |||
| f6b48ed02a | |||
| e37c4efc6e | |||
| 22f5bc643c | |||
| 15fd5ce2fa | |||
| 7f782983ca | |||
| 9d628346eb | |||
| bde533a9c7 | |||
| 2fcb75d86d | |||
| aae6846413 | |||
| 5317a0b755 | |||
| 73de721a63 | |||
| d2f5c3621f | |||
| 1818764840 | |||
| d3e5e607a7 | |||
| c1943ea3af | |||
| 2a82c15bf1 | |||
| 87b6fb37d6 | |||
| 21fbe88e1f | |||
| 1f8b9b4bd5 | |||
| fcbed41cc3 | |||
| 216069d0da | |||
| eefa047974 | |||
| d8dad5c9ea | |||
| bf8a68879c | |||
| f3248a4b37 | |||
| d315d012a4 | |||
| bd9bf3693f | |||
| 15daa2e74a | |||
| 74759b05a5 | |||
| 82ce78a17c | |||
| 9af6c6ceef | |||
| 021372cc4c | |||
| b94ab07c2f | |||
| 7605d07bb2 | |||
| ccc5801112 | |||
| 7c72b25ef0 | |||
| 02c2ec3027 | |||
| 65c31fab12 | |||
| b6b68be052 | |||
| 15911c85f6 | |||
| 5a1612fe32 | |||
| bbb7ae156c | |||
| f9b8d1c699 | |||
| 1443b5927a | |||
| 35ef35b5c1 | |||
| 6806537eb3 | |||
| 64de61d15d | |||
| 22b7c8cd8a | |||
| c4d0c49a5f | |||
| 142a5b0dcd | |||
| 25db1e4eca | |||
| 47a48b6832 | |||
| e98309eb75 | |||
| 87051872a7 | |||
| a2aed12dcd | |||
| d8e6e76e89 | |||
| 8c33fdf5f4 | |||
| ad4e511026 | |||
| 4a562d6732 | |||
| a9082e4f79 | |||
| 6278679ffd | |||
| 0474791cf8 | |||
| 69f819e199 | |||
| f32efd5429 | |||
| 22c247a988 | |||
| 35d71682f6 | |||
| 3d6b88a60e | |||
| 26a0803388 | |||
| ae95384dd8 | |||
| 0f0dcf0c5e | |||
| 6f2406b0b6 | |||
| bb24346e04 | |||
| be45ffd8a4 | |||
| f986b0c493 | |||
| c9e87f0548 | |||
| 43468f4d47 | |||
| 91987d6f7a | |||
| 6b7c98bd0f | |||
| b829e80ecb | |||
| 6e38d0f3ab | |||
| 38342b1df5 | |||
| dbd4c2425e | |||
| 49ce85ee3d | |||
| eba378e4a1 | |||
| 442c50ff00 | |||
| d1448adbda | |||
| 5a21b1f353 | |||
| 123a2fb3a8 | |||
| 2f9e2147f5 | |||
| 75c6fc4f02 | |||
| f9e07d6143 | |||
| 1436858347 | |||
| 8030e12ba5 | |||
| a485b923bf | |||
| 0649aca219 | |||
| b210ea79bc | |||
| 68f80b5fe7 | |||
| e95825a42e | |||
| 931712dc46 | |||
| 54e544e03e | |||
| f86b9abf32 | |||
| 9ef7eda33a | |||
| c9e26401fa | |||
| e53f49e9a9 | |||
| 14f6ac9222 | |||
| b8474295af | |||
| 817e85a3e0 | |||
| fb5ce3b87a | |||
| 6fe028b7c5 | |||
| 1cd7f1e38d | |||
| 043fd8b536 | |||
| 669acbb032 | |||
| 086d8f036e | |||
| 394690dcfb | |||
| 398bca92ff | |||
| fb328b1a64 | |||
| 563f667e30 | |||
| c839b64f6a | |||
| 6425fec366 | |||
| d5059840ef | |||
| 65cba212e8 | |||
| 7a69c9c75a | |||
| 4a425cbac1 | |||
| 615169c4ec | |||
| 5cd9dcb844 | |||
| 54c5c88fe6 | |||
| 443250d135 | |||
| 9b5829c16e | |||
| d749aaab69 | |||
| 62df731006 | |||
| d0a0eb9738 | |||
| 66156b8230 | |||
| 5677f73794 | |||
| ef54200db7 | |||
| 7875efbf61 | |||
| 3e128c116e | |||
| 55a3310446 | |||
| fc03be7891 | |||
| b1b00a5055 | |||
| 2920b0fc6d | |||
| a30a55f3b1 | |||
| ecfb18b26a | |||
| 41fa8fa2d2 | |||
| e94e6adf91 | |||
| 7d433f16c4 | |||
| b06d7bf834 | |||
| b784e458cb | |||
| 9d96b18df0 | |||
| ad2ab6eb3e | |||
| f037c9b286 | |||
| 85912985b6 | |||
| 876f51a708 | |||
| f7d29b4a53 | |||
| 06557fe8be | |||
| 2131046427 | |||
| aaf1abc993 | |||
| 413549bcf5 | |||
| 9a799065b3 | |||
| fd2959fa3a | |||
| 07927e032a | |||
| 15bec32bb4 | |||
| e2b7a08c10 | |||
| ef2fc0f99e | |||
| d063596430 | |||
| bd2dc6c670 | |||
| 684399433b | |||
| b62791617c | |||
| e07c2ab868 | |||
| 203755793c | |||
| 883c98e26f | |||
| f5a20a5d06 | |||
| ef7177ebbd | |||
| 3637aad36e | |||
| 77db9686fb | |||
| c326e5a34e | |||
| c23c982593 | |||
| a3d666356c | |||
| 3cdbc2f414 | |||
| b92cdea578 | |||
| 5e629a99af | |||
| 99c4ffa34f | |||
| a7f266c907 | |||
| 57acacd5a7 | |||
| 42fb3cd95e | |||
| 855ed642c3 | |||
| 629503ff73 | |||
| e3a070e3de | |||
| 5b364bca1f | |||
| c5c1426262 | |||
| be18d435a2 | |||
| 7eea6cdb12 | |||
| 824c55b3a4 | |||
| 76913a9fd5 | |||
| 2f44dac14f | |||
| 5569acd95c | |||
| 1d0211d395 | |||
| 06cd0a636e | |||
| 7f7b489a3d | |||
| bb6f4d7633 | |||
| 6e24dff26a | |||
| e372e4e592 | |||
| 9571b0825e | |||
| 90e2cc3d4c | |||
| 0c0820caef | |||
| 9112ca4e29 | |||
| 8203cb9990 | |||
| d5bce978a8 | |||
| ec84bad882 | |||
| b53376a3a4 | |||
| 0ec722bc54 | |||
| 4640b13c66 | |||
| 1704abaf6b | |||
| ab34f0065c | |||
| e8c0a50862 | |||
| b963f69f34 | |||
| 02d8f3cdc8 | |||
| 7ae69accc0 | |||
| 701b89f377 | |||
| 46d45a6923 | |||
| 7fad0c8b41 | |||
| 6e27264c6b | |||
| 5c83c9724f | |||
| d5aff735be | |||
| 98c26df53e | |||
| 2448a9e047 | |||
| b28d391a22 | |||
| c8b92f6067 | |||
| e7cac8acef | |||
| 31b5acc245 | |||
| 6105997299 | |||
| 8c874884fc | |||
| ebfe81e5fd | |||
| 0b7ca094e4 | |||
| 72802a5972 | |||
| b1f3935c5b | |||
| dbd53af369 | |||
| b09fe0e50e | |||
| fae9000304 | |||
| 8fd07bcd51 | |||
| 6addc7a35d | |||
| 477230c82e | |||
| d1737199ed | |||
| 61101d82d9 | |||
| cebb948da2 | |||
| c61c4b71b2 | |||
| 84f31ed45d | |||
| 8a81e317d6 | |||
| 224d9a752f | |||
| 0db34e4b85 | |||
| 8a9b9832fd | |||
| f66625be67 | |||
| 6825bd7e75 | |||
| 18515a4e3b | |||
| 839b9c9271 | |||
| dd9ed85e22 | |||
| e96c88e914 | |||
| 6c1410f7f5 | |||
| f92450d8b3 | |||
| c133979b8e | |||
| a9269cee29 | |||
| cf42ede92c | |||
| 958a480e53 | |||
| 62151a751d | |||
| f1ab9df2ee | |||
| a42650c065 | |||
| bdad3730f7 | |||
| a5835cecbf | |||
| b19620b324 | |||
| cd6dec49c0 | |||
| d350654aee | |||
| 6877578bbc | |||
| 10693fddfa | |||
| f3682b6149 | |||
| 056ca0c68e | |||
| 25f7a8e406 | |||
| 1f1c267b6c | |||
| eab1dc927b | |||
| fc94ea1ced | |||
| 67d2cf8f30 | |||
| 2c85b84cbc | |||
| 09a25ea7b7 | |||
| 260a63ca73 | |||
| d3f70ea340 | |||
| ceebd35ef7 | |||
| 91b6fe1af3 | |||
| 9803f68522 | |||
| 47b7469a60 | |||
| 4c204707fd | |||
| 8fd6be0827 | |||
| c06e0bfef9 | |||
| 8625a9dbb3 | |||
| 2b71b659e0 | |||
| 0320ac43cb | |||
| 111c7d4026 | |||
| 62c3df0ca3 | |||
| ae011663e8 | |||
| 12591cd241 | |||
| 0499e1c4b0 | |||
| 3158f2d12e | |||
| 51f7f9aaa3 | |||
| 6e359c586e | |||
| 699a24f7e5 | |||
| 5fa3665074 | |||
| 3b7781835e | |||
| 5fe1b46bfd | |||
| f65cce4317 | |||
| 27d0d22e5d | |||
| 407c9ddcbf | |||
| d90d0c8931 | |||
| 216a471bbb | |||
| a7b7860e0e | |||
| d703daa480 | |||
| dc8fdcb9c9 | |||
| 7a6c4e438e | |||
| c468b4e2a8 | |||
| b04956a676 | |||
| 518f6e4d39 | |||
| 483b226cc1 | |||
| 13151cbb2b | |||
| 8e02660a0d | |||
| 16feef2a2c | |||
| 66ff17e452 | |||
| 4c5edacae2 | |||
| 8b4d0255b7 | |||
| 58c129f94a | |||
| 74040b457b | |||
| c259a8ea38 | |||
| 2d51e42305 | |||
| 5e3bfd2148 | |||
| 8b0ab6ead6 | |||
| b1b0aadabf | |||
| ac7d9c449a | |||
| 1346561b9d | |||
| 4bc52897b2 | |||
| 035791669e | |||
| 6017b63a06 | |||
| 11d04279c8 | |||
| 0448728228 | |||
| 12047702f5 | |||
| fb1492f531 | |||
| d14ead7bec | |||
| 05444a0f6a | |||
| b3c54ec81e | |||
| 6c11dbffd5 | |||
| 3b5dbf9046 | |||
| 09c733677a | |||
| 8d6558b236 | |||
| 67f4ba154a | |||
| 440ad20c1d | |||
| 31fba6f434 | |||
| 280442e533 | |||
| 850a945a18 | |||
| 46f9049fb4 | |||
| 58266c9e2c | |||
| d1e775313d | |||
| a65df1e67b | |||
| e700be8cd6 | |||
| 3fdd574f54 | |||
| e0f4dd6027 | |||
| de02eca467 | |||
| 50dbd2cacc | |||
| c2f9cc5824 | |||
| c7f7e67a10 | |||
| 628042e65e | |||
| cde7eeb660 | |||
| 6305b206e1 | |||
| d85da9236e | |||
| 9800760cb3 | |||
| 5c087bdcad | |||
| a547bf517d | |||
| b984bf8d1a | |||
| 18f9cccfa7 | |||
| fb6ab1cca2 | |||
| 56c57e2c53 | |||
| a6057c35cc | |||
| 901887e6bf | |||
| ee54643004 | |||
| 0a17acdb34 | |||
| 72e5212842 | |||
| 714283fae2 | |||
| 3423028713 | |||
| 9d062b37d7 | |||
| 4636d3a9c3 | |||
| c95ede35c1 | |||
| 7415e1aa56 | |||
| f350953a19 | |||
| 958bba5b42 | |||
| 0f2b95b497 | |||
| d07089ceac | |||
| 47dfa62384 | |||
| 3a3265cf88 | |||
| 0ff931dc76 | |||
| 4d708cebe9 | |||
| 4d7c8e3bb8 | |||
| 8cde38404d | |||
| fe7bf6cbbc | |||
| 8b4eb2304b | |||
| ae029191a3 | |||
| bfedea9bad | |||
| 7777d3b43a | |||
| 9ed4fc9687 | |||
| b49b39e99d | |||
| cd3a2de5a3 | |||
| 6e8960ccdd | |||
| e05f3d5d84 | |||
| 94c6cb1323 | |||
| 3f81cd1b22 | |||
| 8da0f4c5bb | |||
| 9acf1024e4 | |||
| b21d3f9b82 | |||
| 2bbf380262 | |||
| f678bcf7ba | |||
| a0f06eac2a | |||
| 83fe1a2732 | |||
| 5c98223c89 | |||
| 663a0b7783 | |||
| 6efe4d1df6 | |||
| fb17f97cf3 | |||
| 6b65ba1551 | |||
| 9202c6e26a | |||
| 59a5456091 | |||
| 8bfe972bab | |||
| fd6622458b | |||
| 8a08861dd9 | |||
| 82dcfd4e10 | |||
| b66d7dc708 | |||
| eebdd2b31d | |||
| b94733ab31 | |||
| 7f2c90a0ed | |||
| 84bb7d05a9 | |||
| 98a84d88e2 | |||
| e470268c7c | |||
| 3a6cd4f73d | |||
| 6ea150fd68 | |||
| a7188bc9d0 | |||
| e1e9ddd4a4 | |||
| a1dd08f2e6 | |||
| d136ac0596 | |||
| c33a237067 | |||
| eb7d3da994 | |||
| 37134e42d4 | |||
| 626a4efaad | |||
| 0c1f8b4e0f | |||
| 857674c3a0 | |||
| 15a75bd79b | |||
| 74887c7372 | |||
| 31188e9327 | |||
| ee6d96eb46 | |||
| 11fe2fd79a | |||
| c2863cc6ef | |||
| d6d01067a0 | |||
| 1d3b18c3f4 | |||
| a15b6f21b8 | |||
| 689179bf18 | |||
| bf749eec61 | |||
| d0f4cc89a5 | |||
| d65debb6bc | |||
| 72daccd468 | |||
| b363400587 | |||
| 6b41f941b6 | |||
| b9f5f9ba3f | |||
| c8ffa59d28 | |||
| 1141187bf2 | |||
| 52aeebebea | |||
| e101384aa4 | |||
| 71f02adfca | |||
| 9de26531e4 | |||
| fadc46b906 | |||
| b1d98febfd | |||
| 1828fb212a | |||
| c97f50e274 | |||
| be92046dfd | |||
| 990fc415f7 | |||
| d8daabae9b | |||
| 84fe4fd156 | |||
| 747d475e76 | |||
| 095b518802 | |||
| 0319ae756a | |||
| 11c7ecb5cf | |||
| 422c396d73 | |||
| ffd57fde90 | |||
| a451d1cb8d | |||
| 14cf8f1b22 | |||
| 5996c8c4d5 | |||
| 21885f9457 | |||
| 6ac48aff46 | |||
| 85ff76e7b0 | |||
| e47a31f9fc | |||
| 517fcd423d | |||
| b780359598 | |||
| aa8b9572b9 | |||
| 8ca14e6267 | |||
| 876e1a91b2 | |||
| 0b7989aa4b | |||
| 2278fc8f47 | |||
| a91f353621 | |||
| cdb1b48ad9 | |||
| a24037bfec | |||
| 2d0f30f062 | |||
| cea2ca8c8e | |||
| f713436dd0 | |||
| b923a62425 | |||
| 67fce4a5b3 | |||
| eaa65b7ade | |||
| 820d94447c | |||
| ed20134a7b | |||
| d19cbc81b5 | |||
| 1fd7946dce | |||
| 027ff0f3a8 | |||
| 8fa80874a6 | |||
| 430669cfad | |||
| 54b561898f | |||
| 65c104a589 | |||
| 0a0416b6ea | |||
| 441babdc41 | |||
| 1bf1fafc86 | |||
| 50d58e9b2d | |||
| e64b9f6751 | |||
| d67a846ec4 | |||
| ca2a1c3f60 | |||
| 93fbb228bf | |||
| 3683673fb0 | |||
| f37a5b6dae | |||
| 31b0decd46 | |||
| eb561e1c05 | |||
| 54c9ecff5b | |||
| edcd72585d | |||
| 1a17fc17bb | |||
| ddad231921 | |||
| e73894fa50 | |||
| 03b94f907f | |||
| 3fa7218c44 | |||
| 0f591d245d | |||
| 1b02e046c2 | |||
| d08e3cc895 | |||
| d98116559b | |||
| 71c95ad0d0 | |||
| 5c1a4ba5f9 | |||
| 698862ec5d | |||
| b4ef5ff294 | |||
| 3db658e51e | |||
| 5a9f7516d6 | |||
| 3039fd4519 | |||
| 095fc0561d | |||
| beb1924437 | |||
| c8e1154f1e | |||
| b204c2dbec | |||
| b22b39de96 | |||
| c242e6c391 | |||
| e05205756f | |||
| d03b244fcd | |||
| 5ef679d8f1 | |||
| d33c527e39 | |||
| 7bc95c47a3 | |||
| 475a88b555 | |||
| 9815dac48f | |||
| 1ece3d1dfe | |||
| 2146ed4033 | |||
| 52b88b52f0 | |||
| ebd4388cca | |||
| 57fd02ee57 | |||
| 0333412148 | |||
| 1c85652cff | |||
| e0086c1be7 | |||
| b29e159604 | |||
| 7883e55da2 | |||
| b197623ed2 | |||
| a15a2556c3 | |||
| 14d29b77ae | |||
| a2514ffeed | |||
| f1bbb7fef5 | |||
| 72394a8319 | |||
| 1cd8e1d8b6 | |||
| 62cd918061 | |||
| 6a04067514 | |||
| 49b3908635 | |||
| 75faef888e | |||
| b67d97b1ba | |||
| b8943fdf19 | |||
| f93183f66e | |||
| 2937711390 | |||
| aa56c6d51d | |||
| 27417459fb | |||
| 5b8fe2e89a | |||
| acc9c033ed | |||
| 8528b265a9 | |||
| 44250f1a52 | |||
| f7560670d9 | |||
| 3891885800 | |||
| b882310e2b | |||
| de0b43de32 | |||
| 48152a56ac | |||
| 29dd7f1d68 | |||
| 6423e4c767 | |||
| 1dd8f0e8f3 | |||
| 2fa35def2c | |||
| 34167c51d5 | |||
| a5f8af4efb | |||
| 5a218f38a1 | |||
| e57e946206 | |||
| b4f71362e9 | |||
| ed37b7a9d5 | |||
| 6511021fbe | |||
| 6197ba851b | |||
| 3ae1f9d852 | |||
| 0db1930f48 | |||
| 89db3fdb5d | |||
| 80fc3a8a52 | |||
| 988a2e8fed | |||
| 2433698372 | |||
| 5d7e8f79ed | |||
| bad229e16e | |||
| d37e514733 | |||
| c73ea27ed7 | |||
| 0159b56717 | |||
| 9e6cc847f8 | |||
| 709eb283d9 | |||
| 76dde82b41 | |||
| 939c0100a6 | |||
| 2d60bf8c50 | |||
| 37e20f6ef2 | |||
| 76905b7a67 | |||
| a469e6768d | |||
| 2fc182d8e6 | |||
| a2cbeaa9e6 | |||
| 444ff20bc5 | |||
| 20ef5e7a6a | |||
| c233c8e329 | |||
| e06127566d | |||
| dfe73629a3 | |||
| b03dd1af17 | |||
| 4bc367c490 | |||
| 3eb2d086b2 | |||
| 70986b6e6e | |||
| 8edc2faaa9 | |||
| ebe395788b | |||
| 12fd6678ee | |||
| 90d35b70b4 | |||
| 9f71369b67 | |||
| 04ae9058ed | |||
| a30cfdd88f | |||
| 1bae32dc96 | |||
| 932d2c3c62 | |||
| 52f4124678 | |||
| 8d8d07ac5c | |||
| 44735be38e | |||
| 1ef1b2ba50 | |||
| 6fdbd778d5 | |||
| 419f351df3 | |||
| 180d6b30ca | |||
| 3fd9059b4e | |||
| a713aee3d5 | |||
| a9f5b58a01 | |||
| d882ba2cb4 | |||
| 90e37a8745 | |||
| 6086f45d25 | |||
| d6351879f3 | |||
| 5655272f5a | |||
| 9b35c72349 | |||
| 98cffbce03 | |||
| 1cd875de1e | |||
| 5a8df7efb3 | |||
| c84e2939e4 | |||
| 641ab24aec | |||
| 71133105d7 | |||
| 625677b189 | |||
| 76943ac05e | |||
| 87cbd41265 | |||
| be92cf5959 | |||
| cc1d8f0057 | |||
| 1f1dcdce65 | |||
| 98a67a3776 | |||
| 9b1e70e4f9 | |||
| 09d4f8cd0f | |||
| 53cbc020b9 | |||
| 63fc6ba2cd | |||
| ce53d7f6c2 | |||
| fe8eed963e | |||
| 97eb7dbf5f | |||
| 59f877fc64 | |||
| f96fe9773c | |||
| 04948b4d55 | |||
| 98ba622679 | |||
| 08103870a5 | |||
| 993e586855 | |||
| 58ec835af0 | |||
| 6aea950d74 | |||
| 7198be5be9 | |||
| 3661aaf8a1 | |||
| a22b4adf4c | |||
| b7bb122be8 | |||
| 8441a3bf5f | |||
| 853c4de75a | |||
| 3597af789e | |||
| 4c9cac0b47 | |||
| 1a0b68498b | |||
| 5246e3be84 | |||
| 8a07000e58 | |||
| 3bb82ef60d | |||
| c8a221a9a7 | |||
| 91f45c4aa6 | |||
| 7c5e4da90c | |||
| d6bc141bd1 | |||
| 7ac64ad24a | |||
| 14e52f29b0 | |||
| 344ae9f84e | |||
| f7db12c7ef | |||
| 962d1f1a71 | |||
| 6d76db9d6c | |||
| 00857f8f59 | |||
| 66239f30ce | |||
| bf89f79694 | |||
| ce299b47ea | |||
| 6dc7109a9f | |||
| bdcb485740 | |||
| e32b948a49 | |||
| 4fe9cbb973 | |||
| 5b242f1d11 | |||
| 34d28dd79f | |||
| 6eef9b4a23 | |||
| 5f1999cc71 | |||
| 40a2c6b882 | |||
| 7ba281728f | |||
| 7b7356f04c | |||
| bbc312fce6 | |||
| 1b0dfb0f58 | |||
| 7260241511 | |||
| 3b1a9b9fdf | |||
| 52769e1e71 | |||
| 72afc2727a | |||
| 808739867c | |||
| 752e18e795 | |||
| 76d822bf1e | |||
| ddeca9f12a | |||
| 19d0340ddf | |||
| 21251d8c22 | |||
| 1f3db03bf0 | |||
| 944c62daf4 | |||
| 9547b7d0e9 | |||
| 76c4ea7682 | |||
| 808ecfe0f2 | |||
| 2894dd4d1a | |||
| 797fa7f97b | |||
| fd8750e959 | |||
| 7be65f66b8 | |||
| 4f5d38a4b1 | |||
| 7e73fc2870 | |||
| d2c9a9e395 | |||
| 0d49b365ff | |||
| 7721595aa9 | |||
| fd6f6fc8df | |||
| 4fb47cd568 | |||
| ecc932d5dd | |||
| b57fbff7c1 | |||
| 4892a766a8 | |||
| 0303cd8625 | |||
| d765b89a63 | |||
| 6e4acf0504 | |||
| 71954faa3a | |||
| 6d22e74d11 | |||
| dc92bb4646 | |||
| 0f0e154315 | |||
| 136d41775f | |||
| ec77d28e62 | |||
| 86420a1f46 | |||
| 7dd8b6c8ed | |||
| 8afa6fefd8 | |||
| 533c9d4fe3 | |||
| a35ef155fc | |||
| 8dd3c41b2a | |||
| 4523da6543 | |||
| ce8456a1a9 | |||
| 1673778633 | |||
| 9ce1884732 | |||
| 23b329b9df | |||
| 0c34e51a75 | |||
| 1633b30979 | |||
| 630dabf4b9 | |||
| fc6c794972 | |||
| 2e33b99c6b | |||
| 3b7292b637 | |||
| e4f469ae7a | |||
| c921dc75c7 | |||
| 86d543d0f6 | |||
| e4e90b53c1 | |||
| 58d776daa0 | |||
| f6b2e89109 | |||
| ac85c2af76 | |||
| 5aba2aedb3 | |||
| bd77f1df4c | |||
| a8332efa94 | |||
| 3dbef72dc7 | |||
| 2d16e74f38 | |||
| de5070446d | |||
| 374abd1e7d | |||
| 0506d9e83d | |||
| bd3dfad8b9 | |||
| 9fff315555 | |||
| 07b6dce1a5 | |||
| 18fb86b7be | |||
| 58a8275e84 | |||
| 196fab6834 | |||
| 85fc7cea97 | |||
| 328d660106 | |||
| c68910005b | |||
| c79bcc8838 | |||
| 0fe58dbb34 | |||
| 6cb2f56395 | |||
| 59e33b3b21 | |||
| 0e3c92c027 | |||
| db7a9b2c37 | |||
| 44097faec1 | |||
| ff5fca76ab | |||
| bf3da5081f | |||
| 5532982857 | |||
| 783dd875f7 | |||
| 97112c69be | |||
| b0b573052a | |||
| 2939000342 | |||
| 41e1654f9a | |||
| e3cb0278ce | |||
| 0c81f1bdb3 | |||
| 6220875803 | |||
| afd4279cd8 | |||
| 0c8dd8046a | |||
| 3c4ef4338f | |||
| 64cf887b28 | |||
| 927a879052 | |||
| dfe0c96b87 | |||
| e856e10ac2 | |||
| 6d6a731d6d | |||
| 928feb0889 | |||
| b3febe2d24 | |||
| b6b26dba87 | |||
| 5c034e26bd | |||
| cef0fb1434 | |||
| 158d0e26a2 | |||
| 78385bfbeb | |||
| 2a13cc28f2 | |||
| 4d761fda81 | |||
| 4bdf41a6c7 | |||
| 3c605c93fe | |||
| 121f18a443 | |||
| 538aeef27a | |||
| be0d2537b7 | |||
| 3307aa1260 | |||
| 57cfdfd8fb | |||
| dc6733dacc | |||
| f696a221af | |||
| ed5b67720c | |||
| 2aac50571d | |||
| 45edd27ad7 | |||
| c302d1cfc8 | |||
| 6287e8c571 | |||
| f69a98ce49 | |||
| d44f3526dc | |||
| 41b633f5ea | |||
| 4f1ff9c4d9 | |||
| 86bb48792c | |||
| 94dbb4a427 | |||
| 048a46ec2a | |||
| 1480340830 | |||
| 877bd95fa3 | |||
| 8ea6fb368d | |||
| 5fd5ddea23 | |||
| b04c0697e1 | |||
| 50a8ba6a6f | |||
| 334f1ed45a | |||
| 20c89ebbb3 | |||
| 6f56ba80b3 | |||
| 6e84283c66 | |||
| 9d6fddcfdf | |||
| a83105df9d | |||
| 9528b55c25 | |||
| 749ce107ee | |||
| b2a67834ac | |||
| aec2aa3497 | |||
| c7dcbfd6c1 | |||
| ff12080ff5 | |||
| 0b6175b742 | |||
| cf49da387b | |||
| ac714e7e3d | |||
| 79fb79b71c | |||
| 98874c3baf | |||
| d89f6af6c4 | |||
| d4bca00df9 | |||
| 2c68a19dfd | |||
| 9e5853ecc0 | |||
| 124544d834 | |||
| b910904fa6 | |||
| eee1ce305c | |||
| 5c61c3ccdc | |||
| fb8d512f58 | |||
| a0fb0c1835 | |||
| e152b2a975 | |||
| a71629d4dd | |||
| c22f3ca7a8 | |||
| 4a92134235 | |||
| 6b9fd256e1 | |||
| d6132b854f | |||
| ff9a74b91f | |||
| b579163802 | |||
| 87f0c8e7e8 | |||
| bb855499e1 | |||
| 96bfa77856 | |||
| 8e997eba4a | |||
| 228c6686f8 | |||
| 52861d3aea | |||
| cc26911c46 | |||
| 7776d064cf | |||
| 2d9b5a65f1 | |||
| c240da6568 | |||
| 157272dc5b | |||
| 9065274d02 | |||
| f4c56026a2 | |||
| 37e3f5de10 | |||
| 5ea629beb2 | |||
| 240164560f | |||
| cf52691959 | |||
| 10e75116ef | |||
| f649968c69 | |||
| 5ce1448049 | |||
| bcedc2b0d9 | |||
| 8e4a45ec41 | |||
| dec942beb6 | |||
| d4e0f13bb3 | |||
| 1f28a3bb80 | |||
| 3a1d3a7952 | |||
| a9f1ad7924 | |||
| 929b9e164e | |||
| 97376f6e8f | |||
| 92a0a59de2 | |||
| cd18599e7b | |||
| 1f22a16b15 | |||
| 433b6fa8fe | |||
| d7cd857c7c | |||
| 99fbfe2421 | |||
| b1b6264bea | |||
| 1fd72d5aea | |||
| 18dffb26e7 | |||
| edba7c987b | |||
| b737c83a66 | |||
| 97a6322de1 | |||
| 037fe4afdc | |||
| afbb63a197 | |||
| 8902561f3c | |||
| a67116b5bc | |||
| 0f7aa4125f | |||
| b62a5c954c | |||
| b8cdf060c8 | |||
| 9fb937986e | |||
| 2c48f6a02b | |||
| 4155c5b695 | |||
| 471467d310 | |||
| c54c13831a | |||
| ae4ee95d25 | |||
| a2e037f0ec | |||
| e9055e9ef7 | |||
| d350b666ff | |||
| 21831b3fe2 | |||
| 895357607a | |||
| ac240a8477 | |||
| bf38c0c0d1 | |||
| 67cf15d036 | |||
| 701a82642b | |||
| 21fe14201f | |||
| 48640b1de2 | |||
| 5682685c80 | |||
| 9c025b8cce | |||
| eef9f13360 | |||
| fa9b361a3d | |||
| 49862ba347 | |||
| ee2afcf70b | |||
| c7d535c648 | |||
| 9986e103cf | |||
| c5b3666089 | |||
| d265fe7f9e | |||
| 91e6af4470 | |||
| b940fe8fca | |||
| 73fe2e95fe | |||
| 316c492842 | |||
| 74418b542a | |||
| 172e63dbb6 | |||
| 21bf5b4db7 | |||
| a406bb0288 | |||
| 1823ab6808 | |||
| 8eec49304d | |||
| ecdc2f2f5f | |||
| 6a6c772ff2 | |||
| e178c55bc3 | |||
| 2c137c0d04 | |||
| 1d35f2b58f | |||
| 638c57e466 | |||
| 5e4213b3be | |||
| 102295f58a | |||
| a0d14f8ff7 | |||
| e0b0a351c6 | |||
| fcd4b3ba9b | |||
| 1d2ff46a89 | |||
| 8f7c739328 | |||
| 1beea3daba | |||
| 1ffd063939 | |||
| 3d94c38ec4 | |||
| f4af2d3cdc | |||
| b57e7321e7 | |||
| e93867488b | |||
| c08790edd2 | |||
| a46baddbc4 | |||
| 3bd9615d0e | |||
| 2871cb5775 | |||
| a6e0ec4e6f | |||
| e956369c4e | |||
| 76f950c663 | |||
| d774a3309b | |||
| b3edb25377 | |||
| 026b87e39b | |||
| 53a816b17a | |||
| 043aaa792d | |||
| aad9cb208a | |||
| edf081c6a2 | |||
| fd349103e8 | |||
| 10b49eb4fb | |||
| 3856d078d2 | |||
| 6b4cb35f4f | |||
| e6eab2091f | |||
| 3cdb609cca | |||
| d6a7f62ff5 | |||
| 72f170f5d2 | |||
| 824d52a82b | |||
| 067ebab9d8 | |||
| 6be6c0d2e3 | |||
| aa874010e2 | |||
| 8ec888d13d | |||
| 916f274c83 | |||
| 7ac53c07af | |||
| bc72e4226e | |||
| 5e0776e96a | |||
| 2f1ef02d35 | |||
| db8442584e | |||
| d46cf50760 | |||
| 0357121d17 | |||
| aff236e20e | |||
| cbd70d26b5 | |||
| 5e763b71dc | |||
| 7e4e7a66af | |||
| 906947a285 | |||
| bfc70bc74e | |||
| 6b4f833a12 | |||
| 426c902b87 | |||
| e4b51235f8 | |||
| 4c6498d726 | |||
| 0a3b1ad4eb | |||
| f23f442d33 | |||
| e465c3587b | |||
| 7109b6d414 | |||
| 8c97f3e9bc | |||
| 3795b2c8ba | |||
| 7725425e05 | |||
| b2f4948bbe | |||
| f802d2ba83 | |||
| ce8548a1a2 | |||
| 490dec981a | |||
| 39fd7b0b3b | |||
| e83930333b | |||
| b0d70a0e5e | |||
| ff5a5c1ee0 | |||
| 290a53d735 | |||
| 2393a13f86 | |||
| 7d8c8de827 | |||
| 3faef829c5 | |||
| 7560fb6f9a | |||
| 2fddcc6a11 | |||
| 69bf39f42e | |||
| f4d5c861f3 | |||
| 564a0afae1 | |||
| 1e332f0eb1 | |||
| cab8d3d568 | |||
| be8c4cb24a | |||
| 65166e4ce4 | |||
| 8249cd4406 | |||
| c6ecaf68ed | |||
| d3f89fa6e3 | |||
| ce8397f7d9 | |||
| cae9aeca00 | |||
| f939d1c183 | |||
| 242d06274a | |||
| 957e3ed729 | |||
| ed02ee4ef4 | |||
| ba9691a0ad | |||
| e7eb94de6b | |||
| b6eb8dff64 | |||
| 7da9e3a6f8 | |||
| 876970baea | |||
| e68e76e143 | |||
| 2bc7ca2d34 | |||
| e94eb9af10 | |||
| 3018b21ab8 | |||
| 0b605c3383 | |||
| e7ac1ea54c | |||
| 5ac6d91525 | |||
| 1cd6713e24 | |||
| 785b429737 | |||
| 4aecd8d039 | |||
| 1b339ea062 | |||
| 236ef03dbd | |||
| 53cc561048 | |||
| bb4b143f3b | |||
| 3af41cd37d | |||
| 7e32a17742 | |||
| 6c265534a4 | |||
| 1d42133d44 | |||
| df911c9b9e | |||
| a6f40dd574 | |||
| 688215e787 | |||
| 1cfa2e04bc | |||
| b4f6901903 | |||
| 0149382cdc | |||
| 697c9973a7 | |||
| 788fd3df81 | |||
| 996cac5fed | |||
| 0a8b78cb84 | |||
| b4eb74f5ff | |||
| 57d1f31054 | |||
| 9f02f51b87 | |||
| 911a17b149 | |||
| 3d969bd2b4 | |||
| f800cee4fa | |||
| b49fc33cb3 | |||
| 00e235a1ee | |||
| 37a6b2da67 | |||
| 913e977c8d | |||
| c2ddcb3b40 | |||
| ab9544c0d3 | |||
| 4bfe849409 | |||
| 3bdb92fcad | |||
| cf9e3069f2 | |||
| ed0cbfb31e | |||
| 32b2f6117e | |||
| ae92521310 | |||
| 5802df4365 | |||
| c1901f4e12 | |||
| 8d98282afd | |||
| dd839bf295 | |||
| 3af6073576 | |||
| 2518af5f9e | |||
| 7b793d84c8 | |||
| af9bc7ea7d | |||
| ac055b09e9 | |||
| df42914da6 | |||
| 2471bdda00 | |||
| c7e01b139d | |||
| 9d80ff5a05 | |||
| 39b3941892 | |||
| b311abed31 | |||
| ce667ddae0 | |||
| 0fee993a4b | |||
| 0ea5c9d8e8 | |||
| 63ac260bd5 | |||
| a01a39b153 | |||
| f9a4ad7904 | |||
| e60b67d246 | |||
| 9004d69c6f | |||
| 8856a2d77b | |||
| 54a061bdda | |||
| 65b4b100a8 | |||
| 7cc9286e0f | |||
| 2f25639ea0 | |||
| 2070c215a2 | |||
| 94b98222c2 | |||
| 9c605ad153 | |||
| b7c7e59dac | |||
| 767c1436d3 | |||
| 699cf6ff45 | |||
| 9201870f6c | |||
| 6722f58668 | |||
| 7b9b7cef11 | |||
| 7d4fce09dc | |||
| 2075501d86 | |||
| bd099f5e71 | |||
| baf257adcb | |||
| 4fd1986885 | |||
| e1afac9439 | |||
| 580d9db85e | |||
| 42e2fd35d8 | |||
| 1a40c7c27c | |||
| f3bec41eb9 | |||
| 825634d24e | |||
| cb097e6b0a | |||
| 1cfb03fb74 | |||
| f293df647c | |||
| 10522438b7 | |||
| e2e5bd6f19 | |||
| cd7a0a9757 | |||
| b3eda248a3 | |||
| 95b51c48be | |||
| 486888f595 | |||
| 17ab8145b5 | |||
| b3ebc69034 | |||
| 761dde2f1b | |||
| 2bb6a3f4d0 | |||
| e83e947ca3 | |||
| 73733a8fb9 | |||
| ce6c23a360 | |||
| 99d8e6a30f | |||
| 2fa1d8ac48 | |||
| ca7e425ce8 | |||
| 8b9a19eef1 | |||
| 7f629df4d5 | |||
| 98ddc3596c | |||
| 5d23be6242 | |||
| d15d3a524b | |||
| 1e1d9acb1b | |||
| 55ee94bed0 | |||
| d228d29944 | |||
| 013cc66d8e | |||
| c7ed6eee5e | |||
| 8082d1fed6 | |||
| d2a10dbe69 | |||
| 14645142db | |||
| f34b2ef90b | |||
| ce894665a8 | |||
| 0d00f3a55b | |||
| 48ff373ff7 | |||
| e9efee0e64 | |||
| 4b3e7aee0b | |||
| dd53b287f2 | |||
| 21526efe51 | |||
| 7413045f0e | |||
| d76c508566 | |||
| 8fb46de5e4 | |||
| af1944f28d | |||
| 214ea14f29 | |||
| 5fb420c703 | |||
| 2420f6c000 | |||
| b0d7332a0c | |||
| f71b56a5d0 | |||
| d55efc791f | |||
| f63645546d | |||
| e2dd3e3587 | |||
| 27ab780317 | |||
| e2d4d097e7 | |||
| ac8cb6ba0d | |||
| 4ce81fd07f | |||
| df9eeb7f8f | |||
| 31c4fdbf79 | |||
| 48e367ff7d | |||
| fd02492cb7 | |||
| addfa35d93 | |||
| 5afdc56796 | |||
| fb1c333a83 | |||
| c3e1da8e04 | |||
| 20a753e2e5 | |||
| 3a398775fb | |||
| 61a7434379 | |||
| 09f5e29327 | |||
| 29edb4ccfe | |||
| 197d6fb644 | |||
| d4e565e595 | |||
| 1fce2b180f | |||
| be6ccd129d | |||
| f7cecf0945 | |||
| 7b2198f7e5 | |||
| 52221db7ef | |||
| befbf48563 | |||
| 56a61bab56 | |||
| d480022711 | |||
| f1abb92f0c | |||
| 5792be71fa | |||
| c2630bb3a3 | |||
| 5e3010d455 | |||
| ccbf65c8e8 | |||
| 9d07cde385 | |||
| 464b9d7c80 | |||
| 5c81d0d89a | |||
| 62cd643868 | |||
| c0bf02b8b2 | |||
| 1b7dd70f72 | |||
| 372a08be49 | |||
| fd46a1c3b3 | |||
| 5aae7178ad | |||
| dea8220eee | |||
| a4be0b88f6 | |||
| d8101573be | |||
| 41cdb357bb | |||
| 38caddffe7 | |||
| 80fe166902 | |||
| 0e26f983d6 | |||
| fc08fcab52 | |||
| 77dc99e71d | |||
| 5041bfcb5c | |||
| 5be76856bd | |||
| 2a3f5e1ad1 | |||
| f8650a3493 | |||
| 90a52a29c5 | |||
| 8859c92f80 | |||
| 01e5632949 | |||
| 18a4276e25 | |||
| c06032f35f | |||
| 95a6b2c991 | |||
| ee28f6caaa | |||
| 30c9e50701 | |||
| 9aadd725d2 | |||
| 6cfb1cb6fd | |||
| 2dc8ac1e62 | |||
| e952e2a691 | |||
| 040ac5cad8 | |||
| 05685863e3 | |||
| d324c0a1c3 | |||
| 03f8b25b50 | |||
| b0e2c2da78 | |||
| f28a8eca91 | |||
| ca69e54cb6 | |||
| 4629abd5a2 | |||
| dc99f4a7a3 | |||
| 389ec21d0c | |||
| 9341201132 | |||
| 88dd83a365 | |||
| 74285d50c4 | |||
| 60d0611ac2 | |||
| f939222942 | |||
| c293c2e9a3 | |||
| e34ca9acd1 | |||
| 83071a3459 | |||
| edf364bf21 | |||
| 1e037883b0 | |||
| d909f167ff | |||
| 4592aaa3e2 | |||
| 95d1a12422 | |||
| 62aa42cccf | |||
| 5cffd3780a | |||
| def75ffcfe | |||
| ad8e611098 | |||
| 3ec1844e4a | |||
| 523670ba0d | |||
| 35dea24ffd | |||
| e55104a155 | |||
| 111745c564 | |||
| c7df1ffc6f | |||
| 2b7e75e079 | |||
| bcdaa09c75 | |||
| 2fc65dcb99 | |||
| 44a3b58e52 | |||
| 0a256053ee | |||
| 46de9ac03e | |||
| a53dc1d9c8 | |||
| f0462322fd | |||
| c59d2a6288 | |||
| 3e3ff2a70b | |||
| 16bc11e72e | |||
| 2719f1efaa | |||
| cff1be0ae8 | |||
| 39ac62a1a1 | |||
| f427dbbd60 | |||
| c3f689a7d9 | |||
| 85f3a9f3b0 | |||
| 13ba4b433d | |||
| 96f27a4965 | |||
| 0e502899a8 | |||
| 424b44c247 | |||
| 01a71c366d | |||
| 990fbeb3a4 | |||
| 5a9a898ba2 | |||
| fe1fbe0005 | |||
| c56a139fdc | |||
| df50eda811 | |||
| f5d3313210 | |||
| 97fcc9ff99 | |||
| 8a6b2b4447 | |||
| 757eaeae92 | |||
| b7dd61f6bc | |||
| d2a95a04a4 | |||
| 0cc993f403 | |||
| 3a64580663 | |||
| d087e28dce | |||
| 96adfaebe1 | |||
| ddf84f8257 | |||
| 507f993075 | |||
| 73a6a60785 | |||
| cf4cf58faf | |||
| 6bc3c74c0c | |||
| 598ce1e354 | |||
| 4685b76e08 | |||
| 78c9109f6c | |||
| 7e248fc0ba | |||
| c526fa9119 | |||
| 520e0fd985 | |||
| 54a7eba358 | |||
| 1494ba2e6e | |||
| a5b3548ede | |||
| 8318aa0113 | |||
| e69c42956b | |||
| 53ca589c11 | |||
| ca8ff8718e | |||
| e8e48e4c4a | |||
| 67e17ed3f8 | |||
| 2a6a40e93b | |||
| eda34423d7 | |||
| 7ce1f6e736 | |||
| 5c53620a72 | |||
| 5f94cec1e2 | |||
| 646350fa7f | |||
| e162a055cc | |||
| 28d3ad3ada | |||
| 0bd44a7764 | |||
| 8be6d887e2 | |||
| 66b14a0d32 | |||
| 153a612253 | |||
| 1a1b55e133 | |||
| 879de20edf | |||
| e77ad3f9bb | |||
| 4ce86ff5fa | |||
| e290c010e6 | |||
| 33d267fa1b | |||
| 601a744159 | |||
| f630d7c3fa | |||
| 91bfefcf8c | |||
| a1b01e6d5f | |||
| 48594617b5 | |||
| b35b9dcff7 | |||
| a3e317773a | |||
| 16431d222c | |||
| ee49a23220 | |||
| a9eef521ec | |||
| 901d33b59c | |||
| 255116fde7 | |||
| 00ebea2536 | |||
| dedf9774c7 | |||
| 6b1c62133d | |||
| d4251b2545 | |||
| b9d1698d74 | |||
| 7c696e1cb6 | |||
| 165d60421d | |||
| c7962118f8 | |||
| 892a204013 | |||
| 0e6aedc7ed | |||
| c547a4d835 | |||
| fc9668baa5 | |||
| ba17d46f15 | |||
| 54a4f93854 | |||
| bdd816488d | |||
| 36dcfee2f7 | |||
| 4d13ddf6b3 | |||
| 9e25475475 | |||
| e955aa7f2a | |||
| 81d2b54dfd | |||
| 7956ff0313 | |||
| 9ff25fb64b | |||
| 04df69f633 | |||
| 908eb57795 | |||
| ecfae074dc | |||
| be5d394e56 | |||
| 849a27ee61 | |||
| 062f3ea43a | |||
| 5cfedcfe33 | |||
| 4d2fc530d0 | |||
| 3970204009 | |||
| f046f557fa | |||
| 401958938d | |||
| 566cffe53d | |||
| 028bc2f9be | |||
| 813d9bc316 | |||
| 79ba458051 | |||
| cf220be9b5 | |||
| c433572585 | |||
| a42b576382 | |||
| fb9b53026d | |||
| 2ac54e5a7b | |||
| 8eecdc6d1f | |||
| 50577e2bd2 | |||
| 7bc1f986e8 | |||
| d796621ccc | |||
| 751e9fb7be | |||
| f6113264f4 | |||
| a3534a730b | |||
| 7f8b8a0e43 | |||
| bd6f7b6d83 | |||
| b0a4beb66a | |||
| 472c2d828c | |||
| 01ee49045e | |||
| 7bd9f821dd | |||
| b20ecc7b54 | |||
| 61eb9d4e29 | |||
| 43eb5a001c | |||
| f58692abb7 | |||
| c1760fb764 | |||
| e9bc0e7e98 | |||
| ffcadcd99e | |||
| 7a733a8d54 | |||
| ce97313fda | |||
| 7b81967a3c | |||
| ff811f594b | |||
| 0bf80b3c89 | |||
| ae3b369fe1 | |||
| 77b15e7194 | |||
| 20537f974e | |||
| 4476a64bdf | |||
| d4b701576e | |||
| 721c053712 | |||
| e3071157f0 | |||
| c07af89e48 | |||
| 9c846106fa | |||
| cf94d1f1f1 | |||
| 6187440f35 | |||
| 57b7c3494f | |||
| dda18c28c5 | |||
| f8d6eaaa96 | |||
| 47d4fabb58 | |||
| 80039f60d5 | |||
| 5a5e9b8a89 | |||
| b7ed3b77bd | |||
| 75b925c326 | |||
| 91d419ee6c | |||
| 23345098ea | |||
| 7ce91ea1a1 | |||
| 41079f1015 | |||
| 712dfa40cd | |||
| decfd6108c | |||
| b890bbfa63 | |||
| 0e3a570b85 | |||
| 7060c809c0 | |||
| 9dbfd84c5b | |||
| fce380a044 | |||
| 46ba15ab03 | |||
| 1e39ca39c3 | |||
| 80ef1ae51c | |||
| 4d0715d226 | |||
| 8a274169da | |||
| 21d8298fe1 | |||
| 5d6f6d8d5b | |||
| bacf6156c1 | |||
| 1d1b213f1f | |||
| 1f11af42f1 | |||
| a026c8748f | |||
| 9f7d89b3cd | |||
| 92a77cc78e | |||
| b0c84e3de7 | |||
| bbc914e174 | |||
| 3fca4055d2 | |||
| 66afa16aed | |||
| 9b0a8de7de | |||
| 04bbede17d | |||
| 0e3bafcc54 | |||
| b48f719b8e | |||
| 289fcbd08c | |||
| f6875bb893 | |||
| 7e803adf13 | |||
| 5b5deee5b3 | |||
| 7dae4cb685 | |||
| 27fad98179 | |||
| 58f7e3a829 | |||
| 4a15bd8ff8 | |||
| b030ef1aca | |||
| cc46a99f97 | |||
| becec6cb6b | |||
| bc33db9fc0 | |||
| 7d4579e737 | |||
| b7c90751b0 | |||
| 88fd1cba71 | |||
| e43cc316ff | |||
| e3f24a29fa | |||
| 890e526bde | |||
| 16ce455fca | |||
| 29b7164468 | |||
| acdd03f609 | |||
| 03b35ecdd0 | |||
| 5307e18085 | |||
| 2cea944cdb | |||
| c08540c7b7 | |||
| 3934700a08 | |||
| 0913eb6655 | |||
| 1bfbe354f5 | |||
| 2d78e20120 | |||
| 77210513c9 | |||
| 2e6f8bdf19 | |||
| 25144fedd5 | |||
| 27f64dd9a4 | |||
| 9d7648f02f | |||
| 1ef8babfef | |||
| 4ea7bf0510 | |||
| 5dcf1d13a9 | |||
| 94d37d05e5 | |||
| 0cbdc458c5 | |||
| c1437c7b46 | |||
| f357f65d04 | |||
| ef8e952fc4 | |||
| a2bc383e15 | |||
| 23930355a7 | |||
| bb9f41e613 | |||
| bc110d8055 | |||
| b23b19e5c3 | |||
| 65b1a4282e | |||
| 1dbb3f6f43 | |||
| af3dc25dfe | |||
| 5a0c0079a1 | |||
| af8f563ed3 | |||
| 93af4a4864 | |||
| 28f188e3ef | |||
| b29224f62f | |||
| d756da41b9 | |||
| cdab4a3b85 | |||
| b88c57ba93 | |||
| 1a5496eced | |||
| b264e6a191 | |||
| ae1b495262 | |||
| 16939ca192 | |||
| 60cd513a33 | |||
| 27d94c64ed | |||
| 21a0f857d3 | |||
| 03a6e8aee2 | |||
| d0862ddf86 | |||
| 4afbb89774 | |||
| 5ec57a9533 | |||
| f088e8960b | |||
| 27dec42ad6 | |||
| b70053090c | |||
| f10e2254ae | |||
| 1f92fc3fc0 | |||
| f71b114a84 | |||
| e3e0532613 | |||
| 2c0f121550 | |||
| 6f41cff75a | |||
| 9b39616c1b | |||
| fad3d66093 | |||
| ff99ef74c8 | |||
| 6990e73b11 | |||
| 860a1237ab | |||
| 97b5bf1fb7 | |||
| ed3418c046 | |||
| a2230868e0 | |||
| 71bab74148 | |||
| 661ea57907 | |||
| 1f18efb0ba | |||
| 8ae46bce93 | |||
| f19a414e09 | |||
| 0ee2933234 | |||
| 9890f579f8 | |||
| 2ee337ead5 | |||
| 362e14fa1a | |||
| 524fe62594 | |||
| 3c87e1e60d | |||
| 0cac868a36 | |||
| 2480c66857 | |||
| 186c477f3c | |||
| 570670be8c | |||
| 22b7226581 | |||
| f16f715b59 | |||
| 75adb787c4 | |||
| 6123377e66 | |||
| 778cccb15d | |||
| 0256dae657 | |||
| 88a93838de | |||
| 0855988427 | |||
| 84b121bbe1 | |||
| 48fb7b0dd7 | |||
| 01e550a9be | |||
| 63a2e0bab6 | |||
| 24657859a8 | |||
| 67d07e895c | |||
| d7df6bc738 | |||
| a4e1de93a7 | |||
| 41be557f0c | |||
| 9417fd933e | |||
| 067d21d0f2 | |||
| 3882da6ac5 | |||
| 77b780b8ca | |||
| 127e8bf3b6 | |||
| 74faed166a | |||
| dbd05d6e82 | |||
| b5d35c7e09 | |||
| c39eb3bacd | |||
| 57fad9148c | |||
| 0f88cdc80e | |||
| e2a9949b16 | |||
| 38e3c7a8f7 | |||
| 67f166fa02 | |||
| c7df5fb119 | |||
| a4be47d7ad | |||
| aaea94a48d | |||
| c3d9c45f58 | |||
| a2a48cc065 | |||
| cf407f7176 | |||
| d6dd17a483 | |||
| a66071099c | |||
| 9a6e569412 | |||
| 7dfa565d00 | |||
| d2e5f01542 | |||
| f4e373e0d2 | |||
| c8691db2b7 | |||
| affe51cb19 | |||
| 57118919d2 | |||
| 7db05a80dd | |||
| a8ba71edef | |||
| 45a99c3fd3 | |||
| 58e6b83e95 | |||
| f556a72fe2 | |||
| cd7a5cab8a | |||
| 67b5e0dbe8 | |||
| b68f0cbde4 | |||
| ebc3627c73 | |||
| 295730408b | |||
| 5a9f133491 | |||
| f30afa4956 | |||
| 171cedf0f0 | |||
| 27d8ef14f8 | |||
| f6d13f57bb | |||
| 5f36167f1a | |||
| 8fb4ae916c | |||
| 48da4aeee0 | |||
| 07df9eecda | |||
| 7f214a0e46 | |||
| e1a0a1e73c | |||
| 1278b0ec73 | |||
| 3e9bd931ed | |||
| 9d588319dd | |||
| 0012ca8ca5 | |||
| 288e276abe | |||
| f22e745514 | |||
| 070c31eac5 | |||
| 1a56ebea70 | |||
| 70e1cbda21 | |||
| 1ede3967c1 | |||
| 60f2df54e0 | |||
| ba708f51f2 | |||
| b106b1c131 | |||
| 0df31f63ab | |||
| 64d4da5a37 | |||
| 7aec38a73e | |||
| a2fd8caa69 | |||
| f546636c52 | |||
| 38ccc4f672 | |||
| 04e669a6be | |||
| cc3f139d1f | |||
| d50442da01 | |||
| 404b05a44c | |||
| 3d7c1ad31d | |||
| d300e775a6 | |||
| 7ee2d1c339 | |||
| 54a98773f8 | |||
| 737a3f0bad | |||
| 3bd9636a5b | |||
| 76b21de0c6 | |||
| dabb058167 | |||
| f394313fee | |||
| b7c5e45fff | |||
| 0a224654c2 | |||
| 47e4a36d7e | |||
| e420a1de4d | |||
| 62dc0f7698 | |||
| 2d31d92271 | |||
| 1981fe2072 | |||
| f68bd37acf | |||
| 76877eb6fa | |||
| 3d66d053c7 | |||
| 0d3ae3810f | |||
| 0e31cff762 | |||
| c27110e37d | |||
| 89441a22aa | |||
| 557135185c | |||
| 4d39fd4165 | |||
| f4c03e56b8 | |||
| d2b6aa9033 | |||
| 5dd40b9377 | |||
| 001b77e7e1 | |||
| 9d91d32d82 | |||
| a60ac7ca17 | |||
| 42ba0da6b0 | |||
| f527c708f2 | |||
| 6f474982ed | |||
| fd6cd52728 | |||
| c9e49f4366 | |||
| 46fd9f4a53 | |||
| 4da641533d | |||
| 79df2c7ce7 | |||
| 3e28af1723 | |||
| 866a95de38 | |||
| 6aa0574a53 | |||
| bb97eafa82 | |||
| 51d5efee1b | |||
| c980804514 | |||
| b883803b21 | |||
| 7e3a7d7044 | |||
| 9ad6012782 | |||
| 5a96cbbeaa | |||
| 416977436e | |||
| 54ec0a1308 | |||
| ebd78e983f | |||
| 1cf726348f | |||
| 41f75e6d1b | |||
| 0e3037631f | |||
| 6fbf4f96b6 | |||
| e35709a99e | |||
| f3602d7d08 | |||
| 526e10a2e0 | |||
| 0b21734571 | |||
| 499872f31d | |||
| 5cc16e098c | |||
| 364e27d5f2 | |||
| 1f4e0bd17c | |||
| 0557e18472 | |||
| cfd66ab8c3 | |||
| 691b763613 | |||
| 3ddb501190 | |||
| 997e808088 | |||
| 13441ad0f8 | |||
| aa508591c1 | |||
| 818f0201fc | |||
| 890f43ffa5 | |||
| e270ab65b3 | |||
| 926373f9c1 | |||
| 111c6177d2 | |||
| 91f72f25ab | |||
| da540ccf8c | |||
| d6396f82fe | |||
| 4fa250a6a1 | |||
| b42cfcea60 | |||
| aca6dfbd60 | |||
| 5f7e6d03ff | |||
| 88ad742da0 | |||
| a8d4042853 | |||
| 44a9339c0a | |||
| 113c7ff49a | |||
| 40dbe243d9 | |||
| d422d24278 | |||
| 109c927dad | |||
| de400f3473 | |||
| 8144a125ce | |||
| 3e34e41a5a | |||
| 2270887d43 | |||
| c6431f9a04 | |||
| 88c0d0120c | |||
| 44fefe5b9f | |||
| 878d368cea | |||
| f2bd026d0e | |||
| 518612492c | |||
| 81e43b87c2 | |||
| a02e17f15c | |||
| c76f86fdbd | |||
| 5b7c00ff52 | |||
| b9f0046ee7 | |||
| 3b79f7e4ae | |||
| 85d2df02b9 | |||
| 2f1e8ba612 | |||
| 84c690cb07 | |||
| 239bbad7ab | |||
| 4be8023408 | |||
| 83e8da57b8 | |||
| dcff6c996d | |||
| 0a66a6f1e5 | |||
| e82a5c5c54 | |||
| 92fdcafb66 | |||
| b9aae1aaae | |||
| 7d70afc937 | |||
| 12b63061c2 | |||
| 038fdeea83 | |||
| 0b6225bcc3 | |||
| f286ef8e17 | |||
| b120bcb60a | |||
| be34fc9134 | |||
| 8591d17d82 | |||
| f6190d6751 | |||
| f0fc77fded | |||
| f56cac6381 | |||
| 4f35054d29 | |||
| d29df6714a | |||
| 7460fb8349 | |||
| a7c430355a | |||
| 1df1517449 | |||
| f7c357ebad | |||
| 20c60aae68 | |||
| b14527b7af | |||
| f840080e5b | |||
| 2c6983a2f1 | |||
| acfb83ec5e | |||
| 3db931dc0e | |||
| 8309ddd486 | |||
| 21c868a646 | |||
| ffe9acfe4a | |||
| 24d904d194 | |||
| b280a37c4d | |||
| 906548d0ba | |||
| 1485a5bf3b | |||
| 9ec197f2e8 | |||
| d21466f595 | |||
| 4f3290309e | |||
| d6fe0f61a9 | |||
| 5a22f2cf0b | |||
| 42d11d9e7d | |||
| e49c184595 | |||
| 99d87c5ca2 | |||
| 4c0f48c548 | |||
| 4ce6d35e30 | |||
| 81bf0c66c6 | |||
| 34dc725d26 | |||
| a5db4ca092 | |||
| 61029fe20b | |||
| fee3f88cb5 | |||
| 932500e43d | |||
| 55d4cdd464 | |||
| fe3e47b1e8 | |||
| 9ca25bd48f | |||
| 91e0823ff0 | |||
| 142c6b11b3 | |||
| ef0b8367b5 | |||
| d1bfb4d2c0 | |||
| 3b5d6f003f | |||
| 26c457860b | |||
| d0515031c7 | |||
| 08f4a0a816 | |||
| 28f95f1fbe | |||
| c791de0e1e | |||
| 36b5426f6e | |||
| 1e72e9b1cd | |||
| 9739e55d0f | |||
| 1cddbc80cf | |||
| 3da9ee15d3 | |||
| 1e2fac054c | |||
| 914bfb2d9c | |||
| 40244994ad | |||
| 556ae07857 | |||
| 17fd71164c | |||
| 81d19156e9 | |||
| 7b82411e6f | |||
| fb268add7a | |||
| 7700973538 | |||
| 54e25a0251 | |||
| 79b3a1fe4e | |||
| faf013ec84 | |||
| 7152915318 | |||
| 886262e58a | |||
| 9c5d9ae376 | |||
| 3d2bc15e9a | |||
| 20c43c447d | |||
| 4caed7cc0d | |||
| 5b68f8ea6a | |||
| 8378bc9958 | |||
| 367cb48096 | |||
| 661b263e77 | |||
| 07c5e72cdb | |||
| 7752cdbfaf | |||
| 4545ecad58 | |||
| ac74237f01 | |||
| 5a36179c19 | |||
| 82d73f387d | |||
| e8c6314770 | |||
| 087c1b98dc | |||
| 68c5ad83fb | |||
| 5acc8c0134 | |||
| c897b6a82d | |||
| d008e90d50 | |||
| ea820b30bf | |||
| 03725dc015 | |||
| 0a6f9bc1eb | |||
| 1946922de3 | |||
| edf1f4233b | |||
| f4b55ea7a7 | |||
| 8dfd1f03e9 | |||
| acf26c5ab7 | |||
| d9800c8135 | |||
| 02bef7560f | |||
| 07dd0692b6 | |||
| 4f3317effe | |||
| 9afdbe3648 | |||
| fe0df01448 | |||
| 12e6907512 | |||
| 5aef492b4c | |||
| 5d7ed8ff7d | |||
| b1754fc5ff | |||
| 19bbf3e142 | |||
| e1755275a0 | |||
| 520037e721 | |||
| cbb0828ab8 | |||
| 8774d10bdf | |||
| df9f479d58 | |||
| 8bb52c9c2a | |||
| 947c423824 | |||
| c3d24fb26d | |||
| 112f9ae087 | |||
| 01b9ff54d9 | |||
| 64a1904136 | |||
| bce6864785 | |||
| ecd54b4cba | |||
| ca2b288a4b | |||
| 1016fbb8f9 | |||
| be3f81c7ec | |||
| 9f3c151c3c | |||
| 9735f3d8f2 | |||
| 34680c5ccf | |||
| 58934e5881 | |||
| ad3f98b8e7 | |||
| 7c33a33ef3 | |||
| 3dfcca68e6 | |||
| 73b74c94a1 | |||
| 18338d60d5 | |||
| e106070640 | |||
| 091a7ae359 | |||
| 70160aeab3 | |||
| 1aa08f594d | |||
| 14d8a931fe | |||
| 30ba85bc67 | |||
| caadcc3ed8 | |||
| 26f55472c6 | |||
| 79a58e275c | |||
| 900e584514 | |||
| bb639d9f29 | |||
| 15dcacc1fc | |||
| 6d53e3c2d7 | |||
| 8ed7346273 | |||
| 3c1220adca | |||
| 4ed0eb7012 | |||
| 2af5445309 | |||
| abb1916bda | |||
| 9424dca9e4 | |||
| db84bb9bd3 | |||
| c603f85488 | |||
| 2f1ee25f50 | |||
| 7bdf9005e5 | |||
| d9c1d79e30 | |||
| bd88b86919 | |||
| 8e29ae8c44 | |||
| d158607f8e | |||
| 939fbb3c38 | |||
| 3b9dfa9d29 | |||
| 0c76fb57f2 | |||
| 9694fa8d3a | |||
| 20761e053e | |||
| 29d885b40f | |||
| 087dc13965 | |||
| e7f559c582 | |||
| 52c5f6e152 | |||
| 26ca59859f | |||
| 23d6770ff9 | |||
| ac36a377b0 | |||
| 1642867136 | |||
| ce40392803 | |||
| 5f1af8a69d | |||
| c57ff2640e | |||
| d7fd396b7c | |||
| 45d145a823 | |||
| 221ef78faa | |||
| d86513cbba | |||
| 25b5904b84 | |||
| c2eb60df4a | |||
| feabd0430c | |||
| 838de23357 | |||
| d7b7040408 | |||
| 44e4bdc6f4 | |||
| 779060bc16 | |||
| 76239fa1ae | |||
| 5e53f767c4 | |||
| 974073a2e5 | |||
| d693431183 | |||
| bedf739d16 | |||
| 082755de1a | |||
| 6299e42aa9 | |||
| 129f41cee9 | |||
| 415bbc74aa | |||
| 13e41f2c68 | |||
| 1e117b780a | |||
| f8c5c24159 | |||
| f5a55c44d4 | |||
| 91a0e7bdaa | |||
| b07e309627 | |||
| 9ea45399ce | |||
| c19b1a143e | |||
| 02c24a860d | |||
| 9f652708ee | |||
| 05fa790584 | |||
| e0db822a9b | |||
| ec0fee6208 | |||
| a188554fe1 | |||
| 8d52c7daf3 | |||
| c49ebaaf1a | |||
| acc9645249 | |||
| 60f961dfe8 | |||
| 0c48b1d993 | |||
| d57b57bddc | |||
| 3837d2b94b | |||
| f81a188ef6 | |||
| 8e417e28d1 | |||
| 4ce6830a7b | |||
| 3a7c79e2c7 | |||
| cb2c2905c5 | |||
| 421160631a | |||
| 60aad1b717 | |||
| 72a17bdd76 | |||
| 9b9ce1c625 | |||
| d7cb6de820 | |||
| 3d5750f31c | |||
| fabf60bc4c | |||
| f5be8ba11f | |||
| 94d587e6fc | |||
| 2ec44f7620 | |||
| bbbf25201a | |||
| d6a3215fe2 | |||
| 75699a3825 | |||
| f3aeed77e5 | |||
| cfbaf7bf1c | |||
| bc6067d195 | |||
| 7203d93fb3 | |||
| ffd497673f | |||
| d00ff3c453 | |||
| 1d9e91e00f | |||
| 7f6ed35347 | |||
| 38027c8f52 | |||
| dd5804c10e | |||
| 84dcd25a36 | |||
| 4519450363 | |||
| 3c70eca758 | |||
| 68a2d6fc40 | |||
| a5923a5d51 | |||
| 769f0b1e24 | |||
| a1271d984f | |||
| db65ec4674 | |||
| a984c55cf9 | |||
| cdd0828c4a | |||
| 1984d0671b | |||
| 3e4efff73d | |||
| f4d1b7c603 | |||
| 200caab82b | |||
| f9b104f37b | |||
| c25b482301 | |||
| 31d7cc2cd4 | |||
| 19ecdc75a8 | |||
| 46724508f8 | |||
| 51b0194b8a | |||
| 9a27c4a2f0 | |||
| 32df742b85 | |||
| 8392765213 | |||
| 806b10b934 | |||
| 1fa0553c71 | |||
| 50a68a1791 | |||
| 0b55a0423e | |||
| 565d95a377 | |||
| f492f72154 | |||
| 4d84f0f6f0 | |||
| a0d0c8e4af | |||
| bef748abbd | |||
| c4373ef290 | |||
| 0b8c5a6872 | |||
| 829ecb2086 | |||
| d3564a4b09 | |||
| a244753f47 | |||
| 745782a77a | |||
| 246cbe1312 | |||
| 6c941122eb | |||
| 1a884cd8e1 | |||
| 18f008f7c7 | |||
| 6d42569ade | |||
| 5ed781a330 | |||
| 66fcd02aa2 | |||
| 1fc0e9a6aa | |||
| 91567ba916 | |||
| d80826b05d | |||
| 45bcf73185 | |||
| 78dc08bdc2 | |||
| f98f115ac2 | |||
| bf409936e7 | |||
| b4364723ef | |||
| bcc6359dec | |||
| 787a72a993 | |||
| d9eb962969 | |||
| f221153776 | |||
| 67596ef0cc | |||
| bf5bfe589f | |||
| af78c3925a | |||
| d144958669 | |||
| 5a64003f6f | |||
| 311718309c | |||
| 98479d7ffd | |||
| 90e505e58f | |||
| 410b8dd0fd | |||
| c2f25b6f62 | |||
| 03a2a74697 | |||
| 2807c11410 | |||
| 39d51ce845 | |||
| a216583d95 |
+9
-2
@@ -1,10 +1,17 @@
|
|||||||
.git
|
.git
|
||||||
.github
|
.github
|
||||||
docs
|
|
||||||
default.etcd
|
default.etcd
|
||||||
*.gz
|
*.gz
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.bzip2
|
*.bzip2
|
||||||
*.zip
|
*.zip
|
||||||
browser/node_modules
|
browser/node_modules
|
||||||
node_modules
|
node_modules
|
||||||
|
docs/debugging/s3-verify/s3-verify
|
||||||
|
docs/debugging/xl-meta/xl-meta
|
||||||
|
docs/debugging/s3-check-md5/s3-check-md5
|
||||||
|
docs/debugging/hash-set/hash-set
|
||||||
|
docs/debugging/healing-bin/healing-bin
|
||||||
|
docs/debugging/inspect/inspect
|
||||||
|
docs/debugging/pprofgoparser/pprofgoparser
|
||||||
|
docs/debugging/reorder-disks/reorder-disks
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: ''
|
|
||||||
labels: community, triage
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--- Provide a general summary of the issue in the Title above -->
|
|
||||||
|
|
||||||
## Expected Behavior
|
|
||||||
<!--- If you're describing a bug, tell us what should happen -->
|
|
||||||
<!--- If you're suggesting a change/improvement, tell us how it should work -->
|
|
||||||
|
|
||||||
## Current Behavior
|
|
||||||
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
|
|
||||||
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
|
|
||||||
|
|
||||||
## Possible Solution
|
|
||||||
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
|
|
||||||
<!--- or ideas how to implement the addition or change -->
|
|
||||||
|
|
||||||
## Steps to Reproduce (for bugs)
|
|
||||||
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
|
||||||
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
|
||||||
<!--- and make sure you have followed https://github.com/minio/minio/tree/release/docs/debugging to capture relevant logs -->
|
|
||||||
|
|
||||||
1.
|
|
||||||
2.
|
|
||||||
3.
|
|
||||||
4.
|
|
||||||
|
|
||||||
## Context
|
|
||||||
<!--- How has this issue affected you? What are you trying to accomplish? -->
|
|
||||||
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
|
|
||||||
|
|
||||||
## Regression
|
|
||||||
<!-- Is this issue a regression? (Yes / No) -->
|
|
||||||
<!-- If Yes, optionally please include minio version or commit id or PR# that caused this regression, if you have these details. -->
|
|
||||||
|
|
||||||
## Your Environment
|
|
||||||
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
|
||||||
* Version used (`minio --version`):
|
|
||||||
* Server setup and configuration:
|
|
||||||
* Operating System and version (`uname -a`):
|
|
||||||
@@ -7,6 +7,14 @@ assignees: ''
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
Report bugs in the PGSTY SILO server (`pgsty/silo`) here. Community maintainers
|
||||||
|
handle reports on a best-effort basis. There is no SLA, SLO, or emergency
|
||||||
|
production-support channel. Follow the
|
||||||
|
[Code of Conduct](https://github.com/pgsty/silo/blob/main/code_of_conduct.md).
|
||||||
|
Report suspected vulnerabilities privately through
|
||||||
|
[SECURITY.md](https://github.com/pgsty/silo/blob/main/SECURITY.md).
|
||||||
|
For patches, see the [contribution guide](https://github.com/pgsty/silo/blob/main/CONTRIBUTING.md).
|
||||||
|
|
||||||
<!--- Provide a general summary of the issue in the Title above -->
|
<!--- Provide a general summary of the issue in the Title above -->
|
||||||
|
|
||||||
## Expected Behavior
|
## Expected Behavior
|
||||||
@@ -24,7 +32,7 @@ assignees: ''
|
|||||||
## Steps to Reproduce (for bugs)
|
## Steps to Reproduce (for bugs)
|
||||||
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
||||||
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
||||||
<!--- and make sure you have followed https://github.com/minio/minio/tree/release/docs/debugging to capture relevant logs -->
|
<!--- and include the relevant Silo logs with secrets and credentials removed -->
|
||||||
|
|
||||||
1.
|
1.
|
||||||
2.
|
2.
|
||||||
@@ -37,10 +45,10 @@ assignees: ''
|
|||||||
|
|
||||||
## Regression
|
## Regression
|
||||||
<!-- Is this issue a regression? (Yes / No) -->
|
<!-- Is this issue a regression? (Yes / No) -->
|
||||||
<!-- If Yes, optionally please include minio version or commit id or PR# that caused this regression, if you have these details. -->
|
<!-- If Yes, optionally include the Silo version, commit id, or PR that caused the regression. -->
|
||||||
|
|
||||||
## Your Environment
|
## Your Environment
|
||||||
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
||||||
* Version used (`minio --version`):
|
* Version used (`silo --version`):
|
||||||
* Server setup and configuration:
|
* Server setup and configuration:
|
||||||
* Operating System and version (`uname -a`):
|
* Operating System and version (`uname -a`):
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: MinIO Community Support
|
- name: Silo Documentation
|
||||||
url: https://slack.min.io
|
url: https://silo.pgsty.com/docs/
|
||||||
about: Join here for Community Support
|
about: Installation, configuration, operations, and compatibility guidance
|
||||||
- name: MinIO SUBNET Support
|
- name: Private Security Report
|
||||||
url: https://min.io/pricing
|
url: https://github.com/pgsty/silo/security/advisories/new
|
||||||
about: Join here for Enterprise Support
|
about: Privately report a suspected vulnerability in Silo
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ assignees: ''
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
Suggest improvements to the PGSTY SILO server (`pgsty/silo`) here.
|
||||||
|
For patches, see the [contribution guide](https://github.com/pgsty/silo/blob/main/CONTRIBUTING.md).
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
**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 [...]
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
|
## Contribution Licensing (no CLA, inbound=outbound, DCO required)
|
||||||
|
|
||||||
|
This pull request contributes to PGSTY SILO (`pgsty/silo`). Code contributions
|
||||||
|
are accepted under AGPL-3.0-or-later, the same license as the server.
|
||||||
|
This project does not use a CLA or require a separate Apache-2.0 license grant.
|
||||||
|
By submitting this pull request I represent that I have the right to contribute
|
||||||
|
the code changes under this repository's
|
||||||
|
[GNU Affero General Public License v3.0 or later](https://www.gnu.org/licenses/agpl-3.0.html)
|
||||||
|
and retain copyright in my original work. Existing copyright and license
|
||||||
|
notices remain intact; separately licensed material keeps its applicable terms.
|
||||||
|
Every commit must carry a DCO `Signed-off-by` trailer
|
||||||
|
(`git commit -s`) certifying the
|
||||||
|
[Developer Certificate of Origin](https://developercertificate.org/) — see
|
||||||
|
[CONTRIBUTING.md](https://github.com/pgsty/silo/blob/main/CONTRIBUTING.md).
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
|
||||||
@@ -7,6 +22,12 @@
|
|||||||
## How to test this PR?
|
## How to test this PR?
|
||||||
|
|
||||||
|
|
||||||
|
## Compatibility impact
|
||||||
|
|
||||||
|
<!-- Note effects on APIs, clients, MINIO_* configuration, metrics, headers,
|
||||||
|
routes, storage metadata, module/import paths, upgrades, or rollback. -->
|
||||||
|
|
||||||
|
|
||||||
## Types of changes
|
## Types of changes
|
||||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||||
- [ ] New feature (non-breaking change which adds functionality)
|
- [ ] New feature (non-breaking change which adds functionality)
|
||||||
@@ -14,6 +35,11 @@
|
|||||||
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||||
|
|
||||||
## Checklist:
|
## Checklist:
|
||||||
|
- [ ] All commits are signed off (`git commit -s`) per the [DCO](https://developercertificate.org/)
|
||||||
- [ ] Fixes a regression (If yes, please add `commit-id` or `PR #` here)
|
- [ ] Fixes a regression (If yes, please add `commit-id` or `PR #` here)
|
||||||
- [ ] Documentation updated
|
|
||||||
- [ ] Unit tests added/updated
|
- [ ] Unit tests added/updated
|
||||||
|
- [ ] `make verifiers` passes
|
||||||
|
- [ ] Relevant package tests and `make build` pass
|
||||||
|
- [ ] Compatibility and rollback impact documented
|
||||||
|
- [ ] Internal documentation updated
|
||||||
|
- [ ] Public documentation update opened in `pgsty/silo.pgsty.com`, if needed
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
version: 2
|
||||||
|
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
|
||||||
|
builds:
|
||||||
|
- id: silo
|
||||||
|
main: .
|
||||||
|
binary: silo
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
- darwin
|
||||||
|
- windows
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
- arm64
|
||||||
|
goamd64:
|
||||||
|
- v1
|
||||||
|
flags:
|
||||||
|
- -tags=kqueue
|
||||||
|
- -trimpath
|
||||||
|
ldflags:
|
||||||
|
- "{{ .Env.LDFLAGS }}"
|
||||||
|
|
||||||
|
archives:
|
||||||
|
- id: silo
|
||||||
|
ids:
|
||||||
|
- silo
|
||||||
|
name_template: "silo_{{ .Env.PKG_VERSION }}_{{ .Os }}_{{ .Arch }}"
|
||||||
|
# Explicit so the license materials cannot silently drop out of the
|
||||||
|
# binary archives: GoReleaser's default file globs would miss NOTICE.
|
||||||
|
# CREDITS stays out deliberately -- at 1.8MB it would dominate the
|
||||||
|
# archive; it remains available in the repository and the OCI image.
|
||||||
|
files:
|
||||||
|
- README.md
|
||||||
|
- LICENSE
|
||||||
|
- NOTICE
|
||||||
|
|
||||||
|
checksum:
|
||||||
|
name_template: "silo_{{ .Env.PKG_VERSION }}_checksums.txt"
|
||||||
|
algorithm: sha256
|
||||||
|
|
||||||
|
# Generate one SPDX JSON document per platform archive. SBOMs are created
|
||||||
|
# before the checksum stage, so the signed checksum manifest covers both the
|
||||||
|
# archives and their corresponding software bills of materials.
|
||||||
|
sboms:
|
||||||
|
- id: silo-archives
|
||||||
|
artifacts: archive
|
||||||
|
# Avoid network-backed package enrichment: the release SBOM must be
|
||||||
|
# reproducible from the artifact alone and the PR gate must work offline.
|
||||||
|
args:
|
||||||
|
- "$artifact"
|
||||||
|
- "--output"
|
||||||
|
- "spdx-json=$document"
|
||||||
|
env:
|
||||||
|
- SYFT_FILE_METADATA_CATALOGER_ENABLED=true
|
||||||
|
- SYFT_CHECK_FOR_APP_UPDATE=false
|
||||||
|
|
||||||
|
# A keyless Sigstore bundle is the detached signature for the checksum
|
||||||
|
# manifest. Consumers can verify the whole archive/SBOM set without trusting a
|
||||||
|
# long-lived project key copied into the repository.
|
||||||
|
signs:
|
||||||
|
- id: silo-checksums
|
||||||
|
cmd: cosign
|
||||||
|
signature: "${artifact}.sigstore.json"
|
||||||
|
args:
|
||||||
|
- sign-blob
|
||||||
|
- "--bundle=${signature}"
|
||||||
|
- "${artifact}"
|
||||||
|
- --yes
|
||||||
|
artifacts: checksum
|
||||||
|
output: true
|
||||||
|
|
||||||
|
release:
|
||||||
|
github:
|
||||||
|
owner: pgsty
|
||||||
|
name: silo
|
||||||
|
draft: true
|
||||||
|
prerelease: false
|
||||||
|
replace_existing_draft: true
|
||||||
|
replace_existing_artifacts: false
|
||||||
|
mode: replace
|
||||||
|
# Draft replacement matches the release name; keep it identical to the tag.
|
||||||
|
name_template: "{{ .Tag }}"
|
||||||
|
|
||||||
|
changelog:
|
||||||
|
sort: asc
|
||||||
|
filters:
|
||||||
|
exclude:
|
||||||
|
- "^docs:"
|
||||||
|
- "^test:"
|
||||||
|
- "Merge pull request"
|
||||||
|
- "Merge branch"
|
||||||
|
|
||||||
|
announce:
|
||||||
|
skip: true
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
# Configuration for Lock Threads - https://github.com/dessant/lock-threads-app
|
|
||||||
|
|
||||||
# Number of days of inactivity before a closed issue or pull request is locked
|
|
||||||
daysUntilLock: 365
|
|
||||||
|
|
||||||
# Skip issues and pull requests created before a given timestamp. Timestamp must
|
|
||||||
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
|
|
||||||
skipCreatedBefore: false
|
|
||||||
|
|
||||||
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
|
|
||||||
exemptLabels: []
|
|
||||||
|
|
||||||
# Label to add before locking, such as `outdated`. Set to `false` to disable
|
|
||||||
lockLabel: true
|
|
||||||
|
|
||||||
# Comment to post before locking. Set to `false` to disable
|
|
||||||
lockComment: >-
|
|
||||||
|
|
||||||
This thread has been automatically locked since there has not been
|
|
||||||
any recent activity after it was closed. Please open a new issue for
|
|
||||||
related bugs.
|
|
||||||
|
|
||||||
# Assign `resolved` as the reason for locking. Set to `false` to disable
|
|
||||||
setLockReason: true
|
|
||||||
|
|
||||||
# Limit to only `issues` or `pulls`
|
|
||||||
only: issues
|
|
||||||
|
|
||||||
# Optionally, specify configuration settings just for `issues` or `pulls`
|
|
||||||
# issues:
|
|
||||||
# exemptLabels:
|
|
||||||
# - help-wanted
|
|
||||||
# lockLabel: outdated
|
|
||||||
|
|
||||||
# pulls:
|
|
||||||
# daysUntilLock: 30
|
|
||||||
|
|
||||||
# Repository to extend settings from
|
|
||||||
# _extends: repo
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 162.612 24.465"><path d="M52.751.414h9.108v23.63h-9.108zM41.711.74l-18.488 9.92a.919.919 0 0 1-.856 0L3.879.74A2.808 2.808 0 0 0 2.558.414h-.023A2.4 2.4 0 0 0 0 2.641v21.376h9.1V13.842a.918.918 0 0 1 1.385-.682l10.361 5.568a3.634 3.634 0 0 0 3.336.028l10.933-5.634a.917.917 0 0 1 1.371.69v10.205h9.1V2.641A2.4 2.4 0 0 0 43.055.414h-.023a2.808 2.808 0 0 0-1.321.326zm65.564-.326h-9.237v10.755a.913.913 0 0 1-1.338.706L72.762.675a2.824 2.824 0 0 0-1.191-.261h-.016a2.4 2.4 0 0 0-2.535 2.227v21.377h9.163V13.275a.914.914 0 0 1 1.337-.707l24.032 11.2a2.813 2.813 0 0 0 1.188.26 2.4 2.4 0 0 0 2.535-2.227zm7.161 23.63V.414h4.191v23.63zm28.856.421c-11.274 0-19.272-4.7-19.272-12.232C124.02 4.741 132.066 0 143.292 0s19.32 4.7 19.32 12.233-7.902 12.232-19.32 12.232zm0-21.333c-8.383 0-14.84 3.217-14.84 9.1 0 5.926 6.457 9.1 14.84 9.1s14.887-3.174 14.887-9.1c0-5.883-6.504-9.1-14.887-9.1z" fill="#c72c48"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 978 B |
@@ -0,0 +1,111 @@
|
|||||||
|
name: silo
|
||||||
|
arch: ${NFPM_ARCH}
|
||||||
|
platform: linux
|
||||||
|
version: ${PKG_VERSION}
|
||||||
|
version_schema: none
|
||||||
|
release: ${NFPM_RELEASE}
|
||||||
|
section: utils
|
||||||
|
priority: optional
|
||||||
|
maintainer: "Ruohang Feng (@Vonng) <rh@vonng.com>"
|
||||||
|
description: S3-Interface Libre Object Storage, a community-maintained S3-compatible server.
|
||||||
|
vendor: PGSTY
|
||||||
|
homepage: https://silo.pgsty.com
|
||||||
|
license: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
contents:
|
||||||
|
- src: ${NFPM_SOURCE}
|
||||||
|
dst: /usr/bin/silo
|
||||||
|
expand: true
|
||||||
|
file_info:
|
||||||
|
mode: 0755
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
- src: ${NFPM_UNIT}
|
||||||
|
dst: /usr/lib/systemd/system/silo.service
|
||||||
|
expand: true
|
||||||
|
file_info:
|
||||||
|
mode: 0644
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
- src: ${NFPM_DEFAULTS}
|
||||||
|
dst: /etc/default/silo
|
||||||
|
type: config|noreplace
|
||||||
|
expand: true
|
||||||
|
file_info:
|
||||||
|
mode: 0644
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
- src: ${NFPM_SYSUSERS}
|
||||||
|
dst: /usr/lib/sysusers.d/silo.conf
|
||||||
|
expand: true
|
||||||
|
file_info:
|
||||||
|
mode: 0644
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
# The license materials are declared once per packager: nfpm only honors
|
||||||
|
# type: license on rpm and silently drops such entries from deb and apk, so
|
||||||
|
# the rpm keeps its %license flag while deb and apk carry plain files at the
|
||||||
|
# same path.
|
||||||
|
- src: ${NFPM_LICENSE}
|
||||||
|
dst: /usr/share/doc/silo/LICENSE
|
||||||
|
type: license
|
||||||
|
packager: rpm
|
||||||
|
expand: true
|
||||||
|
file_info:
|
||||||
|
mode: 0644
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
- src: ${NFPM_NOTICE}
|
||||||
|
dst: /usr/share/doc/silo/NOTICE
|
||||||
|
type: license
|
||||||
|
packager: rpm
|
||||||
|
expand: true
|
||||||
|
file_info:
|
||||||
|
mode: 0644
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
- src: ${NFPM_LICENSE}
|
||||||
|
dst: /usr/share/doc/silo/LICENSE
|
||||||
|
packager: deb
|
||||||
|
expand: true
|
||||||
|
file_info:
|
||||||
|
mode: 0644
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
- src: ${NFPM_NOTICE}
|
||||||
|
dst: /usr/share/doc/silo/NOTICE
|
||||||
|
packager: deb
|
||||||
|
expand: true
|
||||||
|
file_info:
|
||||||
|
mode: 0644
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
- src: ${NFPM_LICENSE}
|
||||||
|
dst: /usr/share/doc/silo/LICENSE
|
||||||
|
packager: apk
|
||||||
|
expand: true
|
||||||
|
file_info:
|
||||||
|
mode: 0644
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
- src: ${NFPM_NOTICE}
|
||||||
|
dst: /usr/share/doc/silo/NOTICE
|
||||||
|
packager: apk
|
||||||
|
expand: true
|
||||||
|
file_info:
|
||||||
|
mode: 0644
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
|
||||||
|
scripts:
|
||||||
|
postinstall: buildscripts/package/postinstall.sh
|
||||||
|
preremove: buildscripts/package/preremove.sh
|
||||||
|
|
||||||
|
rpm:
|
||||||
|
group: Applications/File
|
||||||
|
compression: gzip:9
|
||||||
|
|
||||||
|
deb:
|
||||||
|
compression: gzip
|
||||||
|
fields:
|
||||||
|
License: AGPL-3.0-or-later
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1500" height="570" viewBox="0 0 1500 570"
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
role="img" aria-labelledby="silo-logo-title silo-logo-desc"
|
||||||
|
shape-rendering="geometricPrecision">
|
||||||
|
<title id="silo-logo-title">SILO logo</title>
|
||||||
|
<desc id="silo-logo-desc">The SILO horizontal lockup: the circular silo emblem on the left, the SILO wordmark on the right.</desc>
|
||||||
|
|
||||||
|
<defs>
|
||||||
|
<!-- Emblem gradient, in the emblem's local coordinates; the group transform maps it. -->
|
||||||
|
<linearGradient id="silo-logo-mark-color" x1="276" y1="720" x2="742" y2="286" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#064A83"/>
|
||||||
|
<stop offset="0.5" stop-color="#007FA8"/>
|
||||||
|
<stop offset="1" stop-color="#22C7C9"/>
|
||||||
|
</linearGradient>
|
||||||
|
<!-- Wordmark gradient, in the wordmark's local coordinates. -->
|
||||||
|
<linearGradient id="silo-logo-word-color" x1="44.0" y1="-94.4" x2="1950.0" y2="794.4" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop class="silo-logo-wm-a" offset="0.06" stop-color="#1d588c"/>
|
||||||
|
<stop class="silo-logo-wm-b" offset="0.94" stop-color="#b4762e"/>
|
||||||
|
</linearGradient>
|
||||||
|
<style>
|
||||||
|
/* Light theme values of --pg-strong / --copper; dark theme swaps in its own pair. */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.silo-logo-wm-a { stop-color: #7fb8e8; }
|
||||||
|
.silo-logo-wm-b { stop-color: #e0a35c; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Circular emblem, shifted from its native viewBox (230 213 570 570) to the 0..570 square. -->
|
||||||
|
<g id="silo-logo-mark" transform="translate(-230 -213)" fill="url(#silo-logo-mark-color)">
|
||||||
|
<!-- Outer circular band, intentionally opened at the lower-right plinth. -->
|
||||||
|
<path d="
|
||||||
|
M 734 676
|
||||||
|
A 283.5 278.5 0 1 0 310 692
|
||||||
|
L 359 692
|
||||||
|
A 247 248.5 0 1 1 688 676
|
||||||
|
Z"/>
|
||||||
|
|
||||||
|
<!-- Flowing left wall; its upper tangent matches the inner ellipse. -->
|
||||||
|
<path d="
|
||||||
|
M 300.57 375
|
||||||
|
C 292 390 300 430 328 450
|
||||||
|
C 343 461 357 472 374 481
|
||||||
|
C 410 501 426 517 426 544
|
||||||
|
L 426 676
|
||||||
|
L 336 676
|
||||||
|
C 308 647 286 608 274 565
|
||||||
|
C 262 522 263 479 272 439
|
||||||
|
C 278 413 286 389 300.57 375
|
||||||
|
Z"/>
|
||||||
|
|
||||||
|
<!-- Right column with tangent-continuous upper shoulder. -->
|
||||||
|
<path d="
|
||||||
|
M 602 373
|
||||||
|
Q 602 368 607 370
|
||||||
|
C 619 374 634 381 634 389
|
||||||
|
L 634 647
|
||||||
|
Q 634 649 636 649
|
||||||
|
L 708 649
|
||||||
|
L 734 676
|
||||||
|
L 602 676
|
||||||
|
Z"/>
|
||||||
|
|
||||||
|
<!-- Lower circular cap. -->
|
||||||
|
<path d="
|
||||||
|
M 310 692
|
||||||
|
L 714 692
|
||||||
|
C 668 743 596 774 512 774
|
||||||
|
C 428 774 355 743 310 692
|
||||||
|
Z"/>
|
||||||
|
|
||||||
|
<!-- Main silo body, with a tangent-continuous right shoulder. -->
|
||||||
|
<path d="
|
||||||
|
M 389 389
|
||||||
|
C 389 374 447 351 512 351
|
||||||
|
C 540 351 565 354 580 359
|
||||||
|
Q 583 360 583 364
|
||||||
|
L 583 676
|
||||||
|
L 443 676
|
||||||
|
L 443 541
|
||||||
|
C 443 509 426 490 389 470
|
||||||
|
Z"/>
|
||||||
|
|
||||||
|
<!-- Peaked roof with softly tapered, burr-free tips. -->
|
||||||
|
<path d="
|
||||||
|
M 512 274
|
||||||
|
L 647 365
|
||||||
|
Q 649 370 647 376
|
||||||
|
C 609 350 563 337 512 337
|
||||||
|
C 460 337 414 350 377 376
|
||||||
|
Q 375 370 377 365
|
||||||
|
Z"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Wordmark, scaled 3/7 to a 300-unit cap height and centered on the emblem's axis. -->
|
||||||
|
<g id="silo-logo-word" transform="translate(645.429 135) scale(0.428571)"
|
||||||
|
fill="url(#silo-logo-word-color)" fill-rule="nonzero">
|
||||||
|
<path id="silo-logo-s" d="M0 592V492H134V551L167 584H374L408 550V434L375 401H110L2 293V108L110 0H426L534 108V209H400V149L367 116H169L136 149V252L169 285H434L542 393V590L432 700H108Z"/>
|
||||||
|
<path id="silo-logo-i" d="M637 0H773V700H637Z"/>
|
||||||
|
<path id="silo-logo-l" d="M888 0H1024V585H1374V700H888Z"/>
|
||||||
|
<path id="silo-logo-o" d="M1404 585V115L1519 0H1879L1994 115V585L1879 700H1519ZM1807 584 1858 533V167L1807 116H1591L1540 167V533L1591 584Z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1624" height="570" viewBox="0 0 1994 700"
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
role="img" aria-labelledby="title desc"
|
||||||
|
shape-rendering="geometricPrecision">
|
||||||
|
<title id="title">SILO wordmark</title>
|
||||||
|
<desc id="desc">The SILO wordmark set in Chakra Petch Bold, outlined, with the blue-to-copper brand gradient.</desc>
|
||||||
|
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="silo-wordmark-color" x1="44.0" y1="-94.4" x2="1950.0" y2="794.4" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop class="wm-a" offset="0.06" stop-color="#1d588c"/>
|
||||||
|
<stop class="wm-b" offset="0.94" stop-color="#b4762e"/>
|
||||||
|
</linearGradient>
|
||||||
|
<style>
|
||||||
|
/* Light theme values of --pg-strong / --copper; dark theme swaps in its own pair. */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.wm-a { stop-color: #7fb8e8; }
|
||||||
|
.wm-b { stop-color: #e0a35c; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<g fill="url(#silo-wordmark-color)" fill-rule="nonzero">
|
||||||
|
<path id="silo-s" d="M0 592V492H134V551L167 584H374L408 550V434L375 401H110L2 293V108L110 0H426L534 108V209H400V149L367 116H169L136 149V252L169 285H434L542 393V590L432 700H108Z"/>
|
||||||
|
<path id="silo-i" d="M637 0H773V700H637Z"/>
|
||||||
|
<path id="silo-l" d="M888 0H1024V585H1374V700H888Z"/>
|
||||||
|
<path id="silo-o" d="M1404 585V115L1519 0H1879L1994 115V585L1879 700H1519ZM1807 584 1858 533V167L1807 116H1591L1540 167V533L1591 584Z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,82 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="570" height="570" viewBox="230 213 570 570"
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
role="img" aria-labelledby="title desc"
|
||||||
|
shape-rendering="geometricPrecision">
|
||||||
|
<title id="title">SILO emblem</title>
|
||||||
|
<desc id="desc">A smooth circular SILO mark with a peaked roof, flowing left wall, columns, and a curved base.</desc>
|
||||||
|
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="silo-color" x1="276" y1="720" x2="742" y2="286" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#064A83"/>
|
||||||
|
<stop offset="0.5" stop-color="#007FA8"/>
|
||||||
|
<stop offset="1" stop-color="#22C7C9"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<g fill="url(#silo-color)">
|
||||||
|
<!-- Outer circular band, intentionally opened at the lower-right plinth. -->
|
||||||
|
<path d="
|
||||||
|
M 734 676
|
||||||
|
A 283.5 278.5 0 1 0 310 692
|
||||||
|
L 359 692
|
||||||
|
A 247 248.5 0 1 1 688 676
|
||||||
|
Z"/>
|
||||||
|
|
||||||
|
<!-- Flowing left wall; its upper tangent matches the inner ellipse. -->
|
||||||
|
<path d="
|
||||||
|
M 300.57 375
|
||||||
|
C 292 390 300 430 328 450
|
||||||
|
C 343 461 357 472 374 481
|
||||||
|
C 410 501 426 517 426 544
|
||||||
|
L 426 676
|
||||||
|
L 336 676
|
||||||
|
C 308 647 286 608 274 565
|
||||||
|
C 262 522 263 479 272 439
|
||||||
|
C 278 413 286 389 300.57 375
|
||||||
|
Z"/>
|
||||||
|
|
||||||
|
<!-- Right column with tangent-continuous upper shoulder. -->
|
||||||
|
<path d="
|
||||||
|
M 602 373
|
||||||
|
Q 602 368 607 370
|
||||||
|
C 619 374 634 381 634 389
|
||||||
|
L 634 647
|
||||||
|
Q 634 649 636 649
|
||||||
|
L 708 649
|
||||||
|
L 734 676
|
||||||
|
L 602 676
|
||||||
|
Z"/>
|
||||||
|
|
||||||
|
<!-- Lower circular cap. -->
|
||||||
|
<path d="
|
||||||
|
M 310 692
|
||||||
|
L 714 692
|
||||||
|
C 668 743 596 774 512 774
|
||||||
|
C 428 774 355 743 310 692
|
||||||
|
Z"/>
|
||||||
|
|
||||||
|
<!-- Main silo body, with a tangent-continuous right shoulder. -->
|
||||||
|
<path d="
|
||||||
|
M 389 389
|
||||||
|
C 389 374 447 351 512 351
|
||||||
|
C 540 351 565 354 580 359
|
||||||
|
Q 583 360 583 364
|
||||||
|
L 583 676
|
||||||
|
L 443 676
|
||||||
|
L 443 541
|
||||||
|
C 443 509 426 490 389 470
|
||||||
|
Z"/>
|
||||||
|
|
||||||
|
<!-- Peaked roof with softly tapered, burr-free tips. -->
|
||||||
|
<path d="
|
||||||
|
M 512 274
|
||||||
|
L 647 365
|
||||||
|
Q 649 370 647 376
|
||||||
|
C 609 350 563 337 512 337
|
||||||
|
C 460 337 414 350 377 376
|
||||||
|
Q 375 370 377 365
|
||||||
|
Z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.2 KiB |
+1
-1
@@ -14,7 +14,7 @@ onlyLabels: []
|
|||||||
exemptLabels:
|
exemptLabels:
|
||||||
- "security"
|
- "security"
|
||||||
- "pending discussion"
|
- "pending discussion"
|
||||||
- "do not close"
|
- "do-not-close"
|
||||||
|
|
||||||
# Set to true to ignore issues in a project (defaults to false)
|
# Set to true to ignore issues in a project (defaults to false)
|
||||||
exemptProjects: false
|
exemptProjects: false
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
name: DCO
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
name: Verify DCO sign-off
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
# Every non-merge commit in the pull request must carry a Signed-off-by
|
||||||
|
# trailer matching the commit author's email, certifying the Developer
|
||||||
|
# Certificate of Origin 1.1 (https://developercertificate.org/).
|
||||||
|
# Only commits authored from a GitHub-issued bot address are exempt; a
|
||||||
|
# display name is attacker-controlled and must never grant the exemption.
|
||||||
|
- name: Check Signed-off-by trailers
|
||||||
|
env:
|
||||||
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||||
|
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||||
|
run: |
|
||||||
|
fail=0
|
||||||
|
while read -r sha; do
|
||||||
|
author_name="$(git log -1 --format='%an' "${sha}")"
|
||||||
|
author_email="$(git log -1 --format='%ae' "${sha}")"
|
||||||
|
case "${author_email}" in
|
||||||
|
*"[bot]@users.noreply.github.com") continue ;;
|
||||||
|
esac
|
||||||
|
if ! git log -1 --format='%(trailers:key=Signed-off-by,valueonly)' "${sha}" |
|
||||||
|
grep -qiF "<${author_email}>"; then
|
||||||
|
echo "::error::commit ${sha} by ${author_name} <${author_email}> lacks a matching Signed-off-by trailer; sign with 'git commit -s', repair with 'git rebase --signoff'"
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
|
done < <(git rev-list --no-merges "${BASE_SHA}..${HEAD_SHA}")
|
||||||
|
exit "${fail}"
|
||||||
@@ -0,0 +1,422 @@
|
|||||||
|
name: Publish Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: "Published RELEASE.* tag to package as pgsty/silo"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
recovery:
|
||||||
|
description: "Run the current main workflow against an already-published tag"
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
attestations: write
|
||||||
|
artifact-metadata: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: docker-release
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
# Images are built from a published release rather than from the build
|
||||||
|
# that produced it, so an abandoned draft can never leave :latest
|
||||||
|
# pointing at something nobody shipped.
|
||||||
|
- name: Validate published release
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
INPUT_TAG: ${{ inputs.tag }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
TAG="${INPUT_TAG}"
|
||||||
|
VERSION_HYPHEN="${TAG#RELEASE.}"
|
||||||
|
PKG_VERSION="$(echo "${VERSION_HYPHEN}" | sed -E 's/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2})-([0-9]{2})-([0-9]{2})Z$/\1\2\3\4\5\6.0.0/')"
|
||||||
|
if [ "${PKG_VERSION}" = "${VERSION_HYPHEN}" ]; then
|
||||||
|
echo "Invalid release tag: ${TAG}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
IS_DRAFT="$(gh release view "${TAG}" --repo "${GITHUB_REPOSITORY}" --json isDraft --jq .isDraft)"
|
||||||
|
IS_PRERELEASE="$(gh release view "${TAG}" --repo "${GITHUB_REPOSITORY}" --json isPrerelease --jq .isPrerelease)"
|
||||||
|
PUBLISHED_AT="$(gh release view "${TAG}" --repo "${GITHUB_REPOSITORY}" --json publishedAt --jq .publishedAt)"
|
||||||
|
LATEST_TAG="$(gh release view --repo "${GITHUB_REPOSITORY}" --json tagName --jq .tagName)"
|
||||||
|
|
||||||
|
if [ "${IS_DRAFT}" != false ] || [ "${IS_PRERELEASE}" != false ] || [ -z "${PUBLISHED_AT}" ]; then
|
||||||
|
echo "${TAG} must be a published, non-prerelease GitHub Release"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# This workflow moves :latest, so it must not run for an older tag.
|
||||||
|
if [ "${TAG}" != "${LATEST_TAG}" ]; then
|
||||||
|
echo "Refusing to replace Docker latest with non-latest release ${TAG} (latest is ${LATEST_TAG})"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "RELEASE_TAG=${TAG}"
|
||||||
|
echo "PKG_VERSION=${PKG_VERSION}"
|
||||||
|
echo "PUBLISHED_AT=${PUBLISHED_AT}"
|
||||||
|
} >> "${GITHUB_ENV}"
|
||||||
|
|
||||||
|
- name: Validate Docker Hub credentials
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [ -z "${{ secrets.DOCKERHUB_USERNAME }}" ] || [ -z "${{ secrets.DOCKERHUB_TOKEN }}" ]; then
|
||||||
|
echo "Missing Docker Hub credentials. Set DOCKERHUB_USERNAME and DOCKERHUB_TOKEN repository secrets."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Checkout release tag
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.tag }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Verify workflow identity matches release source
|
||||||
|
env:
|
||||||
|
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||||
|
RECOVERY: ${{ inputs.recovery }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
CHECKED_OUT_REVISION="$(git rev-parse HEAD)"
|
||||||
|
if [ "${CHECKED_OUT_REVISION}" != "${GITHUB_SHA}" ]; then
|
||||||
|
if [ "${RECOVERY}" != "true" ] || [ "${GITHUB_REF}" != "refs/heads/${DEFAULT_BRANCH}" ]; then
|
||||||
|
echo "Checked out ${CHECKED_OUT_REVISION}, but workflow identity is ${GITHUB_SHA}. Dispatch from ${RELEASE_TAG}, or use recovery from ${DEFAULT_BRANCH}." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Recovery workflow ${GITHUB_SHA} is packaging published source ${CHECKED_OUT_REVISION}."
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Prepare verified Docker contexts
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
assets_dir="docker-release/assets"
|
||||||
|
mkdir -p "${assets_dir}"
|
||||||
|
|
||||||
|
amd64_archive="silo_${PKG_VERSION}_linux_amd64.tar.gz"
|
||||||
|
arm64_archive="silo_${PKG_VERSION}_linux_arm64.tar.gz"
|
||||||
|
checksums="silo_${PKG_VERSION}_checksums.txt"
|
||||||
|
gh release download "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" \
|
||||||
|
--dir "${assets_dir}" \
|
||||||
|
--pattern "${amd64_archive}" \
|
||||||
|
--pattern "${arm64_archive}" \
|
||||||
|
--pattern "${checksums}"
|
||||||
|
|
||||||
|
# The binaries going into the images are the published ones, checked
|
||||||
|
# against the published checksums, not a rebuild that merely ought to
|
||||||
|
# match them. awk matches the manifest filename column exactly: a
|
||||||
|
# substring grep would also pull in the archive's .sbom.json line,
|
||||||
|
# whose file is deliberately not downloaded in this lane.
|
||||||
|
cd "${assets_dir}"
|
||||||
|
awk -v name="${amd64_archive}" '$2 == name' "${checksums}" | sha256sum --check
|
||||||
|
awk -v name="${arm64_archive}" '$2 == name' "${checksums}" | sha256sum --check
|
||||||
|
|
||||||
|
# The checksum manifest and both archives must have provenance from
|
||||||
|
# this repository's release workflow at the exact checked-out tag.
|
||||||
|
for artifact in "${checksums}" "${amd64_archive}" "${arm64_archive}"; do
|
||||||
|
gh attestation verify "${artifact}" \
|
||||||
|
--repo "${GITHUB_REPOSITORY}" \
|
||||||
|
--signer-workflow "${GITHUB_REPOSITORY}/.github/workflows/release.yml" \
|
||||||
|
--source-digest "$(git -C "${GITHUB_WORKSPACE}" rev-parse HEAD)" \
|
||||||
|
--source-ref "refs/tags/${RELEASE_TAG}" >/dev/null
|
||||||
|
done
|
||||||
|
cd "${GITHUB_WORKSPACE}"
|
||||||
|
|
||||||
|
# Dockerfile.goreleaser expects the binary at the context root and
|
||||||
|
# the entrypoint scripts under dockerscripts/, which is the layout
|
||||||
|
# GoReleaser used to assemble via extra_files.
|
||||||
|
for arch in amd64 arm64; do
|
||||||
|
context="docker-release/${arch}"
|
||||||
|
archive="${assets_dir}/silo_${PKG_VERSION}_linux_${arch}.tar.gz"
|
||||||
|
mkdir -p "${context}/dockerscripts"
|
||||||
|
tar -xzf "${archive}" -C "${context}" silo
|
||||||
|
cp Dockerfile.goreleaser Dockerfile.distroless LICENSE NOTICE CREDITS "${context}/"
|
||||||
|
cp dockerscripts/docker-entrypoint.sh dockerscripts/build-static-curl.sh \
|
||||||
|
"${context}/dockerscripts/"
|
||||||
|
done
|
||||||
|
|
||||||
|
# The classic image bundles mcli. Resolve its two archive digests
|
||||||
|
# from the immutable published release instead of trusting defaults
|
||||||
|
# copied into an older Server tag. This also gives a recovery run a
|
||||||
|
# narrow override when a tag selected the right mcli release but
|
||||||
|
# accidentally retained stale archive pins.
|
||||||
|
MC_REPO="$(awk -F= '/^ARG MC_REPO=/{print $2; exit}' Dockerfile.goreleaser)"
|
||||||
|
MC_VERSION="$(awk -F= '/^ARG MC_VERSION=/{print $2; exit}' Dockerfile.goreleaser)"
|
||||||
|
test -n "${MC_REPO}"
|
||||||
|
test -n "${MC_VERSION}"
|
||||||
|
MC_VERSION_HYPHEN="${MC_VERSION#RELEASE.}"
|
||||||
|
MC_PKG_VERSION="$(echo "${MC_VERSION_HYPHEN}" | sed -E 's/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2})-([0-9]{2})-([0-9]{2})Z$/\1\2\3\4\5\6.0.0/')"
|
||||||
|
if [ "${MC_PKG_VERSION}" = "${MC_VERSION_HYPHEN}" ]; then
|
||||||
|
echo "Invalid bundled mcli tag: ${MC_VERSION}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ "$(gh release view "${MC_VERSION}" --repo "${MC_REPO}" --json isDraft --jq .isDraft)" != false ] || \
|
||||||
|
[ "$(gh release view "${MC_VERSION}" --repo "${MC_REPO}" --json isPrerelease --jq .isPrerelease)" != false ] || \
|
||||||
|
[ "$(gh release view "${MC_VERSION}" --repo "${MC_REPO}" --json isImmutable --jq .isImmutable)" != true ]; then
|
||||||
|
echo "Bundled mcli ${MC_REPO}@${MC_VERSION} must be a published immutable release" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mc_checksums="mcli_${MC_PKG_VERSION}_checksums.txt"
|
||||||
|
gh release download "${MC_VERSION}" --repo "${MC_REPO}" \
|
||||||
|
--dir "${assets_dir}" --pattern "${mc_checksums}"
|
||||||
|
gh attestation verify "${assets_dir}/${mc_checksums}" \
|
||||||
|
--repo "${MC_REPO}" \
|
||||||
|
--signer-workflow "${MC_REPO}/.github/workflows/release.yml" \
|
||||||
|
--source-ref "refs/tags/${MC_VERSION}" >/dev/null
|
||||||
|
|
||||||
|
MC_AMD64_SHA256="$(awk -v name="mcli_${MC_PKG_VERSION}_linux_amd64.tar.gz" '$2 == name {print $1}' "${assets_dir}/${mc_checksums}")"
|
||||||
|
MC_ARM64_SHA256="$(awk -v name="mcli_${MC_PKG_VERSION}_linux_arm64.tar.gz" '$2 == name {print $1}' "${assets_dir}/${mc_checksums}")"
|
||||||
|
[[ "${MC_AMD64_SHA256}" =~ ^[0-9a-f]{64}$ ]]
|
||||||
|
[[ "${MC_ARM64_SHA256}" =~ ^[0-9a-f]{64}$ ]]
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "MC_AMD64_SHA256=${MC_AMD64_SHA256}"
|
||||||
|
echo "MC_ARM64_SHA256=${MC_ARM64_SHA256}"
|
||||||
|
} >> "${GITHUB_ENV}"
|
||||||
|
|
||||||
|
echo "RELEASE_REVISION=$(git rev-parse HEAD)" >> "${GITHUB_ENV}"
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v4
|
||||||
|
with:
|
||||||
|
platforms: arm64
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v4
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Install Syft
|
||||||
|
uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
|
||||||
|
with:
|
||||||
|
syft-version: v1.50.0
|
||||||
|
|
||||||
|
- name: Build and push amd64 image
|
||||||
|
id: build-amd64
|
||||||
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: docker-release/amd64
|
||||||
|
file: docker-release/amd64/Dockerfile.goreleaser
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
build-args: |
|
||||||
|
MC_AMD64_SHA256=${{ env.MC_AMD64_SHA256 }}
|
||||||
|
MC_ARM64_SHA256=${{ env.MC_ARM64_SHA256 }}
|
||||||
|
tags: |
|
||||||
|
pgsty/silo:${{ env.RELEASE_TAG }}-amd64
|
||||||
|
pgsty/silo:latest-amd64
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.version=${{ env.RELEASE_TAG }}
|
||||||
|
org.opencontainers.image.created=${{ env.PUBLISHED_AT }}
|
||||||
|
org.opencontainers.image.revision=${{ env.RELEASE_REVISION }}
|
||||||
|
|
||||||
|
- name: Build and push arm64 image
|
||||||
|
id: build-arm64
|
||||||
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: docker-release/arm64
|
||||||
|
file: docker-release/arm64/Dockerfile.goreleaser
|
||||||
|
platforms: linux/arm64
|
||||||
|
push: true
|
||||||
|
build-args: |
|
||||||
|
MC_AMD64_SHA256=${{ env.MC_AMD64_SHA256 }}
|
||||||
|
MC_ARM64_SHA256=${{ env.MC_ARM64_SHA256 }}
|
||||||
|
tags: |
|
||||||
|
pgsty/silo:${{ env.RELEASE_TAG }}-arm64
|
||||||
|
pgsty/silo:latest-arm64
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.version=${{ env.RELEASE_TAG }}
|
||||||
|
org.opencontainers.image.created=${{ env.PUBLISHED_AT }}
|
||||||
|
org.opencontainers.image.revision=${{ env.RELEASE_REVISION }}
|
||||||
|
|
||||||
|
# The distroless variant is a pilot published alongside the classic
|
||||||
|
# image; it ships the silo binary alone and relies on the native
|
||||||
|
# `silo healthcheck` subcommand for container health.
|
||||||
|
# Design: https://silo.pgsty.com/compatibility/feature/healthcheck/
|
||||||
|
- name: Build and push amd64 distroless image
|
||||||
|
id: build-amd64-distroless
|
||||||
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: docker-release/amd64
|
||||||
|
file: docker-release/amd64/Dockerfile.distroless
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
pgsty/silo:${{ env.RELEASE_TAG }}-distroless-amd64
|
||||||
|
pgsty/silo:distroless-amd64
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.version=${{ env.RELEASE_TAG }}
|
||||||
|
org.opencontainers.image.created=${{ env.PUBLISHED_AT }}
|
||||||
|
org.opencontainers.image.revision=${{ env.RELEASE_REVISION }}
|
||||||
|
|
||||||
|
- name: Build and push arm64 distroless image
|
||||||
|
id: build-arm64-distroless
|
||||||
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
context: docker-release/arm64
|
||||||
|
file: docker-release/arm64/Dockerfile.distroless
|
||||||
|
platforms: linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
pgsty/silo:${{ env.RELEASE_TAG }}-distroless-arm64
|
||||||
|
pgsty/silo:distroless-arm64
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.version=${{ env.RELEASE_TAG }}
|
||||||
|
org.opencontainers.image.created=${{ env.PUBLISHED_AT }}
|
||||||
|
org.opencontainers.image.revision=${{ env.RELEASE_REVISION }}
|
||||||
|
|
||||||
|
- name: Verify HEALTHCHECK survived the distroless push
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
# HEALTHCHECK is a Docker extension absent from the OCI image
|
||||||
|
# spec, and a publish path can drop it silently. Check the pushed
|
||||||
|
# architecture image now, before the versioned and rolling
|
||||||
|
# multi-arch manifests are created, so a broken health config
|
||||||
|
# stops their promotion. (The architecture-suffixed tags above
|
||||||
|
# are already public by this point - full staging-then-promote
|
||||||
|
# would be a workflow-wide redesign shared with the classic
|
||||||
|
# image lanes.)
|
||||||
|
docker pull "pgsty/silo:${RELEASE_TAG}-distroless-amd64" >/dev/null
|
||||||
|
test "$(docker inspect -f '{{json .Config.Healthcheck.Test}}' "pgsty/silo:${RELEASE_TAG}-distroless-amd64")" \
|
||||||
|
= '["CMD","/usr/bin/silo","healthcheck","ready"]'
|
||||||
|
|
||||||
|
- name: Publish multi-architecture manifests
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir -p docker-release/metadata
|
||||||
|
docker buildx imagetools create \
|
||||||
|
--tag "pgsty/silo:${RELEASE_TAG}" \
|
||||||
|
--metadata-file docker-release/metadata/release.json \
|
||||||
|
"pgsty/silo:${RELEASE_TAG}-amd64" \
|
||||||
|
"pgsty/silo:${RELEASE_TAG}-arm64"
|
||||||
|
docker buildx imagetools create \
|
||||||
|
--tag "pgsty/silo:latest" \
|
||||||
|
--metadata-file docker-release/metadata/latest.json \
|
||||||
|
"pgsty/silo:latest-amd64" \
|
||||||
|
"pgsty/silo:latest-arm64"
|
||||||
|
docker buildx imagetools inspect "pgsty/silo:${RELEASE_TAG}"
|
||||||
|
docker buildx imagetools inspect "pgsty/silo:latest"
|
||||||
|
|
||||||
|
docker buildx imagetools create \
|
||||||
|
--tag "pgsty/silo:${RELEASE_TAG}-distroless" \
|
||||||
|
--metadata-file docker-release/metadata/release-distroless.json \
|
||||||
|
"pgsty/silo:${RELEASE_TAG}-distroless-amd64" \
|
||||||
|
"pgsty/silo:${RELEASE_TAG}-distroless-arm64"
|
||||||
|
docker buildx imagetools create \
|
||||||
|
--tag "pgsty/silo:distroless" \
|
||||||
|
--metadata-file docker-release/metadata/distroless.json \
|
||||||
|
"pgsty/silo:distroless-amd64" \
|
||||||
|
"pgsty/silo:distroless-arm64"
|
||||||
|
docker buildx imagetools inspect "pgsty/silo:${RELEASE_TAG}-distroless"
|
||||||
|
docker buildx imagetools inspect "pgsty/silo:distroless"
|
||||||
|
|
||||||
|
RELEASE_DIGEST="$(jq -r '."containerimage.descriptor".digest' docker-release/metadata/release.json)"
|
||||||
|
LATEST_DIGEST="$(jq -r '."containerimage.descriptor".digest' docker-release/metadata/latest.json)"
|
||||||
|
if ! [[ "${RELEASE_DIGEST}" =~ ^sha256:[0-9a-f]{64}$ ]]; then
|
||||||
|
echo "Invalid release manifest digest: ${RELEASE_DIGEST}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ "${RELEASE_DIGEST}" != "${LATEST_DIGEST}" ]; then
|
||||||
|
echo "Release and latest tags resolved to different manifests" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "SILO_IMAGE_DIGEST=${RELEASE_DIGEST}" >> "${GITHUB_ENV}"
|
||||||
|
|
||||||
|
DISTROLESS_RELEASE_DIGEST="$(jq -r '."containerimage.descriptor".digest' docker-release/metadata/release-distroless.json)"
|
||||||
|
DISTROLESS_ROLLING_DIGEST="$(jq -r '."containerimage.descriptor".digest' docker-release/metadata/distroless.json)"
|
||||||
|
if ! [[ "${DISTROLESS_RELEASE_DIGEST}" =~ ^sha256:[0-9a-f]{64}$ ]]; then
|
||||||
|
echo "Invalid distroless manifest digest: ${DISTROLESS_RELEASE_DIGEST}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ "${DISTROLESS_RELEASE_DIGEST}" != "${DISTROLESS_ROLLING_DIGEST}" ]; then
|
||||||
|
echo "Distroless release and rolling tags resolved to different manifests" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "SILO_DISTROLESS_DIGEST=${DISTROLESS_RELEASE_DIGEST}" >> "${GITHUB_ENV}"
|
||||||
|
|
||||||
|
- name: Generate architecture image SBOMs
|
||||||
|
env:
|
||||||
|
AMD64_DIGEST: ${{ steps.build-amd64.outputs.digest }}
|
||||||
|
ARM64_DIGEST: ${{ steps.build-arm64.outputs.digest }}
|
||||||
|
DISTROLESS_AMD64_DIGEST: ${{ steps.build-amd64-distroless.outputs.digest }}
|
||||||
|
DISTROLESS_ARM64_DIGEST: ${{ steps.build-arm64-distroless.outputs.digest }}
|
||||||
|
SYFT_CHECK_FOR_APP_UPDATE: "false"
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir -p docker-release/sbom
|
||||||
|
# Each per-architecture digest names an OCI index (image plus the
|
||||||
|
# provenance attestation buildx attaches), and Syft's platform
|
||||||
|
# default on an index follows the amd64 runner - an arm64-only
|
||||||
|
# index would fail outright. Select the platform explicitly.
|
||||||
|
syft "registry:index.docker.io/pgsty/silo@${AMD64_DIGEST}" \
|
||||||
|
--platform linux/amd64 \
|
||||||
|
--output "spdx-json=docker-release/sbom/linux-amd64.spdx.json"
|
||||||
|
syft "registry:index.docker.io/pgsty/silo@${ARM64_DIGEST}" \
|
||||||
|
--platform linux/arm64 \
|
||||||
|
--output "spdx-json=docker-release/sbom/linux-arm64.spdx.json"
|
||||||
|
syft "registry:index.docker.io/pgsty/silo@${DISTROLESS_AMD64_DIGEST}" \
|
||||||
|
--platform linux/amd64 \
|
||||||
|
--output "spdx-json=docker-release/sbom/linux-amd64-distroless.spdx.json"
|
||||||
|
syft "registry:index.docker.io/pgsty/silo@${DISTROLESS_ARM64_DIGEST}" \
|
||||||
|
--platform linux/arm64 \
|
||||||
|
--output "spdx-json=docker-release/sbom/linux-arm64-distroless.spdx.json"
|
||||||
|
|
||||||
|
- name: Attest amd64 image SBOM
|
||||||
|
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
|
||||||
|
with:
|
||||||
|
subject-name: index.docker.io/pgsty/silo
|
||||||
|
subject-digest: ${{ steps.build-amd64.outputs.digest }}
|
||||||
|
sbom-path: docker-release/sbom/linux-amd64.spdx.json
|
||||||
|
push-to-registry: true
|
||||||
|
|
||||||
|
- name: Attest arm64 image SBOM
|
||||||
|
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
|
||||||
|
with:
|
||||||
|
subject-name: index.docker.io/pgsty/silo
|
||||||
|
subject-digest: ${{ steps.build-arm64.outputs.digest }}
|
||||||
|
sbom-path: docker-release/sbom/linux-arm64.spdx.json
|
||||||
|
push-to-registry: true
|
||||||
|
|
||||||
|
- name: Attest amd64 distroless image SBOM
|
||||||
|
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
|
||||||
|
with:
|
||||||
|
subject-name: index.docker.io/pgsty/silo
|
||||||
|
subject-digest: ${{ steps.build-amd64-distroless.outputs.digest }}
|
||||||
|
sbom-path: docker-release/sbom/linux-amd64-distroless.spdx.json
|
||||||
|
push-to-registry: true
|
||||||
|
|
||||||
|
- name: Attest arm64 distroless image SBOM
|
||||||
|
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
|
||||||
|
with:
|
||||||
|
subject-name: index.docker.io/pgsty/silo
|
||||||
|
subject-digest: ${{ steps.build-arm64-distroless.outputs.digest }}
|
||||||
|
sbom-path: docker-release/sbom/linux-arm64-distroless.spdx.json
|
||||||
|
push-to-registry: true
|
||||||
|
|
||||||
|
- name: Attest multi-architecture image provenance
|
||||||
|
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
|
||||||
|
with:
|
||||||
|
subject-name: index.docker.io/pgsty/silo
|
||||||
|
subject-digest: ${{ env.SILO_IMAGE_DIGEST }}
|
||||||
|
push-to-registry: true
|
||||||
|
|
||||||
|
- name: Attest multi-architecture distroless image provenance
|
||||||
|
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
|
||||||
|
with:
|
||||||
|
subject-name: index.docker.io/pgsty/silo
|
||||||
|
subject-digest: ${{ env.SILO_DISTROLESS_DIGEST }}
|
||||||
|
push-to-registry: true
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
name: Finalize Release Packages
|
||||||
|
|
||||||
|
# Manual-only lane that runs AFTER the maintainer has GPG-signed the RPMs in a
|
||||||
|
# Draft release (buildscripts/sign-release-rpms.sh --upload) and BEFORE the
|
||||||
|
# release is published. GPG signing rewrites the RPM bytes, which strands the
|
||||||
|
# SBOMs, the packages checksum manifest, and the attestations that release.yml
|
||||||
|
# generated from the as-built packages. This lane regenerates those materials
|
||||||
|
# from the published (signed) bytes under the workflow identity, so the
|
||||||
|
# sigstore layer describes exactly what the release ships.
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: "Draft RELEASE.* tag whose signed RPMs need refreshed SBOMs and checksums"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
id-token: write
|
||||||
|
attestations: write
|
||||||
|
artifact-metadata: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: release-${{ inputs.tag }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
finalize:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout release tag
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.tag }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Verify workflow identity matches release source
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
CHECKED_OUT_REVISION="$(git rev-parse HEAD)"
|
||||||
|
if [ "${CHECKED_OUT_REVISION}" != "${GITHUB_SHA}" ]; then
|
||||||
|
echo "Checked out ${CHECKED_OUT_REVISION}, but workflow identity is ${GITHUB_SHA}. Dispatch from the release tag." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Compute release variables
|
||||||
|
env:
|
||||||
|
# Environment passthrough keeps the dispatch input out of the script
|
||||||
|
# source, mirroring release.yml.
|
||||||
|
INPUT_TAG: ${{ inputs.tag }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
TAG="${INPUT_TAG}"
|
||||||
|
if [[ ! "${TAG}" =~ ^RELEASE\.[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}-[0-9]{2}-[0-9]{2}Z$ ]]; then
|
||||||
|
echo "Invalid release tag format: ${TAG}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
VERSION_HYPHEN="${TAG#RELEASE.}"
|
||||||
|
PKG_VERSION="$(echo "${VERSION_HYPHEN}" | sed -E 's/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2})-([0-9]{2})-([0-9]{2})Z$/\1\2\3\4\5\6.0.0/')"
|
||||||
|
{
|
||||||
|
echo "RELEASE_TAG=${TAG}"
|
||||||
|
echo "PKG_VERSION=${PKG_VERSION}"
|
||||||
|
} >> "${GITHUB_ENV}"
|
||||||
|
|
||||||
|
- name: Refuse to touch a published release
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [ "$(gh release view "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" --json isDraft --jq .isDraft)" != "true" ]; then
|
||||||
|
echo "${RELEASE_TAG} is not a Draft release; finalize runs only before publishing." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Install Syft
|
||||||
|
uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
|
||||||
|
with:
|
||||||
|
syft-version: v1.50.0
|
||||||
|
|
||||||
|
- name: Install Cosign
|
||||||
|
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
||||||
|
with:
|
||||||
|
cosign-release: v3.1.2
|
||||||
|
|
||||||
|
- name: Download and verify the package set
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
# sign-release-rpms.sh owns the package identity; import its values
|
||||||
|
# the same way test-release.yml does so the lanes cannot drift.
|
||||||
|
eval "$(grep -E '^expected_(release|fingerprint)=' buildscripts/sign-release-rpms.sh)"
|
||||||
|
test -n "${expected_release}"
|
||||||
|
test -n "${expected_fingerprint}"
|
||||||
|
|
||||||
|
packages_dir="finalize/packages"
|
||||||
|
sidecar_dir="finalize/sidecars"
|
||||||
|
mkdir -p "${packages_dir}" "${sidecar_dir}"
|
||||||
|
|
||||||
|
manifest="silo_${PKG_VERSION}_packages_checksums.txt"
|
||||||
|
|
||||||
|
# Exactly the twelve manifest subjects land in packages_dir; the
|
||||||
|
# tarball SBOMs in the release root do not match these patterns.
|
||||||
|
gh release download "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" --dir "${packages_dir}" \
|
||||||
|
--pattern '*.rpm' --pattern '*.deb' --pattern '*.apk' \
|
||||||
|
--pattern '*.rpm.sbom.json' --pattern '*.deb.sbom.json' --pattern '*.apk.sbom.json'
|
||||||
|
gh release download "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" --dir "${sidecar_dir}" \
|
||||||
|
--pattern '*.rpm.sha256sum' --pattern "${manifest}"
|
||||||
|
|
||||||
|
cd "${packages_dir}"
|
||||||
|
subject_count="$(find . -maxdepth 1 -type f | wc -l | tr -d ' ')"
|
||||||
|
if [ "${subject_count}" -ne 12 ]; then
|
||||||
|
echo "Expected twelve package subjects, found ${subject_count}" >&2
|
||||||
|
find . -maxdepth 1 -type f >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The signed RPMs must match the .sha256sum sidecars the signing
|
||||||
|
# script regenerated and uploaded alongside them.
|
||||||
|
for rpm_file in "silo-${PKG_VERSION}-${expected_release}.x86_64.rpm" \
|
||||||
|
"silo-${PKG_VERSION}-${expected_release}.aarch64.rpm"; do
|
||||||
|
test -s "${rpm_file}"
|
||||||
|
actual="$(sha256sum "${rpm_file}" | awk '{print $1}')"
|
||||||
|
recorded="$(awk '{print $1}' "../sidecars/${rpm_file}.sha256sum")"
|
||||||
|
if [ "${actual}" != "${recorded}" ]; then
|
||||||
|
echo "Digest mismatch for ${rpm_file}: sidecar ${recorded}, asset ${actual}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Everything the maintainer did not re-sign must still match the
|
||||||
|
# manifest release.yml generated: this lane refreshes RPM materials,
|
||||||
|
# it does not accept drift anywhere else.
|
||||||
|
for package_file in *.deb *.apk *.deb.sbom.json *.apk.sbom.json; do
|
||||||
|
awk -v name="${package_file}" '$2 == name' "../sidecars/${manifest}" | sha256sum --check
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Verify RPM GPG signatures
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
eval "$(grep -E '^expected_fingerprint=' buildscripts/sign-release-rpms.sh)"
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install --yes rpm
|
||||||
|
sudo rpmkeys --import buildscripts/pgsty-rpm-signing-key.asc
|
||||||
|
key_id="$(printf '%s' "${expected_fingerprint}" | tail -c 8 | tr '[:upper:]' '[:lower:]')"
|
||||||
|
for rpm_file in finalize/packages/*.rpm; do
|
||||||
|
signature_output="$(sudo rpmkeys --checksig --verbose "${rpm_file}")"
|
||||||
|
printf '%s\n' "${signature_output}"
|
||||||
|
if ! printf '%s\n' "${signature_output}" | tr '[:upper:]' '[:lower:]' | grep -q "key id ${key_id}: ok"; then
|
||||||
|
echo "Signature verification failed for ${rpm_file}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Regenerate RPM SBOMs and the packages checksum manifest
|
||||||
|
env:
|
||||||
|
SYFT_CHECK_FOR_APP_UPDATE: "false"
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
cd finalize/packages
|
||||||
|
for rpm_file in *.rpm; do
|
||||||
|
rm -f "${rpm_file}.sbom.json"
|
||||||
|
syft "${rpm_file}" --output "spdx-json=${rpm_file}.sbom.json"
|
||||||
|
done
|
||||||
|
|
||||||
|
manifest="silo_${PKG_VERSION}_packages_checksums.txt"
|
||||||
|
mapfile -t subjects < <(find . -maxdepth 1 -type f \
|
||||||
|
\( -name '*.rpm' -o -name '*.deb' -o -name '*.apk' -o -name '*.sbom.json' \) | sort)
|
||||||
|
if [ "${#subjects[@]}" -ne 12 ]; then
|
||||||
|
echo "Expected six packages and six SBOMs, found ${#subjects[@]} subjects" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sha256sum "${subjects[@]}" | sed 's# \./# #' > "${manifest}"
|
||||||
|
cosign sign-blob --bundle="${manifest}.sigstore.json" "${manifest}" --yes
|
||||||
|
|
||||||
|
- name: Attest the finalized package artifacts
|
||||||
|
id: attest-finalize
|
||||||
|
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
|
||||||
|
with:
|
||||||
|
subject-path: |
|
||||||
|
finalize/packages/*.rpm
|
||||||
|
finalize/packages/*.rpm.sbom.json
|
||||||
|
finalize/packages/*_checksums.txt
|
||||||
|
finalize/packages/*_checksums.txt.sigstore.json
|
||||||
|
finalize/sidecars/*.rpm.sha256sum
|
||||||
|
|
||||||
|
- name: Preserve finalize provenance bundle as a release asset
|
||||||
|
env:
|
||||||
|
BUNDLE_PATH: ${{ steps.attest-finalize.outputs.bundle-path }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
test -s "${BUNDLE_PATH}"
|
||||||
|
cp "${BUNDLE_PATH}" "finalize/packages/silo_${PKG_VERSION}_packages_provenance.sigstore.json"
|
||||||
|
|
||||||
|
- name: Upload finalized assets to the Draft release
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
eval "$(grep -E '^expected_release=' buildscripts/sign-release-rpms.sh)"
|
||||||
|
manifest="silo_${PKG_VERSION}_packages_checksums.txt"
|
||||||
|
cd finalize/packages
|
||||||
|
files=(
|
||||||
|
"silo-${PKG_VERSION}-${expected_release}.x86_64.rpm.sbom.json"
|
||||||
|
"silo-${PKG_VERSION}-${expected_release}.aarch64.rpm.sbom.json"
|
||||||
|
"${manifest}"
|
||||||
|
"${manifest}.sigstore.json"
|
||||||
|
"silo_${PKG_VERSION}_packages_provenance.sigstore.json"
|
||||||
|
)
|
||||||
|
gh release upload "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" --clobber "${files[@]}"
|
||||||
|
|
||||||
|
for asset in "${files[@]}"; do
|
||||||
|
local_digest="sha256:$(sha256sum "${asset}" | awk '{print $1}')"
|
||||||
|
remote_digest=""
|
||||||
|
for attempt in 1 2 3 4 5; do
|
||||||
|
remote_digest="$(gh release view "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" --json assets \
|
||||||
|
--jq ".assets[] | select(.name == \"${asset}\") | .digest")"
|
||||||
|
if [ "${local_digest}" = "${remote_digest}" ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if [ "${attempt}" -lt 5 ]; then
|
||||||
|
sleep 2
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ "${local_digest}" != "${remote_digest}" ]; then
|
||||||
|
echo "GitHub asset digest mismatch for ${asset}" >&2
|
||||||
|
echo "Local: ${local_digest}" >&2
|
||||||
|
echo "Remote: ${remote_digest}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Verified GitHub asset: ${asset} ${remote_digest}"
|
||||||
|
done
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
name: Go
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: MinIO crosscompile tests on ${{ matrix.go-version }} and ${{ matrix.os }}
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
go-version: [1.16.x, 1.17.x]
|
|
||||||
os: [ubuntu-latest]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: '12'
|
|
||||||
- uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: ${{ matrix.go-version }}
|
|
||||||
- name: Build on ${{ matrix.os }}
|
|
||||||
if: matrix.os == 'ubuntu-latest'
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
GO111MODULE: on
|
|
||||||
run: |
|
|
||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
|
||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
|
||||||
make crosscompile
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
name: Go
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: MinIO tests on ${{ matrix.go-version }} and ${{ matrix.os }}
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
go-version: [1.16.x, 1.17.x]
|
|
||||||
os: [ubuntu-latest, windows-latest]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: ${{ matrix.go-version }}
|
|
||||||
- name: Build on ${{ matrix.os }}
|
|
||||||
if: matrix.os == 'windows-latest'
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
GO111MODULE: on
|
|
||||||
run: |
|
|
||||||
go build --ldflags="-s -w" -o %GOPATH%\bin\minio.exe
|
|
||||||
go test -v --timeout 50m ./...
|
|
||||||
- name: Build on ${{ matrix.os }}
|
|
||||||
if: matrix.os == 'ubuntu-latest'
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
GO111MODULE: on
|
|
||||||
run: |
|
|
||||||
sudo apt install jq -y
|
|
||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
|
||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
|
||||||
nancy_version=$(curl --retry 10 -Ls -o /dev/null -w "%{url_effective}" https://github.com/sonatype-nexus-community/nancy/releases/latest | sed "s/https:\/\/github.com\/sonatype-nexus-community\/nancy\/releases\/tag\///")
|
|
||||||
curl -L -o nancy https://github.com/sonatype-nexus-community/nancy/releases/download/${nancy_version}/nancy-${nancy_version}-linux-amd64 && chmod +x nancy
|
|
||||||
go list -deps -json ./... | jq -s 'unique_by(.Module.Path)|.[]|select(has("Module"))|.Module' | ./nancy sleuth
|
|
||||||
make
|
|
||||||
make test-race
|
|
||||||
+143
-22
@@ -1,35 +1,156 @@
|
|||||||
name: Go
|
name: Go CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- main
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# Cancel superseded runs for the same PR; never cancel main push runs.
|
||||||
|
# Keyed on PR number (not head_ref) so fork PRs sharing a branch name
|
||||||
|
# do not collide.
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
verify:
|
||||||
name: MinIO Setup on ${{ matrix.go-version }} and ${{ matrix.os }}
|
name: Format, Build, Vet
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
timeout-minutes: 20
|
||||||
matrix:
|
|
||||||
go-version: [1.16.x, 1.17.x]
|
|
||||||
os: [ubuntu-latest]
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v7
|
||||||
- uses: actions/setup-go@v2
|
|
||||||
|
- uses: actions/setup-go@v7
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version-file: go.mod
|
||||||
- name: Build on ${{ matrix.os }}
|
cache: true
|
||||||
if: matrix.os == 'ubuntu-latest'
|
|
||||||
|
- name: Check gofmt
|
||||||
|
run: |
|
||||||
|
mapfile -t unformatted < <(gofmt -l main.go cmd internal \
|
||||||
|
buildscripts/rebrand-guard buildscripts/helm-migration-guard)
|
||||||
|
if [ "${#unformatted[@]}" -ne 0 ]; then
|
||||||
|
echo "The following files are not gofmt-formatted:"
|
||||||
|
printf '%s\n' "${unformatted[@]}"
|
||||||
|
gofmt -d "${unformatted[@]}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
GO111MODULE: on
|
run: go build ./...
|
||||||
MINIO_KMS_KES_CERT_FILE: /home/runner/work/minio/minio/.github/workflows/root.cert
|
|
||||||
MINIO_KMS_KES_KEY_FILE: /home/runner/work/minio/minio/.github/workflows/root.key
|
- name: Vet
|
||||||
MINIO_KMS_KES_ENDPOINT: "https://play.min.io:7373"
|
run: go vet ./...
|
||||||
MINIO_KMS_KES_KEY_NAME: "my-minio-key"
|
|
||||||
MINIO_KMS_AUTO_ENCRYPTION: on
|
- name: Verify rebrand compatibility contracts
|
||||||
|
run: |
|
||||||
|
go run ./buildscripts/rebrand-guard
|
||||||
|
buildscripts/verify-rebrand.sh
|
||||||
|
dockerscripts/docker-entrypoint_test.sh
|
||||||
|
|
||||||
|
quality:
|
||||||
|
name: Lint, Generated Files
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 25
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v7
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: make lint
|
||||||
|
|
||||||
|
- name: Check generated files
|
||||||
|
run: make check-gen
|
||||||
|
|
||||||
|
race-s3select:
|
||||||
|
name: Race, S3 Select
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v7
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Run S3 Select tests under race detector
|
||||||
|
run: go test -race ./internal/s3select/... -count=1
|
||||||
|
|
||||||
|
crosscompile:
|
||||||
|
name: Cross Compile
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 20
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v7
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Build supported targets
|
||||||
|
run: make crosscompile
|
||||||
|
|
||||||
|
test-internal:
|
||||||
|
name: Test internal/
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 25
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v7
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
# internal/http listener tests bind [::1]; runners disable IPv6 by default.
|
||||||
|
- name: Enable IPv6
|
||||||
run: |
|
run: |
|
||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
make verify
|
|
||||||
make verify-healing
|
- name: Run internal tests
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
MINIO_API_REQUESTS_MAX: "10000"
|
||||||
|
run: go test ./internal/... -count=1
|
||||||
|
|
||||||
|
test-cmd:
|
||||||
|
name: Test cmd/
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 35
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v7
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
# Some server tests bind IPv6 listeners; runners disable IPv6 by default.
|
||||||
|
- name: Enable IPv6
|
||||||
|
run: |
|
||||||
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
|
|
||||||
|
- name: Run cmd tests
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
MINIO_API_REQUESTS_MAX: "10000"
|
||||||
|
# cmd/ is one large package; raise go test's default 10m per-package
|
||||||
|
# timeout so slower runners fail on the job timeout, not a panic.
|
||||||
|
run: go test ./cmd/ -count=1 -timeout 30m
|
||||||
|
|||||||
@@ -0,0 +1,246 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
# Retry contract: an absent or single unfinalized Draft may be rebuilt from
|
||||||
|
# scratch; a published release or a Draft carrying finalize's GPG-derived
|
||||||
|
# provenance marker is terminal for this lane. The per-tag lock serializes
|
||||||
|
# workflows, but a maintainer must not publish the Draft while this job runs.
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "RELEASE.*"
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: "Release tag (e.g. RELEASE.2026-03-24T12-00-00Z)"
|
||||||
|
required: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
id-token: write
|
||||||
|
attestations: write
|
||||||
|
artifact-metadata: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: release-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
# Build the code at the tag being released, not whatever branch the
|
||||||
|
# dispatch ran from. On a tag push this is the tag ref already; on
|
||||||
|
# workflow_dispatch it pins the checkout to the requested tag so the
|
||||||
|
# artifacts cannot be built from one ref and published under another.
|
||||||
|
ref: ${{ github.event.inputs.tag || github.ref }}
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v7
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Verify clean checkout
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
# GitHub's OIDC certificate records GITHUB_SHA, not the ref passed to
|
||||||
|
# actions/checkout. A manual dispatch must therefore be launched from
|
||||||
|
# the release tag itself; otherwise the provenance identity would
|
||||||
|
# describe different source from the bytes being published.
|
||||||
|
CHECKED_OUT_REVISION="$(git rev-parse HEAD)"
|
||||||
|
if [ "${CHECKED_OUT_REVISION}" != "${GITHUB_SHA}" ]; then
|
||||||
|
echo "Checked out ${CHECKED_OUT_REVISION}, but workflow identity is ${GITHUB_SHA}. Dispatch from the release tag." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -n "$(git status --porcelain)" ]; then
|
||||||
|
echo "Refusing to release from a dirty working tree:" >&2
|
||||||
|
git status --porcelain >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Verify rebrand compatibility contracts
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
go run ./buildscripts/rebrand-guard
|
||||||
|
buildscripts/verify-rebrand.sh
|
||||||
|
dockerscripts/docker-entrypoint_test.sh
|
||||||
|
|
||||||
|
- name: Compute release variables
|
||||||
|
env:
|
||||||
|
# Passed through the environment, never interpolated into the script
|
||||||
|
# body: a dispatch input reaches bash as data, so it cannot inject
|
||||||
|
# commands the way a `${{ ... }}` splice into the source would.
|
||||||
|
INPUT_TAG: ${{ github.event.inputs.tag }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
TAG="${INPUT_TAG:-${GITHUB_REF_NAME}}"
|
||||||
|
# Whitelist the exact tag shape before the value is used anywhere. bash
|
||||||
|
# =~ anchors ^...$ to the whole string (not per line, as sed would), so
|
||||||
|
# a tag carrying a newline cannot pass and then smuggle extra lines into
|
||||||
|
# $GITHUB_ENV below.
|
||||||
|
if [[ ! "${TAG}" =~ ^RELEASE\.[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}-[0-9]{2}-[0-9]{2}Z$ ]]; then
|
||||||
|
echo "Invalid release tag format: ${TAG}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! TAG_COMMIT="$(git rev-parse "${TAG}^{commit}" 2>/dev/null)"; then
|
||||||
|
echo "Release tag ${TAG} does not resolve to a commit" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
HEAD_COMMIT="$(git rev-parse HEAD)"
|
||||||
|
if [ "${TAG_COMMIT}" != "${HEAD_COMMIT}" ]; then
|
||||||
|
echo "Release tag ${TAG} resolves to ${TAG_COMMIT}, checkout is ${HEAD_COMMIT}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
VERSION_HYPHEN="${TAG#RELEASE.}"
|
||||||
|
PKG_VERSION="$(echo "${VERSION_HYPHEN}" | sed -E 's/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2})-([0-9]{2})-([0-9]{2})Z$/\1\2\3\4\5\6.0.0/')"
|
||||||
|
VERSION_COLON="$(echo "${VERSION_HYPHEN}" | sed -E 's/T([0-9]{2})-([0-9]{2})-([0-9]{2})Z$/T\1:\2:\3Z/')"
|
||||||
|
LDFLAGS="$(MINIO_RELEASE=RELEASE go run buildscripts/gen-ldflags.go "${VERSION_COLON}")"
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "RELEASE_TAG=${TAG}"
|
||||||
|
echo "PKG_VERSION=${PKG_VERSION}"
|
||||||
|
echo "LDFLAGS=${LDFLAGS}"
|
||||||
|
} >> "${GITHUB_ENV}"
|
||||||
|
|
||||||
|
echo "Release tag: ${TAG}"
|
||||||
|
echo "Package version: ${PKG_VERSION}"
|
||||||
|
echo "LDFLAGS: ${LDFLAGS}"
|
||||||
|
|
||||||
|
- name: Check existing release state
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
buildscripts/check-release-state.sh "${RELEASE_TAG}"
|
||||||
|
|
||||||
|
# Both installer actions are pinned to immutable commits. The explicit
|
||||||
|
# tool versions keep the release format reproducible across workflow
|
||||||
|
# reruns while the installers verify the downloaded executables.
|
||||||
|
- name: Install Syft
|
||||||
|
uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
|
||||||
|
with:
|
||||||
|
syft-version: v1.50.0
|
||||||
|
|
||||||
|
- name: Install Cosign
|
||||||
|
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
||||||
|
with:
|
||||||
|
cosign-release: v3.1.2
|
||||||
|
|
||||||
|
- name: Build Draft release with GoReleaser
|
||||||
|
uses: goreleaser/goreleaser-action@v7
|
||||||
|
with:
|
||||||
|
version: "~> v2"
|
||||||
|
args: release --clean --skip=validate --config .github/goreleaser.yml
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GORELEASER_CURRENT_TAG: ${{ env.RELEASE_TAG }}
|
||||||
|
LDFLAGS: ${{ env.LDFLAGS }}
|
||||||
|
PKG_VERSION: ${{ env.PKG_VERSION }}
|
||||||
|
|
||||||
|
- name: Verify binary provenance stamps
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
buildscripts/verify-build-provenance.sh
|
||||||
|
|
||||||
|
- name: Install nFPM
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
go install github.com/goreleaser/nfpm/v2/cmd/nfpm@v2.47.0
|
||||||
|
echo "$(go env GOPATH)/bin" >> "${GITHUB_PATH}"
|
||||||
|
|
||||||
|
- name: Build nFPM packages
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
buildscripts/package-release.sh
|
||||||
|
|
||||||
|
- name: Generate package SBOMs and signed checksum manifest
|
||||||
|
env:
|
||||||
|
SYFT_CHECK_FOR_APP_UPDATE: "false"
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
packages_dir="dist/packages"
|
||||||
|
mapfile -t packages < <(find "${packages_dir}" -maxdepth 1 -type f \
|
||||||
|
\( -name '*.rpm' -o -name '*.deb' -o -name '*.apk' \) | sort)
|
||||||
|
if [ "${#packages[@]}" -ne 6 ]; then
|
||||||
|
echo "Expected six Linux packages, found ${#packages[@]}" >&2
|
||||||
|
printf '%s\n' "${packages[@]}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for package in "${packages[@]}"; do
|
||||||
|
syft "${package}" --output "spdx-json=${package}.sbom.json"
|
||||||
|
done
|
||||||
|
|
||||||
|
manifest="${packages_dir}/silo_${PKG_VERSION}_packages_checksums.txt"
|
||||||
|
(
|
||||||
|
cd "${packages_dir}"
|
||||||
|
mapfile -t subjects < <(find . -maxdepth 1 -type f \
|
||||||
|
\( -name '*.rpm' -o -name '*.deb' -o -name '*.apk' -o -name '*.sbom.json' \) | sort)
|
||||||
|
if [ "${#subjects[@]}" -ne 12 ]; then
|
||||||
|
echo "Expected six packages and six SBOMs, found ${#subjects[@]} subjects" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sha256sum "${subjects[@]}" | sed 's# \./# #' > "$(basename "${manifest}")"
|
||||||
|
)
|
||||||
|
cosign sign-blob --bundle="${manifest}.sigstore.json" "${manifest}" --yes
|
||||||
|
|
||||||
|
- name: Attest downloadable release artifacts
|
||||||
|
id: attest-release
|
||||||
|
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
|
||||||
|
with:
|
||||||
|
subject-path: |
|
||||||
|
dist/*.tar.gz
|
||||||
|
dist/*.zip
|
||||||
|
dist/*.sbom.json
|
||||||
|
dist/*_checksums.txt
|
||||||
|
dist/*.sigstore.json
|
||||||
|
dist/packages/*.rpm
|
||||||
|
dist/packages/*.deb
|
||||||
|
dist/packages/*.apk
|
||||||
|
dist/packages/*.sha256sum
|
||||||
|
dist/packages/*.sbom.json
|
||||||
|
dist/packages/*_checksums.txt
|
||||||
|
dist/packages/*.sigstore.json
|
||||||
|
|
||||||
|
- name: Preserve provenance bundle as a release asset
|
||||||
|
env:
|
||||||
|
BUNDLE_PATH: ${{ steps.attest-release.outputs.bundle-path }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
test -s "${BUNDLE_PATH}"
|
||||||
|
cp "${BUNDLE_PATH}" "dist/silo_${PKG_VERSION}_provenance.sigstore.json"
|
||||||
|
|
||||||
|
- name: Confirm unfinalized Draft release state
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
REQUIRE_DRAFT: "true"
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
buildscripts/check-release-state.sh "${RELEASE_TAG}"
|
||||||
|
|
||||||
|
- name: Upload nFPM packages to Draft release
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
mapfile -t files < <(find dist/packages -maxdepth 1 -type f \
|
||||||
|
\( -name '*.rpm' -o -name '*.deb' -o -name '*.apk' -o -name '*.sha256sum' \
|
||||||
|
-o -name '*.sbom.json' -o -name '*_checksums.txt' -o -name '*.sigstore.json' \) | sort)
|
||||||
|
if [ "${#files[@]}" -eq 0 ]; then
|
||||||
|
echo "No packages were generated."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
gh release upload "${RELEASE_TAG}" "${files[@]}" \
|
||||||
|
"dist/silo_${PKG_VERSION}_provenance.sigstore.json"
|
||||||
|
|
||||||
|
- name: Upload dist artifact
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: dist
|
||||||
|
path: dist/
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIBKDCB26ADAgECAhB6vebGMUfKnmBKyqoApRSOMAUGAytlcDAbMRkwFwYDVQQD
|
|
||||||
DBByb290QHBsYXkubWluLmlvMB4XDTIwMDQzMDE1MjIyNVoXDTI1MDQyOTE1MjIy
|
|
||||||
NVowGzEZMBcGA1UEAwwQcm9vdEBwbGF5Lm1pbi5pbzAqMAUGAytlcAMhALzn735W
|
|
||||||
fmSH/ghKs+4iPWziZMmWdiWr/sqvqeW+WwSxozUwMzAOBgNVHQ8BAf8EBAMCB4Aw
|
|
||||||
EwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAFBgMrZXADQQDZOrGK
|
|
||||||
b2ATkDlu2pTcP3LyhSBDpYh7V4TvjRkBTRgjkacCzwFLm+mh+7US8V4dBpIDsJ4u
|
|
||||||
uWoF0y6vbLVGIlkG
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MC4CAQAwBQYDK2VwBCIEID9E7FSYWrMD+VjhI6q545cYT9YOyFxZb7UnjEepYDRc
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
@@ -0,0 +1,530 @@
|
|||||||
|
name: Test Release Pipeline
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- ".github/goreleaser.yml"
|
||||||
|
- ".github/nfpm.yml"
|
||||||
|
- "Dockerfile.goreleaser"
|
||||||
|
- "Dockerfile.distroless"
|
||||||
|
- "cmd/healthcheck-main.go"
|
||||||
|
- "cmd/main.go"
|
||||||
|
- "dockerscripts/build-static-curl.sh"
|
||||||
|
- "dockerscripts/docker-entrypoint.sh"
|
||||||
|
- "dockerscripts/docker-entrypoint_test.sh"
|
||||||
|
- "silo.service"
|
||||||
|
- "silo.env"
|
||||||
|
- "silo.sysusers"
|
||||||
|
- "buildscripts/package-release.sh"
|
||||||
|
- "buildscripts/package/postinstall.sh"
|
||||||
|
- "buildscripts/package/preremove.sh"
|
||||||
|
- "buildscripts/package/lifecycle_test.sh"
|
||||||
|
- "buildscripts/minio-upgrade.sh"
|
||||||
|
- "buildscripts/sign-release-rpms.sh"
|
||||||
|
- "buildscripts/verify-build-provenance.sh"
|
||||||
|
- "buildscripts/check-release-state.sh"
|
||||||
|
- "buildscripts/check-release-state_test.sh"
|
||||||
|
- "buildscripts/verify-rebrand.sh"
|
||||||
|
- "buildscripts/verify-helm-migration.sh"
|
||||||
|
- "buildscripts/helm-migration-guard/**"
|
||||||
|
- "helm/silo/**"
|
||||||
|
- "buildscripts/rebrand-guard/**"
|
||||||
|
- "buildscripts/gen-ldflags.go"
|
||||||
|
- ".github/workflows/release.yml"
|
||||||
|
- ".github/workflows/docker-release.yml"
|
||||||
|
- ".github/workflows/finalize-release.yml"
|
||||||
|
- ".github/workflows/test-release.yml"
|
||||||
|
- ".gitignore"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
curl:
|
||||||
|
name: Static curl (${{ matrix.arch }})
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- arch: amd64
|
||||||
|
runner: ubuntu-latest
|
||||||
|
- arch: arm64
|
||||||
|
runner: ubuntu-24.04-arm
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- name: Build and exercise curl in an empty runtime
|
||||||
|
run: |
|
||||||
|
docker build --build-arg TARGETARCH=${{ matrix.arch }} \
|
||||||
|
--target curl-runtime -f Dockerfile.goreleaser -t silo-curl-test .
|
||||||
|
docker run --rm silo-curl-test --version | tee curl-version.txt
|
||||||
|
grep -F 'curl 8.22.0 ' curl-version.txt
|
||||||
|
grep -F 'HTTP2' curl-version.txt
|
||||||
|
docker run --rm silo-curl-test --fail --silent --show-error \
|
||||||
|
--connect-timeout 15 --max-time 60 https://curl.se/robots.txt
|
||||||
|
|
||||||
|
validate:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v7
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Compute test variables
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
RELEASE_TAG="RELEASE.2026-02-14T12-00-00Z"
|
||||||
|
VERSION_COLON="2026-02-14T12:00:00Z"
|
||||||
|
PKG_VERSION="20260214120000.0.0"
|
||||||
|
LDFLAGS="$(MINIO_RELEASE=RELEASE go run buildscripts/gen-ldflags.go "${VERSION_COLON}")"
|
||||||
|
{
|
||||||
|
echo "RELEASE_TAG=${RELEASE_TAG}"
|
||||||
|
echo "PKG_VERSION=${PKG_VERSION}"
|
||||||
|
echo "LDFLAGS=${LDFLAGS}"
|
||||||
|
} >> "${GITHUB_ENV}"
|
||||||
|
echo "PKG_VERSION: ${PKG_VERSION}"
|
||||||
|
echo "LDFLAGS: ${LDFLAGS}"
|
||||||
|
|
||||||
|
- name: GoReleaser config check
|
||||||
|
uses: goreleaser/goreleaser-action@v7
|
||||||
|
with:
|
||||||
|
version: "~> v2"
|
||||||
|
args: check --config .github/goreleaser.yml
|
||||||
|
|
||||||
|
- name: Validate Helm chart and legacy upgrade identity
|
||||||
|
run: buildscripts/verify-helm-migration.sh
|
||||||
|
|
||||||
|
- name: Install Syft
|
||||||
|
uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
|
||||||
|
with:
|
||||||
|
syft-version: v1.50.0
|
||||||
|
|
||||||
|
- name: Build snapshot artifacts
|
||||||
|
uses: goreleaser/goreleaser-action@v7
|
||||||
|
with:
|
||||||
|
version: "~> v2"
|
||||||
|
# A pull-request snapshot has no trusted release identity. Exercise
|
||||||
|
# the SBOM/checksum pipeline here, and reserve keyless signing for
|
||||||
|
# the tag-triggered release workflow with GitHub OIDC.
|
||||||
|
args: release --snapshot --clean --skip=publish,docker,sign --config .github/goreleaser.yml
|
||||||
|
env:
|
||||||
|
LDFLAGS: ${{ env.LDFLAGS }}
|
||||||
|
PKG_VERSION: ${{ env.PKG_VERSION }}
|
||||||
|
|
||||||
|
- name: Verify archive SBOM and checksum coverage
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
mapfile -t archives < <(find dist -maxdepth 1 -type f -name 'silo_*.tar.gz' | sort)
|
||||||
|
mapfile -t sboms < <(find dist -maxdepth 1 -type f -name 'silo_*.tar.gz.sbom.json' | sort)
|
||||||
|
test "${#archives[@]}" -eq 6
|
||||||
|
test "${#sboms[@]}" -eq 6
|
||||||
|
manifest="dist/silo_${PKG_VERSION}_checksums.txt"
|
||||||
|
test -s "${manifest}"
|
||||||
|
(
|
||||||
|
cd dist
|
||||||
|
sha256sum --check "$(basename "${manifest}")"
|
||||||
|
)
|
||||||
|
test "$(wc -l < "${manifest}" | tr -d ' ')" -eq 12
|
||||||
|
|
||||||
|
- name: Verify binary provenance stamps
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
buildscripts/verify-build-provenance.sh
|
||||||
|
|
||||||
|
- name: Install package validation tools
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
go install github.com/goreleaser/nfpm/v2/cmd/nfpm@v2.47.0
|
||||||
|
echo "$(go env GOPATH)/bin" >> "${GITHUB_PATH}"
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install --yes rpm binutils
|
||||||
|
|
||||||
|
- name: Package snapshot binaries with nFPM
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
buildscripts/package-release.sh
|
||||||
|
|
||||||
|
- name: Validate package names, checksums, metadata, and payload
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
cd dist/packages
|
||||||
|
|
||||||
|
# The signing script asserts these same values, but it runs on the
|
||||||
|
# maintainer's machine after the release workflow has already built
|
||||||
|
# and uploaded. Take its expectations as the single source of truth
|
||||||
|
# so nfpm.yml and the signing script cannot drift apart without
|
||||||
|
# failing here first, while a fix is still cheap.
|
||||||
|
#
|
||||||
|
# This grep is deliberately limited to the eight identity variables,
|
||||||
|
# all of which are single-line. That is what makes the eval safe:
|
||||||
|
# should one ever become multi-line, the grep captures an
|
||||||
|
# unterminated quote and the eval aborts on a syntax error under
|
||||||
|
# set -e rather than quietly binding an empty value and comparing
|
||||||
|
# against nothing. expected_payload is multi-line by design and must
|
||||||
|
# stay out of this set for the same reason.
|
||||||
|
eval "$(grep -E '^expected_(release|vendor|packager|url|summary|description|license|group)=' \
|
||||||
|
../../buildscripts/sign-release-rpms.sh)"
|
||||||
|
for value in "${expected_release}" "${expected_vendor}" "${expected_packager}" \
|
||||||
|
"${expected_url}" "${expected_summary}" "${expected_description}" \
|
||||||
|
"${expected_license}" "${expected_group}"; do
|
||||||
|
test -n "${value}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# These are the public download names; a drift here breaks every
|
||||||
|
# script that fetches packages by URL. RPM and DEB carry the PGSTY
|
||||||
|
# release segment; APK cannot (Alpine pkgrel admits only -r<integer>),
|
||||||
|
# so it stays bare. package-release.sh builds the same three shapes.
|
||||||
|
expected=(
|
||||||
|
"silo-${PKG_VERSION}-${expected_release}.aarch64.rpm"
|
||||||
|
"silo-${PKG_VERSION}-${expected_release}.x86_64.rpm"
|
||||||
|
"silo_${PKG_VERSION}-${expected_release}_amd64.deb"
|
||||||
|
"silo_${PKG_VERSION}-${expected_release}_arm64.deb"
|
||||||
|
"silo_${PKG_VERSION}_aarch64.apk"
|
||||||
|
"silo_${PKG_VERSION}_x86_64.apk"
|
||||||
|
)
|
||||||
|
|
||||||
|
for package in "${expected[@]}"; do
|
||||||
|
test -s "${package}"
|
||||||
|
test -s "${package}.sha256sum"
|
||||||
|
sha256sum --check "${package}.sha256sum"
|
||||||
|
done
|
||||||
|
|
||||||
|
test "$(find . -maxdepth 1 -type f \( -name '*.rpm' -o -name '*.deb' -o -name '*.apk' \) | wc -l)" -eq 6
|
||||||
|
|
||||||
|
service_sha="$(sha256sum ../../silo.service | awk '{print $1}')"
|
||||||
|
defaults_sha="$(sha256sum ../../silo.env | awk '{print $1}')"
|
||||||
|
sysusers_sha="$(sha256sum ../../silo.sysusers | awk '{print $1}')"
|
||||||
|
license_sha="$(sha256sum ../../LICENSE | awk '{print $1}')"
|
||||||
|
notice_sha="$(sha256sum ../../NOTICE | awk '{print $1}')"
|
||||||
|
|
||||||
|
rpm_file="silo-${PKG_VERSION}-${expected_release}.x86_64.rpm"
|
||||||
|
test "$(rpm -qp --queryformat '%{RELEASE}' "${rpm_file}")" = "${expected_release}"
|
||||||
|
test "$(rpm -qp --queryformat '%{VENDOR}' "${rpm_file}")" = "${expected_vendor}"
|
||||||
|
test "$(rpm -qp --queryformat '%{PACKAGER}' "${rpm_file}")" = "${expected_packager}"
|
||||||
|
test "$(rpm -qp --queryformat '%{URL}' "${rpm_file}")" = "${expected_url}"
|
||||||
|
test "$(rpm -qp --queryformat '%{SUMMARY}' "${rpm_file}")" = "${expected_summary}"
|
||||||
|
test "$(rpm -qp --queryformat '%{DESCRIPTION}' "${rpm_file}")" = "${expected_description}"
|
||||||
|
test "$(rpm -qp --queryformat '%{LICENSE}' "${rpm_file}")" = "${expected_license}"
|
||||||
|
test "$(rpm -qp --queryformat '%{GROUP}' "${rpm_file}")" = "${expected_group}"
|
||||||
|
rpm -qpl "${rpm_file}" | grep -Fx '/usr/bin/silo'
|
||||||
|
rpm -qpl "${rpm_file}" | grep -Fx '/usr/lib/systemd/system/silo.service'
|
||||||
|
rpm -qpl "${rpm_file}" | grep -Fx '/etc/default/silo'
|
||||||
|
rpm -qpl "${rpm_file}" | grep -Fx '/usr/lib/sysusers.d/silo.conf'
|
||||||
|
rpm -qpl "${rpm_file}" | grep -Fx '/usr/share/doc/silo/LICENSE'
|
||||||
|
rpm -qpl "${rpm_file}" | grep -Fx '/usr/share/doc/silo/NOTICE'
|
||||||
|
test "$(rpm -qpl "${rpm_file}" | wc -l)" -eq 6
|
||||||
|
# nfpm only honors type: license on rpm, which is why nfpm.yml
|
||||||
|
# declares the license materials once per packager. Pin the rpm
|
||||||
|
# %license flag so that split cannot silently regress.
|
||||||
|
rpm -qp --queryformat '[%{FILEFLAGS:fflags} %{FILENAMES}\n]' "${rpm_file}" \
|
||||||
|
| grep -Fx 'l /usr/share/doc/silo/LICENSE'
|
||||||
|
rpm -qp --queryformat '[%{FILEFLAGS:fflags} %{FILENAMES}\n]' "${rpm_file}" \
|
||||||
|
| grep -Fx 'l /usr/share/doc/silo/NOTICE'
|
||||||
|
if rpm -qp --conflicts "${rpm_file}" | grep -qi minio; then
|
||||||
|
echo "RPM must not declare a cross-name conflict with MinIO" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if rpm -qp --obsoletes "${rpm_file}" | grep -qi minio; then
|
||||||
|
echo "RPM must not obsolete a MinIO package" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if rpm -qp --provides "${rpm_file}" | grep -qi minio; then
|
||||||
|
echo "RPM must not provide a MinIO package alias" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
deb_file="silo_${PKG_VERSION}-${expected_release}_amd64.deb"
|
||||||
|
test "$(dpkg-deb --field "${deb_file}" Maintainer)" = "${expected_packager}"
|
||||||
|
test "$(dpkg-deb --field "${deb_file}" Version)" = "${PKG_VERSION}-${expected_release}"
|
||||||
|
test "$(dpkg-deb --field "${deb_file}" License)" = "${expected_license}"
|
||||||
|
test "$(dpkg-deb --field "${deb_file}" Section)" = "utils"
|
||||||
|
test "$(dpkg-deb --field "${deb_file}" Homepage)" = "${expected_url}"
|
||||||
|
test "$(dpkg-deb --field "${deb_file}" Description)" = "${expected_description}"
|
||||||
|
dpkg-deb --contents "${deb_file}" | grep -E 'usr/bin/silo$'
|
||||||
|
dpkg-deb --contents "${deb_file}" | grep -E 'usr/lib/systemd/system/silo\.service$'
|
||||||
|
dpkg-deb --contents "${deb_file}" | grep -E 'etc/default/silo$'
|
||||||
|
dpkg-deb --contents "${deb_file}" | grep -E 'usr/lib/sysusers\.d/silo\.conf$'
|
||||||
|
dpkg-deb --contents "${deb_file}" | grep -E 'usr/share/doc/silo/LICENSE$'
|
||||||
|
dpkg-deb --contents "${deb_file}" | grep -E 'usr/share/doc/silo/NOTICE$'
|
||||||
|
test "$(dpkg-deb --contents "${deb_file}" | awk '$1 !~ /^d/ { count++ } END { print count + 0 }')" -eq 6
|
||||||
|
test -z "$(dpkg-deb --field "${deb_file}" Conflicts)"
|
||||||
|
test -z "$(dpkg-deb --field "${deb_file}" Replaces)"
|
||||||
|
test -z "$(dpkg-deb --field "${deb_file}" Provides)"
|
||||||
|
|
||||||
|
apk_info="$(tar -xOzf "silo_${PKG_VERSION}_x86_64.apk" .PKGINFO)"
|
||||||
|
grep -Fx "pkgver = ${PKG_VERSION}" <<< "${apk_info}"
|
||||||
|
grep -Fx "url = ${expected_url}" <<< "${apk_info}"
|
||||||
|
grep -Fx "maintainer = ${expected_packager}" <<< "${apk_info}"
|
||||||
|
grep -Fx "license = ${expected_license}" <<< "${apk_info}"
|
||||||
|
grep -Fx "pkgdesc = ${expected_description}" <<< "${apk_info}"
|
||||||
|
tar -tzf "silo_${PKG_VERSION}_x86_64.apk" | grep -Fx 'usr/bin/silo'
|
||||||
|
tar -tzf "silo_${PKG_VERSION}_x86_64.apk" | grep -Fx 'usr/lib/systemd/system/silo.service'
|
||||||
|
tar -tzf "silo_${PKG_VERSION}_x86_64.apk" | grep -Fx 'etc/default/silo'
|
||||||
|
tar -tzf "silo_${PKG_VERSION}_x86_64.apk" | grep -Fx 'usr/lib/sysusers.d/silo.conf'
|
||||||
|
tar -tzf "silo_${PKG_VERSION}_x86_64.apk" | grep -Fx 'usr/share/doc/silo/LICENSE'
|
||||||
|
tar -tzf "silo_${PKG_VERSION}_x86_64.apk" | grep -Fx 'usr/share/doc/silo/NOTICE'
|
||||||
|
test "$(tar -tzf "silo_${PKG_VERSION}_x86_64.apk" | awk '$0 !~ /^\./ && $0 !~ /\/$/ { count++ } END { print count + 0 }')" -eq 6
|
||||||
|
if grep -Ei '^provides = .*minio' <<< "${apk_info}"; then
|
||||||
|
echo "APK must not provide a MinIO package alias" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for arch in amd64 arm64; do
|
||||||
|
if [ "${arch}" = amd64 ]; then
|
||||||
|
rpm_arch=x86_64
|
||||||
|
deb_arch=amd64
|
||||||
|
apk_arch=x86_64
|
||||||
|
else
|
||||||
|
rpm_arch=aarch64
|
||||||
|
deb_arch=arm64
|
||||||
|
apk_arch=aarch64
|
||||||
|
fi
|
||||||
|
|
||||||
|
test "$(rpm -qp --queryformat '%{ARCH}' "silo-${PKG_VERSION}-${expected_release}.${rpm_arch}.rpm")" = "${rpm_arch}"
|
||||||
|
test "$(dpkg-deb --field "silo_${PKG_VERSION}-${expected_release}_${deb_arch}.deb" Architecture)" = "${deb_arch}"
|
||||||
|
grep -Fx "arch = ${apk_arch}" <<< "$(tar -xOzf "silo_${PKG_VERSION}_${apk_arch}.apk" .PKGINFO)"
|
||||||
|
|
||||||
|
# Accepted weakness: this takes the first match, unsorted, where
|
||||||
|
# find_binary in package-release.sh demands exactly one. It cannot
|
||||||
|
# be reached with an ambiguous match today, because packaging runs
|
||||||
|
# earlier in this same job and hard-fails on one. Revisit if
|
||||||
|
# goamd64 gains a second level, or if find_binary's exactly-one
|
||||||
|
# contract is ever relaxed -- at that point this weak copy would be
|
||||||
|
# the only one left choosing silently.
|
||||||
|
source_binary="$(find .. -maxdepth 2 -type f -path "../silo_linux_${arch}*/silo" | head -n 1)"
|
||||||
|
source_sha="$(sha256sum "${source_binary}" | awk '{print $1}')"
|
||||||
|
|
||||||
|
# Do not pipe rpm2cpio here: Debian's build exits non-zero even when
|
||||||
|
# it writes a correct payload, which trips `set -o pipefail`. Use
|
||||||
|
# rpm's own digests instead -- -K checks the payload against the
|
||||||
|
# header, and FILEDIGESTS is the sha256 rpm itself verifies on
|
||||||
|
# install.
|
||||||
|
rpm -K "silo-${PKG_VERSION}-${expected_release}.${rpm_arch}.rpm"
|
||||||
|
rpm_sha="$(rpm -qp --queryformat '[%{FILENAMES} %{FILEDIGESTS}\n]' \
|
||||||
|
"silo-${PKG_VERSION}-${expected_release}.${rpm_arch}.rpm" | awk '$1 == "/usr/bin/silo" { print $2 }')"
|
||||||
|
rpm_service_sha="$(rpm -qp --queryformat '[%{FILENAMES} %{FILEDIGESTS}\n]' \
|
||||||
|
"silo-${PKG_VERSION}-${expected_release}.${rpm_arch}.rpm" | awk '$1 == "/usr/lib/systemd/system/silo.service" { print $2 }')"
|
||||||
|
rpm_defaults_sha="$(rpm -qp --queryformat '[%{FILENAMES} %{FILEDIGESTS}\n]' \
|
||||||
|
"silo-${PKG_VERSION}-${expected_release}.${rpm_arch}.rpm" | awk '$1 == "/etc/default/silo" { print $2 }')"
|
||||||
|
rpm_sysusers_sha="$(rpm -qp --queryformat '[%{FILENAMES} %{FILEDIGESTS}\n]' \
|
||||||
|
"silo-${PKG_VERSION}-${expected_release}.${rpm_arch}.rpm" | awk '$1 == "/usr/lib/sysusers.d/silo.conf" { print $2 }')"
|
||||||
|
rpm_license_sha="$(rpm -qp --queryformat '[%{FILENAMES} %{FILEDIGESTS}\n]' \
|
||||||
|
"silo-${PKG_VERSION}-${expected_release}.${rpm_arch}.rpm" | awk '$1 == "/usr/share/doc/silo/LICENSE" { print $2 }')"
|
||||||
|
rpm_notice_sha="$(rpm -qp --queryformat '[%{FILENAMES} %{FILEDIGESTS}\n]' \
|
||||||
|
"silo-${PKG_VERSION}-${expected_release}.${rpm_arch}.rpm" | awk '$1 == "/usr/share/doc/silo/NOTICE" { print $2 }')"
|
||||||
|
deb_sha="$(ar p "silo_${PKG_VERSION}-${expected_release}_${deb_arch}.deb" data.tar.gz | tar -xzOf - ./usr/bin/silo | sha256sum | awk '{print $1}')"
|
||||||
|
deb_service_sha="$(ar p "silo_${PKG_VERSION}-${expected_release}_${deb_arch}.deb" data.tar.gz | tar -xzOf - ./usr/lib/systemd/system/silo.service | sha256sum | awk '{print $1}')"
|
||||||
|
deb_defaults_sha="$(ar p "silo_${PKG_VERSION}-${expected_release}_${deb_arch}.deb" data.tar.gz | tar -xzOf - ./etc/default/silo | sha256sum | awk '{print $1}')"
|
||||||
|
deb_sysusers_sha="$(ar p "silo_${PKG_VERSION}-${expected_release}_${deb_arch}.deb" data.tar.gz | tar -xzOf - ./usr/lib/sysusers.d/silo.conf | sha256sum | awk '{print $1}')"
|
||||||
|
deb_license_sha="$(ar p "silo_${PKG_VERSION}-${expected_release}_${deb_arch}.deb" data.tar.gz | tar -xzOf - ./usr/share/doc/silo/LICENSE | sha256sum | awk '{print $1}')"
|
||||||
|
deb_notice_sha="$(ar p "silo_${PKG_VERSION}-${expected_release}_${deb_arch}.deb" data.tar.gz | tar -xzOf - ./usr/share/doc/silo/NOTICE | sha256sum | awk '{print $1}')"
|
||||||
|
apk_sha="$(tar -xzOf "silo_${PKG_VERSION}_${apk_arch}.apk" usr/bin/silo | sha256sum | awk '{print $1}')"
|
||||||
|
apk_service_sha="$(tar -xzOf "silo_${PKG_VERSION}_${apk_arch}.apk" usr/lib/systemd/system/silo.service | sha256sum | awk '{print $1}')"
|
||||||
|
apk_defaults_sha="$(tar -xzOf "silo_${PKG_VERSION}_${apk_arch}.apk" etc/default/silo | sha256sum | awk '{print $1}')"
|
||||||
|
apk_sysusers_sha="$(tar -xzOf "silo_${PKG_VERSION}_${apk_arch}.apk" usr/lib/sysusers.d/silo.conf | sha256sum | awk '{print $1}')"
|
||||||
|
apk_license_sha="$(tar -xzOf "silo_${PKG_VERSION}_${apk_arch}.apk" usr/share/doc/silo/LICENSE | sha256sum | awk '{print $1}')"
|
||||||
|
apk_notice_sha="$(tar -xzOf "silo_${PKG_VERSION}_${apk_arch}.apk" usr/share/doc/silo/NOTICE | sha256sum | awk '{print $1}')"
|
||||||
|
|
||||||
|
test "${source_sha}" = "${rpm_sha}"
|
||||||
|
test "${source_sha}" = "${deb_sha}"
|
||||||
|
test "${source_sha}" = "${apk_sha}"
|
||||||
|
test "${service_sha}" = "${rpm_service_sha}"
|
||||||
|
test "${service_sha}" = "${deb_service_sha}"
|
||||||
|
test "${service_sha}" = "${apk_service_sha}"
|
||||||
|
test "${defaults_sha}" = "${rpm_defaults_sha}"
|
||||||
|
test "${defaults_sha}" = "${deb_defaults_sha}"
|
||||||
|
test "${defaults_sha}" = "${apk_defaults_sha}"
|
||||||
|
test "${sysusers_sha}" = "${rpm_sysusers_sha}"
|
||||||
|
test "${sysusers_sha}" = "${deb_sysusers_sha}"
|
||||||
|
test "${sysusers_sha}" = "${apk_sysusers_sha}"
|
||||||
|
test "${license_sha}" = "${rpm_license_sha}"
|
||||||
|
test "${license_sha}" = "${deb_license_sha}"
|
||||||
|
test "${license_sha}" = "${apk_license_sha}"
|
||||||
|
test "${notice_sha}" = "${rpm_notice_sha}"
|
||||||
|
test "${notice_sha}" = "${deb_notice_sha}"
|
||||||
|
test "${notice_sha}" = "${apk_notice_sha}"
|
||||||
|
done
|
||||||
|
|
||||||
|
find . -maxdepth 1 -type f | sort
|
||||||
|
|
||||||
|
- name: Build release runtime image and verify graceful shutdown
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
# docker-release.yml is workflow_dispatch only, so this is the only
|
||||||
|
# automated build of the release runtime layer and entrypoint before a
|
||||||
|
# real publish. Assemble a minimal image from the linux/amd64 binary
|
||||||
|
# goreleaser already produced; the mcli-download build stage is skipped
|
||||||
|
# on purpose to keep this gate offline and deterministic.
|
||||||
|
ctx="$(mktemp -d)"
|
||||||
|
tar -xzf "dist/silo_${PKG_VERSION}_linux_amd64.tar.gz" -C "${ctx}" silo
|
||||||
|
cp dockerscripts/docker-entrypoint.sh "${ctx}/docker-entrypoint.sh"
|
||||||
|
{
|
||||||
|
echo "FROM registry.access.redhat.com/ubi9/ubi-micro:latest"
|
||||||
|
echo "COPY silo /usr/bin/silo"
|
||||||
|
echo "COPY docker-entrypoint.sh /usr/bin/docker-entrypoint.sh"
|
||||||
|
echo "RUN mkdir -p /data && chmod 0777 /data && chmod +x /usr/bin/silo /usr/bin/docker-entrypoint.sh"
|
||||||
|
echo 'ENV HOME=/tmp'
|
||||||
|
echo 'ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]'
|
||||||
|
echo 'CMD ["silo"]'
|
||||||
|
} > "${ctx}/Dockerfile"
|
||||||
|
docker build -t silo-runtime-test:snapshot "${ctx}"
|
||||||
|
|
||||||
|
# PID 1 must be silo, not the entry shell, on every privilege path, so
|
||||||
|
# a SIGTERM from docker stop reaches the server and it exits gracefully
|
||||||
|
# instead of being killed at the stop timeout. Regression guard for the
|
||||||
|
# exec-into-chroot entrypoint fix.
|
||||||
|
assert_graceful() {
|
||||||
|
name="$1"; shift
|
||||||
|
docker rm -f "${name}" >/dev/null 2>&1 || true
|
||||||
|
docker run -d --name "${name}" \
|
||||||
|
-e MINIO_CI_CD=1 -e MINIO_ROOT_USER=ciadmin -e MINIO_ROOT_PASSWORD=ciadmin-secret-123 \
|
||||||
|
"$@" silo-runtime-test:snapshot silo server /data --address :9000 >/dev/null
|
||||||
|
up=""
|
||||||
|
for _ in $(seq 1 60); do
|
||||||
|
if docker logs "${name}" 2>&1 | grep -q "API:"; then up=1; break; fi
|
||||||
|
if [ "$(docker inspect -f '{{.State.Running}}' "${name}")" != "true" ]; then break; fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
if [ -z "${up}" ]; then echo "server did not start (${name}):"; docker logs "${name}" | tail -5; exit 1; fi
|
||||||
|
pid1="$(docker exec "${name}" cat /proc/1/comm 2>/dev/null || echo '?')"
|
||||||
|
start="$(date +%s)"; docker stop -t 15 "${name}" >/dev/null; end="$(date +%s)"
|
||||||
|
code="$(docker inspect -f '{{.State.ExitCode}}' "${name}")"
|
||||||
|
elapsed=$((end - start))
|
||||||
|
echo "${name}: pid1=${pid1} stop=${elapsed}s exit=${code}"
|
||||||
|
graceful=0; docker logs "${name}" 2>&1 | grep -q "Exiting on signal" && graceful=1
|
||||||
|
docker rm -f "${name}" >/dev/null 2>&1 || true
|
||||||
|
[ "${graceful}" = "1" ] || { echo "no graceful-shutdown log (${name}) - signal not forwarded"; exit 1; }
|
||||||
|
[ "${code}" = "0" ] || { echo "non-zero exit (${name}): ${code}"; exit 1; }
|
||||||
|
[ "${elapsed}" -lt 10 ] || { echo "shutdown too slow (${name}): ${elapsed}s - signal not forwarded"; exit 1; }
|
||||||
|
}
|
||||||
|
assert_graceful silo-rt-default
|
||||||
|
assert_graceful silo-rt-dropuser -e MINIO_USERNAME=silo-user -e MINIO_GROUPNAME=silo-group
|
||||||
|
assert_graceful silo-rt-rootless --user 1001:1001
|
||||||
|
|
||||||
|
# The compatibility shim translates only the legacy first argv token;
|
||||||
|
# the image contains no /usr/bin/minio file.
|
||||||
|
docker run --rm silo-runtime-test:snapshot sh -c 'test ! -e /usr/bin/minio'
|
||||||
|
docker run --rm -d --name silo-rt-legacy \
|
||||||
|
-e MINIO_CI_CD=1 -e MINIO_ROOT_USER=ciadmin -e MINIO_ROOT_PASSWORD=ciadmin-secret-123 \
|
||||||
|
silo-runtime-test:snapshot minio server /data --address :9000 >/dev/null
|
||||||
|
sleep 2
|
||||||
|
test "$(docker exec silo-rt-legacy cat /proc/1/comm)" = silo
|
||||||
|
docker rm -f silo-rt-legacy >/dev/null
|
||||||
|
|
||||||
|
- name: Build distroless runtime image and verify native healthcheck
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
# Unlike the classic image, Dockerfile.distroless has no release
|
||||||
|
# download stages, so the real shipped file can be built and gated
|
||||||
|
# here. It must keep working with nothing in it but the silo
|
||||||
|
# binary: no shell, no mc, no entrypoint script.
|
||||||
|
ctx="$(mktemp -d)"
|
||||||
|
tar -xzf "dist/silo_${PKG_VERSION}_linux_amd64.tar.gz" -C "${ctx}" silo
|
||||||
|
cp Dockerfile.distroless LICENSE NOTICE CREDITS "${ctx}/"
|
||||||
|
docker build -t silo-distroless-test:snapshot -f "${ctx}/Dockerfile.distroless" "${ctx}"
|
||||||
|
|
||||||
|
# HEALTHCHECK is a Docker extension absent from the OCI image
|
||||||
|
# spec; assert the exact probe command survived into the image
|
||||||
|
# config, not merely a substring of it.
|
||||||
|
test "$(docker inspect -f '{{json .Config.Healthcheck.Test}}' silo-distroless-test:snapshot)" \
|
||||||
|
= '["CMD","/usr/bin/silo","healthcheck","ready"]'
|
||||||
|
|
||||||
|
# /data ships in the image layer world-writable (issue #55):
|
||||||
|
# there is no entrypoint left to repair ownership at runtime.
|
||||||
|
# Export the rootfs once, then assert each required and each
|
||||||
|
# forbidden entry individually: tar's member-argument mode exits
|
||||||
|
# non-zero on any missing name, which under pipefail masks a
|
||||||
|
# found forbidden file, and -tv prints symlinks as 'name ->
|
||||||
|
# target' which defeats $-anchored greps.
|
||||||
|
probe="$(docker create silo-distroless-test:snapshot server /data)"
|
||||||
|
docker export "${probe}" -o "${ctx}/rootfs.tar"
|
||||||
|
docker rm "${probe}" >/dev/null
|
||||||
|
tar -tf "${ctx}/rootfs.tar" > "${ctx}/names.txt"
|
||||||
|
tar -tvf "${ctx}/rootfs.tar" > "${ctx}/verbose.txt"
|
||||||
|
grep -E '^drwxrwxrwx.* data/$' "${ctx}/verbose.txt" >/dev/null
|
||||||
|
for want in usr/bin/silo licenses/LICENSE licenses/NOTICE licenses/CREDITS; do
|
||||||
|
grep -Fxq "${want}" "${ctx}/names.txt" || { echo "missing ${want}"; exit 1; }
|
||||||
|
done
|
||||||
|
for forbid in bin/sh usr/bin/sh busybox/sh usr/bin/minio usr/bin/mc usr/bin/mcli; do
|
||||||
|
if grep -Fxq "${forbid}" "${ctx}/names.txt"; then
|
||||||
|
echo "distroless image unexpectedly contains ${forbid}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# The baked-in healthcheck must drive Docker's health state on its
|
||||||
|
# own, the probe binary must be directly exec-able without any
|
||||||
|
# shell, and SIGTERM must still reach PID 1 (the server binary is
|
||||||
|
# the entrypoint) for a graceful stop.
|
||||||
|
assert_distroless() {
|
||||||
|
name="$1"; shift
|
||||||
|
docker rm -f "${name}" >/dev/null 2>&1 || true
|
||||||
|
docker run -d --name "${name}" \
|
||||||
|
-e MINIO_CI_CD=1 -e MINIO_ROOT_USER=ciadmin -e MINIO_ROOT_PASSWORD=ciadmin-secret-123 \
|
||||||
|
"$@" silo-distroless-test:snapshot server /data --address :9000 >/dev/null
|
||||||
|
status=""
|
||||||
|
for _ in $(seq 1 90); do
|
||||||
|
status="$(docker inspect -f '{{.State.Health.Status}}' "${name}" 2>/dev/null || echo '?')"
|
||||||
|
if [ "${status}" = "healthy" ]; then break; fi
|
||||||
|
if [ "$(docker inspect -f '{{.State.Running}}' "${name}")" != "true" ]; then break; fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
if [ "${status}" != "healthy" ]; then
|
||||||
|
echo "container never became healthy (${name}): status=${status}"
|
||||||
|
docker logs "${name}" 2>&1 | tail -5
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
docker exec "${name}" /usr/bin/silo healthcheck ready
|
||||||
|
docker exec "${name}" /usr/bin/silo healthcheck cluster
|
||||||
|
start="$(date +%s)"; docker stop -t 15 "${name}" >/dev/null; end="$(date +%s)"
|
||||||
|
code="$(docker inspect -f '{{.State.ExitCode}}' "${name}")"
|
||||||
|
elapsed=$((end - start))
|
||||||
|
graceful=0; docker logs "${name}" 2>&1 | grep -q "Exiting on signal" && graceful=1
|
||||||
|
echo "${name}: health=${status} stop=${elapsed}s exit=${code}"
|
||||||
|
docker rm -f "${name}" >/dev/null 2>&1 || true
|
||||||
|
[ "${graceful}" = "1" ] || { echo "no graceful-shutdown log (${name}) - signal not forwarded"; exit 1; }
|
||||||
|
[ "${code}" = "0" ] || { echo "non-zero exit (${name}): ${code}"; exit 1; }
|
||||||
|
[ "${elapsed}" -lt 10 ] || { echo "shutdown too slow (${name}): ${elapsed}s - signal not forwarded"; exit 1; }
|
||||||
|
}
|
||||||
|
assert_distroless silo-dl-default
|
||||||
|
assert_distroless silo-dl-rootless --user 1001:1001
|
||||||
|
|
||||||
|
- name: Validate release scripts
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
bash -n buildscripts/package-release.sh
|
||||||
|
bash -n buildscripts/minio-upgrade.sh
|
||||||
|
bash -n buildscripts/sign-release-rpms.sh
|
||||||
|
bash -n buildscripts/verify-build-provenance.sh
|
||||||
|
bash -n buildscripts/check-release-state.sh
|
||||||
|
bash -n buildscripts/check-release-state_test.sh
|
||||||
|
bash -n buildscripts/verify-rebrand.sh
|
||||||
|
bash -n buildscripts/verify-helm-migration.sh
|
||||||
|
sh -n buildscripts/package/postinstall.sh
|
||||||
|
sh -n buildscripts/package/preremove.sh
|
||||||
|
bash -n buildscripts/package/lifecycle_test.sh
|
||||||
|
buildscripts/package/lifecycle_test.sh
|
||||||
|
bash -n dockerscripts/docker-entrypoint_test.sh
|
||||||
|
bash -n dockerscripts/build-static-curl.sh
|
||||||
|
dockerscripts/docker-entrypoint_test.sh
|
||||||
|
go run ./buildscripts/rebrand-guard
|
||||||
|
buildscripts/verify-rebrand.sh
|
||||||
|
test -x buildscripts/package-release.sh
|
||||||
|
test -x buildscripts/sign-release-rpms.sh
|
||||||
|
test -x buildscripts/verify-build-provenance.sh
|
||||||
|
test -x buildscripts/check-release-state.sh
|
||||||
|
test -x buildscripts/check-release-state_test.sh
|
||||||
|
test -x buildscripts/verify-rebrand.sh
|
||||||
|
test -x buildscripts/verify-helm-migration.sh
|
||||||
|
test -x buildscripts/package/postinstall.sh
|
||||||
|
test -x buildscripts/package/preremove.sh
|
||||||
|
test -x buildscripts/package/lifecycle_test.sh
|
||||||
|
test -x dockerscripts/docker-entrypoint_test.sh
|
||||||
|
buildscripts/check-release-state_test.sh
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
name: VulnCheck
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
vulncheck:
|
||||||
|
name: Analysis
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 20
|
||||||
|
steps:
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v7
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Install govulncheck
|
||||||
|
run: |
|
||||||
|
go install golang.org/x/vuln/cmd/govulncheck@v1.8.0
|
||||||
|
echo "$(go env GOPATH)/bin" >> "${GITHUB_PATH}"
|
||||||
|
|
||||||
|
- name: Run govulncheck
|
||||||
|
run: govulncheck -show verbose ./...
|
||||||
+50
-3
@@ -2,6 +2,7 @@
|
|||||||
cover.out
|
cover.out
|
||||||
*~
|
*~
|
||||||
minio
|
minio
|
||||||
|
silo
|
||||||
!*/
|
!*/
|
||||||
site/
|
site/
|
||||||
**/*.test
|
**/*.test
|
||||||
@@ -9,8 +10,7 @@ site/
|
|||||||
/.idea/
|
/.idea/
|
||||||
/Minio.iml
|
/Minio.iml
|
||||||
**/access.log
|
**/access.log
|
||||||
vendor/**/*.js
|
vendor/
|
||||||
vendor/**/*.json
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.syso
|
*.syso
|
||||||
coverage.txt
|
coverage.txt
|
||||||
@@ -21,4 +21,51 @@ prime/
|
|||||||
stage/
|
stage/
|
||||||
.sia_temp/
|
.sia_temp/
|
||||||
config.json
|
config.json
|
||||||
node_modules/
|
node_modules/
|
||||||
|
mc.*
|
||||||
|
s3-check-md5*
|
||||||
|
xl-meta*
|
||||||
|
healing-*
|
||||||
|
inspect*.zip
|
||||||
|
200M*
|
||||||
|
hash-set
|
||||||
|
minio.RELEASE*
|
||||||
|
mc
|
||||||
|
nancy
|
||||||
|
inspects/*
|
||||||
|
.bin/
|
||||||
|
*.gz
|
||||||
|
docs/debugging/s3-verify/s3-verify
|
||||||
|
docs/debugging/xl-meta/xl-meta
|
||||||
|
docs/debugging/s3-check-md5/s3-check-md5
|
||||||
|
docs/debugging/hash-set/hash-set
|
||||||
|
docs/debugging/healing-bin/healing-bin
|
||||||
|
docs/debugging/inspect/inspect
|
||||||
|
docs/debugging/pprofgoparser/pprofgoparser
|
||||||
|
docs/debugging/reorder-disks/reorder-disks
|
||||||
|
docs/debugging/populate-hard-links/populate-hardlinks
|
||||||
|
docs/debugging/xattr/xattr
|
||||||
|
hash-set
|
||||||
|
healing-bin
|
||||||
|
inspect
|
||||||
|
pprofgoparser
|
||||||
|
reorder-disks
|
||||||
|
s3-check-md5
|
||||||
|
s3-verify
|
||||||
|
xattr
|
||||||
|
xl-meta
|
||||||
|
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
dist/
|
||||||
|
|
||||||
|
|
||||||
|
.claude/
|
||||||
|
.codex/
|
||||||
|
AGENTS.md
|
||||||
|
CLAUDE.md
|
||||||
|
_bmad/
|
||||||
|
_bmad-output/
|
||||||
|
docs/security/
|
||||||
|
docs/rebranding.md
|
||||||
|
.release-sign/
|
||||||
|
|||||||
+58
-28
@@ -1,34 +1,64 @@
|
|||||||
linters-settings:
|
version: "2"
|
||||||
golint:
|
|
||||||
min-confidence: 0
|
|
||||||
|
|
||||||
misspell:
|
|
||||||
locale: US
|
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
disable-all: true
|
default: none
|
||||||
enable:
|
enable:
|
||||||
- typecheck
|
- durationcheck
|
||||||
- goimports
|
- forcetypeassert
|
||||||
- misspell
|
- gocritic
|
||||||
|
- gomodguard_v2
|
||||||
- govet
|
- govet
|
||||||
- revive
|
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- gosimple
|
- misspell
|
||||||
- deadcode
|
- revive
|
||||||
- structcheck
|
- staticcheck
|
||||||
- gomodguard
|
|
||||||
- gofmt
|
|
||||||
- unused
|
|
||||||
- structcheck
|
|
||||||
- unconvert
|
- unconvert
|
||||||
- varcheck
|
- unused
|
||||||
|
- usetesting
|
||||||
|
- whitespace
|
||||||
|
settings:
|
||||||
|
misspell:
|
||||||
|
locale: US
|
||||||
|
staticcheck:
|
||||||
|
checks:
|
||||||
|
- all
|
||||||
|
- -SA1008
|
||||||
|
- -SA1019
|
||||||
|
- -SA4000
|
||||||
|
- -SA9004
|
||||||
|
- -ST1000
|
||||||
|
- -ST1005
|
||||||
|
- -ST1016
|
||||||
|
- -U1000
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
rules:
|
||||||
|
- linters:
|
||||||
|
- forcetypeassert
|
||||||
|
path: _test\.go
|
||||||
|
- path: (.+)\.go$
|
||||||
|
text: 'empty-block:'
|
||||||
|
- path: (.+)\.go$
|
||||||
|
text: 'unused-parameter:'
|
||||||
|
- path: (.+)\.go$
|
||||||
|
text: 'dot-imports:'
|
||||||
|
- path: (.+)\.go$
|
||||||
|
text: should have a package comment
|
||||||
|
- path: (.+)\.go$
|
||||||
|
text: error strings should not be capitalized or end with punctuation or a newline
|
||||||
|
paths:
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
issues:
|
issues:
|
||||||
exclude-use-default: false
|
max-issues-per-linter: 100
|
||||||
exclude:
|
max-same-issues: 100
|
||||||
- should have a package comment
|
formatters:
|
||||||
- error strings should not be capitalized or end with punctuation or a newline
|
enable:
|
||||||
|
- gofumpt
|
||||||
service:
|
- goimports
|
||||||
golangci-lint-version: 1.20.0 # use the fixed version to not introduce new linters unexpectedly
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
paths:
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
CVE-2020-26160
|
|
||||||
CVE-2020-15136
|
|
||||||
CVE-2020-15115
|
|
||||||
CVE-2020-15114
|
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
[files]
|
||||||
|
extend-exclude = [".git/", "docs/", "CREDITS", "go.mod", "go.sum"]
|
||||||
|
ignore-hidden = false
|
||||||
|
|
||||||
|
[default]
|
||||||
|
extend-ignore-re = [
|
||||||
|
"Patrick Collison",
|
||||||
|
"Copyright 2014 Unknwon",
|
||||||
|
"[0-9A-Za-z/+=]{64}",
|
||||||
|
"ZXJuZXQxDjAMBgNVBA-some-junk-Q4wDAYDVQQLEwVNaW5pbzEOMAwGA1UEAxMF",
|
||||||
|
"eyJmb28iOiJiYXIifQ",
|
||||||
|
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.*",
|
||||||
|
"MIIDBTCCAe2gAwIBAgIQWHw7h.*",
|
||||||
|
'http\.Header\{"X-Amz-Server-Side-Encryptio":',
|
||||||
|
"ZoEoZdLlzVbOlT9rbhD7ZN7TLyiYXSAlB79uGEge",
|
||||||
|
"ERRO:",
|
||||||
|
"(?Rm)^.*(#|//)\\s*spellchecker:disable-line$", # ignore line
|
||||||
|
]
|
||||||
|
|
||||||
|
[default.extend-words]
|
||||||
|
"encrypter" = "encrypter"
|
||||||
|
"kms" = "kms"
|
||||||
|
"requestor" = "requestor"
|
||||||
|
|
||||||
|
[default.extend-identifiers]
|
||||||
|
"HashiCorp" = "HashiCorp"
|
||||||
|
|
||||||
|
[type.go.extend-identifiers]
|
||||||
|
"bui" = "bui"
|
||||||
|
"dm2nd" = "dm2nd"
|
||||||
|
"ot" = "ot"
|
||||||
|
"ParseND" = "ParseND"
|
||||||
|
"ParseNDStream" = "ParseNDStream"
|
||||||
|
"pn" = "pn"
|
||||||
|
"TestGetPartialObjectMisAligned" = "TestGetPartialObjectMisAligned"
|
||||||
|
"thr" = "thr"
|
||||||
|
"toi" = "toi"
|
||||||
|
|
||||||
|
[type.go]
|
||||||
|
extend-ignore-identifiers-re = [
|
||||||
|
# Variants of `typ` used to mean `type` in golang as it is otherwise a
|
||||||
|
# keyword - some of these (like typ1 -> type1) can be fixed, but probably
|
||||||
|
# not worth the effort.
|
||||||
|
"[tT]yp[0-9]*",
|
||||||
|
]
|
||||||
+104
@@ -0,0 +1,104 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
The entries below describe source changes on main since the latest published Server.
|
||||||
|
**The latest published Server remains 20260903.** These changes are not in its
|
||||||
|
binaries, packages or images. See the [component matrix](https://silo.pgsty.com/compatibility/versions/)
|
||||||
|
and [complete commit range](https://github.com/pgsty/silo/compare/RELEASE.2026-09-03T13-18-01Z...main).
|
||||||
|
|
||||||
|
### Authorization and security
|
||||||
|
|
||||||
|
- Reject unsigned `x-amz-*` request headers that could turn a signed PUT into a
|
||||||
|
copy of another object accessible to the signer (SN-2026-011). The latest
|
||||||
|
public Server is affected; the fix is on main. See [the advisory ledger](docs/security/advisories.md).
|
||||||
|
- Align signed request fields with policy conditions and enforce header-only
|
||||||
|
presigned payload checksums. See [the signed-header review](https://silo.pgsty.com/blog/design/signed-header-coverage/).
|
||||||
|
- **Breaking policy semantics:** separate self-service `admin:ChangeMyPassword`
|
||||||
|
from `admin:CreateUser`. Built-in read-only policies follow the split. Preserve
|
||||||
|
both denies if the previous combined restriction must survive upgrades or
|
||||||
|
rollback. Saved policies are not rewritten. Deploy with the matching Console
|
||||||
|
and pkg; see [the migration guide](docs/iam/password-permissions.md).
|
||||||
|
|
||||||
|
### Object storage and replication
|
||||||
|
|
||||||
|
- Evaluate conditional multipart completion against the logical current object
|
||||||
|
across all pools while holding the existing object lock. A stale `If-Match`
|
||||||
|
can no longer replace newer data in another pool, and the current ETag is no
|
||||||
|
longer rejected because the upload resides next to an older copy. Conditions
|
||||||
|
are evaluated once; a current delete marker counts as an absent object.
|
||||||
|
**Availability change:** if metadata cannot be read from any pool, conditional
|
||||||
|
completion fails even when another pool can still serve GET/HEAD. This also
|
||||||
|
applies when the unreadable pool may not hold the object: absence cannot be
|
||||||
|
verified. Retry after the pool recovers. Unconditional completion and the
|
||||||
|
single-pool path retain their existing behavior.
|
||||||
|
|
||||||
|
- Reconcile ordinary single-object version DELETE across all pools, including
|
||||||
|
null versions, delete markers and unqualified directory-marker DELETE. This
|
||||||
|
applies the deletion to every resolved pool copy under existing quorum
|
||||||
|
rules. Pending outbound delete replication retains versions until the
|
||||||
|
existing replication worker completes their purge; a successful response
|
||||||
|
does not imply immediate physical removal from every drive. Unreadable
|
||||||
|
pools now consistently return 503 instead of depending on pool traversal
|
||||||
|
order; insufficient read quorum returns `SlowDownRead`. This extends the
|
||||||
|
existing failure surface. Retry after recovery.
|
||||||
|
Cleanup failures also return an error. Batch deletion already fans out across
|
||||||
|
pools; replication and scanner cleanup keep their existing contracts. See
|
||||||
|
[scope and limitations](docs/bucket/lifecycle/access-tiering-removal.md#version-deletion-scope).
|
||||||
|
|
||||||
|
- Remove the opt-in GET-frequency pool-tiering feature from PR #60, including
|
||||||
|
its tracker, mover, scanner hooks, configuration, XML actions and metrics.
|
||||||
|
Accept and ignore retired configuration/XML and preserve ordinary statistics
|
||||||
|
when reading v9 caches. See [migration notes](docs/bucket/lifecycle/access-tiering-removal.md).
|
||||||
|
The [decision record](docs/investigations/access-tiering-revert.md) preserves
|
||||||
|
the feature's introduction, subsequent fixes, rollback scope and review history.
|
||||||
|
- Preserve the independent multi-pool write, metadata, healing and conditional
|
||||||
|
deletion fixes from PR #178, including shared remote-tier reference protection.
|
||||||
|
- Enforce `If-Match` on DELETE, preserve retention and independently ordered
|
||||||
|
Object Lock/tag updates, and correctly retransmit encrypted replicas.
|
||||||
|
- Preserve plaintext part sizes and raw SSE-C replicas; prevent SSE-C
|
||||||
|
compression, honor key-rotation checksums, and complete attributes pagination.
|
||||||
|
- Repair federated CopyObject checksums, destination timestamps, reserved
|
||||||
|
metadata, encrypted-object forwarding, legal hold and KMS context.
|
||||||
|
- Make resync counters, target selection, cancellation and worker lifetimes
|
||||||
|
reflect actual work; complete delete-marker purges and report bounded MRF drops.
|
||||||
|
- Converge bucket metadata with deterministic source state, deletion tombstones,
|
||||||
|
creation time recovery and diagnostics. The mixed-version export gate requires
|
||||||
|
coordinated upgrades before tombstones are exported. See [the #77 record](docs/investigations/issue-77-current.md).
|
||||||
|
- Include per-bucket CORS in metadata export/import, close metadata publication
|
||||||
|
and logger races, and report effective bucket quotas in metrics.
|
||||||
|
|
||||||
|
### Console, dependencies and delivery
|
||||||
|
|
||||||
|
- Restore embedded Console login over loopback TLS, trusted-proxy handling and
|
||||||
|
all four WebSocket connection limits. Preserve Go TLS defaults across transports.
|
||||||
|
- Directly require `github.com/pgsty/silo-pkg/v3` v3.14.0; select Console
|
||||||
|
`v0.0.0-20260913015128-417559bb2c97` and MC
|
||||||
|
`v0.0.0-20260913012246-4f609a4da3bb` with explicit PGSTY replacements.
|
||||||
|
- Pin upstream minio-go `v7.3.1-0.20260910142817-60bd07042d49`; refresh Go x/*
|
||||||
|
modules and security fixes including bounded AMQP frame handling. Keep Go
|
||||||
|
1.27.1 and go-systemd v22.6.0's NetBSD compatibility replacement.
|
||||||
|
- Refresh container base digests and build static curl 8.22.0 from verified
|
||||||
|
source for both Linux architectures. Pin the actual mcli 20260913 archives and
|
||||||
|
hashes. Helm's client image follows that release; its Server image still names
|
||||||
|
the latest published Server 20260903.
|
||||||
|
|
||||||
|
The dependency update passed the final candidate's Go, vulnerability and Test
|
||||||
|
Release workflows; native curl builds passed on both architectures. A local
|
||||||
|
ARM64 image passed startup, health, S3 transfer and embedded Console checks.
|
||||||
|
These checks do not publish a Server tag or production image and do not replace
|
||||||
|
cluster upgrade/rollback acceptance for the next release. Dated investigations
|
||||||
|
retain the exact source and runtime boundaries they tested.
|
||||||
|
|
||||||
|
## RELEASE.2026-09-03T13-18-01Z
|
||||||
|
|
||||||
|
Published source: `9b11dc9469e650815b775cb47b039610644f5da4`.
|
||||||
|
[Complete release notes](https://silo.pgsty.com/blog/release/silo-20260903/) ·
|
||||||
|
[GitHub release](https://github.com/pgsty/silo/releases/tag/RELEASE.2026-09-03T13-18-01Z)
|
||||||
|
|
||||||
|
This release ships Go 1.27.1, silo-pkg v3.13.2, upstream minio-go `0e78d3f18efe`,
|
||||||
|
mcli 20260903 and embedded Console source `464a59d73ada` (v2.3.0 version identity).
|
||||||
|
Installing the newer standalone mcli or Console does not replace components
|
||||||
|
inside this existing Server binary or image.
|
||||||
|
|
||||||
|
Earlier releases: [release archive](https://github.com/pgsty/silo/releases).
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# AGPLv3 Compliance
|
||||||
|
|
||||||
|
Silo is distributed under the [GNU Affero General Public License v3.0](LICENSE).
|
||||||
|
It incorporates source code from the MinIO project and preserves the original
|
||||||
|
copyright, license, and attribution notices in [`NOTICE`](NOTICE),
|
||||||
|
[`CREDITS`](CREDITS), and source-file headers.
|
||||||
|
|
||||||
|
You are responsible for determining how the AGPLv3 applies to your use,
|
||||||
|
modification, deployment, and distribution of Silo and its dependencies. The
|
||||||
|
Silo maintainers cannot provide legal advice or determine whether a particular
|
||||||
|
application or service satisfies the license. Consult qualified counsel when
|
||||||
|
the obligations are material to your deployment.
|
||||||
|
|
||||||
|
If you convey modified binaries or provide network access to a modified
|
||||||
|
version, review the complete AGPLv3 text and ensure that the corresponding
|
||||||
|
source and notices are made available as required. Dependency licenses and
|
||||||
|
separate notices continue to apply independently.
|
||||||
+119
-30
@@ -1,68 +1,157 @@
|
|||||||
# MinIO Contribution Guide [](https://slack.min.io) [](https://hub.docker.com/r/minio/minio/)
|
# Contributing to Silo
|
||||||
|
|
||||||
``MinIO`` community welcomes your contribution. To make the process as seamless as possible, we recommend you read this contribution guide.
|
Silo welcomes focused contributions that improve security, reliability,
|
||||||
|
compatibility, packaging, tests, or maintainability. This repository preserves
|
||||||
|
MinIO-compatible interfaces and storage formats, so changes must identify and
|
||||||
|
test any compatibility impact.
|
||||||
|
|
||||||
## Development Workflow
|
## Development Workflow
|
||||||
|
|
||||||
Start by forking the MinIO GitHub repository, make changes in a branch and then send a pull request. We encourage pull requests to discuss code changes. Here are the steps in details:
|
Fork the current Silo source repository, create a topic branch, and submit a
|
||||||
|
pull request. Discuss broad or compatibility-sensitive changes in an issue
|
||||||
|
before implementation.
|
||||||
|
|
||||||
### Setup your MinIO GitHub Repository
|
### Set up a checkout
|
||||||
Fork [MinIO upstream](https://github.com/minio/minio/fork) source repository to your own personal repository. Copy the URL of your MinIO fork (you will need it for the `git clone` command below).
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ git clone https://github.com/minio/minio
|
git clone https://github.com/pgsty/silo
|
||||||
$ go install -v
|
cd silo
|
||||||
$ ls /go/bin/minio
|
go build -o silo .
|
||||||
|
./silo --version
|
||||||
```
|
```
|
||||||
|
|
||||||
### Set up git remote as ``upstream``
|
### Keep the lineage remote separate
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ cd minio
|
git remote add lineage https://github.com/minio/minio
|
||||||
$ git remote add upstream https://github.com/minio/minio
|
git fetch lineage
|
||||||
$ git fetch upstream
|
|
||||||
$ git merge upstream/master
|
|
||||||
...
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Do not merge an upstream branch into a pull request unless the maintainers have
|
||||||
|
agreed on the scope. Silo intentionally carries a small downstream delta.
|
||||||
|
|
||||||
### Create your feature branch
|
### Create your feature branch
|
||||||
Before making code changes, make sure you create a separate branch for these changes
|
|
||||||
|
Create a separate branch before making code changes:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git checkout -b my-new-feature
|
git checkout -b my-new-feature
|
||||||
```
|
```
|
||||||
|
|
||||||
### Test MinIO server changes
|
### Test Silo server changes
|
||||||
After your code changes, make sure
|
|
||||||
|
|
||||||
- To add test cases for the new code. If you have questions about how to do it, please ask on our [Slack](https://slack.min.io) channel.
|
Before opening a pull request:
|
||||||
- To run `make verifiers`
|
|
||||||
- To squash your commits into a single commit. `git rebase -i`. It's okay to force update your pull request.
|
- Add or update tests for changed behavior.
|
||||||
- To run `make test` and `make build` completes.
|
- Run `make verifiers`.
|
||||||
|
- If `make rebrand-guard` reports a changed compatibility set, review the
|
||||||
|
listed identifiers; when the change is intended, refresh the baseline with
|
||||||
|
`go run ./buildscripts/rebrand-guard --write` and commit
|
||||||
|
`buildscripts/rebrand-guard/compat-baseline.json`.
|
||||||
|
- Run the smallest relevant package tests, then `make test` when practical.
|
||||||
|
- Run `make build` and confirm the generated executable is `silo`.
|
||||||
|
- Explain any preserved `MINIO_*`, `minio_*`, `x-minio-*`, `/minio/*`,
|
||||||
|
`.minio.sys`, ARN, module/import-path, or serialized compatibility name.
|
||||||
|
|
||||||
### Commit changes
|
### Commit changes
|
||||||
After verification, commit your changes. This is a [great post](https://chris.beams.io/posts/git-commit/) on how to write useful commit messages
|
|
||||||
|
After verification, commit your changes with a concise message and a DCO
|
||||||
|
sign-off (see [Licensing of Contributions](#licensing-of-contributions)):
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git commit -am 'Add some feature'
|
git commit -s -am 'Fix object replication retry handling'
|
||||||
```
|
```
|
||||||
|
|
||||||
### Push to the branch
|
### Push to the branch
|
||||||
|
|
||||||
Push your locally committed changes to the remote origin (your fork)
|
Push your locally committed changes to the remote origin (your fork)
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git push origin my-new-feature
|
git push origin my-new-feature
|
||||||
```
|
```
|
||||||
|
|
||||||
### Create a Pull Request
|
### Create a Pull Request
|
||||||
Pull requests can be created via GitHub. Refer to [this document](https://help.github.com/articles/creating-a-pull-request/) for detailed steps on how to create a pull request. After a Pull Request gets peer reviewed and approved, it will be merged.
|
|
||||||
|
Pull requests should include motivation, reproduction steps where applicable,
|
||||||
|
test evidence, compatibility notes, and documentation impact. Public product
|
||||||
|
documentation is owned by the separate
|
||||||
|
[`pgsty/silo.pgsty.com`](https://github.com/pgsty/silo.pgsty.com) repository.
|
||||||
|
|
||||||
|
## Licensing of Contributions
|
||||||
|
|
||||||
|
Code contributions to PGSTY SILO (`pgsty/silo`) are accepted under the
|
||||||
|
[GNU AGPL v3.0 or later](LICENSE), the same license as the server. Submit issues
|
||||||
|
and pull requests to this repository's maintainers. No separate Apache-2.0
|
||||||
|
license grant to SILO or upstream MinIO maintainers is required.
|
||||||
|
|
||||||
|
* **No CLA.** We do not ask you to sign a Contributor License Agreement and we
|
||||||
|
do not take your copyright. Contributions are accepted inbound=outbound: you
|
||||||
|
keep the copyright to your changes and license them under the same
|
||||||
|
AGPL-3.0-or-later as the project itself. The maintainers receive no rights
|
||||||
|
beyond the project license.
|
||||||
|
|
||||||
|
* **DCO sign-off required.** Every commit must carry a
|
||||||
|
`Signed-off-by: Your Name <you@example.com>` trailer certifying the
|
||||||
|
[Developer Certificate of Origin 1.1](https://developercertificate.org/) —
|
||||||
|
your statement that you have the right to submit the code under the project
|
||||||
|
license. Sign each commit with:
|
||||||
|
|
||||||
|
```
|
||||||
|
git commit -s
|
||||||
|
```
|
||||||
|
|
||||||
|
Forgot some? Repair your branch with `git rebase --signoff` and force-push.
|
||||||
|
CI rejects pull requests containing unsigned commits; the sign-off email
|
||||||
|
must match the commit author email. (Lowercase `-s` is the plain-text DCO
|
||||||
|
sign-off; cryptographic `-S`/GPG signing is welcome but independent.)
|
||||||
|
|
||||||
|
* **Provenance.** Only submit code you are entitled to submit. This matters
|
||||||
|
more here than in most projects: Silo carries a downstream delta over an
|
||||||
|
upstream code base, and cherry-picks from the lineage remote or other forks
|
||||||
|
are routine. When relaying a patch written by someone else, preserve original
|
||||||
|
authorship (`git cherry-pick -x`, keep the author field and any existing
|
||||||
|
`Signed-off-by` trailers) and add your own sign-off as the person passing it
|
||||||
|
along. Never import code from a proprietary distribution.
|
||||||
|
|
||||||
|
* **File headers.** Preserve existing copyright and license notices in inherited
|
||||||
|
and third-party files. New original files name their actual copyright holders
|
||||||
|
and use AGPL-3.0-or-later. Use a header such as the following, then append the
|
||||||
|
standard AGPL boilerplate:
|
||||||
|
|
||||||
|
```
|
||||||
|
// Copyright (c) 2026 Your Name
|
||||||
|
```
|
||||||
|
|
||||||
|
* **Separately licensed material.** Documentation contributions in `docs/`
|
||||||
|
follow its existing [CC BY 4.0 license](docs/LICENSE). Third-party components
|
||||||
|
and earlier Apache-2.0 contributions retain their original licenses and
|
||||||
|
attribution; this policy does not relicense earlier work.
|
||||||
|
|
||||||
|
* **Squash merges** must keep the `Signed-off-by:` trailers in the resulting
|
||||||
|
commit message.
|
||||||
|
|
||||||
|
* **Authorship and tooling.** The human contributor is the author of the commit
|
||||||
|
and the sole signatory of its DCO sign-off. Attribution trailers for
|
||||||
|
assistive tooling (for example `Co-Authored-By:` naming an AI assistant) are
|
||||||
|
informational only: they record which tools were used, and do not create
|
||||||
|
authorship, co-authorship, or any copyright claim. Whoever signs off remains
|
||||||
|
responsible for the content of the commit, whatever produced it.
|
||||||
|
|
||||||
## FAQs
|
## FAQs
|
||||||
### How does ``MinIO`` manage dependencies?
|
|
||||||
``MinIO`` uses `go mod` to manage its dependencies.
|
### How does Silo manage dependencies?
|
||||||
|
|
||||||
|
Silo uses Go modules. Preserve the compatibility module and import paths in
|
||||||
|
`go.mod`; downstream forks are selected with explicit `replace` directives.
|
||||||
|
|
||||||
- Run `go get foo/bar` in the source folder to add the dependency to `go.mod` file.
|
- Run `go get foo/bar` in the source folder to add the dependency to `go.mod` file.
|
||||||
|
|
||||||
To remove a dependency
|
To remove a dependency
|
||||||
|
|
||||||
- Edit your code and remove the import reference.
|
- Edit your code and remove the import reference.
|
||||||
- Run `go mod tidy` in the source folder to remove dependency from `go.mod` file.
|
- Run `go mod tidy` in the source folder to remove dependency from `go.mod` file.
|
||||||
|
|
||||||
### What are the coding guidelines for MinIO?
|
### What are the coding guidelines?
|
||||||
``MinIO`` is fully conformant with Golang style. Refer: [Effective Go](https://github.com/golang/go/wiki/CodeReviewComments) article from Golang project. If you observe offending code, please feel free to send a pull request or ping us on [Slack](https://slack.min.io).
|
|
||||||
|
Follow the existing Go style, run `gofmt` on changed Go files, and keep changes
|
||||||
|
compact. See the Go project's [code review comments](https://go.dev/wiki/CodeReviewComments).
|
||||||
|
|||||||
+184
File diff suppressed because one or more lines are too long
@@ -1,7 +0,0 @@
|
|||||||
FROM minio/minio:latest
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
|
||||||
|
|
||||||
VOLUME ["/data"]
|
|
||||||
|
|
||||||
CMD ["minio"]
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
FROM minio/minio:edge
|
|
||||||
|
|
||||||
CMD ["minio", "server", "/data"]
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
FROM minio/minio:edge
|
|
||||||
|
|
||||||
LABEL maintainer="MinIO Inc <dev@min.io>"
|
|
||||||
|
|
||||||
COPY minio /usr/bin/
|
|
||||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/
|
|
||||||
|
|
||||||
RUN chmod +x /usr/bin/minio && \
|
|
||||||
chmod +x /usr/bin/docker-entrypoint.sh
|
|
||||||
|
|
||||||
EXPOSE 9000
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
|
||||||
|
|
||||||
VOLUME ["/data"]
|
|
||||||
|
|
||||||
CMD ["minio"]
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# The distroless variant ships exactly one program: the silo binary.
|
||||||
|
# No shell, no mc, no curl, no entrypoint script; health checking is
|
||||||
|
# provided by the binary itself (`silo healthcheck`).
|
||||||
|
# Design note: https://silo.pgsty.com/compatibility/feature/healthcheck/
|
||||||
|
|
||||||
|
# A distroless final stage cannot RUN anything, so /data is prepared in a
|
||||||
|
# throwaway stage. It ships world-writable (see pgsty/silo#55): Docker
|
||||||
|
# seeds fresh volumes from the image-layer mountpoint, no entrypoint
|
||||||
|
# exists to repair ownership at runtime, and 0777 is what keeps every
|
||||||
|
# privilege mode working, --user included.
|
||||||
|
FROM busybox:1.37.0 AS prep
|
||||||
|
RUN mkdir -p /prep/data && chmod 0777 /prep/data
|
||||||
|
|
||||||
|
FROM gcr.io/distroless/static-debian12:latest
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.title="Silo" \
|
||||||
|
org.opencontainers.image.description="S3-Interface Libre Object Storage (distroless)" \
|
||||||
|
org.opencontainers.image.url="https://silo.pgsty.com" \
|
||||||
|
org.opencontainers.image.source="https://github.com/pgsty/silo" \
|
||||||
|
org.opencontainers.image.licenses="AGPL-3.0-or-later" \
|
||||||
|
maintainer="PGSTY <https://silo.pgsty.com>"
|
||||||
|
|
||||||
|
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
|
MINIO_ROOT_USER_FILE=access_key \
|
||||||
|
MINIO_ROOT_PASSWORD_FILE=secret_key \
|
||||||
|
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
|
||||||
|
MINIO_CONFIG_ENV_FILE=config.env \
|
||||||
|
HOME=/tmp
|
||||||
|
|
||||||
|
COPY --chmod=0755 silo /usr/bin/silo
|
||||||
|
# COPY of a directory copies its contents, not the directory entry, so an
|
||||||
|
# empty /prep/data would arrive as a default root:0755 /data and non-root
|
||||||
|
# runs would fail storage init. Copying the parent makes data/ itself a
|
||||||
|
# copied entry, which --chmod then actually applies to.
|
||||||
|
COPY --from=prep --chmod=0777 /prep/ /
|
||||||
|
COPY LICENSE NOTICE CREDITS /licenses/
|
||||||
|
|
||||||
|
EXPOSE 9000
|
||||||
|
VOLUME ["/data"]
|
||||||
|
|
||||||
|
# Exec form is mandatory: there is no /bin/sh in this image. `ready`
|
||||||
|
# rather than `live` because Docker health feeds start-order gating
|
||||||
|
# (readiness semantics); the two are identical unless KMS/etcd are used.
|
||||||
|
# The outer timeout stays above the probe's own 5s deadline so the
|
||||||
|
# probe can report its diagnostic line instead of being SIGKILLed.
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=2m --start-interval=2s --retries=3 \
|
||||||
|
CMD ["/usr/bin/silo", "healthcheck", "ready"]
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/silo"]
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
FROM golang:1.27.1-alpine@sha256:cf6fca6641884b8433441b2b0652976f975e1d0fdd26d177eaaf8596087f3125 AS curl-build
|
||||||
|
ARG TARGETARCH
|
||||||
|
COPY dockerscripts/build-static-curl.sh /build/build-static-curl
|
||||||
|
RUN /bin/sh /build/build-static-curl
|
||||||
|
|
||||||
|
# Exercise the exact shipped curl without a dynamic loader or shared libraries.
|
||||||
|
FROM scratch AS curl-runtime
|
||||||
|
COPY --from=curl-build /go/bin/curl /curl
|
||||||
|
COPY --from=curl-build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||||
|
ENTRYPOINT ["/curl"]
|
||||||
|
|
||||||
|
FROM golang:1.27.1-alpine@sha256:cf6fca6641884b8433441b2b0652976f975e1d0fdd26d177eaaf8596087f3125 AS build
|
||||||
|
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
ENV GOPATH=/go
|
||||||
|
ENV CGO_ENABLED=0
|
||||||
|
|
||||||
|
ARG MC_REPO=pgsty/mc
|
||||||
|
ARG MC_VERSION=RELEASE.2026-09-13T00-00-00Z
|
||||||
|
ARG MC_AMD64_SHA256=9d2a92de9c7b887d9b944fe9ddce68d23f1b6df3415092e737594e56593f5e2b
|
||||||
|
ARG MC_ARM64_SHA256=3d82e9ea6c601c4cb44fe5dd5f2ad1b7d7d64369378110f9ada9c524688a452a
|
||||||
|
|
||||||
|
RUN apk add -U --no-cache \
|
||||||
|
ca-certificates \
|
||||||
|
bash \
|
||||||
|
curl \
|
||||||
|
jq && \
|
||||||
|
case "${TARGETARCH}" in \
|
||||||
|
amd64) MC_ARCH=amd64; MC_PINNED_SHA256="${MC_AMD64_SHA256}" ;; \
|
||||||
|
arm64) MC_ARCH=arm64; MC_PINNED_SHA256="${MC_ARM64_SHA256}" ;; \
|
||||||
|
*) echo "Unsupported TARGETARCH=${TARGETARCH}"; exit 1 ;; \
|
||||||
|
esac && \
|
||||||
|
if [ "${MC_VERSION}" = "latest" ]; then \
|
||||||
|
MC_RELEASE_URL="https://api.github.com/repos/${MC_REPO}/releases/latest"; \
|
||||||
|
else \
|
||||||
|
MC_RELEASE_URL="https://api.github.com/repos/${MC_REPO}/releases/tags/${MC_VERSION}"; \
|
||||||
|
fi && \
|
||||||
|
curl -fsSL "${MC_RELEASE_URL}" -o /tmp/mc-release.json && \
|
||||||
|
MC_ARCHIVE_URL=$(jq -r --arg arch "${MC_ARCH}" \
|
||||||
|
'.assets[] | select(.name | endswith("_linux_" + $arch + ".tar.gz")) | .browser_download_url' \
|
||||||
|
/tmp/mc-release.json | head -n 1) && \
|
||||||
|
MC_CHECKSUM_URL=$(jq -r \
|
||||||
|
'.assets[] | select(.name | endswith("_checksums.txt")) | .browser_download_url' \
|
||||||
|
/tmp/mc-release.json | head -n 1) && \
|
||||||
|
[ -n "${MC_ARCHIVE_URL}" ] || { echo "Cannot find mcli archive for linux/${MC_ARCH}"; exit 1; } && \
|
||||||
|
[ -n "${MC_CHECKSUM_URL}" ] || { echo "Cannot find mcli checksums file"; exit 1; } && \
|
||||||
|
ARCHIVE_NAME=$(basename "${MC_ARCHIVE_URL}") && \
|
||||||
|
echo "Downloading ${ARCHIVE_NAME} ..." && \
|
||||||
|
curl -fsSL "${MC_ARCHIVE_URL}" -o /tmp/mcli.tar.gz && \
|
||||||
|
curl -fsSL "${MC_CHECKSUM_URL}" -o /tmp/mcli_checksums.txt && \
|
||||||
|
EXPECTED=$(grep " ${ARCHIVE_NAME}$" /tmp/mcli_checksums.txt | awk '{print $1}') && \
|
||||||
|
ACTUAL=$(sha256sum /tmp/mcli.tar.gz | awk '{print $1}') && \
|
||||||
|
[ -n "${EXPECTED}" ] || { echo "Checksum entry not found for ${ARCHIVE_NAME}"; exit 1; } && \
|
||||||
|
[ "${EXPECTED}" = "${MC_PINNED_SHA256}" ] || { echo "Published checksum drift for ${ARCHIVE_NAME}"; exit 1; } && \
|
||||||
|
[ "${MC_PINNED_SHA256}" = "${ACTUAL}" ] || { echo "Checksum mismatch: expected ${MC_PINNED_SHA256}, got ${ACTUAL}"; exit 1; } && \
|
||||||
|
echo "Checksum OK: ${ACTUAL}" && \
|
||||||
|
mkdir -p /tmp/mcli-extract && \
|
||||||
|
tar -xzf /tmp/mcli.tar.gz -C /tmp/mcli-extract/ && \
|
||||||
|
if [ -f /tmp/mcli-extract/mcli ]; then \
|
||||||
|
cp /tmp/mcli-extract/mcli /go/bin/mcli; \
|
||||||
|
elif [ -f /tmp/mcli-extract/mc ]; then \
|
||||||
|
cp /tmp/mcli-extract/mc /go/bin/mcli; \
|
||||||
|
else \
|
||||||
|
echo "No mc or mcli binary found in archive:"; ls -la /tmp/mcli-extract/; exit 1; \
|
||||||
|
fi && \
|
||||||
|
chmod +x /go/bin/mcli && \
|
||||||
|
ln -sf mcli /go/bin/mc
|
||||||
|
|
||||||
|
FROM registry.access.redhat.com/ubi9/ubi:latest@sha256:206b65b8ee0f04b992818c9a51b29081b14974630d4850bc358097d0c44ea156 AS certs
|
||||||
|
RUN dnf -y install ca-certificates && \
|
||||||
|
update-ca-trust && \
|
||||||
|
cp /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem /tmp/ca-certificates.crt && \
|
||||||
|
dnf clean all && \
|
||||||
|
rm -rf /var/cache/dnf
|
||||||
|
|
||||||
|
FROM registry.access.redhat.com/ubi9/ubi-micro:latest@sha256:f332c99eb8f798a8486821c91937f10ad64ee83d7e739303be2df051040918f6
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.title="Silo" \
|
||||||
|
org.opencontainers.image.description="S3-Interface Libre Object Storage" \
|
||||||
|
org.opencontainers.image.url="https://silo.pgsty.com" \
|
||||||
|
org.opencontainers.image.source="https://github.com/pgsty/silo" \
|
||||||
|
org.opencontainers.image.licenses="AGPL-3.0-or-later" \
|
||||||
|
maintainer="PGSTY <https://silo.pgsty.com>"
|
||||||
|
|
||||||
|
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||||
|
MINIO_SECRET_KEY_FILE=secret_key \
|
||||||
|
MINIO_ROOT_USER_FILE=access_key \
|
||||||
|
MINIO_ROOT_PASSWORD_FILE=secret_key \
|
||||||
|
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
|
||||||
|
MINIO_CONFIG_ENV_FILE=config.env \
|
||||||
|
HOME=/tmp \
|
||||||
|
MC_CONFIG_DIR=/tmp/.mc
|
||||||
|
|
||||||
|
COPY --from=certs /tmp/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||||
|
COPY silo /usr/bin/silo
|
||||||
|
COPY --from=build /go/bin/mcli /usr/bin/mcli
|
||||||
|
COPY --from=curl-build /go/bin/curl /usr/bin/curl
|
||||||
|
COPY --from=curl-build /go/share/curl /licenses/curl
|
||||||
|
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
||||||
|
COPY LICENSE /licenses/LICENSE
|
||||||
|
COPY NOTICE /licenses/NOTICE
|
||||||
|
COPY CREDITS /licenses/CREDITS
|
||||||
|
|
||||||
|
RUN chmod +x /usr/bin/silo /usr/bin/mcli /usr/bin/docker-entrypoint.sh && \
|
||||||
|
ln -sf mcli /usr/bin/mc
|
||||||
|
|
||||||
|
EXPOSE 9000
|
||||||
|
VOLUME ["/data"]
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||||
|
CMD ["silo"]
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4
|
|
||||||
|
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
ARG RELEASE
|
|
||||||
|
|
||||||
LABEL name="MinIO" \
|
|
||||||
vendor="MinIO Inc <dev@min.io>" \
|
|
||||||
maintainer="MinIO Inc <dev@min.io>" \
|
|
||||||
version="${RELEASE}" \
|
|
||||||
release="${RELEASE}" \
|
|
||||||
summary="MinIO is a High Performance Object Storage, API compatible with Amazon S3 cloud storage service." \
|
|
||||||
description="MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads."
|
|
||||||
|
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|
||||||
MINIO_SECRET_KEY_FILE=secret_key \
|
|
||||||
MINIO_ROOT_USER_FILE=access_key \
|
|
||||||
MINIO_ROOT_PASSWORD_FILE=secret_key \
|
|
||||||
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
|
|
||||||
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" \
|
|
||||||
MINIO_CONFIG_ENV_FILE=config.env
|
|
||||||
|
|
||||||
COPY dockerscripts/verify-minio.sh /usr/bin/verify-minio.sh
|
|
||||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
|
||||||
COPY CREDITS /licenses/CREDITS
|
|
||||||
COPY LICENSE /licenses/LICENSE
|
|
||||||
|
|
||||||
RUN \
|
|
||||||
microdnf update --nodocs && \
|
|
||||||
microdnf install curl ca-certificates shadow-utils util-linux iproute iputils --nodocs && \
|
|
||||||
rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && \
|
|
||||||
microdnf install minisign --nodocs && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE} -o /usr/bin/minio && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.sha256sum -o /usr/bin/minio.sha256sum && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.minisig -o /usr/bin/minio.minisig && \
|
|
||||||
microdnf clean all && \
|
|
||||||
chmod +x /usr/bin/minio && \
|
|
||||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
|
||||||
chmod +x /usr/bin/verify-minio.sh && \
|
|
||||||
/usr/bin/verify-minio.sh
|
|
||||||
|
|
||||||
EXPOSE 9000
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
|
||||||
|
|
||||||
VOLUME ["/data"]
|
|
||||||
|
|
||||||
CMD ["minio"]
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4
|
|
||||||
|
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
ARG RELEASE
|
|
||||||
|
|
||||||
LABEL name="MinIO" \
|
|
||||||
vendor="MinIO Inc <dev@min.io>" \
|
|
||||||
maintainer="MinIO Inc <dev@min.io>" \
|
|
||||||
version="${RELEASE}" \
|
|
||||||
release="${RELEASE}" \
|
|
||||||
summary="MinIO is a High Performance Object Storage, API compatible with Amazon S3 cloud storage service." \
|
|
||||||
description="MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads."
|
|
||||||
|
|
||||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|
||||||
MINIO_SECRET_KEY_FILE=secret_key \
|
|
||||||
MINIO_ROOT_USER_FILE=access_key \
|
|
||||||
MINIO_ROOT_PASSWORD_FILE=secret_key \
|
|
||||||
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
|
|
||||||
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" \
|
|
||||||
MINIO_CONFIG_ENV_FILE=config.env
|
|
||||||
|
|
||||||
COPY dockerscripts/verify-minio.sh /usr/bin/verify-minio.sh
|
|
||||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
|
||||||
COPY CREDITS /licenses/CREDITS
|
|
||||||
COPY LICENSE /licenses/LICENSE
|
|
||||||
|
|
||||||
RUN \
|
|
||||||
microdnf update --nodocs && \
|
|
||||||
microdnf install curl ca-certificates shadow-utils util-linux iproute iputils --nodocs && \
|
|
||||||
rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && \
|
|
||||||
microdnf install minisign --nodocs && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.fips -o /usr/bin/minio && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.fips.sha256sum -o /usr/bin/minio.sha256sum && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.fips.minisig -o /usr/bin/minio.minisig && \
|
|
||||||
microdnf clean all && \
|
|
||||||
chmod +x /usr/bin/minio && \
|
|
||||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
|
||||||
chmod +x /usr/bin/verify-minio.sh && \
|
|
||||||
/usr/bin/verify-minio.sh
|
|
||||||
|
|
||||||
EXPOSE 9000
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
|
||||||
|
|
||||||
VOLUME ["/data"]
|
|
||||||
|
|
||||||
CMD ["minio"]
|
|
||||||
@@ -2,90 +2,259 @@ PWD := $(shell pwd)
|
|||||||
GOPATH := $(shell go env GOPATH)
|
GOPATH := $(shell go env GOPATH)
|
||||||
LDFLAGS := $(shell go run buildscripts/gen-ldflags.go)
|
LDFLAGS := $(shell go run buildscripts/gen-ldflags.go)
|
||||||
|
|
||||||
GOARCH := $(shell go env GOARCH)
|
GOOS ?= $(shell go env GOOS)
|
||||||
GOOS := $(shell go env GOOS)
|
GOARCH ?= $(shell go env GOARCH)
|
||||||
|
GOLANGCI_VERSION ?= v2.13.1
|
||||||
|
|
||||||
VERSION ?= $(shell git describe --tags)
|
VERSION ?= $(shell git describe --tags)
|
||||||
TAG ?= "minio/minio:$(VERSION)"
|
REPO ?= docker.io/pgsty
|
||||||
|
TAG ?= $(REPO)/silo:$(VERSION)
|
||||||
|
|
||||||
|
GOLANGCI_DIR = .bin/golangci/$(GOLANGCI_VERSION)
|
||||||
|
GOLANGCI = $(GOLANGCI_DIR)/golangci-lint
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
checks:
|
checks: ## check dependencies
|
||||||
@echo "Checking dependencies"
|
@echo "Checking dependencies"
|
||||||
@(env bash $(PWD)/buildscripts/checkdeps.sh)
|
@(env bash $(PWD)/buildscripts/checkdeps.sh)
|
||||||
|
|
||||||
getdeps:
|
help: ## print this help
|
||||||
@mkdir -p ${GOPATH}/bin
|
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-40s\033[0m %s\n", $$1, $$2}'
|
||||||
@echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin v1.40.1
|
|
||||||
@which msgp 1>/dev/null || (echo "Installing msgp" && go install -v github.com/tinylib/msgp@v1.1.3)
|
|
||||||
@which stringer 1>/dev/null || (echo "Installing stringer" && go install -v golang.org/x/tools/cmd/stringer)
|
|
||||||
|
|
||||||
crosscompile:
|
getdeps: ## fetch necessary dependencies
|
||||||
|
@mkdir -p ${GOPATH}/bin
|
||||||
|
@if [ ! -x "$(GOLANGCI)" ]; then \
|
||||||
|
set -e; \
|
||||||
|
echo "Installing golangci-lint $(GOLANGCI_VERSION)"; \
|
||||||
|
script=$$(mktemp); \
|
||||||
|
trap 'rm -f "$$script"' EXIT; \
|
||||||
|
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/$(GOLANGCI_VERSION)/install.sh -o "$$script"; \
|
||||||
|
sh "$$script" -b $(GOLANGCI_DIR) $(GOLANGCI_VERSION); \
|
||||||
|
fi
|
||||||
|
|
||||||
|
crosscompile: ## cross compile Silo
|
||||||
@(env bash $(PWD)/buildscripts/cross-compile.sh)
|
@(env bash $(PWD)/buildscripts/cross-compile.sh)
|
||||||
|
|
||||||
verifiers: getdeps lint check-gen
|
verifiers: lint check-gen rebrand-guard
|
||||||
|
|
||||||
check-gen:
|
rebrand-guard: ## verify Silo branding and protected compatibility identifiers
|
||||||
|
@go run ./buildscripts/rebrand-guard
|
||||||
|
@env bash $(PWD)/buildscripts/verify-rebrand.sh
|
||||||
|
@env bash $(PWD)/dockerscripts/docker-entrypoint_test.sh
|
||||||
|
|
||||||
|
credits: ## regenerate CREDITS from the licenses of Go modules linked into the binary
|
||||||
|
@env bash $(PWD)/buildscripts/gen-credits.sh
|
||||||
|
|
||||||
|
check-gen: ## check for updated autogenerated files
|
||||||
@go generate ./... >/dev/null
|
@go generate ./... >/dev/null
|
||||||
@(! git diff --name-only | grep '_gen.go$$') || (echo "Non-committed changes in auto-generated code is detected, please commit them to proceed." && false)
|
@go mod tidy -compat=1.27
|
||||||
|
@env bash $(PWD)/buildscripts/gen-credits.sh
|
||||||
|
@changed=$$(git diff --name-only -- '*_gen.go' '*_gen_test.go' '*_msgp_test.go' '*_string.go' go.mod go.sum CREDITS); \
|
||||||
|
if [ -n "$$changed" ]; then \
|
||||||
|
echo "Non-committed generated changes detected:"; \
|
||||||
|
echo "$$changed"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
@untracked=$$(git ls-files --others --exclude-standard -- '*_gen.go' '*_gen_test.go' '*_msgp_test.go' '*_string.go'); \
|
||||||
|
if [ -n "$$untracked" ]; then \
|
||||||
|
echo "Untracked generated files detected:"; \
|
||||||
|
echo "$$untracked"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
lint:
|
lint: getdeps ## runs golangci-lint suite of linters
|
||||||
@echo "Running $@ check"
|
@echo "Running $@ check"
|
||||||
@GO111MODULE=on ${GOPATH}/bin/golangci-lint cache clean
|
@$(GOLANGCI) run --build-tags kqueue --timeout=10m --config ./.golangci.yml
|
||||||
@GO111MODULE=on ${GOPATH}/bin/golangci-lint run --build-tags kqueue --timeout=10m --config ./.golangci.yml
|
@if command -v typos >/dev/null 2>&1; then typos ./; else echo "typos binary is not found.. skipping.."; fi
|
||||||
|
|
||||||
|
lint-fix: getdeps ## runs golangci-lint suite of linters with automatic fixes
|
||||||
|
@echo "Running $@ check"
|
||||||
|
@$(GOLANGCI) run --build-tags kqueue --timeout=10m --config ./.golangci.yml --fix
|
||||||
|
|
||||||
# Builds minio, runs the verifiers then runs the tests.
|
|
||||||
check: test
|
check: test
|
||||||
test: verifiers build
|
test: verifiers build ## builds Silo, runs linters, tests
|
||||||
@echo "Running unit tests"
|
@echo "Running unit tests"
|
||||||
@GOGC=25 GO111MODULE=on CGO_ENABLED=0 go test -tags kqueue ./... 1>/dev/null
|
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -v -tags kqueue,dev ./...
|
||||||
|
|
||||||
test-race: verifiers build
|
test-root-disable: install-race
|
||||||
|
@echo "Running Silo root lockdown tests"
|
||||||
|
@env bash $(PWD)/buildscripts/disable-root.sh
|
||||||
|
|
||||||
|
test-ilm: install-race
|
||||||
|
@echo "Running ILM tests"
|
||||||
|
@env bash $(PWD)/docs/bucket/replication/setup_ilm_expiry_replication.sh
|
||||||
|
|
||||||
|
test-ilm-transition: install-race
|
||||||
|
@echo "Running ILM tiering tests with healing"
|
||||||
|
@env bash $(PWD)/docs/bucket/lifecycle/setup_ilm_transition.sh
|
||||||
|
|
||||||
|
test-pbac: install-race
|
||||||
|
@echo "Running bucket policies tests"
|
||||||
|
@env bash $(PWD)/docs/iam/policies/pbac-tests.sh
|
||||||
|
|
||||||
|
test-decom: install-race
|
||||||
|
@echo "Running Silo decom tests"
|
||||||
|
@env bash $(PWD)/docs/distributed/decom.sh
|
||||||
|
@env bash $(PWD)/docs/distributed/decom-encrypted.sh
|
||||||
|
@env bash $(PWD)/docs/distributed/decom-encrypted-sse-s3.sh
|
||||||
|
@env bash $(PWD)/docs/distributed/decom-compressed-sse-s3.sh
|
||||||
|
@env bash $(PWD)/docs/distributed/decom-encrypted-kes.sh
|
||||||
|
|
||||||
|
test-versioning: install-race
|
||||||
|
@echo "Running Silo versioning tests"
|
||||||
|
@env bash $(PWD)/docs/bucket/versioning/versioning-tests.sh
|
||||||
|
|
||||||
|
test-configfile: install-race
|
||||||
|
@env bash $(PWD)/docs/distributed/distributed-from-config-file.sh
|
||||||
|
|
||||||
|
test-upgrade:
|
||||||
|
@echo "Running MinIO-to-Silo upgrade tests"
|
||||||
|
@(env bash $(PWD)/buildscripts/minio-upgrade.sh)
|
||||||
|
|
||||||
|
test-race: verifiers build ## builds Silo, runs linters, tests (race)
|
||||||
@echo "Running unit tests under -race"
|
@echo "Running unit tests under -race"
|
||||||
@(env bash $(PWD)/buildscripts/race.sh)
|
@(env bash $(PWD)/buildscripts/race.sh)
|
||||||
|
|
||||||
# Verify minio binary
|
test-iam: install-race ## verify IAM (external IDP, etcd backends)
|
||||||
verify:
|
@echo "Running tests for IAM (external IDP, etcd backends)"
|
||||||
|
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -timeout 15m -tags kqueue,dev -v -run TestIAM* ./cmd
|
||||||
|
@echo "Running tests for IAM (external IDP, etcd backends) with -race"
|
||||||
|
@MINIO_API_REQUESTS_MAX=10000 GORACE=history_size=7 CGO_ENABLED=1 go test -timeout 15m -race -tags kqueue,dev -v -run TestIAM* ./cmd
|
||||||
|
|
||||||
|
test-iam-ldap-upgrade-import: install-race ## verify IAM (external LDAP IDP)
|
||||||
|
@echo "Running upgrade tests for IAM (LDAP backend)"
|
||||||
|
@env bash $(PWD)/buildscripts/minio-iam-ldap-upgrade-import-test.sh
|
||||||
|
|
||||||
|
test-iam-import-with-missing-entities: install-race ## test import of external iam config withg missing entities
|
||||||
|
@echo "Test IAM import configurations with missing entities"
|
||||||
|
@env bash $(PWD)/docs/distributed/iam-import-with-missing-entities.sh
|
||||||
|
|
||||||
|
test-iam-import-with-openid: install-race
|
||||||
|
@echo "Test IAM import configurations with openid"
|
||||||
|
@env bash $(PWD)/docs/distributed/iam-import-with-openid.sh
|
||||||
|
|
||||||
|
test-sio-error:
|
||||||
|
@(env bash $(PWD)/docs/bucket/replication/sio-error.sh)
|
||||||
|
|
||||||
|
test-replication-2site:
|
||||||
|
@(env bash $(PWD)/docs/bucket/replication/setup_2site_existing_replication.sh)
|
||||||
|
|
||||||
|
test-replication-3site:
|
||||||
|
@(env bash $(PWD)/docs/bucket/replication/setup_3site_replication.sh)
|
||||||
|
|
||||||
|
test-delete-replication:
|
||||||
|
@(env bash $(PWD)/docs/bucket/replication/delete-replication.sh)
|
||||||
|
|
||||||
|
test-delete-marker-proxying:
|
||||||
|
@(env bash $(PWD)/docs/bucket/replication/test_del_marker_proxying.sh)
|
||||||
|
|
||||||
|
test-replication: install-race test-replication-2site test-replication-3site test-delete-replication test-sio-error test-delete-marker-proxying ## verify multi site replication
|
||||||
|
@echo "Running tests for replicating three sites"
|
||||||
|
|
||||||
|
test-site-replication-ldap: install-race ## verify automatic site replication
|
||||||
|
@echo "Running tests for automatic site replication of IAM (with LDAP)"
|
||||||
|
@(env bash $(PWD)/docs/site-replication/run-multi-site-ldap.sh)
|
||||||
|
|
||||||
|
test-site-replication-oidc: install-race ## verify automatic site replication
|
||||||
|
@echo "Running tests for automatic site replication of IAM (with OIDC)"
|
||||||
|
@(env bash $(PWD)/docs/site-replication/run-multi-site-oidc.sh)
|
||||||
|
|
||||||
|
test-site-replication-silo: install-race ## verify automatic site replication
|
||||||
|
@echo "Running tests for automatic site replication of IAM (with Silo IDP)"
|
||||||
|
@(env bash $(PWD)/docs/site-replication/run-multi-site-silo-idp.sh)
|
||||||
|
@echo "Running tests for automatic site replication of SSE-C objects"
|
||||||
|
@(env bash $(PWD)/docs/site-replication/run-ssec-object-replication.sh)
|
||||||
|
@echo "Running tests for automatic site replication of SSE-C objects with SSE-KMS enabled for bucket"
|
||||||
|
@(env bash $(PWD)/docs/site-replication/run-sse-kms-object-replication.sh)
|
||||||
|
@echo "Running tests for automatic site replication of SSE-C objects with compression enabled for site"
|
||||||
|
@(env bash $(PWD)/docs/site-replication/run-ssec-object-replication-with-compression.sh)
|
||||||
|
|
||||||
|
test-multipart: install-race ## test multipart
|
||||||
|
@echo "Test multipart behavior when part files are missing"
|
||||||
|
@(env bash $(PWD)/buildscripts/multipart-quorum-test.sh)
|
||||||
|
|
||||||
|
test-timeout: install-race ## test multipart
|
||||||
|
@echo "Test server timeout"
|
||||||
|
@(env bash $(PWD)/buildscripts/test-timeout.sh)
|
||||||
|
|
||||||
|
verify: install-race ## verify Silo in various setups
|
||||||
@echo "Verifying build with race"
|
@echo "Verifying build with race"
|
||||||
@GO111MODULE=on CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
|
||||||
@(env bash $(PWD)/buildscripts/verify-build.sh)
|
@(env bash $(PWD)/buildscripts/verify-build.sh)
|
||||||
|
|
||||||
# Verify healing of disks with minio binary
|
verify-healing: install-race ## verify healing and replacing disks with the Silo binary
|
||||||
verify-healing:
|
|
||||||
@echo "Verify healing build with race"
|
@echo "Verify healing build with race"
|
||||||
@GO111MODULE=on CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
|
||||||
@(env bash $(PWD)/buildscripts/verify-healing.sh)
|
@(env bash $(PWD)/buildscripts/verify-healing.sh)
|
||||||
|
@(env bash $(PWD)/buildscripts/verify-healing-empty-erasure-set.sh)
|
||||||
|
@(env bash $(PWD)/buildscripts/heal-inconsistent-versions.sh)
|
||||||
|
|
||||||
# Builds minio locally.
|
verify-healing-with-root-disks: install-race ## verify healing root disks
|
||||||
build: checks
|
@echo "Verify healing with root drives"
|
||||||
@echo "Building minio binary to './minio'"
|
@(env bash $(PWD)/buildscripts/verify-healing-with-root-disks.sh)
|
||||||
@GO111MODULE=on CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
|
||||||
|
|
||||||
hotfix-vars:
|
verify-healing-with-rewrite: install-race ## verify healing to rewrite old xl.meta -> new xl.meta
|
||||||
$(eval LDFLAGS := $(shell MINIO_RELEASE="RELEASE" MINIO_HOTFIX="hotfix.$(shell git rev-parse --short HEAD)" go run buildscripts/gen-ldflags.go $(shell git describe --tags --abbrev=0 | \
|
@echo "Verify healing with rewrite"
|
||||||
sed 's#RELEASE\.\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)T\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)Z#\1-\2-\3T\4:\5:\6Z#')))
|
@(env bash $(PWD)/buildscripts/rewrite-old-new.sh)
|
||||||
$(eval TAG := "minio/minio:$(shell git describe --tags --abbrev=0).hotfix.$(shell git rev-parse --short HEAD)")
|
|
||||||
hotfix: hotfix-vars install
|
|
||||||
|
|
||||||
docker-hotfix: hotfix checks
|
verify-healing-inconsistent-versions: install-race ## verify resolving inconsistent versions
|
||||||
@echo "Building minio docker image '$(TAG)'"
|
@echo "Verify resolving inconsistent versions build with race"
|
||||||
@docker build -t $(TAG) . -f Dockerfile.dev
|
@(env bash $(PWD)/buildscripts/resolve-right-versions.sh)
|
||||||
|
|
||||||
docker: build checks
|
build-debugging:
|
||||||
@echo "Building minio docker image '$(TAG)'"
|
@(env bash $(PWD)/docs/debugging/build.sh)
|
||||||
@docker build -t $(TAG) . -f Dockerfile.dev
|
|
||||||
|
|
||||||
# Builds minio and installs it to $GOPATH/bin.
|
build: checks build-debugging ## builds Silo to $(PWD)
|
||||||
install: build
|
@echo "Building Silo binary to './silo'"
|
||||||
@echo "Installing minio binary to '$(GOPATH)/bin/minio'"
|
@CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/silo 1>/dev/null
|
||||||
@mkdir -p $(GOPATH)/bin && cp -f $(PWD)/minio $(GOPATH)/bin/minio
|
|
||||||
@echo "Installation successful. To learn more, try \"minio --help\"."
|
|
||||||
|
|
||||||
clean:
|
docker: checks build-debugging ## builds the local Linux Silo container image
|
||||||
|
@echo "Building Silo container image '$(TAG)'"
|
||||||
|
@set -e; \
|
||||||
|
context=$$(mktemp -d); \
|
||||||
|
trap 'rm -rf "$$context"' EXIT; \
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=$(GOARCH) go build -tags kqueue -trimpath \
|
||||||
|
--ldflags "$(LDFLAGS)" -o "$$context/silo"; \
|
||||||
|
mkdir -p "$$context/dockerscripts"; \
|
||||||
|
cp Dockerfile.goreleaser LICENSE NOTICE CREDITS "$$context/"; \
|
||||||
|
cp dockerscripts/docker-entrypoint.sh dockerscripts/build-static-curl.sh \
|
||||||
|
"$$context/dockerscripts/"; \
|
||||||
|
docker build -q --no-cache --platform linux/$(GOARCH) -t $(TAG) --build-arg TARGETARCH=$(GOARCH) \
|
||||||
|
-f "$$context/Dockerfile.goreleaser" "$$context"
|
||||||
|
|
||||||
|
docker-distroless: checks build-debugging ## builds the local Linux Silo distroless container image
|
||||||
|
@echo "Building Silo distroless container image '$(TAG)-distroless'"
|
||||||
|
@set -e; \
|
||||||
|
context=$$(mktemp -d); \
|
||||||
|
trap 'rm -rf "$$context"' EXIT; \
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=$(GOARCH) go build -tags kqueue -trimpath \
|
||||||
|
--ldflags "$(LDFLAGS)" -o "$$context/silo"; \
|
||||||
|
cp Dockerfile.distroless LICENSE NOTICE CREDITS "$$context/"; \
|
||||||
|
docker build -q --no-cache --platform linux/$(GOARCH) -t $(TAG)-distroless \
|
||||||
|
-f "$$context/Dockerfile.distroless" "$$context"
|
||||||
|
|
||||||
|
test-resiliency: build
|
||||||
|
@echo "Running resiliency tests"
|
||||||
|
@(DOCKER_COMPOSE_FILE=$(PWD)/docs/resiliency/docker-compose.yaml env bash $(PWD)/docs/resiliency/resiliency-tests.sh)
|
||||||
|
|
||||||
|
install-race: checks build-debugging ## builds Silo to $(PWD)
|
||||||
|
@echo "Building Silo binary with -race to './silo'"
|
||||||
|
@GORACE=history_size=7 CGO_ENABLED=1 go build -tags kqueue,dev -race -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/silo 1>/dev/null
|
||||||
|
@echo "Installing Silo binary with -race to '$(GOPATH)/bin/silo'"
|
||||||
|
@mkdir -p $(GOPATH)/bin && cp -af $(PWD)/silo $(GOPATH)/bin/silo
|
||||||
|
|
||||||
|
install: build ## builds Silo and installs it to $GOPATH/bin.
|
||||||
|
@echo "Installing Silo binary to '$(GOPATH)/bin/silo'"
|
||||||
|
@mkdir -p $(GOPATH)/bin && cp -af $(PWD)/silo $(GOPATH)/bin/silo
|
||||||
|
@echo "Installation successful. To learn more, try \"silo --help\"."
|
||||||
|
|
||||||
|
clean: ## cleanup all generated assets
|
||||||
@echo "Cleaning up all the generated files"
|
@echo "Cleaning up all the generated files"
|
||||||
@find . -name '*.test' | xargs rm -fv
|
@find . -name '*.test' | xargs rm -fv
|
||||||
@find . -name '*~' | xargs rm -fv
|
@find . -name '*~' | xargs rm -fv
|
||||||
@rm -rvf minio
|
@find . -name '.#*#' | xargs rm -fv
|
||||||
|
@find . -name '#*#' | xargs rm -fv
|
||||||
|
@rm -rvf silo
|
||||||
@rm -rvf build
|
@rm -rvf build
|
||||||
@rm -rvf release
|
@rm -rvf release
|
||||||
@rm -rvf .verify*
|
@rm -rvf .verify*
|
||||||
|
@rm -rvf pkger_*.deb
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
MinIO Project, (C) 2015-2021 MinIO, Inc.
|
MinIO Project, (C) 2015-2025 MinIO, Inc.
|
||||||
|
|
||||||
This product includes software developed at MinIO, Inc.
|
This product includes software developed at MinIO, Inc.
|
||||||
(https://min.io/).
|
(https://min.io/).
|
||||||
@@ -7,3 +7,9 @@ The MinIO project contains unmodified/modified subcomponents too with
|
|||||||
separate copyright notices and license terms. Your use of the source
|
separate copyright notices and license terms. Your use of the source
|
||||||
code for these subcomponents is subject to the terms and conditions
|
code for these subcomponents is subject to the terms and conditions
|
||||||
of GNU Affero General Public License 3.0.
|
of GNU Affero General Public License 3.0.
|
||||||
|
|
||||||
|
Silo Project modifications, (C) 2025-2026 PGSTY.
|
||||||
|
|
||||||
|
Silo is an independent community-maintained project incorporating MinIO
|
||||||
|
source code. It is not affiliated with or endorsed by MinIO, Inc. Modified
|
||||||
|
source and Silo release artifacts are maintained by the Silo project.
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
# Silo Pull Request Guidelines
|
||||||
|
|
||||||
|
These guidelines ensure high-quality commits in Silo's GitHub repositories, maintaining
|
||||||
|
a clear, valuable commit history for our open-source projects. They apply to all contributors,
|
||||||
|
fostering efficient reviews and robust code.
|
||||||
|
|
||||||
|
## Why Pull Requests?
|
||||||
|
|
||||||
|
Pull Requests (PRs) drive quality in Silo's codebase by:
|
||||||
|
- Enabling peer review without pair programming.
|
||||||
|
- Documenting changes for future reference.
|
||||||
|
- Ensuring commits tell a clear story of development.
|
||||||
|
|
||||||
|
**A poor commit lasts forever, even if code is refactored.**
|
||||||
|
|
||||||
|
## Crafting a Quality PR
|
||||||
|
|
||||||
|
A strong Silo PR:
|
||||||
|
- Delivers a complete, valuable change (feature, bug fix, or improvement).
|
||||||
|
- Has a concise title (e.g., `[S3] Fix bucket policy parsing #1234`) and a summary with context, referencing issues (e.g., `#1234`).
|
||||||
|
- Contains well-written, logical commits explaining *why* changes were made (e.g., “Add S3 bucket tagging support so that users can organize resources efficiently”).
|
||||||
|
- Is small, focused, and easy to review—ideally one commit, unless multiple commits better narrate complex work.
|
||||||
|
- Adheres to Silo's coding standards (e.g., Go style, error handling, testing).
|
||||||
|
|
||||||
|
PRs must flow smoothly through review to reach production. Large PRs should be split into smaller, manageable ones.
|
||||||
|
|
||||||
|
## Submitting PRs
|
||||||
|
|
||||||
|
1. **Title and Summary**:
|
||||||
|
- Use a scannable title: `[Subsystem] Action Description #Issue` (e.g., `[IAM] Add role-based access control #567`).
|
||||||
|
- Include context in the summary: what changed, why, and any issue references.
|
||||||
|
- Use `[WIP]` for in-progress PRs to avoid premature merging or choose GitHub draft PRs.
|
||||||
|
|
||||||
|
2. **Commits**:
|
||||||
|
- Write clear messages: what changed and why (e.g., “Refactor S3 API handler to reduce latency so that requests process 20% faster”).
|
||||||
|
- Rebase to tidy commits before submitting (e.g., `git rebase -i main` to squash typos or reword messages), unless multiple contributors worked on the branch.
|
||||||
|
- Keep PRs focused—one feature or fix. Split large changes into multiple PRs.
|
||||||
|
|
||||||
|
3. **Testing**:
|
||||||
|
- Include unit tests for new functionality or bug fixes.
|
||||||
|
- Ensure existing tests pass (`make test`).
|
||||||
|
- Document testing steps in the PR summary if manual testing was performed.
|
||||||
|
|
||||||
|
4. **Before Submitting**:
|
||||||
|
- Run `make verify` to check formatting, linting, and tests.
|
||||||
|
- Reference related issues (e.g., “Closes #1234”).
|
||||||
|
- Notify team members via GitHub `@mentions` if urgent or complex.
|
||||||
|
|
||||||
|
## Reviewing PRs
|
||||||
|
|
||||||
|
Reviewers ensure Silo's commit history remains a clear, reliable record. Responsibilities include:
|
||||||
|
|
||||||
|
1. **Commit Quality**:
|
||||||
|
- Verify each commit explains *why* the change was made (e.g., “So that…”).
|
||||||
|
- Request rebasing if commits are unclear, redundant, or lack context (e.g., “Please squash typo fixes into the parent commit”).
|
||||||
|
|
||||||
|
2. **Code Quality**:
|
||||||
|
- Check adherence to Silo's Go standards (e.g., error handling, documentation).
|
||||||
|
- Ensure tests cover new code and pass CI.
|
||||||
|
- Flag bugs or critical issues for immediate fixes; suggest non-blocking improvements as follow-up issues.
|
||||||
|
|
||||||
|
3. **Flow**:
|
||||||
|
- Review promptly to avoid blocking progress.
|
||||||
|
- Balance quality and speed—minor issues can be addressed later via issues, not PR blocks.
|
||||||
|
- If unable to complete the review, tag another reviewer (e.g., `@username please take over`).
|
||||||
|
|
||||||
|
4. **Shared Responsibility**:
|
||||||
|
- All Silo contributors are reviewers. The first commenter on a PR owns the review unless they delegate.
|
||||||
|
- Multiple reviewers are encouraged for complex PRs.
|
||||||
|
|
||||||
|
5. **No Self-Edits**:
|
||||||
|
- Don’t modify the PR directly (e.g., fixing bugs). Request changes from the submitter or create a follow-up PR.
|
||||||
|
- If you edit, you’re a collaborator, not a reviewer, and cannot merge.
|
||||||
|
|
||||||
|
6. **Testing**:
|
||||||
|
- Assume the submitter tested the code. If testing is unclear, ask for details (e.g., “How was this tested?”).
|
||||||
|
- Reject untested PRs unless testing is infeasible, then assist with test setup.
|
||||||
|
|
||||||
|
## Tips for Success
|
||||||
|
|
||||||
|
- **Small PRs**: Easier to review, faster to merge. Split large changes logically.
|
||||||
|
- **Clear Commits**: Use `git rebase -i` to refine history before submitting.
|
||||||
|
- **Engage Early**: Discuss complex changes in a GitHub issue before coding.
|
||||||
|
- **Be Responsive**: Address reviewer feedback promptly to keep PRs moving.
|
||||||
|
- **Learn from Reviews**: Use feedback to improve future contributions.
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [Silo Contribution Guide](CONTRIBUTING.md)
|
||||||
|
- [Effective Commit Messages](https://mislav.net/2014/02/hidden-documentation/)
|
||||||
|
- [GitHub PR Tips](https://github.com/blog/1943-how-to-write-the-perfect-pull-request)
|
||||||
|
|
||||||
|
By following these guidelines, we ensure Silo's codebase remains high-quality, maintainable, and a joy to contribute to. Happy coding!
|
||||||
@@ -1,261 +1,191 @@
|
|||||||
# MinIO Quickstart Guide
|
<h1 align="center">
|
||||||
[](https://slack.min.io) [](https://hub.docker.com/r/minio/minio/) [](https://github.com/minio/minio/blob/master/LICENSE)
|
<a href="https://silo.pgsty.com/">
|
||||||
|
<img src=".github/silo-logo.svg" alt="Silo" width="160">
|
||||||
|
</a>
|
||||||
|
</h1>
|
||||||
|
|
||||||
[](https://min.io)
|
|
||||||
|
|
||||||
MinIO is a High Performance Object Storage released under GNU Affero General Public License v3.0. It is API compatible with Amazon S3 cloud storage service. Use MinIO to build high performance infrastructure for machine learning, analytics and application data workloads.
|
<p align="center">
|
||||||
|
<strong>S3-compatible object storage — a MinIO fork maintained by PGSTY</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
This README provides quickstart instructions on running MinIO on baremetal hardware, including container-based installations. For Kubernetes environments, use the [MinIO Kubernetes Operator](https://github.com/minio/operator/blob/master/README.md).
|
|
||||||
|
|
||||||
# Container Installation
|
<p align="center">
|
||||||
|
<a href="https://silo.pgsty.com/">Website</a> ·
|
||||||
|
<a href="https://silo.pgsty.com/docs/">Documentation</a> ·
|
||||||
|
<a href="https://silo.pgsty.com/download/">Download</a> ·
|
||||||
|
<a href="https://silo.pgsty.com/tags/silo/">Release Notes</a> ·
|
||||||
|
<a href="https://silo.pgsty.com/compatibility/server/">Compatibility</a> ·
|
||||||
|
<a href="https://silo.pgsty.com/about/manifesto/">Manifesto</a> ·
|
||||||
|
<a href="SECURITY.md">Security</a> ·
|
||||||
|
<a href="README_ZH.md">中文</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
Use the following commands to run a standalone MinIO server as a container.
|
<p align="center">
|
||||||
|
<a href="https://silo.pgsty.com/"><img alt="Website" src="https://img.shields.io/badge/Website-silo.pgsty.com-1d588c"></a>
|
||||||
|
<a href="https://github.com/pgsty/silo/releases"><img alt="GitHub Release" src="https://img.shields.io/github/v/release/pgsty/silo?include_prereleases&label=release&logo=github"></a>
|
||||||
|
<a href="https://hub.docker.com/r/pgsty/silo"><img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/pgsty/minio?logo=docker"></a>
|
||||||
|
<a href="go.mod"><img alt="Go Version" src="https://img.shields.io/github/go-mod/go-version/pgsty/silo?logo=go"></a>
|
||||||
|
<a href="LICENSE"><img alt="License" src="https://img.shields.io/badge/license-AGPLv3-blue"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication
|
> [!IMPORTANT]
|
||||||
require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically,
|
> **PGSTY Silo** (hereinafter “Silo”) is an independent, community-maintained fork of the open-source MinIO server, published by [Pigsty](https://pigsty.io) from [`pgsty/silo`](https://github.com/pgsty/silo). It is not affiliated with, endorsed by, or sponsored by MinIO, Inc. “MinIO” is used only to identify the upstream project and compatibility lineage.
|
||||||
with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Quickstart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide.html)
|
|
||||||
for more complete documentation.
|
|
||||||
|
|
||||||
## Stable
|
> [!NOTE]
|
||||||
|
> Renamed from `pgsty/minio` to `pgsty/silo`, default branch `master` → `main`, on 2026-08-06. Artifacts under the original MinIO identity stay published on the archived [`minio`](https://github.com/pgsty/silo/tree/minio) branch and in releases up to [`RELEASE.2026-08-04T00-00-00Z`](https://github.com/pgsty/silo/releases/tag/RELEASE.2026-08-04T00-00-00Z).
|
||||||
|
|
||||||
Run the following command to run the latest stable image of MinIO as a container using an ephemeral data volume:
|
## Current release and main branch
|
||||||
|
|
||||||
```sh
|
The latest published Server is [20260903](https://github.com/pgsty/silo/releases/tag/RELEASE.2026-09-03T13-18-01Z).
|
||||||
podman run -p 9000:9000 -p 9001:9001 \
|
As of 2026-09-13, the main branch has newer security, storage, Console and
|
||||||
quay.io/minio/minio server /data --console-address ":9001"
|
shared-package changes that have not shipped in a Server release. See
|
||||||
|
[CHANGELOG.md](CHANGELOG.md) and the [component version matrix](https://silo.pgsty.com/compatibility/versions/)
|
||||||
|
for the exact release/source boundary, including SN-2026-011 and password-policy migration.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
PGSTY SILO keeps one maintained release line of the open-source MinIO server alive after upstream ended community distribution: builds, packages, multi-arch images, security fixes, and the full web console. Pigsty runs it in production as its PostgreSQL backup repository.
|
||||||
|
|
||||||
|
It follows one rule — **the product and its delivery surfaces are renamed; the protocol and your data are not.** Everything else lives on [silo.pgsty.com](https://silo.pgsty.com/).
|
||||||
|
|
||||||
|
**Related:** [`pgsty/mc`](https://github.com/pgsty/mc) client (shipped as `mcli`) · [`pgsty/silo-console`](https://github.com/pgsty/silo-console) · [`pgsty/silo-pkg`](https://github.com/pgsty/silo-pkg) · [`pgsty/pigsty`](https://github.com/pgsty/pigsty)
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://silo.pgsty.com/images/silo-console/console-metrics-simple.webp" alt="Silo Console">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d --name silo -p 9000:9000 -p 9001:9001 \
|
||||||
|
-e MINIO_ROOT_USER=minioadmin \
|
||||||
|
-e MINIO_ROOT_PASSWORD=change-me-long-password \
|
||||||
|
-v "$PWD/data:/data" \
|
||||||
|
docker.io/pgsty/silo:latest server /data --console-address ":9001"
|
||||||
```
|
```
|
||||||
|
|
||||||
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded
|
<p align="center">
|
||||||
object browser built into MinIO Server. Point a web browser running on the host machine to http://127.0.0.1:9000 and log in with the
|
<img src="https://silo.pgsty.com/images/silo-console/console-login.webp" alt="Silo Console">
|
||||||
root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
</p>
|
||||||
|
|
||||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See
|
Console on <http://localhost:9001>, S3 API on <http://localhost:9000>. The image bundles the client as `mcli`:
|
||||||
[Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers,
|
|
||||||
see https://docs.min.io/docs/ and click **MinIO SDKs** in the navigation to view MinIO SDKs for supported languages.
|
|
||||||
|
|
||||||
> NOTE: To deploy MinIO on with persistent storage, you must map local persistent directories from the host OS to the container using the `podman -v` option. For example, `-v /mnt/data:/data` maps the host OS drive at `/mnt/data` to `/data` on the container.
|
```bash
|
||||||
|
docker exec silo mcli alias set local http://127.0.0.1:9000 minioadmin change-me-long-password
|
||||||
# macOS
|
docker exec silo mcli mb local/demo && docker exec silo mcli ls local
|
||||||
|
|
||||||
Use the following commands to run a standalone MinIO server on macOS.
|
|
||||||
|
|
||||||
Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically, with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Quickstart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide.html) for more complete documentation.
|
|
||||||
|
|
||||||
## Homebrew (recommended)
|
|
||||||
|
|
||||||
Run the following command to install the latest stable MinIO package using [Homebrew](https://brew.sh/). Replace ``/data`` with the path to the drive or directory in which you want MinIO to store data.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
brew install minio/stable/minio
|
|
||||||
minio server /data
|
|
||||||
```
|
```
|
||||||
|
|
||||||
> NOTE: If you previously installed minio using `brew install minio` then it is recommended that you reinstall minio from `minio/stable/minio` official repo instead.
|
> [!WARNING]
|
||||||
|
> For production, pin a release, use unique credentials and TLS, monitor the service, keep independent backups, and test recovery. Start from the [documentation](https://silo.pgsty.com/docs/).
|
||||||
```sh
|
|
||||||
brew uninstall minio
|
## Install
|
||||||
brew install minio/stable/minio
|
|
||||||
```
|
| Method | Where |
|
||||||
|
| :-- | :-- |
|
||||||
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded web-based object browser built into MinIO Server. Point a web browser running on the host machine to http://127.0.0.1:9000 and log in with the root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
| Container | [`pgsty/silo`](https://hub.docker.com/r/pgsty/silo), multi-arch for `linux/amd64` and `linux/arm64` |
|
||||||
|
| Binaries | [GitHub Releases](https://github.com/pgsty/silo/releases) — Linux, macOS, Windows on `amd64` and `arm64` |
|
||||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see https://docs.min.io/docs/ and click **MinIO SDKs** in the navigation to view MinIO SDKs for supported languages.
|
| Packages | RPM, DEB, and APK, also via the [Pigsty repository](https://pigsty.io/docs/repo/) |
|
||||||
|
| Kubernetes | Helm chart, see [Download & Install](https://silo.pgsty.com/download/) |
|
||||||
## Binary Download
|
| Source | `go build -o silo . && ./silo --version` |
|
||||||
|
|
||||||
Use the following command to download and run a standalone MinIO server on macOS. Replace ``/data`` with the path to the drive or directory in which you want MinIO to store data.
|
Every release ships checksums, SPDX SBOMs, Sigstore-signed manifests, and GitHub build attestations. Installation methods and verification commands are documented at [Download & Install](https://silo.pgsty.com/download/); migrating from upstream MinIO — taking over an existing `minio.service` and its `/etc/default/minio`, and keeping data ownership stable with a `/etc/systemd/system/silo.service.d/10-legacy-user.conf` drop-in — is covered by the [migration guide](https://silo.pgsty.com/compatibility/migration/) and the [binary & service notes](https://silo.pgsty.com/compatibility/binary/).
|
||||||
|
|
||||||
```sh
|
## Compatibility
|
||||||
wget https://dl.min.io/server/minio/release/darwin-amd64/minio
|
|
||||||
chmod +x minio
|
The S3 API, `MINIO_*` variables, `minio_*` metrics, `x-minio-*` headers, `/minio/*` routes, the `github.com/minio/*` import paths, and the on-disk format (including `.minio.sys`) are preserved and held in place by a CI compatibility check. Only Silo-owned delivery surfaces change: the `silo` executable, package, service, Helm chart, and container image — no `minio` binary alias is installed.
|
||||||
./minio server /data
|
|
||||||
```
|
Every divergence from upstream is listed in the code-verified [compatibility audit](https://silo.pgsty.com/compatibility/server/). Treat each release as a downstream upgrade: pin versions, read the [release notes](https://silo.pgsty.com/tags/silo/), and keep a rollback path.
|
||||||
|
|
||||||
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded web-based object browser built into MinIO Server. Point a web browser running on the host machine to http://127.0.0.1:9000 and log in with the root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
### TLS and Go upgrades
|
||||||
|
|
||||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see https://docs.min.io/docs/ and click **MinIO SDKs** in the navigation to view MinIO SDKs for supported languages.
|
TLS key exchange follows Go's defaults across the S3 listener, node links,
|
||||||
|
replication, identity providers, etcd, and external HTTP services. If an endpoint
|
||||||
# GNU/Linux
|
cannot accept ML-KEM, `GODEBUG=tlsmlkem=0` disables the default hybrid exchanges
|
||||||
|
for the process; certificate verification remains enabled. This option does not
|
||||||
Use the following command to run a standalone MinIO server on Linux hosts running 64-bit Intel/AMD architectures. Replace ``/data`` with the path to the drive or directory in which you want MinIO to store data.
|
disable ML-DSA signatures or resolve every TLS reset. Prefer updating the
|
||||||
|
incompatible endpoint before removing the temporary setting.
|
||||||
```sh
|
If only the new SecP hybrids cause problems, `GODEBUG=tlssecpmlkem=0` disables
|
||||||
wget https://dl.min.io/server/minio/release/linux-amd64/minio
|
those groups while retaining X25519MLKEM768.
|
||||||
chmod +x minio
|
|
||||||
./minio server /data
|
For builds targeting Go 1.27, setting either `SSL_CERT_FILE` or `SSL_CERT_DIR`
|
||||||
```
|
on macOS replaces Keychain trust with on-disk roots and Go's verifier. Stale or
|
||||||
|
incomplete CA paths can break previously trusted connections; unset inherited
|
||||||
Replace ``/data`` with the path to the drive or directory in which you want MinIO to store data.
|
values to restore Keychain trust. Explicit certificates in the configured `CAs`
|
||||||
|
directory remain additive to the selected root pool.
|
||||||
The following table lists supported architectures. Replace the `wget` URL with the architecture for your Linux host.
|
Go 1.27 binaries require macOS 13 or later. See the
|
||||||
|
[Go release notes](https://go.dev/doc/go1.27) and the
|
||||||
| Architecture | URL |
|
[SILO stack investigation](docs/investigations/go127-stack.md).
|
||||||
| -------- | ------ |
|
|
||||||
| 64-bit Intel/AMD | https://dl.min.io/server/minio/release/linux-amd64/minio |
|
## Security & Contributing
|
||||||
| 64-bit ARM | https://dl.min.io/server/minio/release/linux-arm64/minio |
|
|
||||||
| 64-bit PowerPC LE (ppc64le) | https://dl.min.io/server/minio/release/linux-ppc64le/minio |
|
Report vulnerabilities privately as described in [`SECURITY.md`](SECURITY.md); every fix ships with a public [advisory](https://silo.pgsty.com/blog/security/). Contributions are accepted inbound=outbound under AGPL-3.0-or-later with no CLA — only DCO sign-off (`git commit -s`) is required; see [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
||||||
| IBM Z-Series (S390X) | https://dl.min.io/server/minio/release/linux-s390x/minio |
|
|
||||||
|
## Contributors
|
||||||
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded web-based object browser built into MinIO Server. Point a web browser running on the host machine to http://127.0.0.1:9000 and log in with the root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
|
||||||
|
**41 community contributors** build SILO, Console, mcli, shared packages, and related projects. The list includes maintainers and every human Issue or PR author, ordered by merged PRs, other PRs, then issue reports. Gold rings highlight significant contributions.
|
||||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see https://docs.min.io/docs/ and click **MinIO SDKs** in the navigation to view MinIO SDKs for supported languages.
|
|
||||||
|
<p align="center">
|
||||||
> NOTE: Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically, with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Quickstart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide.html) for more complete documentation.
|
<a href="https://github.com/Vonng"><img src="https://silo.pgsty.com/images/contributors/Vonng.svg" width="60" height="60" alt="@Vonng" title="@Vonng — Maintains SILO, Console, mcli, shared packages, releases, and documentation"></a>
|
||||||
|
<a href="https://github.com/h5vx"><img src="https://silo.pgsty.com/images/contributors/h5vx.svg" width="60" height="60" alt="@h5vx" title="@h5vx — Implemented per-bucket CORS configuration and enforcement"></a>
|
||||||
# Microsoft Windows
|
<a href="https://github.com/mrjavadseydi"><img src="https://silo.pgsty.com/images/contributors/mrjavadseydi.svg" width="60" height="60" alt="@mrjavadseydi" title="@mrjavadseydi — Fixed effective bucket quota metrics; proposed access-frequency ILM"></a>
|
||||||
|
<a href="https://github.com/Dansyuqri"><img src="https://silo.pgsty.com/images/contributors/Dansyuqri.svg" width="60" height="60" alt="@Dansyuqri" title="@Dansyuqri — Added ChecksumType to multipart completion responses"></a>
|
||||||
To run MinIO on 64-bit Windows hosts, download the MinIO executable from the following URL:
|
<a href="https://github.com/ycjlin"><img src="https://silo.pgsty.com/images/contributors/ycjlin.svg" width="60" height="60" alt="@ycjlin" title="@ycjlin — Fixed missing-bucket ListObjects semantics"></a>
|
||||||
|
<a href="https://github.com/pinginfo"><img src="https://silo.pgsty.com/images/contributors/pinginfo.svg" width="60" height="60" alt="@pinginfo" title="@pinginfo — Repaired bucket notification streaming"></a>
|
||||||
```sh
|
<a href="https://github.com/ZouhairCharef"><img src="https://silo.pgsty.com/images/contributors/ZouhairCharef.svg" width="60" height="60" alt="@ZouhairCharef" title="@ZouhairCharef — Patched CVE-2026-34986 in go-jose"></a>
|
||||||
https://dl.min.io/server/minio/release/windows-amd64/minio.exe
|
<a href="https://github.com/mfredenhagen"><img src="https://silo.pgsty.com/images/contributors/mfredenhagen.svg" width="60" height="60" alt="@mfredenhagen" title="@mfredenhagen — Patched CVE-2026-39883 in OpenTelemetry"></a>
|
||||||
```
|
<a href="https://github.com/waterkip"><img src="https://silo.pgsty.com/images/contributors/waterkip.svg" width="60" height="60" alt="@waterkip" title="@waterkip — Repointed documentation links to the SILO portal"></a>
|
||||||
|
<a href="https://github.com/mikemikimike"><img src="https://silo.pgsty.com/images/contributors/mikemikimike.svg" width="60" height="60" alt="@mikemikimike" title="@mikemikimike — Contributed the replicated SSE-C plaintext part-size fix"></a>
|
||||||
Use the following command to run a standalone MinIO server on the Windows host. Replace ``D:\`` with the path to the drive or directory in which you want MinIO to store data. You must change the terminal or powershell directory to the location of the ``minio.exe`` executable, *or* add the path to that directory to the system ``$PATH``:
|
<a href="https://github.com/metaneutrons"><img src="https://silo.pgsty.com/images/contributors/metaneutrons.svg" width="60" height="60" alt="@metaneutrons" title="@metaneutrons — Reported and proposed explicit-version delete authorization"></a>
|
||||||
|
<a href="https://github.com/magicxor"><img src="https://silo.pgsty.com/images/contributors/magicxor.svg" width="60" height="60" alt="@magicxor" title="@magicxor — Reported and proposed conditional DELETE support for If-Match"></a>
|
||||||
```sh
|
<a href="https://github.com/davinkevin"><img src="https://silo.pgsty.com/images/contributors/davinkevin.svg" width="60" height="60" alt="@davinkevin" title="@davinkevin — Proposed the distroless container image and dependency automation"></a>
|
||||||
minio.exe server D:\
|
<a href="https://github.com/lem21h"><img src="https://silo.pgsty.com/images/contributors/lem21h.svg" width="48" height="48" alt="@lem21h" title="@lem21h — Proposed robustness and goroutine improvements"></a>
|
||||||
```
|
<a href="https://github.com/sulin37392"><img src="https://silo.pgsty.com/images/contributors/sulin37392.svg" width="48" height="48" alt="@sulin37392" title="@sulin37392 — Proposed dependency updates"></a>
|
||||||
|
<a href="https://github.com/cbornet"><img src="https://silo.pgsty.com/images/contributors/cbornet.svg" width="60" height="60" alt="@cbornet" title="@cbornet — Reported multipart and streaming checksum defects and missing-bucket semantics"></a>
|
||||||
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded web-based object browser built into MinIO Server. Point a web browser running on the host machine to http://127.0.0.1:9000 and log in with the root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
<a href="https://github.com/vampywiz17"><img src="https://silo.pgsty.com/images/contributors/vampywiz17.svg" width="60" height="60" alt="@vampywiz17" title="@vampywiz17 — Reported LDAP TLS and Console login regressions"></a>
|
||||||
|
<a href="https://github.com/orenyomtov"><img src="https://silo.pgsty.com/images/contributors/orenyomtov.svg" width="60" height="60" alt="@orenyomtov" title="@orenyomtov — Reported the unsigned-header CopyObject cross-object read (SN-2026-011)"></a>
|
||||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see https://docs.min.io/docs/ and click **MinIO SDKs** in the navigation to view MinIO SDKs for supported languages.
|
<a href="https://github.com/mumu-lab"><img src="https://silo.pgsty.com/images/contributors/mumu-lab.svg" width="48" height="48" alt="@mumu-lab" title="@mumu-lab — Reported bucket quota metrics reading a deprecated field"></a>
|
||||||
|
<a href="https://github.com/jvasile"><img src="https://silo.pgsty.com/images/contributors/jvasile.svg" width="48" height="48" alt="@jvasile" title="@jvasile — Reported missing user, group, and defaults in Debian packages"></a>
|
||||||
> NOTE: Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically, with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Quickstart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide.html) for more complete documentation.
|
<a href="https://github.com/pmezhuev"><img src="https://silo.pgsty.com/images/contributors/pmezhuev.svg" width="48" height="48" alt="@pmezhuev" title="@pmezhuev — Reported missing RPM package signatures"></a>
|
||||||
|
<a href="https://github.com/TLINDEN"><img src="https://silo.pgsty.com/images/contributors/TLINDEN.svg" width="48" height="48" alt="@TLINDEN" title="@TLINDEN — Reported the missing client in release tarballs"></a>
|
||||||
# Install from Source
|
<a href="https://github.com/makinikm"><img src="https://silo.pgsty.com/images/contributors/makinikm.svg" width="48" height="48" alt="@makinikm" title="@makinikm — Reported the missing client in the container image"></a>
|
||||||
|
<a href="https://github.com/meesudzu"><img src="https://silo.pgsty.com/images/contributors/meesudzu.svg" width="48" height="48" alt="@meesudzu" title="@meesudzu — Requested the migration guide from upstream MinIO"></a>
|
||||||
Use the following commands to compile and run a standalone MinIO server from source. Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://golang.org/doc/install). Minimum version required is [go1.16](https://golang.org/dl/#stable)
|
<a href="https://github.com/kuldeep-link11"><img src="https://silo.pgsty.com/images/contributors/kuldeep-link11.svg" width="48" height="48" alt="@kuldeep-link11" title="@kuldeep-link11 — Reported NATS JWT credentials and target reload issues"></a>
|
||||||
|
<a href="https://github.com/sargarass"><img src="https://silo.pgsty.com/images/contributors/sargarass.svg" width="48" height="48" alt="@sargarass" title="@sargarass — Reported ListMultipartUploads prefix and pagination semantics"></a>
|
||||||
```sh
|
<a href="https://github.com/liuhaodongliu990-cmyk"><img src="https://silo.pgsty.com/images/contributors/liuhaodongliu990-cmyk.svg" width="48" height="48" alt="@liuhaodongliu990-cmyk" title="@liuhaodongliu990-cmyk — Reported indeterminate progress for prefix downloads"></a>
|
||||||
GO111MODULE=on go install github.com/minio/minio@latest
|
<a href="https://github.com/Xavier-777"><img src="https://silo.pgsty.com/images/contributors/Xavier-777.svg" width="48" height="48" alt="@Xavier-777" title="@Xavier-777 — Reported Console lifecycle management and file preview gaps"></a>
|
||||||
```
|
<a href="https://github.com/spaceg00se-r"><img src="https://silo.pgsty.com/images/contributors/spaceg00se-r.svg" width="48" height="48" alt="@spaceg00se-r" title="@spaceg00se-r — Requested cpuv1 support and reported a workflow token failure"></a>
|
||||||
|
<a href="https://github.com/kh0mka"><img src="https://silo.pgsty.com/images/contributors/kh0mka.svg" width="48" height="48" alt="@kh0mka" title="@kh0mka — Reported inter-node I/O timeouts in ReadFileStreamHandler"></a>
|
||||||
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded web-based object browser built into MinIO Server. Point a web browser running on the host machine to http://127.0.0.1:9000 and log in with the root credentials. You can use the Browser to create buckets, upload objects, and browse the contents of the MinIO server.
|
<a href="https://github.com/bagutzu"><img src="https://silo.pgsty.com/images/contributors/bagutzu.svg" width="48" height="48" alt="@bagutzu" title="@bagutzu — Requested KES-compatible external KMS and OpenBao support"></a>
|
||||||
|
<a href="https://github.com/DestroyLee"><img src="https://silo.pgsty.com/images/contributors/DestroyLee.svg" width="48" height="48" alt="@DestroyLee" title="@DestroyLee — Reported the missing documentation navigation"></a>
|
||||||
You can also connect using any S3-compatible tool, such as the MinIO Client `mc` commandline tool. See [Test using MinIO Client `mc`](#test-using-minio-client-mc) for more information on using the `mc` commandline tool. For application developers, see https://docs.min.io/docs/ and click **MinIO SDKs** in the navigation to view MinIO SDKs for supported languages.
|
<a href="https://github.com/mosesdd"><img src="https://silo.pgsty.com/images/contributors/mosesdd.svg" width="48" height="48" alt="@mosesdd" title="@mosesdd — Requested a maintained Helm chart"></a>
|
||||||
|
<a href="https://github.com/zylpsrs"><img src="https://silo.pgsty.com/images/contributors/zylpsrs.svg" width="48" height="48" alt="@zylpsrs" title="@zylpsrs — Reported missing Console tiering and site replication"></a>
|
||||||
> NOTE: Standalone MinIO servers are best suited for early development and evaluation. Certain features such as versioning, object locking, and bucket replication require distributed deploying MinIO with Erasure Coding. For extended development and production, deploy MinIO with Erasure Coding enabled - specifically, with a *minimum* of 4 drives per MinIO server. See [MinIO Erasure Code Quickstart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide.html) for more complete documentation.
|
<a href="https://github.com/heroes1412"><img src="https://silo.pgsty.com/images/contributors/heroes1412.svg" width="48" height="48" alt="@heroes1412" title="@heroes1412 — Reported the unusable profiling option"></a>
|
||||||
|
<a href="https://github.com/redfoxfox"><img src="https://silo.pgsty.com/images/contributors/redfoxfox.svg" width="48" height="48" alt="@redfoxfox" title="@redfoxfox — Reported Chinese documentation availability"></a>
|
||||||
MinIO strongly recommends *against* using compiled-from-source MinIO servers for production environments.
|
<a href="https://github.com/jiadzh"><img src="https://silo.pgsty.com/images/contributors/jiadzh.svg" width="48" height="48" alt="@jiadzh" title="@jiadzh — Requested Windows build guidance"></a>
|
||||||
|
<a href="https://github.com/AntonOfTheWoods"><img src="https://silo.pgsty.com/images/contributors/AntonOfTheWoods.svg" width="48" height="48" alt="@AntonOfTheWoods" title="@AntonOfTheWoods — Asked for clarity on Helm chart and operator options"></a>
|
||||||
# Deployment Recommendations
|
<a href="https://github.com/chalukyaj"><img src="https://silo.pgsty.com/images/contributors/chalukyaj.svg" width="48" height="48" alt="@chalukyaj" title="@chalukyaj — Proposed making the SILO Operator easier to discover"></a>
|
||||||
|
<a href="https://github.com/nsanitate"><img src="https://silo.pgsty.com/images/contributors/nsanitate.svg" width="48" height="48" alt="@nsanitate" title="@nsanitate — Proposed CNCF Sandbox governance"></a>
|
||||||
## Allow port access for Firewalls
|
<a href="https://github.com/Kesavaambati"><img src="https://silo.pgsty.com/images/contributors/Kesavaambati.svg" width="48" height="48" alt="@Kesavaambati" title="@Kesavaambati — Asked about community support and image maintenance"></a>
|
||||||
|
</p>
|
||||||
By default MinIO uses the port 9000 to listen for incoming connections. If your platform blocks the port by default, you may need to enable access to the port.
|
|
||||||
|
[View the full contribution record](CONTRIBUTORS.md) for each person's proposals, fixes, and reports.
|
||||||
### ufw
|
|
||||||
|
## Background
|
||||||
For hosts with ufw enabled (Debian based distros), you can use `ufw` command to allow traffic to specific ports. Use below command to allow access to port 9000
|
|
||||||
|
Upstream wound down its community edition: the web console was cut back to a stub, prebuilt community binaries stopped, and the community repository was archived. Silo exists to keep those deployments running. The fork is a means, not an identity — if upstream restores its community edition, we will narrow our scope and offer the fixes back.
|
||||||
```sh
|
|
||||||
ufw allow 9000
|
The [**Manifesto**](https://silo.pgsty.com/about/manifesto/) is the project's public commitment in eleven articles, under one discipline: every article is either something already done with public evidence, or something explicitly refused. In short:
|
||||||
```
|
|
||||||
|
- **Compatibility contract** — the protocol and your data do not change, and every release documents its tested rollback target and path.
|
||||||
Below command enables all incoming traffic to ports ranging from 9000 to 9010.
|
- **The license cannot change** — AGPLv3, no CLA, no copyright aggregation; nobody here, ourselves included, holds enough copyright to relicense on everyone else's behalf.
|
||||||
|
- **The never list**, append-only — no paywalling existing features, no registration wall on downloads, no telemetry (upstream's phone-home paths are removed outright), no CLA, no license change, no trademark enforcement against normal use.
|
||||||
```sh
|
- **Security and release discipline** — a public advisory for every fix, and a release every one to two months, at most a quarter apart. Judge both against the public record.
|
||||||
ufw allow 9000:9010/tcp
|
|
||||||
```
|
Essays: [MinIO Is Dead](https://silo.pgsty.com/blog/post/minio-is-dead/) · [Who Takes Over?](https://silo.pgsty.com/blog/post/minio-alternative/) · [Long Live MinIO](https://silo.pgsty.com/blog/post/minio-resurrect/) · [Promise Kept](https://silo.pgsty.com/blog/post/minio-promise-kept/)
|
||||||
|
|
||||||
### firewall-cmd
|
## License & Trademark
|
||||||
|
|
||||||
For hosts with firewall-cmd enabled (CentOS), you can use `firewall-cmd` command to allow traffic to specific ports. Use below commands to allow access to port 9000
|
Silo is [AGPL-3.0-or-later](LICENSE), derived from [`minio/minio`](https://github.com/minio/minio) with upstream copyright and third-party notices preserved in [`NOTICE`](NOTICE) and [`CREDITS`](CREDITS). MinIO is a trademark of MinIO, Inc.; the name is used here only to identify the upstream project and compatibility lineage.
|
||||||
|
|
||||||
```sh
|
Details: [license](https://silo.pgsty.com/about/license/) · [attribution](https://silo.pgsty.com/about/attribution/) · [trademark](https://silo.pgsty.com/about/trademark/)
|
||||||
firewall-cmd --get-active-zones
|
|
||||||
```
|
|
||||||
|
|
||||||
This command gets the active zone(s). Now, apply port rules to the relevant zones returned above. For example if the zone is `public`, use
|
|
||||||
|
|
||||||
```sh
|
|
||||||
firewall-cmd --zone=public --add-port=9000/tcp --permanent
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that `permanent` makes sure the rules are persistent across firewall start, restart or reload. Finally reload the firewall for changes to take effect.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
firewall-cmd --reload
|
|
||||||
```
|
|
||||||
|
|
||||||
### iptables
|
|
||||||
|
|
||||||
For hosts with iptables enabled (RHEL, CentOS, etc), you can use `iptables` command to enable all traffic coming to specific ports. Use below command to allow
|
|
||||||
access to port 9000
|
|
||||||
|
|
||||||
```sh
|
|
||||||
iptables -A INPUT -p tcp --dport 9000 -j ACCEPT
|
|
||||||
service iptables restart
|
|
||||||
```
|
|
||||||
|
|
||||||
Below command enables all incoming traffic to ports ranging from 9000 to 9010.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
iptables -A INPUT -p tcp --dport 9000:9010 -j ACCEPT
|
|
||||||
service iptables restart
|
|
||||||
```
|
|
||||||
|
|
||||||
## Pre-existing data
|
|
||||||
When deployed on a single drive, MinIO server lets clients access any pre-existing data in the data directory. For example, if MinIO is started with the command `minio server /mnt/data`, any pre-existing data in the `/mnt/data` directory would be accessible to the clients.
|
|
||||||
|
|
||||||
The above statement is also valid for all gateway backends.
|
|
||||||
|
|
||||||
# Test MinIO Connectivity
|
|
||||||
|
|
||||||
## Test using MinIO Console
|
|
||||||
MinIO Server comes with an embedded web based object browser. Point your web browser to http://127.0.0.1:9000 to ensure your server has started successfully.
|
|
||||||
|
|
||||||
> NOTE: MinIO runs console on random port by default if you wish choose a specific port use `--console-address` to pick a specific interface and port.
|
|
||||||
|
|
||||||
### Things to consider
|
|
||||||
MinIO redirects browser access requests to the configured server port (i.e. `127.0.0.1:9000`) to the configured Console port. MinIO uses the hostname or IP address specified in the request when building the redirect URL. The URL and port *must* be accessible by the client for the redirection to work.
|
|
||||||
|
|
||||||
For deployments behind a load balancer, proxy, or ingress rule where the MinIO host IP address or port is not public, use the `MINIO_BROWSER_REDIRECT_URL` environment variable to specify the external hostname for the redirect. The LB/Proxy must have rules for directing traffic to the Console port specifically.
|
|
||||||
|
|
||||||
For example, consider a MinIO deployment behind a proxy `https://minio.example.net`, `https://console.minio.example.net` with rules for forwarding traffic on port :9000 and :9001 to MinIO and the MinIO Console respectively on the internal network. Set `MINIO_BROWSER_REDIRECT_URL` to `https://console.minio.example.net` to ensure the browser receives a valid reachable URL.
|
|
||||||
|
|
||||||
Similarly, if your TLS certificates do not have the IP SAN for the MinIO server host, the MinIO Console may fail to validate the connection to the server. Use the `MINIO_SERVER_URL` environment variable and specify the proxy-accessible hostname of the MinIO server to allow the Console to use the MinIO server API using the TLS certificate.
|
|
||||||
|
|
||||||
For example: `export MINIO_SERVER_URL="https://minio.example.net"`
|
|
||||||
|
|
||||||
|
|
||||||
| Dashboard | Creating a bucket |
|
|
||||||
| ------------- | ------------- |
|
|
||||||
|  |  |
|
|
||||||
|
|
||||||
## Test using MinIO Client `mc`
|
|
||||||
`mc` provides a modern alternative to UNIX commands like ls, cat, cp, mirror, diff etc. It supports filesystems and Amazon S3 compatible cloud storage services. Follow the MinIO Client [Quickstart Guide](https://docs.min.io/docs/minio-client-quickstart-guide) for further instructions.
|
|
||||||
|
|
||||||
# Upgrading MinIO
|
|
||||||
MinIO server supports rolling upgrades, i.e. you can update one MinIO instance at a time in a distributed cluster. This allows upgrades with no downtime. Upgrades can be done manually by replacing the binary with the latest release and restarting all servers in a rolling fashion. However, we recommend all our users to use [`mc admin update`](https://docs.min.io/docs/minio-admin-complete-guide.html#update) from the client. This will update all the nodes in the cluster simultaneously and restart them, as shown in the following command from the MinIO client (mc):
|
|
||||||
|
|
||||||
```
|
|
||||||
mc admin update <minio alias, e.g., myminio>
|
|
||||||
```
|
|
||||||
|
|
||||||
> NOTE: some releases might not allow rolling upgrades, this is always called out in the release notes and it is generally advised to read release notes before upgrading. In such a situation `mc admin update` is the recommended upgrading mechanism to upgrade all servers at once.
|
|
||||||
|
|
||||||
## Important things to remember during MinIO upgrades
|
|
||||||
|
|
||||||
- `mc admin update` will only work if the user running MinIO has write access to the parent directory where the binary is located, for example if the current binary is at `/usr/local/bin/minio`, you would need write access to `/usr/local/bin`.
|
|
||||||
- `mc admin update` updates and restarts all servers simultaneously, applications would retry and continue their respective operations upon upgrade.
|
|
||||||
- `mc admin update` is disabled in kubernetes/container environments, container environments provide their own mechanisms to rollout of updates.
|
|
||||||
- In the case of federated setups `mc admin update` should be run against each cluster individually. Avoid updating `mc` to any new releases until all clusters have been successfully updated.
|
|
||||||
- If using `kes` as KMS with MinIO, just replace the binary and restart `kes` more information about `kes` can be found [here](https://github.com/minio/kes/wiki)
|
|
||||||
- If using Vault as KMS with MinIO, ensure you have followed the Vault upgrade procedure outlined here: https://www.vaultproject.io/docs/upgrading/index.html
|
|
||||||
- If using etcd with MinIO for the federation, ensure you have followed the etcd upgrade procedure outlined here: https://github.com/etcd-io/etcd/blob/master/Documentation/upgrades/upgrading-etcd.md
|
|
||||||
|
|
||||||
# Explore Further
|
|
||||||
- [MinIO Erasure Code QuickStart Guide](https://docs.min.io/docs/minio-erasure-code-quickstart-guide)
|
|
||||||
- [Use `mc` with MinIO Server](https://docs.min.io/docs/minio-client-quickstart-guide)
|
|
||||||
- [Use `aws-cli` with MinIO Server](https://docs.min.io/docs/aws-cli-with-minio)
|
|
||||||
- [Use `s3cmd` with MinIO Server](https://docs.min.io/docs/s3cmd-with-minio)
|
|
||||||
- [Use `minio-go` SDK with MinIO Server](https://docs.min.io/docs/golang-client-quickstart-guide)
|
|
||||||
- [The MinIO documentation website](https://docs.min.io)
|
|
||||||
|
|
||||||
# Contribute to MinIO Project
|
|
||||||
Please follow MinIO [Contributor's Guide](https://github.com/minio/minio/blob/master/CONTRIBUTING.md)
|
|
||||||
|
|
||||||
# License
|
|
||||||
MinIO source is licensed under the GNU AGPLv3 license that can be found in the [LICENSE](https://github.com/minio/minio/blob/master/LICENSE) file.
|
|
||||||
MinIO [Documentation](https://github.com/minio/minio/tree/master/docs) © 2021 by MinIO, Inc is licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
|
|
||||||
|
|||||||
+170
@@ -0,0 +1,170 @@
|
|||||||
|
<h1 align="center">
|
||||||
|
<a href="https://silo.pgsty.com/zh/">
|
||||||
|
<img src=".github/silo-logo.svg" alt="Silo" width="160">
|
||||||
|
</a>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>S3 兼容对象存储 —— 由 PGSTY 维护的 MinIO 社区分支</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://silo.pgsty.com/zh/">官网</a> ·
|
||||||
|
<a href="https://silo.pgsty.com/zh/docs/">文档</a> ·
|
||||||
|
<a href="https://silo.pgsty.com/zh/download/">下载</a> ·
|
||||||
|
<a href="https://silo.pgsty.com/zh/tags/silo/">版本说明</a> ·
|
||||||
|
<a href="https://silo.pgsty.com/zh/compatibility/server/">兼容性</a> ·
|
||||||
|
<a href="https://silo.pgsty.com/zh/about/manifesto/">宣言</a> ·
|
||||||
|
<a href="SECURITY.md">安全策略</a> ·
|
||||||
|
<a href="README.md">English</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://silo.pgsty.com/zh/"><img alt="官网" src="https://img.shields.io/badge/%E5%AE%98%E7%BD%91-silo.pgsty.com%2Fzh-1d588c"></a>
|
||||||
|
<a href="https://github.com/pgsty/silo/releases"><img alt="GitHub Release" src="https://img.shields.io/github/v/release/pgsty/silo?include_prereleases&label=release&logo=github"></a>
|
||||||
|
<a href="https://hub.docker.com/r/pgsty/silo"><img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/pgsty/minio?logo=docker"></a>
|
||||||
|
<a href="go.mod"><img alt="Go Version" src="https://img.shields.io/github/go-mod/go-version/pgsty/silo?logo=go"></a>
|
||||||
|
<a href="LICENSE"><img alt="License" src="https://img.shields.io/badge/license-AGPLv3-blue"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> **PGSTY Silo**(以下简称 Silo)是由 [Pigsty](https://pigsty.cc) 独立维护、从 [`pgsty/silo`](https://github.com/pgsty/silo) 发布的开源 MinIO 社区分支。本项目与 MinIO, Inc. 不存在隶属、背书或赞助关系;文中使用 “MinIO” 仅用于说明上游项目及兼容谱系。
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> 2026-08-06,本仓库由 `pgsty/minio` 更名为 `pgsty/silo`,默认分支由 `master` 更名为 `main`。以原 MinIO 形态维持的归档构件仍位于归档的 [`minio`](https://github.com/pgsty/silo/tree/minio) 分支,以及截止 [`RELEASE.2026-08-04T00-00-00Z`](https://github.com/pgsty/silo/releases/tag/RELEASE.2026-08-04T00-00-00Z) 的历次发布中。
|
||||||
|
|
||||||
|
## 当前发行版与主分支
|
||||||
|
|
||||||
|
最新已发布的 Server 仍为 [20260903](https://github.com/pgsty/silo/releases/tag/RELEASE.2026-09-03T13-18-01Z)。
|
||||||
|
截至 2026-09-13,主分支已合入更新的安全、存储、Console 与共享包改动,但尚未发布新 Server。
|
||||||
|
准确的已发布/源码边界见 [CHANGELOG.md](CHANGELOG.md) 与[组件版本矩阵](https://silo.pgsty.com/zh/compatibility/versions/),
|
||||||
|
其中包括 SN-2026-011 修复状态与密码权限迁移要求。
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
上游停止社区发行后,Silo 为开源 MinIO 服务端维护一条持续可用的版本线:构建、软件包、多架构镜像、安全修复与完整 Web 控制台。Pigsty 在生产环境中用它承载 PostgreSQL 备份存储。
|
||||||
|
|
||||||
|
它只遵循一条原则:**改名的是产品与交付物,不是协议与你的数据。** 其余内容都在 [silo.pgsty.com](https://silo.pgsty.com/zh/)。
|
||||||
|
|
||||||
|
**相关项目:**[`pgsty/mc`](https://github.com/pgsty/mc) 客户端(以 `mcli` 发行) · [`pgsty/silo-console`](https://github.com/pgsty/silo-console) · [`pgsty/silo-pkg`](https://github.com/pgsty/silo-pkg) · [`pgsty/pigsty`](https://github.com/pgsty/pigsty)
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://silo.pgsty.com/images/silo-console/console-metrics-simple.webp" alt="Silo 控制台">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## 快速上手
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d --name silo -p 9000:9000 -p 9001:9001 \
|
||||||
|
-e MINIO_ROOT_USER=minioadmin \
|
||||||
|
-e MINIO_ROOT_PASSWORD=change-me-long-password \
|
||||||
|
-v "$PWD/data:/data" \
|
||||||
|
docker.io/pgsty/silo:latest server /data --console-address ":9001"
|
||||||
|
```
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://silo.pgsty.com/images/silo-console/console-login.webp" alt="Silo 控制台">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
控制台位于 <http://localhost:9001>,S3 API 位于 <http://localhost:9000>。镜像内置客户端 `mcli`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec silo mcli alias set local http://127.0.0.1:9000 minioadmin change-me-long-password
|
||||||
|
docker exec silo mcli mb local/demo && docker exec silo mcli ls local
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> 生产环境应锁定版本,使用独立凭据与 TLS,配置监控,保留独立备份,并验证恢复流程。请从[文档](https://silo.pgsty.com/zh/docs/)开始。
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
| 方式 | 位置 |
|
||||||
|
| :-- | :-- |
|
||||||
|
| 容器镜像 | [`pgsty/silo`](https://hub.docker.com/r/pgsty/silo),支持 `linux/amd64` 与 `linux/arm64` |
|
||||||
|
| 二进制 | [GitHub Releases](https://github.com/pgsty/silo/releases),覆盖 Linux、macOS、Windows 的 `amd64` 与 `arm64` |
|
||||||
|
| 软件包 | RPM、DEB、APK,也可通过 [Pigsty 软件仓库](https://pigsty.cc/docs/repo/) 安装 |
|
||||||
|
| Kubernetes | Helm Chart,参见[下载与安装](https://silo.pgsty.com/zh/download/) |
|
||||||
|
| 源码构建 | `go build -o silo . && ./silo --version` |
|
||||||
|
|
||||||
|
每个版本都附带校验和、SPDX SBOM、Sigstore 签名清单与 GitHub 构建证明。完整安装方式与验证命令见[下载与安装](https://silo.pgsty.com/zh/download/);从上游 MinIO 迁移 —— 接管既有 `minio.service` 与 `/etc/default/minio`,并用 `/etc/systemd/system/silo.service.d/10-legacy-user.conf` drop-in 保持数据属主不变 —— 见[迁移指南](https://silo.pgsty.com/zh/compatibility/migration/)与[二进制与服务说明](https://silo.pgsty.com/zh/compatibility/binary/)。
|
||||||
|
|
||||||
|
## 兼容性
|
||||||
|
|
||||||
|
S3 API、`MINIO_*` 环境变量、`minio_*` 指标、`x-minio-*` 头、`/minio/*` 路由、`github.com/minio/*` 导入路径与磁盘格式(含 `.minio.sys`)原样保留,并由 CI 兼容性门禁冻结。只有 Silo 自有交付面改名:`silo` 可执行文件、软件包、服务、Helm Chart 与容器镜像 —— 原生交付物不会安装 `minio` 二进制别名。
|
||||||
|
|
||||||
|
与上游的全部分歧,以逐项核验代码的[兼容性审计](https://silo.pgsty.com/zh/compatibility/server/)形式维护。每个版本仍应视为下游升级:锁定版本,阅读[版本说明](https://silo.pgsty.com/zh/tags/silo/),并保留回滚路径。
|
||||||
|
|
||||||
|
## 安全与贡献
|
||||||
|
|
||||||
|
请按照 [`SECURITY.md`](SECURITY.md) 私密报告漏洞;每项修复都会发布公开[安全公告](https://silo.pgsty.com/zh/blog/security/)。本项目不要求签署 CLA:贡献按 AGPL-3.0-or-later(inbound=outbound)接收,只需 DCO 签署(`git commit -s`),详见 [`CONTRIBUTING.md`](CONTRIBUTING.md)。
|
||||||
|
|
||||||
|
## 贡献者
|
||||||
|
|
||||||
|
**41 位社区贡献者**共同建设 SILO、Console、mcli、公共包与相关项目。名单包含维护者,以及所有提出 Issue 或 PR 的真人作者;按已合并 PR、其他 PR、Issue 报告排序,黄圈标记显著贡献。
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/Vonng"><img src="https://silo.pgsty.com/images/contributors/Vonng.svg" width="60" height="60" alt="@Vonng" title="@Vonng — 维护 SILO、Console、mcli、公共包、发行与文档"></a>
|
||||||
|
<a href="https://github.com/h5vx"><img src="https://silo.pgsty.com/images/contributors/h5vx.svg" width="60" height="60" alt="@h5vx" title="@h5vx — 实现单桶 CORS 配置与请求执行"></a>
|
||||||
|
<a href="https://github.com/mrjavadseydi"><img src="https://silo.pgsty.com/images/contributors/mrjavadseydi.svg" width="60" height="60" alt="@mrjavadseydi" title="@mrjavadseydi — 修复有效桶配额指标,并提交按访问频率分层的 ILM 方案"></a>
|
||||||
|
<a href="https://github.com/Dansyuqri"><img src="https://silo.pgsty.com/images/contributors/Dansyuqri.svg" width="60" height="60" alt="@Dansyuqri" title="@Dansyuqri — 为分片上传完成响应补充 ChecksumType"></a>
|
||||||
|
<a href="https://github.com/ycjlin"><img src="https://silo.pgsty.com/images/contributors/ycjlin.svg" width="60" height="60" alt="@ycjlin" title="@ycjlin — 修复缺失桶的 ListObjects 语义"></a>
|
||||||
|
<a href="https://github.com/pinginfo"><img src="https://silo.pgsty.com/images/contributors/pinginfo.svg" width="60" height="60" alt="@pinginfo" title="@pinginfo — 修复桶通知的流式输出"></a>
|
||||||
|
<a href="https://github.com/ZouhairCharef"><img src="https://silo.pgsty.com/images/contributors/ZouhairCharef.svg" width="60" height="60" alt="@ZouhairCharef" title="@ZouhairCharef — 修复 go-jose 中的 CVE-2026-34986"></a>
|
||||||
|
<a href="https://github.com/mfredenhagen"><img src="https://silo.pgsty.com/images/contributors/mfredenhagen.svg" width="60" height="60" alt="@mfredenhagen" title="@mfredenhagen — 修复 OpenTelemetry 中的 CVE-2026-39883"></a>
|
||||||
|
<a href="https://github.com/waterkip"><img src="https://silo.pgsty.com/images/contributors/waterkip.svg" width="60" height="60" alt="@waterkip" title="@waterkip — 将文档链接指向 SILO 门户"></a>
|
||||||
|
<a href="https://github.com/mikemikimike"><img src="https://silo.pgsty.com/images/contributors/mikemikimike.svg" width="60" height="60" alt="@mikemikimike" title="@mikemikimike — 提交 SSE-C 复制分片明文尺寸修复"></a>
|
||||||
|
<a href="https://github.com/metaneutrons"><img src="https://silo.pgsty.com/images/contributors/metaneutrons.svg" width="60" height="60" alt="@metaneutrons" title="@metaneutrons — 报告并提交显式版本删除鉴权方案"></a>
|
||||||
|
<a href="https://github.com/magicxor"><img src="https://silo.pgsty.com/images/contributors/magicxor.svg" width="60" height="60" alt="@magicxor" title="@magicxor — 报告并提交 DELETE If-Match 条件请求支持方案"></a>
|
||||||
|
<a href="https://github.com/davinkevin"><img src="https://silo.pgsty.com/images/contributors/davinkevin.svg" width="60" height="60" alt="@davinkevin" title="@davinkevin — 提交 distroless 容器镜像与依赖自动更新方案"></a>
|
||||||
|
<a href="https://github.com/lem21h"><img src="https://silo.pgsty.com/images/contributors/lem21h.svg" width="48" height="48" alt="@lem21h" title="@lem21h — 提交健壮性与 goroutine 改进"></a>
|
||||||
|
<a href="https://github.com/sulin37392"><img src="https://silo.pgsty.com/images/contributors/sulin37392.svg" width="48" height="48" alt="@sulin37392" title="@sulin37392 — 提交依赖更新"></a>
|
||||||
|
<a href="https://github.com/cbornet"><img src="https://silo.pgsty.com/images/contributors/cbornet.svg" width="60" height="60" alt="@cbornet" title="@cbornet — 报告分片与流式校验和缺陷及缺失桶语义问题"></a>
|
||||||
|
<a href="https://github.com/vampywiz17"><img src="https://silo.pgsty.com/images/contributors/vampywiz17.svg" width="60" height="60" alt="@vampywiz17" title="@vampywiz17 — 报告 LDAP TLS 与 Console 登录回归"></a>
|
||||||
|
<a href="https://github.com/orenyomtov"><img src="https://silo.pgsty.com/images/contributors/orenyomtov.svg" width="60" height="60" alt="@orenyomtov" title="@orenyomtov — 报告未签名头导致的 CopyObject 跨对象读取(SN-2026-011)"></a>
|
||||||
|
<a href="https://github.com/mumu-lab"><img src="https://silo.pgsty.com/images/contributors/mumu-lab.svg" width="48" height="48" alt="@mumu-lab" title="@mumu-lab — 报告桶配额指标读取已弃用字段的问题"></a>
|
||||||
|
<a href="https://github.com/jvasile"><img src="https://silo.pgsty.com/images/contributors/jvasile.svg" width="48" height="48" alt="@jvasile" title="@jvasile — 报告 Debian 包缺少用户、用户组与默认配置"></a>
|
||||||
|
<a href="https://github.com/pmezhuev"><img src="https://silo.pgsty.com/images/contributors/pmezhuev.svg" width="48" height="48" alt="@pmezhuev" title="@pmezhuev — 报告 RPM 包缺少 GPG 签名"></a>
|
||||||
|
<a href="https://github.com/TLINDEN"><img src="https://silo.pgsty.com/images/contributors/TLINDEN.svg" width="48" height="48" alt="@TLINDEN" title="@TLINDEN — 报告发布压缩包缺少客户端"></a>
|
||||||
|
<a href="https://github.com/makinikm"><img src="https://silo.pgsty.com/images/contributors/makinikm.svg" width="48" height="48" alt="@makinikm" title="@makinikm — 报告容器镜像缺少客户端"></a>
|
||||||
|
<a href="https://github.com/meesudzu"><img src="https://silo.pgsty.com/images/contributors/meesudzu.svg" width="48" height="48" alt="@meesudzu" title="@meesudzu — 提出从上游 MinIO 迁移的指南需求"></a>
|
||||||
|
<a href="https://github.com/kuldeep-link11"><img src="https://silo.pgsty.com/images/contributors/kuldeep-link11.svg" width="48" height="48" alt="@kuldeep-link11" title="@kuldeep-link11 — 报告 NATS JWT 凭据与通知目标重载问题"></a>
|
||||||
|
<a href="https://github.com/sargarass"><img src="https://silo.pgsty.com/images/contributors/sargarass.svg" width="48" height="48" alt="@sargarass" title="@sargarass — 报告 ListMultipartUploads 前缀与分页语义问题"></a>
|
||||||
|
<a href="https://github.com/liuhaodongliu990-cmyk"><img src="https://silo.pgsty.com/images/contributors/liuhaodongliu990-cmyk.svg" width="48" height="48" alt="@liuhaodongliu990-cmyk" title="@liuhaodongliu990-cmyk — 报告前缀下载进度显示异常"></a>
|
||||||
|
<a href="https://github.com/Xavier-777"><img src="https://silo.pgsty.com/images/contributors/Xavier-777.svg" width="48" height="48" alt="@Xavier-777" title="@Xavier-777 — 报告 Console 生命周期管理与文件预览缺失"></a>
|
||||||
|
<a href="https://github.com/spaceg00se-r"><img src="https://silo.pgsty.com/images/contributors/spaceg00se-r.svg" width="48" height="48" alt="@spaceg00se-r" title="@spaceg00se-r — 提出 cpuv1 支持需求并报告工作流令牌错误"></a>
|
||||||
|
<a href="https://github.com/kh0mka"><img src="https://silo.pgsty.com/images/contributors/kh0mka.svg" width="48" height="48" alt="@kh0mka" title="@kh0mka — 报告 ReadFileStreamHandler 节点间 I/O 超时"></a>
|
||||||
|
<a href="https://github.com/bagutzu"><img src="https://silo.pgsty.com/images/contributors/bagutzu.svg" width="48" height="48" alt="@bagutzu" title="@bagutzu — 提出兼容 KES 的外部 KMS 与 OpenBao 支持需求"></a>
|
||||||
|
<a href="https://github.com/DestroyLee"><img src="https://silo.pgsty.com/images/contributors/DestroyLee.svg" width="48" height="48" alt="@DestroyLee" title="@DestroyLee — 报告文档目录导航缺失"></a>
|
||||||
|
<a href="https://github.com/mosesdd"><img src="https://silo.pgsty.com/images/contributors/mosesdd.svg" width="48" height="48" alt="@mosesdd" title="@mosesdd — 提出维护 Helm Chart 的需求"></a>
|
||||||
|
<a href="https://github.com/zylpsrs"><img src="https://silo.pgsty.com/images/contributors/zylpsrs.svg" width="48" height="48" alt="@zylpsrs" title="@zylpsrs — 报告 Console 缺少分层与站点复制"></a>
|
||||||
|
<a href="https://github.com/heroes1412"><img src="https://silo.pgsty.com/images/contributors/heroes1412.svg" width="48" height="48" alt="@heroes1412" title="@heroes1412 — 报告性能分析选项不可用"></a>
|
||||||
|
<a href="https://github.com/redfoxfox"><img src="https://silo.pgsty.com/images/contributors/redfoxfox.svg" width="48" height="48" alt="@redfoxfox" title="@redfoxfox — 报告中文文档站点不可用"></a>
|
||||||
|
<a href="https://github.com/jiadzh"><img src="https://silo.pgsty.com/images/contributors/jiadzh.svg" width="48" height="48" alt="@jiadzh" title="@jiadzh — 提出 Windows 构建指导需求"></a>
|
||||||
|
<a href="https://github.com/AntonOfTheWoods"><img src="https://silo.pgsty.com/images/contributors/AntonOfTheWoods.svg" width="48" height="48" alt="@AntonOfTheWoods" title="@AntonOfTheWoods — 提出明确 Helm Chart 与 Operator 选项的需求"></a>
|
||||||
|
<a href="https://github.com/chalukyaj"><img src="https://silo.pgsty.com/images/contributors/chalukyaj.svg" width="48" height="48" alt="@chalukyaj" title="@chalukyaj — 提出改善 SILO Operator 可发现性的建议"></a>
|
||||||
|
<a href="https://github.com/nsanitate"><img src="https://silo.pgsty.com/images/contributors/nsanitate.svg" width="48" height="48" alt="@nsanitate" title="@nsanitate — 提出加入 CNCF Sandbox 的治理建议"></a>
|
||||||
|
<a href="https://github.com/Kesavaambati"><img src="https://silo.pgsty.com/images/contributors/Kesavaambati.svg" width="48" height="48" alt="@Kesavaambati" title="@Kesavaambati — 提出社区支持与容器镜像维护问题"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
[查看完整贡献记录](CONTRIBUTORS.md),了解每位贡献者的提案、修复与问题报告。
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
本项目因上游收缩社区版而生:Web 控制台被削减为残桩、社区预编译制品停发、社区仓库被归档。Silo 的存在就是让这些部署继续跑下去。Fork 是手段,不是身份 —— 若上游恢复社区版承诺,我们乐意收缩范围,并把修复回馈上游。
|
||||||
|
|
||||||
|
[**宣言**](https://silo.pgsty.com/zh/about/manifesto/)是项目的公开承诺,共十一条,通篇遵循一项纪律:**每一条,要么是已经在做且有公开证据的事实,要么是刻意拒绝的承诺。** 摘要:
|
||||||
|
|
||||||
|
- **兼容性合同** —— 协议与数据不改,每个版本都标注经过测试的回滚目标与路径。
|
||||||
|
- **许可证无法变更** —— AGPLv3、无 CLA、不做版权聚合;包括我们自己在内,没有人握有足够版权代表所有贡献者重新授权。
|
||||||
|
- **永不清单**(只增不减)—— 永不将既有功能移入付费墙、永不给下载设注册墙、永不加入遥测(上游回连路径已整体移除)、永不引入 CLA、永不变更许可证、永不以商标追究正常使用。
|
||||||
|
- **安全与发布纪律** —— 每项安全修复配一篇公开公告;通常每一到两个月发布一版,最长不超过一个季度。请拿公开记录检验这两条。
|
||||||
|
|
||||||
|
延伸阅读:[MinIO已死](https://silo.pgsty.com/zh/blog/post/minio-is-dead/) · [谁能接盘?](https://silo.pgsty.com/zh/blog/post/minio-alternative/) · [MinIO 复生](https://silo.pgsty.com/zh/blog/post/minio-resurrect/) · [承诺兑现](https://silo.pgsty.com/zh/blog/post/minio-promise-kept/)
|
||||||
|
|
||||||
|
## 许可证与商标
|
||||||
|
|
||||||
|
Silo 采用 [AGPL-3.0-or-later](LICENSE),衍生自 [`minio/minio`](https://github.com/minio/minio),上游版权与第三方声明完整保留于 [`NOTICE`](NOTICE) 与 [`CREDITS`](CREDITS)。MinIO 是 MinIO, Inc. 的商标,此处使用仅为标识上游项目与兼容谱系。
|
||||||
|
|
||||||
|
详见:[许可证](https://silo.pgsty.com/zh/about/license/) · [署名归属](https://silo.pgsty.com/zh/about/attribution/) · [商标声明](https://silo.pgsty.com/zh/about/trademark/)
|
||||||
+35
-31
@@ -1,41 +1,45 @@
|
|||||||
# Security Policy
|
# Security Policy
|
||||||
|
|
||||||
|
Silo is an independent, community-maintained object-storage server derived from
|
||||||
|
the open-source MinIO server. Upstream MinIO security contacts do not handle
|
||||||
|
Silo-specific fixes or release notes.
|
||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
We always provide security updates for the [latest release](https://github.com/minio/minio/releases/latest).
|
Security fixes are tracked on the active development branch and summarized in
|
||||||
Whenever there is a security update you just need to upgrade to the latest version.
|
[docs/security/advisories.md](docs/security/advisories.md). Only the current
|
||||||
|
Silo release line is supported unless an advisory says otherwise.
|
||||||
|
|
||||||
|
## Inherited Fix Evidence
|
||||||
|
|
||||||
|
The canonical ledger also records security fixes inherited from upstream when
|
||||||
|
they are part of the Silo release baseline. Source and fork commits are linked
|
||||||
|
separately even when the fork preserves the original commit object and SHA.
|
||||||
|
|
||||||
|
- [CVE-2025-62506](https://github.com/advisories/GHSA-jjjj-jwhf-8rgr):
|
||||||
|
upstream [PR #21642](https://github.com/minio/minio/pull/21642) merged as
|
||||||
|
[`minio/minio@c1a49490`](https://github.com/minio/minio/commit/c1a49490c78e9c3ebcad86ba0662319138ace190),
|
||||||
|
inherited unchanged as
|
||||||
|
[`pgsty/silo@c1a49490`](https://github.com/pgsty/silo/commit/c1a49490c78e9c3ebcad86ba0662319138ace190),
|
||||||
|
and is present in every Silo community release beginning with
|
||||||
|
[`RELEASE.2025-12-03T12-00-00Z`](https://github.com/pgsty/silo/releases/tag/RELEASE.2025-12-03T12-00-00Z).
|
||||||
|
The inherited [service-account](https://github.com/pgsty/silo/blob/c1a49490c78e9c3ebcad86ba0662319138ace190/cmd/admin-handlers-users_test.go#L211-L212)
|
||||||
|
and [STS](https://github.com/pgsty/silo/blob/c1a49490c78e9c3ebcad86ba0662319138ace190/cmd/sts-handlers_test.go#L45-L46)
|
||||||
|
regression groups remain part of `go test ./cmd`; see the
|
||||||
|
[canonical ledger](docs/security/advisories.md#inherited-upstream-advisory-baseline)
|
||||||
|
for the operator-facing record.
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
All security bugs in [minio/minio](https://github,com/minio/minio) (or other minio/* repositories)
|
For vulnerabilities in this fork:
|
||||||
should be reported by email to security@min.io. Your email will be acknowledged within 48 hours,
|
|
||||||
and you'll receive a more detailed response to your email within 72 hours indicating the next steps
|
|
||||||
in handling your report.
|
|
||||||
|
|
||||||
Please, provide a detailed explanation of the issue. In particular, outline the type of the security
|
1. Follow the fork-specific expectations in [VULNERABILITY_REPORT.md](VULNERABILITY_REPORT.md).
|
||||||
issue (DoS, authentication bypass, information disclose, ...) and the assumptions you're making (e.g. do
|
2. Prefer this repository's [private GitHub security advisory](https://github.com/pgsty/silo/security/advisories/new) workflow.
|
||||||
you need access credentials for a successful exploit).
|
3. If private reporting is unavailable, contact the maintainers through the
|
||||||
|
repository without publishing exploit details until a private channel is
|
||||||
|
established.
|
||||||
|
4. If you confirm the issue also affects upstream `minio/minio`, report it upstream separately.
|
||||||
|
|
||||||
If you have not received a reply to your email within 48 hours or you have not heard from the security team
|
## Disclosure Process
|
||||||
for the past five days please contact the security team directly:
|
|
||||||
- Primary security coordinator: aead@min.io
|
|
||||||
- Secondary coordinator: harsha@min.io
|
|
||||||
- If you receive no response: dev@min.io
|
|
||||||
|
|
||||||
### Disclosure Process
|
Fork-specific fixes and user-visible upgrade notes are published in [docs/security/advisories.md](docs/security/advisories.md). The fork-specific triage and remediation process is described in [VULNERABILITY_REPORT.md](VULNERABILITY_REPORT.md).
|
||||||
|
|
||||||
MinIO uses the following disclosure process:
|
|
||||||
|
|
||||||
1. Once the security report is received one member of the security team tries to verify and reproduce
|
|
||||||
the issue and determines the impact it has.
|
|
||||||
2. A member of the security team will respond and either confirm or reject the security report.
|
|
||||||
If the report is rejected the response explains why.
|
|
||||||
3. Code is audited to find any potential similar problems.
|
|
||||||
4. Fixes are prepared for the latest release.
|
|
||||||
5. On the date that the fixes are applied a security advisory will be published on https://blog.min.io.
|
|
||||||
Please inform us in your report email whether MinIO should mention your contribution w.r.t. fixing
|
|
||||||
the security issue. By default MinIO will **not** publish this information to protect your privacy.
|
|
||||||
|
|
||||||
This process can take some time, especially when coordination is required with maintainers of other projects.
|
|
||||||
Every effort will be made to handle the bug in as timely a manner as possible, however it's important that we
|
|
||||||
follow the process described above to ensure that disclosures are handled consistently.
|
|
||||||
|
|||||||
+27
-29
@@ -1,39 +1,37 @@
|
|||||||
## Vulnerability Management Policy
|
# Vulnerability Management Policy
|
||||||
|
|
||||||
This document formally describes the process of addressing and managing a
|
This document describes how the Silo maintainers investigate,
|
||||||
reported vulnerability that has been found in the MinIO server code base,
|
assess, and remediate reported vulnerabilities affecting this fork, any
|
||||||
any directly connected ecosystem component or a direct / indirect dependency
|
directly shipped component, or a direct / indirect dependency used by this
|
||||||
of the code base.
|
repository.
|
||||||
|
|
||||||
### Scope
|
## Scope
|
||||||
|
|
||||||
The vulnerability management policy described in this document covers the
|
This policy covers vulnerability reports opened by repository maintainers or
|
||||||
process of investigating, assessing and resolving a vulnerability report
|
external third parties against Silo itself, its release artifacts, or
|
||||||
opened by a MinIO employee or an external third party.
|
dependencies that materially affect this fork.
|
||||||
|
|
||||||
Therefore, it lists pre-conditions and actions that should be performed to
|
It defines the information needed for triage and the expected remediation
|
||||||
resolve and fix a reported vulnerability.
|
workflow for supported fixes.
|
||||||
|
|
||||||
### Vulnerability Management Process
|
## Vulnerability Management Process
|
||||||
|
|
||||||
The vulnerability management process requires that the vulnerability report
|
A useful vulnerability report should contain the following information:
|
||||||
contains the following information:
|
|
||||||
|
|
||||||
- The project / component that contains the reported vulnerability.
|
- The project / component that contains the reported vulnerability.
|
||||||
- A description of the vulnerability. In particular, the type of the
|
- A description of the vulnerability. In particular, the type of the
|
||||||
reported vulnerability and how it might be exploited. Alternatively,
|
reported vulnerability and how it might be exploited. Alternatively,
|
||||||
a well-established vulnerability identifier, e.g. CVE number, can be
|
a well-established vulnerability identifier, such as a CVE or GHSA ID, can
|
||||||
used instead.
|
be used instead.
|
||||||
|
|
||||||
Based on the description mentioned above, a MinIO engineer or security team
|
Based on the report, the Silo maintainers investigate:
|
||||||
member investigates:
|
|
||||||
|
|
||||||
- Whether the reported vulnerability exists.
|
- Whether the reported vulnerability exists.
|
||||||
- The conditions that are required such that the vulnerability can be exploited.
|
- The conditions that are required such that the vulnerability can be exploited.
|
||||||
- The steps required to fix the vulnerability.
|
- Which releases, branches, or deployment paths are affected.
|
||||||
|
- The steps required to fix the vulnerability.
|
||||||
In general, if the vulnerability exists in one of the MinIO code bases
|
|
||||||
itself - not in a code dependency - then MinIO will, if possible, fix
|
|
||||||
the vulnerability or implement reasonable countermeasures such that the
|
|
||||||
vulnerability cannot be exploited anymore.
|
|
||||||
|
|
||||||
|
If the vulnerability exists in this fork itself, the maintainers will, when
|
||||||
|
feasible, fix the issue or implement reasonable countermeasures such that the
|
||||||
|
vulnerability can no longer be exploited. Fork-specific upgrade notes and
|
||||||
|
security advisories are published in `docs/security/advisories.md`.
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
theme: jekyll-theme-minimal
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
**/*.swp
|
|
||||||
cover.out
|
|
||||||
*~
|
|
||||||
minio
|
|
||||||
!*/
|
|
||||||
site/
|
|
||||||
**/*.test
|
|
||||||
**/*.sublime-workspace
|
|
||||||
/.idea/
|
|
||||||
/Minio.iml
|
|
||||||
**/access.log
|
|
||||||
build
|
|
||||||
vendor/**/*.js
|
|
||||||
vendor/**/*.json
|
|
||||||
.DS_Store
|
|
||||||
*.syso
|
|
||||||
coverage.txt
|
|
||||||
node_modules
|
|
||||||
Executable
+76
@@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Fail closed before a release job can replace published or finalized assets.
|
||||||
|
# An ordinary Draft is retry state; a finalized Draft contains GPG-derived
|
||||||
|
# materials and must never be replaced by the build lane.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
release_tag="${1:-}"
|
||||||
|
fixture="${2:-}"
|
||||||
|
repository="${GITHUB_REPOSITORY:-pgsty/silo}"
|
||||||
|
require_draft="${REQUIRE_DRAFT:-false}"
|
||||||
|
|
||||||
|
if ! command -v jq >/dev/null 2>&1; then
|
||||||
|
echo "jq is required to inspect GitHub release state" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! "${release_tag}" =~ ^RELEASE\.[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}-[0-9]{2}-[0-9]{2}Z$ ]]; then
|
||||||
|
echo "Invalid release tag format: ${release_tag:-<empty>}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "${fixture}" ]; then
|
||||||
|
release_json="$(<"${fixture}")"
|
||||||
|
else
|
||||||
|
error_file="$(mktemp)"
|
||||||
|
trap 'rm -f "${error_file}"' EXIT
|
||||||
|
if ! release_json="$(
|
||||||
|
gh api --paginate "repos/${repository}/releases?per_page=100" --jq '.[]' 2>"${error_file}" |
|
||||||
|
jq --arg tag "${release_tag}" -s '[.[] | select(.tag_name == $tag)]'
|
||||||
|
)"; then
|
||||||
|
cat "${error_file}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! jq -e 'type == "array" and all(.[]; type == "object" and (.tag_name | type == "string") and (.draft | type == "boolean"))' \
|
||||||
|
<<<"${release_json}" >/dev/null 2>&1; then
|
||||||
|
echo "Invalid release state response for ${release_tag}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! jq -e --arg tag "${release_tag}" 'all(.[]; .tag_name == $tag)' \
|
||||||
|
<<<"${release_json}" >/dev/null 2>&1; then
|
||||||
|
echo "Release state returned a tag other than ${release_tag}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
release_count="$(jq 'length' <<<"${release_json}")"
|
||||||
|
if [ "${release_count}" -eq 0 ]; then
|
||||||
|
if [ "${require_draft}" = "true" ]; then
|
||||||
|
echo "Expected one Draft release for ${release_tag}, found none" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "No existing release for ${release_tag}."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${release_count}" -ne 1 ]; then
|
||||||
|
echo "Refusing to choose among ${release_count} releases for ${release_tag}; clean duplicate Drafts first" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$(jq -r '.[0].draft' <<<"${release_json}")" != "true" ]; then
|
||||||
|
echo "Refusing to overwrite published release ${release_tag}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
finalize_markers="$(jq '[.[0].assets[]? | select(.name | endswith("_packages_provenance.sigstore.json"))] | length' <<<"${release_json}")"
|
||||||
|
if [ "${finalize_markers}" -ne 0 ]; then
|
||||||
|
echo "Refusing to replace finalized Draft ${release_tag}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Existing unfinalized Draft ${release_tag} will be replaced from scratch."
|
||||||
Executable
+68
@@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
checker="${script_dir}/check-release-state.sh"
|
||||||
|
tag="RELEASE.2026-08-29T00-00-00Z"
|
||||||
|
fixture="$(mktemp)"
|
||||||
|
stdout_file="$(mktemp)"
|
||||||
|
stderr_file="$(mktemp)"
|
||||||
|
trap 'rm -f "${fixture}" "${stdout_file}" "${stderr_file}"' EXIT
|
||||||
|
|
||||||
|
expect_success() {
|
||||||
|
if ! "${checker}" "$@" >"${stdout_file}" 2>"${stderr_file}"; then
|
||||||
|
cat "${stderr_file}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
expect_failure() {
|
||||||
|
if "${checker}" "$@" >"${stdout_file}" 2>"${stderr_file}"; then
|
||||||
|
echo "Expected release-state check to fail: $*" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
printf '[]\n' >"${fixture}"
|
||||||
|
expect_success "${tag}" "${fixture}"
|
||||||
|
grep -qF "No existing release for ${tag}." "${stdout_file}"
|
||||||
|
|
||||||
|
if REQUIRE_DRAFT=true "${checker}" "${tag}" "${fixture}" >"${stdout_file}" 2>"${stderr_file}"; then
|
||||||
|
echo "Expected required-Draft check to fail when no release exists" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
grep -qF "Expected one Draft release for ${tag}, found none" "${stderr_file}"
|
||||||
|
|
||||||
|
printf '[{"tag_name":"%s","draft":true,"assets":[]}]\n' "${tag}" >"${fixture}"
|
||||||
|
expect_success "${tag}" "${fixture}"
|
||||||
|
grep -qF "Existing unfinalized Draft ${tag} will be replaced from scratch." "${stdout_file}"
|
||||||
|
if ! REQUIRE_DRAFT=true "${checker}" "${tag}" "${fixture}" >"${stdout_file}" 2>"${stderr_file}"; then
|
||||||
|
cat "${stderr_file}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '[{"tag_name":"%s","draft":true,"assets":[{"name":"silo_20260829000000.0.0_packages_provenance.sigstore.json"}]}]\n' "${tag}" >"${fixture}"
|
||||||
|
expect_failure "${tag}" "${fixture}"
|
||||||
|
grep -qF "Refusing to replace finalized Draft ${tag}" "${stderr_file}"
|
||||||
|
|
||||||
|
printf '[{"tag_name":"%s","draft":false}]\n' "${tag}" >"${fixture}"
|
||||||
|
expect_failure "${tag}" "${fixture}"
|
||||||
|
grep -qF "Refusing to overwrite published release ${tag}" "${stderr_file}"
|
||||||
|
|
||||||
|
printf '[{"tag_name":"%s","draft":true},{"tag_name":"%s","draft":true}]\n' "${tag}" "${tag}" >"${fixture}"
|
||||||
|
expect_failure "${tag}" "${fixture}"
|
||||||
|
grep -qF "Refusing to choose among 2 releases" "${stderr_file}"
|
||||||
|
|
||||||
|
printf '[{"tag_name":"RELEASE.2026-08-28T00-00-00Z","draft":true}]\n' >"${fixture}"
|
||||||
|
expect_failure "${tag}" "${fixture}"
|
||||||
|
grep -qF "other than ${tag}" "${stderr_file}"
|
||||||
|
|
||||||
|
printf '{not-json}\n' >"${fixture}"
|
||||||
|
expect_failure "${tag}" "${fixture}"
|
||||||
|
grep -qF "Invalid release state response for ${tag}" "${stderr_file}"
|
||||||
|
|
||||||
|
expect_failure "not-a-release-tag" "${fixture}"
|
||||||
|
grep -qF "Invalid release tag format" "${stderr_file}"
|
||||||
|
|
||||||
|
echo "release-state decision tests passed"
|
||||||
Regular → Executable
+85
-84
@@ -3,19 +3,19 @@
|
|||||||
|
|
||||||
_init() {
|
_init() {
|
||||||
|
|
||||||
shopt -s extglob
|
shopt -s extglob
|
||||||
|
|
||||||
## Minimum required versions for build dependencies
|
## Minimum required versions for build dependencies
|
||||||
GIT_VERSION="1.0"
|
GIT_VERSION="1.0"
|
||||||
GO_VERSION="1.16"
|
GO_VERSION="1.27.1"
|
||||||
OSX_VERSION="10.8"
|
OSX_VERSION="10.8"
|
||||||
KNAME=$(uname -s)
|
KNAME=$(uname -s)
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
case "${KNAME}" in
|
case "${KNAME}" in
|
||||||
SunOS )
|
SunOS)
|
||||||
ARCH=$(isainfo -k)
|
ARCH=$(isainfo -k)
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
## FIXME:
|
## FIXME:
|
||||||
@@ -28,24 +28,23 @@ _init() {
|
|||||||
## }
|
## }
|
||||||
##
|
##
|
||||||
readlink() {
|
readlink() {
|
||||||
TARGET_FILE=$1
|
TARGET_FILE=$1
|
||||||
|
|
||||||
cd `dirname $TARGET_FILE`
|
cd $(dirname $TARGET_FILE)
|
||||||
TARGET_FILE=`basename $TARGET_FILE`
|
TARGET_FILE=$(basename $TARGET_FILE)
|
||||||
|
|
||||||
# Iterate down a (possible) chain of symlinks
|
# Iterate down a (possible) chain of symlinks
|
||||||
while [ -L "$TARGET_FILE" ]
|
while [ -L "$TARGET_FILE" ]; do
|
||||||
do
|
TARGET_FILE=$(env readlink $TARGET_FILE)
|
||||||
TARGET_FILE=$(env readlink $TARGET_FILE)
|
cd $(dirname $TARGET_FILE)
|
||||||
cd `dirname $TARGET_FILE`
|
TARGET_FILE=$(basename $TARGET_FILE)
|
||||||
TARGET_FILE=`basename $TARGET_FILE`
|
done
|
||||||
done
|
|
||||||
|
|
||||||
# Compute the canonicalized name by finding the physical path
|
# Compute the canonicalized name by finding the physical path
|
||||||
# for the directory we're in and appending the target file.
|
# for the directory we're in and appending the target file.
|
||||||
PHYS_DIR=`pwd -P`
|
PHYS_DIR=$(pwd -P)
|
||||||
RESULT=$PHYS_DIR/$TARGET_FILE
|
RESULT=$PHYS_DIR/$TARGET_FILE
|
||||||
echo $RESULT
|
echo $RESULT
|
||||||
}
|
}
|
||||||
|
|
||||||
## FIXME:
|
## FIXME:
|
||||||
@@ -59,84 +58,86 @@ readlink() {
|
|||||||
## }
|
## }
|
||||||
##
|
##
|
||||||
check_minimum_version() {
|
check_minimum_version() {
|
||||||
IFS='.' read -r -a varray1 <<< "$1"
|
IFS='.' read -r -a varray1 <<<"$1"
|
||||||
IFS='.' read -r -a varray2 <<< "$2"
|
IFS='.' read -r -a varray2 <<<"$2"
|
||||||
|
|
||||||
for i in "${!varray1[@]}"; do
|
for i in "${!varray1[@]}"; do
|
||||||
if [[ ${varray1[i]} -lt ${varray2[i]} ]]; then
|
if [[ ${varray1[i]} -lt ${varray2[i]} ]]; then
|
||||||
return 0
|
return 0
|
||||||
elif [[ ${varray1[i]} -gt ${varray2[i]} ]]; then
|
elif [[ ${varray1[i]} -gt ${varray2[i]} ]]; then
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_is_supported_arch() {
|
assert_is_supported_arch() {
|
||||||
case "${ARCH}" in
|
case "${ARCH}" in
|
||||||
x86_64 | amd64 | aarch64 | ppc64le | arm* | s390x )
|
x86_64 | amd64 | aarch64 | ppc64le | arm* | s390x | loong64 | loongarch64 | riscv64)
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Arch '${ARCH}' is not supported. Supported Arch: [x86_64, amd64, aarch64, ppc64le, arm*, s390x]"
|
echo "Arch '${ARCH}' is not supported. Supported Arch: [x86_64, amd64, aarch64, ppc64le, arm*, s390x, loong64, loongarch64, riscv64]"
|
||||||
exit 1
|
exit 1
|
||||||
esac
|
;;
|
||||||
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_is_supported_os() {
|
assert_is_supported_os() {
|
||||||
case "${KNAME}" in
|
case "${KNAME}" in
|
||||||
Linux | FreeBSD | OpenBSD | NetBSD | DragonFly | SunOS )
|
Linux | FreeBSD | OpenBSD | NetBSD | DragonFly | SunOS)
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
Darwin )
|
Darwin)
|
||||||
osx_host_version=$(env sw_vers -productVersion)
|
osx_host_version=$(env sw_vers -productVersion)
|
||||||
if ! check_minimum_version "${OSX_VERSION}" "${osx_host_version}"; then
|
if ! check_minimum_version "${OSX_VERSION}" "${osx_host_version}"; then
|
||||||
echo "OSX version '${osx_host_version}' is not supported. Minimum supported version: ${OSX_VERSION}"
|
echo "OSX version '${osx_host_version}' is not supported. Minimum supported version: ${OSX_VERSION}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "OS '${KNAME}' is not supported. Supported OS: [Linux, FreeBSD, OpenBSD, NetBSD, Darwin, DragonFly]"
|
echo "OS '${KNAME}' is not supported. Supported OS: [Linux, FreeBSD, OpenBSD, NetBSD, Darwin, DragonFly]"
|
||||||
exit 1
|
exit 1
|
||||||
esac
|
;;
|
||||||
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_check_golang_env() {
|
assert_check_golang_env() {
|
||||||
if ! which go >/dev/null 2>&1; then
|
if ! which go >/dev/null 2>&1; then
|
||||||
echo "Cannot find go binary in your PATH configuration, please refer to Go installation document at https://golang.org/doc/install"
|
echo "Cannot find go binary in your PATH configuration, please refer to Go installation document at https://golang.org/doc/install"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
installed_go_version=$(go version | sed 's/^.* go\([0-9.]*\).*$/\1/')
|
installed_go_version=$(go version | sed 's/^.* go\([0-9.]*\).*$/\1/')
|
||||||
if ! check_minimum_version "${GO_VERSION}" "${installed_go_version}"; then
|
if ! check_minimum_version "${GO_VERSION}" "${installed_go_version}"; then
|
||||||
echo "Go runtime version '${installed_go_version}' is unsupported. Minimum supported version: ${GO_VERSION} to compile."
|
echo "Go runtime version '${installed_go_version}' is unsupported. Minimum supported version: ${GO_VERSION} to compile."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_check_deps() {
|
assert_check_deps() {
|
||||||
# support unusual Git versions such as: 2.7.4 (Apple Git-66)
|
# support unusual Git versions such as: 2.7.4 (Apple Git-66)
|
||||||
installed_git_version=$(git version | perl -ne '$_ =~ m/git version (.*?)( |$)/; print "$1\n";')
|
installed_git_version=$(git version | perl -ne '$_ =~ m/git version (.*?)( |$)/; print "$1\n";')
|
||||||
if ! check_minimum_version "${GIT_VERSION}" "${installed_git_version}"; then
|
if ! check_minimum_version "${GIT_VERSION}" "${installed_git_version}"; then
|
||||||
echo "Git version '${installed_git_version}' is not supported. Minimum supported version: ${GIT_VERSION}"
|
echo "Git version '${installed_git_version}' is not supported. Minimum supported version: ${GIT_VERSION}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
## Check for supported arch
|
## Check for supported arch
|
||||||
assert_is_supported_arch
|
assert_is_supported_arch
|
||||||
|
|
||||||
## Check for supported os
|
## Check for supported os
|
||||||
assert_is_supported_os
|
assert_is_supported_os
|
||||||
|
|
||||||
## Check for Go environment
|
## Check for Go environment
|
||||||
assert_check_golang_env
|
assert_check_golang_env
|
||||||
|
|
||||||
## Check for dependencies
|
## Check for dependencies
|
||||||
assert_check_deps
|
assert_check_deps
|
||||||
}
|
}
|
||||||
|
|
||||||
_init && main "$@"
|
_init && main "$@"
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -5,33 +5,36 @@ set -e
|
|||||||
[ -n "$BASH_XTRACEFD" ] && set -x
|
[ -n "$BASH_XTRACEFD" ] && set -x
|
||||||
|
|
||||||
function _init() {
|
function _init() {
|
||||||
## All binaries are static make sure to disable CGO.
|
## All binaries are static make sure to disable CGO.
|
||||||
export CGO_ENABLED=0
|
export CGO_ENABLED=0
|
||||||
|
|
||||||
## List of architectures and OS to test coss compilation.
|
## Cross-compile only the OS/arch combinations we actually publish, kept in
|
||||||
SUPPORTED_OSARCH="linux/ppc64le linux/mips64 linux/arm64 linux/s390x darwin/arm64 darwin/amd64 freebsd/amd64 windows/amd64 linux/arm linux/386 netbsd/amd64 linux/mips openbsd/amd64"
|
## sync with the goos/goarch matrix in .github/goreleaser.yml. Compile-checking
|
||||||
|
## targets we never ship (ppc64le, s390x, mips*, riscv64, 386, arm, the BSDs)
|
||||||
|
## spent CI minutes on unshipped code and timed the gate out on a cold cache.
|
||||||
|
SUPPORTED_OSARCH="linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 windows/arm64"
|
||||||
}
|
}
|
||||||
|
|
||||||
function _build() {
|
function _build() {
|
||||||
local osarch=$1
|
local osarch=$1
|
||||||
IFS=/ read -r -a arr <<<"$osarch"
|
IFS=/ read -r -a arr <<<"$osarch"
|
||||||
os="${arr[0]}"
|
os="${arr[0]}"
|
||||||
arch="${arr[1]}"
|
arch="${arr[1]}"
|
||||||
package=$(go list -f '{{.ImportPath}}')
|
package=$(go list -f '{{.ImportPath}}')
|
||||||
printf -- "--> %15s:%s\n" "${osarch}" "${package}"
|
printf -- "--> %15s:%s\n" "${osarch}" "${package}"
|
||||||
|
|
||||||
# go build -trimpath to build the binary.
|
# go build -trimpath to build the binary.
|
||||||
export GOOS=$os
|
export GOOS=$os
|
||||||
export GOARCH=$arch
|
export GOARCH=$arch
|
||||||
export GO111MODULE=on
|
export GO111MODULE=on
|
||||||
go build -trimpath -tags kqueue -o /dev/null
|
go build -trimpath -tags kqueue -o /dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
echo "Testing builds for OS/Arch: ${SUPPORTED_OSARCH}"
|
echo "Testing builds for OS/Arch: ${SUPPORTED_OSARCH}"
|
||||||
for each_osarch in ${SUPPORTED_OSARCH}; do
|
for each_osarch in ${SUPPORTED_OSARCH}; do
|
||||||
_build "${each_osarch}"
|
_build "${each_osarch}"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
_init && main "$@"
|
_init && main "$@"
|
||||||
|
|||||||
Executable
+123
@@ -0,0 +1,123 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -x
|
||||||
|
|
||||||
|
export MINIO_CI_CD=1
|
||||||
|
killall -9 silo
|
||||||
|
|
||||||
|
rm -rf ${HOME}/tmp/dist
|
||||||
|
|
||||||
|
scheme="http"
|
||||||
|
nr_servers=4
|
||||||
|
|
||||||
|
addr="localhost"
|
||||||
|
args=""
|
||||||
|
for ((i = 0; i < $((nr_servers)); i++)); do
|
||||||
|
args="$args $scheme://$addr:$((9100 + i))/${HOME}/tmp/dist/path1/$i"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo $args
|
||||||
|
|
||||||
|
for ((i = 0; i < $((nr_servers)); i++)); do
|
||||||
|
(silo server --address ":$((9100 + i))" $args 2>&1 >/tmp/log$i.txt) &
|
||||||
|
done
|
||||||
|
|
||||||
|
sleep 10s
|
||||||
|
|
||||||
|
if [ ! -f ./mc ]; then
|
||||||
|
"$(git rev-parse --show-toplevel)/buildscripts/install-mcli.sh" ./mc
|
||||||
|
fi
|
||||||
|
|
||||||
|
set +e
|
||||||
|
|
||||||
|
export MC_HOST_siloadm=http://minioadmin:minioadmin@localhost:9100/
|
||||||
|
./mc ready siloadm
|
||||||
|
|
||||||
|
./mc ls siloadm/
|
||||||
|
|
||||||
|
./mc admin config set siloadm/ api root_access=off
|
||||||
|
|
||||||
|
sleep 3s # let things settle a little
|
||||||
|
|
||||||
|
./mc ls siloadm/
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "listing succeeded, 'minioadmin' was not disabled"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
killall -9 silo
|
||||||
|
|
||||||
|
export MINIO_API_ROOT_ACCESS=on
|
||||||
|
for ((i = 0; i < $((nr_servers)); i++)); do
|
||||||
|
(silo server --address ":$((9100 + i))" $args 2>&1 >/tmp/log$i.txt) &
|
||||||
|
done
|
||||||
|
|
||||||
|
set +e
|
||||||
|
|
||||||
|
./mc ready siloadm/
|
||||||
|
|
||||||
|
./mc ls siloadm/
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "listing failed, 'minioadmin' should be enabled"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
killall -9 silo
|
||||||
|
|
||||||
|
rm -rf /tmp/multisitea/
|
||||||
|
rm -rf /tmp/multisiteb/
|
||||||
|
|
||||||
|
echo "Setup site-replication and then disable root credentials"
|
||||||
|
|
||||||
|
silo server --address 127.0.0.1:9001 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
|
||||||
|
"http://127.0.0.1:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_1.log 2>&1 &
|
||||||
|
silo server --address 127.0.0.1:9002 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
|
||||||
|
"http://127.0.0.1:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_2.log 2>&1 &
|
||||||
|
|
||||||
|
silo server --address 127.0.0.1:9003 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
|
||||||
|
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_1.log 2>&1 &
|
||||||
|
silo server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
|
||||||
|
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_2.log 2>&1 &
|
||||||
|
|
||||||
|
export MC_HOST_sitea=http://minioadmin:minioadmin@127.0.0.1:9001
|
||||||
|
export MC_HOST_siteb=http://minioadmin:minioadmin@127.0.0.1:9004
|
||||||
|
|
||||||
|
./mc ready sitea
|
||||||
|
./mc ready siteb
|
||||||
|
|
||||||
|
./mc admin replicate add sitea siteb
|
||||||
|
|
||||||
|
./mc admin user add sitea foobar foo12345
|
||||||
|
|
||||||
|
./mc admin policy attach sitea/ consoleAdmin --user=foobar
|
||||||
|
|
||||||
|
./mc admin user info siteb foobar
|
||||||
|
|
||||||
|
killall -9 silo
|
||||||
|
|
||||||
|
echo "turning off root access, however site replication must continue"
|
||||||
|
export MINIO_API_ROOT_ACCESS=off
|
||||||
|
|
||||||
|
silo server --address 127.0.0.1:9001 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
|
||||||
|
"http://127.0.0.1:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_1.log 2>&1 &
|
||||||
|
silo server --address 127.0.0.1:9002 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
|
||||||
|
"http://127.0.0.1:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_2.log 2>&1 &
|
||||||
|
|
||||||
|
silo server --address 127.0.0.1:9003 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
|
||||||
|
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_1.log 2>&1 &
|
||||||
|
silo server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
|
||||||
|
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_2.log 2>&1 &
|
||||||
|
|
||||||
|
export MC_HOST_sitea=http://foobar:foo12345@127.0.0.1:9001
|
||||||
|
export MC_HOST_siteb=http://foobar:foo12345@127.0.0.1:9004
|
||||||
|
|
||||||
|
./mc ready sitea
|
||||||
|
./mc ready siteb
|
||||||
|
|
||||||
|
./mc admin user add sitea foobar-admin foo12345
|
||||||
|
|
||||||
|
sleep 2s
|
||||||
|
|
||||||
|
./mc admin user info siteb foobar-admin
|
||||||
Executable
+125
@@ -0,0 +1,125 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Regenerates CREDITS from the license text of every Go module linked into the
|
||||||
|
# silo binary. The module set is what `go list -deps` reports for the main
|
||||||
|
# package, so test-only and tool dependencies stay out: CREDITS documents what
|
||||||
|
# a shipped binary actually contains. check-gen runs this and fails on a diff,
|
||||||
|
# which keeps CREDITS from drifting when go.mod changes.
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
repo_dir="$(cd "${script_dir}/.." && pwd)"
|
||||||
|
cd "${repo_dir}"
|
||||||
|
|
||||||
|
out_file="${1:-${repo_dir}/CREDITS}"
|
||||||
|
tmp_file="${out_file}.tmp"
|
||||||
|
trap 'rm -f "${tmp_file}"' EXIT
|
||||||
|
|
||||||
|
rule_dash='----------------------------------------------------------------'
|
||||||
|
rule_equal='================================================================'
|
||||||
|
|
||||||
|
# These modules repackage Go standard library code and publish no license
|
||||||
|
# file; their source files carry the Go Authors' BSD-style header pointing at
|
||||||
|
# the Go project license, so that text is reproduced for them.
|
||||||
|
stdlib_derived='github.com/minio/colorjson github.com/minio/csvparser github.com/minio/filepath'
|
||||||
|
|
||||||
|
is_stdlib_derived() {
|
||||||
|
case " ${stdlib_derived} " in
|
||||||
|
*" $1 "*) return 0 ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Command substitution strips trailing newlines; printf adds exactly one back,
|
||||||
|
# so every entry ends the same way regardless of how the license file ends.
|
||||||
|
emit_text() {
|
||||||
|
printf '%s\n' "$(cat "$1")"
|
||||||
|
}
|
||||||
|
|
||||||
|
# The module cache must hold every dependency before .Dir can resolve.
|
||||||
|
go mod download
|
||||||
|
|
||||||
|
# The Go project license text is taken from the pinned golang.org/x/sys module
|
||||||
|
# rather than GOROOT: Homebrew's Go does not ship GOROOT/LICENSE, and the
|
||||||
|
# module copy is version-locked by go.mod, so the output cannot vary with the
|
||||||
|
# machine's toolchain packaging.
|
||||||
|
go_license="$(go list -m -f '{{.Dir}}' golang.org/x/sys)/LICENSE"
|
||||||
|
if [ ! -f "${go_license}" ]; then
|
||||||
|
echo "Missing Go license text: ${go_license}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
printf '%s\n' \
|
||||||
|
'Silo bundles third-party software under the licenses reproduced below.' \
|
||||||
|
'Generated by buildscripts/gen-credits.sh (make credits) from the Go' \
|
||||||
|
'modules linked into the silo binary. Do not edit by hand.' \
|
||||||
|
'' \
|
||||||
|
"${rule_equal}" \
|
||||||
|
''
|
||||||
|
printf '%s\n%s\n%s\n' 'Go (the standard library)' 'https://golang.org/' "${rule_dash}"
|
||||||
|
emit_text "${go_license}"
|
||||||
|
printf '\n%s\n\n' "${rule_equal}"
|
||||||
|
|
||||||
|
# The dependency closure is GOOS/GOARCH-specific: platform-only modules
|
||||||
|
# (darwin's go-m1cpu, windows' wmi, ...) enter and leave it with the host.
|
||||||
|
# Pin the primary shipped target and the release build tags so regenerating
|
||||||
|
# CREDITS produces identical output on every machine, including CI.
|
||||||
|
GOOS=linux GOARCH=amd64 go list -deps -tags kqueue \
|
||||||
|
-f '{{if and (not .Standard) .Module}}{{.Module.Path}}{{end}}' . \
|
||||||
|
| LC_ALL=C sort -u \
|
||||||
|
| grep -vx 'github.com/minio/minio' \
|
||||||
|
| xargs go list -m -f '{{.Path}}|{{with .Replace}}{{.Path}}{{end}}|{{.Dir}}' \
|
||||||
|
| while IFS='|' read -r path replacement dir; do
|
||||||
|
if [ -z "${dir}" ] || [ ! -d "${dir}" ]; then
|
||||||
|
echo "Module cache directory missing for ${path}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
name="${path}"
|
||||||
|
url="https://${path}"
|
||||||
|
# A same-path replace only pins a version; the annotation is for
|
||||||
|
# dependencies actually served from a different repository.
|
||||||
|
if [ -n "${replacement}" ] && [ "${replacement}" != "${path}" ]; then
|
||||||
|
name="${path} (replaced by ${replacement})"
|
||||||
|
url="https://${replacement}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n%s\n%s\n' "${name}" "${url}" "${rule_dash}"
|
||||||
|
|
||||||
|
if is_stdlib_derived "${path}"; then
|
||||||
|
printf '%s\n%s\n\n' \
|
||||||
|
'This module repackages Go standard library code and publishes no' \
|
||||||
|
'license file; the Go project license below applies per its file headers.'
|
||||||
|
emit_text "${go_license}"
|
||||||
|
else
|
||||||
|
license_file=''
|
||||||
|
for candidate in LICENSE LICENSE.txt LICENSE.md COPYING COPYING.txt LICENCE UNLICENSE; do
|
||||||
|
if [ -f "${dir}/${candidate}" ]; then
|
||||||
|
license_file="${dir}/${candidate}"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ -z "${license_file}" ]; then
|
||||||
|
echo "No license file found for ${path} in ${dir}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
emit_text "${license_file}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Apache License 2.0 section 4(d) requires redistributing the NOTICE
|
||||||
|
# file contents alongside the licensed work.
|
||||||
|
for notice in NOTICE NOTICE.txt; do
|
||||||
|
if [ -f "${dir}/${notice}" ]; then
|
||||||
|
printf '\n%s\n\n' 'Bundled NOTICE file:'
|
||||||
|
emit_text "${dir}/${notice}"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '\n%s\n\n' "${rule_equal}"
|
||||||
|
done
|
||||||
|
} > "${tmp_file}"
|
||||||
|
|
||||||
|
mv "${tmp_file}" "${out_file}"
|
||||||
@@ -24,23 +24,31 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func genLDFlags(version string) string {
|
func genLDFlags(version string) string {
|
||||||
|
releaseTag, date := releaseTag(version)
|
||||||
|
copyrightYear := strconv.Itoa(date.Year())
|
||||||
ldflagsStr := "-s -w"
|
ldflagsStr := "-s -w"
|
||||||
ldflagsStr += " -X github.com/minio/minio/cmd.Version=" + version
|
ldflagsStr += " -X github.com/minio/minio/cmd.Version=" + version
|
||||||
ldflagsStr += " -X github.com/minio/minio/cmd.ReleaseTag=" + releaseTag(version)
|
ldflagsStr += " -X github.com/minio/minio/cmd.CopyrightYear=" + copyrightYear
|
||||||
|
ldflagsStr += " -X github.com/minio/minio/cmd.ReleaseTag=" + releaseTag
|
||||||
ldflagsStr += " -X github.com/minio/minio/cmd.CommitID=" + commitID()
|
ldflagsStr += " -X github.com/minio/minio/cmd.CommitID=" + commitID()
|
||||||
ldflagsStr += " -X github.com/minio/minio/cmd.ShortCommitID=" + commitID()[:12]
|
ldflagsStr += " -X github.com/minio/minio/cmd.ShortCommitID=" + commitID()[:12]
|
||||||
ldflagsStr += " -X github.com/minio/minio/cmd.GOPATH=" + os.Getenv("GOPATH")
|
// GOPATH/GOROOT are deliberately not stamped in. They only seed the logger's
|
||||||
ldflagsStr += " -X github.com/minio/minio/cmd.GOROOT=" + os.Getenv("GOROOT")
|
// source-path trim list, which -trimpath already makes moot (paths are
|
||||||
|
// relative in the binary, so there is no build-machine prefix left to trim),
|
||||||
|
// and stamping them baked the builder's absolute paths into the released
|
||||||
|
// binary - defeating -trimpath and reproducible builds. cmd.GOPATH/GOROOT
|
||||||
|
// keep their empty defaults, exactly as a plain `go build` leaves them.
|
||||||
return ldflagsStr
|
return ldflagsStr
|
||||||
}
|
}
|
||||||
|
|
||||||
// genReleaseTag prints release tag to the console for easy git tagging.
|
// genReleaseTag prints release tag to the console for easy git tagging.
|
||||||
func releaseTag(version string) string {
|
func releaseTag(version string) (string, time.Time) {
|
||||||
relPrefix := "DEVELOPMENT"
|
relPrefix := "DEVELOPMENT"
|
||||||
if prefix := os.Getenv("MINIO_RELEASE"); prefix != "" {
|
if prefix := os.Getenv("MINIO_RELEASE"); prefix != "" {
|
||||||
relPrefix = prefix
|
relPrefix = prefix
|
||||||
@@ -53,14 +61,17 @@ func releaseTag(version string) string {
|
|||||||
|
|
||||||
relTag := strings.Replace(version, " ", "-", -1)
|
relTag := strings.Replace(version, " ", "-", -1)
|
||||||
relTag = strings.Replace(relTag, ":", "-", -1)
|
relTag = strings.Replace(relTag, ":", "-", -1)
|
||||||
|
t, err := time.Parse("2006-01-02T15-04-05Z", relTag)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
relTag = strings.Replace(relTag, ",", "", -1)
|
relTag = strings.Replace(relTag, ",", "", -1)
|
||||||
relTag = relPrefix + "." + relTag
|
relTag = relPrefix + "." + relTag
|
||||||
|
|
||||||
if relSuffix != "" {
|
if relSuffix != "" {
|
||||||
relTag += "." + relSuffix
|
relTag += "." + relSuffix
|
||||||
}
|
}
|
||||||
|
|
||||||
return relTag
|
return relTag, t
|
||||||
}
|
}
|
||||||
|
|
||||||
// commitID returns the abbreviated commit-id hash of the last commit.
|
// commitID returns the abbreviated commit-id hash of the last commit.
|
||||||
|
|||||||
Executable
+81
@@ -0,0 +1,81 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
set -E
|
||||||
|
set -o pipefail
|
||||||
|
set -x
|
||||||
|
|
||||||
|
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||||
|
SILO_CONFIG_DIR="$WORK_DIR/.silo"
|
||||||
|
SILO=("$PWD/silo" --config-dir "$SILO_CONFIG_DIR" server)
|
||||||
|
|
||||||
|
if [ ! -x "$PWD/silo" ]; then
|
||||||
|
echo "Silo executable binary not found in current directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -x "$PWD/silo" ]; then
|
||||||
|
echo "Silo executable binary not found in current directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
function start_silo_4drive() {
|
||||||
|
start_port=$1
|
||||||
|
|
||||||
|
export MINIO_ROOT_USER=silo
|
||||||
|
export MINIO_ROOT_PASSWORD=silo1234
|
||||||
|
export MC_HOST_silo="http://silo:silo1234@127.0.0.1:${start_port}/"
|
||||||
|
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||||
|
export MINIO_CI_CD=1
|
||||||
|
|
||||||
|
mkdir ${WORK_DIR}
|
||||||
|
if [ ! -x "$PWD/mc" ]; then
|
||||||
|
"$(git rev-parse --show-toplevel)/buildscripts/install-mcli.sh" "$PWD/mc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
"${SILO[@]}" --address ":$start_port" "${WORK_DIR}/disk{1...4}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||||
|
pid=$!
|
||||||
|
disown $pid
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||||
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
"${PWD}/mc" mb --with-versioning silo/bucket
|
||||||
|
|
||||||
|
for i in $(seq 1 4); do
|
||||||
|
"${PWD}/mc" cp /etc/hosts silo/bucket/testobj
|
||||||
|
|
||||||
|
sudo chown -R root. "${WORK_DIR}/disk${i}"
|
||||||
|
|
||||||
|
"${PWD}/mc" cp /etc/hosts silo/bucket/testobj
|
||||||
|
|
||||||
|
sudo chown -R ${USER}. "${WORK_DIR}/disk${i}"
|
||||||
|
done
|
||||||
|
|
||||||
|
for vid in $("${PWD}/mc" ls --json --versions silo/bucket/testobj | jq -r .versionId); do
|
||||||
|
"${PWD}/mc" cat --vid "${vid}" silo/bucket/testobj | md5sum
|
||||||
|
done
|
||||||
|
|
||||||
|
pkill silo
|
||||||
|
sleep 3
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
|
|
||||||
|
start_silo_4drive ${start_port}
|
||||||
|
}
|
||||||
|
|
||||||
|
function purge() {
|
||||||
|
rm -rf "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
(main "$@")
|
||||||
|
rv=$?
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit "$rv"
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
//go:build ignore
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
//
|
||||||
|
// MinIO Object Storage (c) 2022 MinIO, Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/madmin-go/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
|
||||||
|
// dummy values, please replace them with original values.
|
||||||
|
|
||||||
|
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
|
||||||
|
// New returns an MinIO Admin client object.
|
||||||
|
madmClnt, err := madmin.New(os.Args[1], os.Args[2], os.Args[3], false)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := madmin.HealOpts{
|
||||||
|
Recursive: true, // recursively heal all objects at 'prefix'
|
||||||
|
Remove: true, // remove content that has lost quorum and not recoverable
|
||||||
|
ScanMode: madmin.HealNormalScan, // by default do not do 'deep' scanning
|
||||||
|
}
|
||||||
|
|
||||||
|
start, _, err := madmClnt.Heal(context.Background(), "healing-rewrite-bucket", "", opts, "", false, false)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
fmt.Println("Healstart sequence ===")
|
||||||
|
enc := json.NewEncoder(os.Stdout)
|
||||||
|
if err = enc.Encode(&start); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
for {
|
||||||
|
_, status, err := madmClnt.Heal(context.Background(), "healing-rewrite-bucket", "", opts, start.ClientToken, false, false)
|
||||||
|
if status.Summary == "finished" {
|
||||||
|
fmt.Println("Healstatus on items ===")
|
||||||
|
for _, item := range status.Items {
|
||||||
|
if err = enc.Encode(&item); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if status.Summary == "stopped" {
|
||||||
|
fmt.Println("Healstatus on items ===")
|
||||||
|
fmt.Println("Heal failed with", status.FailureDetail)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range status.Items {
|
||||||
|
if err = enc.Encode(&item); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,331 @@
|
|||||||
|
// Copyright 2026 PGSTY contributors.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// helm-migration-guard compares a rendered legacy MinIO chart with the Silo
|
||||||
|
// upgrade candidate. Product labels, images, and commands may change; resource
|
||||||
|
// identity, selectors, PVCs, storage mounts, ports, secrets, and service-account
|
||||||
|
// references must not.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go.yaml.in/yaml/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type resource struct {
|
||||||
|
key string
|
||||||
|
doc map[string]any
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) != 3 {
|
||||||
|
fatal(errors.New("usage: helm-migration-guard OLD_RENDER NEW_RENDER"))
|
||||||
|
}
|
||||||
|
oldResources, err := readResources(os.Args[1])
|
||||||
|
if err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
newResources, err := readResources(os.Args[2])
|
||||||
|
if err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
if err := compare(oldResources, newResources); err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Silo Helm migration identity is stable across %d rendered resources\n", len(oldResources))
|
||||||
|
}
|
||||||
|
|
||||||
|
func readResources(path string) (map[string]resource, error) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("open %s: %w", path, err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
resources := make(map[string]resource)
|
||||||
|
decoder := yaml.NewDecoder(file)
|
||||||
|
for document := 1; ; document++ {
|
||||||
|
var doc map[string]any
|
||||||
|
err = decoder.Decode(&doc)
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decode %s document %d: %w", path, document, err)
|
||||||
|
}
|
||||||
|
if len(doc) == 0 || text(doc["kind"]) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
metadata := object(doc["metadata"])
|
||||||
|
key := strings.Join([]string{text(doc["kind"]), text(metadata["namespace"]), text(metadata["name"])}, "/")
|
||||||
|
if _, exists := resources[key]; exists {
|
||||||
|
return nil, fmt.Errorf("%s contains duplicate resource %s", path, key)
|
||||||
|
}
|
||||||
|
resources[key] = resource{key: key, doc: doc}
|
||||||
|
}
|
||||||
|
return resources, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func compare(oldResources, newResources map[string]resource) error {
|
||||||
|
for key := range oldResources {
|
||||||
|
if _, ok := newResources[key]; !ok {
|
||||||
|
return fmt.Errorf("legacy resource would be removed or renamed: %s", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for key := range newResources {
|
||||||
|
if _, ok := oldResources[key]; !ok {
|
||||||
|
return fmt.Errorf("upgrade candidate unexpectedly adds a resource: %s", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := make([]string, 0, len(oldResources))
|
||||||
|
for key := range oldResources {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
for _, key := range keys {
|
||||||
|
oldDoc := oldResources[key].doc
|
||||||
|
newDoc := newResources[key].doc
|
||||||
|
kind := text(oldDoc["kind"])
|
||||||
|
switch kind {
|
||||||
|
case "Service":
|
||||||
|
if err := same(key, "Service selector", at(oldDoc, "spec", "selector"), at(newDoc, "spec", "selector")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := same(key, "Service ports", at(oldDoc, "spec", "ports"), at(newDoc, "spec", "ports")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "Deployment", "StatefulSet":
|
||||||
|
if err := same(key, "workload selector", at(oldDoc, "spec", "selector"), at(newDoc, "spec", "selector")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if kind == "StatefulSet" {
|
||||||
|
if err := same(key, "StatefulSet serviceName", at(oldDoc, "spec", "serviceName"), at(newDoc, "spec", "serviceName")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := same(key, "volume claim templates", claimTemplates(oldDoc), claimTemplates(newDoc)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if kind == "PersistentVolumeClaim" {
|
||||||
|
if err := same(key, "PVC specification", at(oldDoc, "spec"), at(newDoc, "spec")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if kind == "Secret" {
|
||||||
|
if err := same(key, "Secret keys", secretKeys(oldDoc), secretKeys(newDoc)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if kind == "Deployment" || kind == "StatefulSet" || kind == "Job" {
|
||||||
|
if err := comparePod(key, kind, oldDoc, newDoc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func comparePod(key, kind string, oldDoc, newDoc map[string]any) error {
|
||||||
|
oldPod := object(at(oldDoc, "spec", "template", "spec"))
|
||||||
|
newPod := object(at(newDoc, "spec", "template", "spec"))
|
||||||
|
if err := same(key, "service account", oldPod["serviceAccountName"], newPod["serviceAccountName"]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := same(key, "referenced volume sources", volumeSources(oldPod), volumeSources(newPod)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
oldContainers := containers(oldPod)
|
||||||
|
newContainers := containers(newPod)
|
||||||
|
if err := same(key, "container identities", sortedKeys(oldContainers), sortedKeys(newContainers)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, name := range sortedKeys(oldContainers) {
|
||||||
|
oldContainer := oldContainers[name]
|
||||||
|
newContainer := newContainers[name]
|
||||||
|
if err := same(key, name+" ports", oldContainer["ports"], newContainer["ports"]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := same(key, name+" environment", oldContainer["env"], newContainer["env"]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := same(key, name+" envFrom", oldContainer["envFrom"], newContainer["envFrom"]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := same(key, name+" storage mounts", normalizedMounts(oldContainer, oldPod), normalizedMounts(newContainer, newPod)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
image := text(newContainer["image"])
|
||||||
|
if strings.HasPrefix(image, "pgsty/minio:") || strings.HasPrefix(image, "docker.io/pgsty/minio:") {
|
||||||
|
return fmt.Errorf("%s container %s still uses frozen image %s", key, name, image)
|
||||||
|
}
|
||||||
|
command := commandText(newContainer)
|
||||||
|
if strings.Contains(command, "/usr/bin/minio") {
|
||||||
|
return fmt.Errorf("%s container %s still invokes /usr/bin/minio", key, name)
|
||||||
|
}
|
||||||
|
if (kind == "Deployment" || kind == "StatefulSet") && strings.Contains(image, "pgsty/silo:") {
|
||||||
|
if !strings.Contains(command, "silo") || !strings.Contains(command, "server") {
|
||||||
|
return fmt.Errorf("%s container %s does not invoke the Silo server: %q", key, name, command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func claimTemplates(doc map[string]any) []string {
|
||||||
|
var result []string
|
||||||
|
for _, raw := range list(at(doc, "spec", "volumeClaimTemplates")) {
|
||||||
|
claim := object(raw)
|
||||||
|
metadata := object(claim["metadata"])
|
||||||
|
result = append(result, text(metadata["name"])+"="+canonical(claim["spec"]))
|
||||||
|
}
|
||||||
|
sort.Strings(result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func secretKeys(doc map[string]any) []string {
|
||||||
|
var result []string
|
||||||
|
for _, section := range []string{"data", "stringData"} {
|
||||||
|
for key := range object(doc[section]) {
|
||||||
|
result = append(result, section+":"+key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func volumeSources(pod map[string]any) []string {
|
||||||
|
var result []string
|
||||||
|
for _, raw := range list(pod["volumes"]) {
|
||||||
|
volume := cloneObject(object(raw))
|
||||||
|
delete(volume, "name")
|
||||||
|
result = append(result, canonical(volume))
|
||||||
|
}
|
||||||
|
sort.Strings(result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func volumeSourceByName(pod map[string]any) map[string]string {
|
||||||
|
result := make(map[string]string)
|
||||||
|
for _, raw := range list(pod["volumes"]) {
|
||||||
|
volume := cloneObject(object(raw))
|
||||||
|
name := text(volume["name"])
|
||||||
|
delete(volume, "name")
|
||||||
|
result[name] = canonical(volume)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizedMounts(container, pod map[string]any) []string {
|
||||||
|
sources := volumeSourceByName(pod)
|
||||||
|
var result []string
|
||||||
|
for _, raw := range list(container["volumeMounts"]) {
|
||||||
|
mount := cloneObject(object(raw))
|
||||||
|
name := text(mount["name"])
|
||||||
|
delete(mount, "name")
|
||||||
|
mount["source"] = sources[name]
|
||||||
|
result = append(result, canonical(mount))
|
||||||
|
}
|
||||||
|
sort.Strings(result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func containers(pod map[string]any) map[string]map[string]any {
|
||||||
|
result := make(map[string]map[string]any)
|
||||||
|
for _, section := range []string{"initContainers", "containers"} {
|
||||||
|
for _, raw := range list(pod[section]) {
|
||||||
|
container := object(raw)
|
||||||
|
result[section+":"+text(container["name"])] = container
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func commandText(container map[string]any) string {
|
||||||
|
var parts []string
|
||||||
|
for _, field := range []string{"command", "args"} {
|
||||||
|
for _, value := range list(container[field]) {
|
||||||
|
parts = append(parts, text(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(parts, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func at(root map[string]any, path ...string) any {
|
||||||
|
var current any = root
|
||||||
|
for _, part := range path {
|
||||||
|
current = object(current)[part]
|
||||||
|
}
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
func object(value any) map[string]any {
|
||||||
|
if value == nil {
|
||||||
|
return map[string]any{}
|
||||||
|
}
|
||||||
|
result, _ := value.(map[string]any)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneObject(value map[string]any) map[string]any {
|
||||||
|
result := make(map[string]any, len(value))
|
||||||
|
for key, item := range value {
|
||||||
|
result[key] = item
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func list(value any) []any {
|
||||||
|
result, _ := value.([]any)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func text(value any) string {
|
||||||
|
result, _ := value.(string)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortedKeys[T any](values map[string]T) []string {
|
||||||
|
result := make([]string, 0, len(values))
|
||||||
|
for key := range values {
|
||||||
|
result = append(result, key)
|
||||||
|
}
|
||||||
|
sort.Strings(result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func same(resourceKey, field string, oldValue, newValue any) error {
|
||||||
|
oldCanonical := canonical(oldValue)
|
||||||
|
newCanonical := canonical(newValue)
|
||||||
|
if oldCanonical != newCanonical {
|
||||||
|
return fmt.Errorf("%s changes %s\nold: %s\nnew: %s", resourceKey, field, oldCanonical, newCanonical)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func canonical(value any) string {
|
||||||
|
data, err := json.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("<unmarshalable %T: %v>", value, err)
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fatal(err error) {
|
||||||
|
fmt.Fprintf(os.Stderr, "Silo Helm migration check failed: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
Executable
+91
@@ -0,0 +1,91 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ "$#" -ne 1 ]; then
|
||||||
|
echo "usage: $0 TARGET" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
target=$1
|
||||||
|
target_dir=$(dirname "${target}")
|
||||||
|
if [ ! -d "${target_dir}" ]; then
|
||||||
|
echo "target directory does not exist: ${target_dir}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sha256_file() {
|
||||||
|
if command -v sha256sum >/dev/null 2>&1; then
|
||||||
|
sha256sum "$1" | awk '{print $1}'
|
||||||
|
else
|
||||||
|
shasum -a 256 "$1" | awk '{print $1}'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -n "${MCLI_BIN:-}" ]; then
|
||||||
|
if [ ! -f "${MCLI_BIN}" ]; then
|
||||||
|
echo "MCLI_BIN is not a regular file: ${MCLI_BIN}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! printf '%s\n' "${MCLI_SHA256:-}" | grep -Eq '^[0-9a-fA-F]{64}$'; then
|
||||||
|
echo "MCLI_SHA256 must contain the expected SHA-256 for MCLI_BIN" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
actual=$(sha256_file "${MCLI_BIN}")
|
||||||
|
if [ "${actual}" != "${MCLI_SHA256,,}" ]; then
|
||||||
|
echo "MCLI_BIN checksum mismatch: expected ${MCLI_SHA256,,}, got ${actual}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
install -m 0755 "${MCLI_BIN}" "${target}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
release=${MCLI_RELEASE:-RELEASE.2026-09-13T00-00-00Z}
|
||||||
|
version_hyphen=${release#RELEASE.}
|
||||||
|
package_version=$(printf '%s\n' "${version_hyphen}" | sed -E 's/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2})-([0-9]{2})-([0-9]{2})Z$/\1\2\3\4\5\6.0.0/')
|
||||||
|
if [ "${package_version}" = "${version_hyphen}" ]; then
|
||||||
|
echo "invalid MCLI_RELEASE: ${release}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case $(uname -s) in
|
||||||
|
Linux) os=linux ;;
|
||||||
|
Darwin) os=darwin ;;
|
||||||
|
*) echo "unsupported mcli host OS: $(uname -s)" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
case $(uname -m) in
|
||||||
|
x86_64 | amd64) arch=amd64 ;;
|
||||||
|
aarch64 | arm64) arch=arm64 ;;
|
||||||
|
*) echo "unsupported mcli host architecture: $(uname -m)" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
archive="mcli_${package_version}_${os}_${arch}.tar.gz"
|
||||||
|
checksums="mcli_${package_version}_checksums.txt"
|
||||||
|
base_url="https://github.com/pgsty/mc/releases/download/${release}"
|
||||||
|
tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/silo-mcli.XXXXXX")
|
||||||
|
trap 'rm -rf "${tmp_dir}"' EXIT
|
||||||
|
|
||||||
|
curl --fail --location --retry 3 --silent --show-error \
|
||||||
|
"${base_url}/${checksums}" --output "${tmp_dir}/${checksums}"
|
||||||
|
curl --fail --location --retry 3 --silent --show-error \
|
||||||
|
"${base_url}/${archive}" --output "${tmp_dir}/${archive}"
|
||||||
|
|
||||||
|
expected=$(awk -v asset="${archive}" '
|
||||||
|
{
|
||||||
|
name=$2
|
||||||
|
sub(/^\*/, "", name)
|
||||||
|
if (name == asset && length($1) == 64 && $1 ~ /^[0-9a-fA-F]+$/) print tolower($1)
|
||||||
|
}
|
||||||
|
' "${tmp_dir}/${checksums}")
|
||||||
|
if ! printf '%s\n' "${expected}" | grep -Eq '^[0-9a-f]{64}$'; then
|
||||||
|
echo "checksum manifest does not contain exactly one valid entry for ${archive}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
actual=$(sha256_file "${tmp_dir}/${archive}")
|
||||||
|
if [ "${actual}" != "${expected}" ]; then
|
||||||
|
echo "downloaded ${archive} checksum mismatch" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
tar -xzf "${tmp_dir}/${archive}" -C "${tmp_dir}" mcli
|
||||||
|
install -m 0755 "${tmp_dir}/mcli" "${target}"
|
||||||
Executable
+48
@@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ "$#" -ne 3 ]; then
|
||||||
|
echo "usage: $0 SOURCE SHA256 TARGET" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
source_ref=$1
|
||||||
|
expected=${2,,}
|
||||||
|
target=$3
|
||||||
|
if ! printf '%s\n' "${expected}" | grep -Eq '^[0-9a-f]{64}$'; then
|
||||||
|
echo "expected checksum must be a lowercase SHA-256 digest" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ ! -d "$(dirname "${target}")" ]; then
|
||||||
|
echo "target directory does not exist: $(dirname "${target}")" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp_file=$(mktemp "${TMPDIR:-/tmp}/silo-fixture.XXXXXX")
|
||||||
|
trap 'rm -f "${tmp_file}"' EXIT
|
||||||
|
case ${source_ref} in
|
||||||
|
https://*)
|
||||||
|
curl --fail --location --retry 3 --silent --show-error \
|
||||||
|
"${source_ref}" --output "${tmp_file}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [ ! -f "${source_ref}" ]; then
|
||||||
|
echo "fixture is not a regular file: ${source_ref}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cp "${source_ref}" "${tmp_file}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if command -v sha256sum >/dev/null 2>&1; then
|
||||||
|
actual=$(sha256sum "${tmp_file}" | awk '{print $1}')
|
||||||
|
else
|
||||||
|
actual=$(shasum -a 256 "${tmp_file}" | awk '{print $1}')
|
||||||
|
fi
|
||||||
|
if [ "${actual}" != "${expected}" ]; then
|
||||||
|
echo "fixture checksum mismatch: expected ${expected}, got ${actual}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
install -m 0755 "${tmp_file}" "${target}"
|
||||||
+127
@@ -0,0 +1,127 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# This script tests IAM migration from an old MinIO compatibility fixture into
|
||||||
|
# the current Silo server.
|
||||||
|
#
|
||||||
|
# To run it locally, start the LDAP server in github.com/minio/minio-iam-testing
|
||||||
|
# repo (e.g. make podman-run), and then run this script.
|
||||||
|
#
|
||||||
|
# This script assumes that LDAP server is at:
|
||||||
|
#
|
||||||
|
# `localhost:389`
|
||||||
|
#
|
||||||
|
# if this is not the case, set the environment variable
|
||||||
|
# `_MINIO_LDAP_TEST_SERVER`.
|
||||||
|
|
||||||
|
OLD_VERSION=RELEASE.2024-03-26T22-10-45Z
|
||||||
|
OLD_BINARY_LINK=https://dl.min.io/server/minio/release/linux-amd64/archive/minio.${OLD_VERSION}
|
||||||
|
OLD_BINARY_SHA256=2050199d89e3057571620a1d453118fed5bd2de9d4f3b266b11365fdf984d676
|
||||||
|
|
||||||
|
__init__() {
|
||||||
|
if which curl &>/dev/null; then
|
||||||
|
echo "curl is already installed"
|
||||||
|
else
|
||||||
|
echo "Installing curl:"
|
||||||
|
sudo apt install curl -y
|
||||||
|
fi
|
||||||
|
|
||||||
|
export GOPATH=/tmp/gopath
|
||||||
|
export PATH="${PATH}":"${GOPATH}"/bin
|
||||||
|
|
||||||
|
if [ ! -x "${GOPATH}/bin/mc" ]; then
|
||||||
|
echo "Installing verified compatible client fixture"
|
||||||
|
mkdir -p "${GOPATH}/bin"
|
||||||
|
"$(git rev-parse --show-toplevel)/buildscripts/install-mcli.sh" "${GOPATH}/bin/mc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -x ./minio.${OLD_VERSION} ]; then
|
||||||
|
echo "Installing verified upstream compatibility fixture minio.${OLD_VERSION}"
|
||||||
|
"$(git rev-parse --show-toplevel)/buildscripts/install-verified-fixture.sh" \
|
||||||
|
"${OLD_BINARY_LINK}" "${OLD_BINARY_SHA256}" "minio.${OLD_VERSION}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$_MINIO_LDAP_TEST_SERVER" ]; then
|
||||||
|
export _MINIO_LDAP_TEST_SERVER=localhost:389
|
||||||
|
echo "Using default LDAP endpoint: $_MINIO_LDAP_TEST_SERVER"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf /tmp/data
|
||||||
|
}
|
||||||
|
|
||||||
|
create_iam_content_in_old_minio() {
|
||||||
|
echo "Creating IAM content in the old MinIO compatibility fixture."
|
||||||
|
|
||||||
|
MINIO_CI_CD=1 ./minio.${OLD_VERSION} server /tmp/data/{1...4} &
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
set -x
|
||||||
|
mc alias set old-minio http://localhost:9000 minioadmin minioadmin
|
||||||
|
mc ready old-minio
|
||||||
|
mc idp ldap add old-minio \
|
||||||
|
server_addr=localhost:389 \
|
||||||
|
server_insecure=on \
|
||||||
|
lookup_bind_dn=cn=admin,dc=min,dc=io \
|
||||||
|
lookup_bind_password=admin \
|
||||||
|
user_dn_search_base_dn=dc=min,dc=io \
|
||||||
|
user_dn_search_filter="(uid=%s)" \
|
||||||
|
group_search_base_dn=ou=swengg,dc=min,dc=io \
|
||||||
|
group_search_filter="(&(objectclass=groupOfNames)(member=%d))"
|
||||||
|
mc admin service restart old-minio
|
||||||
|
|
||||||
|
mc idp ldap policy attach old-minio readwrite --user=UID=dillon,ou=people,ou=swengg,dc=min,dc=io
|
||||||
|
mc idp ldap policy attach old-minio readwrite --group=CN=project.c,ou=groups,ou=swengg,dc=min,dc=io
|
||||||
|
|
||||||
|
mc idp ldap policy entities old-minio
|
||||||
|
|
||||||
|
mc admin cluster iam export old-minio
|
||||||
|
set +x
|
||||||
|
|
||||||
|
mc admin service stop old-minio
|
||||||
|
}
|
||||||
|
|
||||||
|
import_iam_content_in_new_minio() {
|
||||||
|
echo "Importing IAM content into the current Silo instance."
|
||||||
|
# Assume the current Silo binary exists.
|
||||||
|
MINIO_CI_CD=1 ./silo server /tmp/data/{1...4} &
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
set -x
|
||||||
|
mc alias set new-minio http://localhost:9000 minioadmin minioadmin
|
||||||
|
echo "BEFORE IMPORT mappings:"
|
||||||
|
mc ready new-minio
|
||||||
|
mc idp ldap policy entities new-minio
|
||||||
|
mc admin cluster iam import new-minio ./old-minio-iam-info.zip
|
||||||
|
echo "AFTER IMPORT mappings:"
|
||||||
|
mc idp ldap policy entities new-minio
|
||||||
|
set +x
|
||||||
|
|
||||||
|
# mc admin service stop new-minio
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_iam_content_in_new_minio() {
|
||||||
|
output=$(mc idp ldap policy entities new-minio --json)
|
||||||
|
|
||||||
|
groups=$(echo "$output" | jq -r '.result.policyMappings[] | select(.policy == "readwrite") | .groups[]')
|
||||||
|
if [ "$groups" != "cn=project.c,ou=groups,ou=swengg,dc=min,dc=io" ]; then
|
||||||
|
echo "Failed to verify groups: $groups"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
users=$(echo "$output" | jq -r '.result.policyMappings[] | select(.policy == "readwrite") | .users[]')
|
||||||
|
if [ "$users" != "uid=dillon,ou=people,ou=swengg,dc=min,dc=io" ]; then
|
||||||
|
echo "Failed to verify users: $users"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mc admin service stop new-minio
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
create_iam_content_in_old_minio
|
||||||
|
|
||||||
|
import_iam_content_in_new_minio
|
||||||
|
|
||||||
|
verify_iam_content_in_new_minio
|
||||||
|
}
|
||||||
|
|
||||||
|
(__init__ "$@" && main "$@")
|
||||||
Executable
+124
@@ -0,0 +1,124 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Exercise both directions of the on-disk compatibility contract using an
|
||||||
|
# immutable pre-rebrand image and a container built from the current checkout.
|
||||||
|
# Every Docker resource is uniquely named and removed explicitly; this test
|
||||||
|
# never prunes unrelated images, containers, networks, or volumes.
|
||||||
|
|
||||||
|
repo_dir="$(git rev-parse --show-toplevel)"
|
||||||
|
old_image="${OLD_IMAGE:-docker.io/pgsty/minio@sha256:b6bfe7239bfc83fb90d31612d9704d86039dd714f7904b3f1ad68f211e602372}"
|
||||||
|
new_image="${NEW_IMAGE:-silo-upgrade-test:dev}"
|
||||||
|
suffix="$(date +%s)-$$"
|
||||||
|
network="silo-upgrade-net-${suffix}"
|
||||||
|
volume="silo-upgrade-data-${suffix}"
|
||||||
|
old_container="silo-upgrade-old-${suffix}"
|
||||||
|
new_container="silo-upgrade-new-${suffix}"
|
||||||
|
rollback_container="silo-upgrade-rollback-${suffix}"
|
||||||
|
root_user=silo-upgrade-admin
|
||||||
|
root_password=silo-upgrade-secret-123
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
status=$?
|
||||||
|
trap - EXIT
|
||||||
|
if [ "${status}" -ne 0 ]; then
|
||||||
|
for name in "${old_container}" "${new_container}" "${rollback_container}"; do
|
||||||
|
docker logs "${name}" 2>/dev/null | tail -n 80 >&2 || true
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
if [ "${KEEP_UPGRADE_TEST_RESOURCES:-0}" = 1 ]; then
|
||||||
|
printf 'Retained Docker resources for inspection: %s %s\n' "${network}" "${volume}" >&2
|
||||||
|
exit "${status}"
|
||||||
|
fi
|
||||||
|
docker rm -f "${old_container}" "${new_container}" "${rollback_container}" >/dev/null 2>&1 || true
|
||||||
|
docker network rm "${network}" >/dev/null 2>&1 || true
|
||||||
|
docker volume rm "${volume}" >/dev/null 2>&1 || true
|
||||||
|
exit "${status}"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
trap 'exit 130' INT TERM
|
||||||
|
|
||||||
|
wait_ready() {
|
||||||
|
name="$1"
|
||||||
|
ready=""
|
||||||
|
for _ in $(seq 1 90); do
|
||||||
|
if docker logs "${name}" 2>&1 | grep -q 'API:'; then
|
||||||
|
ready=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if [ "$(docker inspect -f '{{.State.Running}}' "${name}")" != true ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
if [ -z "${ready}" ]; then
|
||||||
|
echo "Server did not become ready: ${name}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
start_server() {
|
||||||
|
name="$1"
|
||||||
|
image="$2"
|
||||||
|
shift 2
|
||||||
|
docker run -d --name "${name}" --network "${network}" \
|
||||||
|
--mount "source=${volume},target=/data" \
|
||||||
|
-e MINIO_CI_CD=1 -e MINIO_ROOT_USER="${root_user}" -e MINIO_ROOT_PASSWORD="${root_password}" \
|
||||||
|
"${image}" "$@" >/dev/null
|
||||||
|
wait_ready "${name}"
|
||||||
|
docker exec "${name}" mcli alias set local http://127.0.0.1:9000 "${root_user}" "${root_password}" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_server() {
|
||||||
|
name="$1"
|
||||||
|
docker stop -t 20 "${name}" >/dev/null
|
||||||
|
test "$(docker inspect -f '{{.State.ExitCode}}' "${name}")" = 0
|
||||||
|
docker logs "${name}" 2>&1 | grep -q 'Exiting on signal'
|
||||||
|
docker rm "${name}" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
command -v docker >/dev/null
|
||||||
|
docker info >/dev/null
|
||||||
|
if ! docker image inspect "${old_image}" >/dev/null 2>&1; then
|
||||||
|
docker pull "${old_image}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${SILO_UPGRADE_SKIP_BUILD:-0}" != 1 ]; then
|
||||||
|
make -C "${repo_dir}" docker TAG="${new_image}"
|
||||||
|
fi
|
||||||
|
docker image inspect "${new_image}" >/dev/null
|
||||||
|
|
||||||
|
docker network create "${network}" >/dev/null
|
||||||
|
docker volume create "${volume}" >/dev/null
|
||||||
|
|
||||||
|
start_server "${old_container}" "${old_image}" minio server /data --address :9000
|
||||||
|
docker exec "${old_container}" mcli mb local/compat >/dev/null
|
||||||
|
docker exec "${old_container}" mcli version enable local/compat >/dev/null
|
||||||
|
printf 'old-version-1\n' | docker exec -i "${old_container}" mcli pipe local/compat/versioned.txt >/dev/null
|
||||||
|
printf 'old-version-2\n' | docker exec -i "${old_container}" mcli pipe local/compat/versioned.txt >/dev/null
|
||||||
|
test "$(docker exec "${old_container}" mcli ls --versions local/compat/versioned.txt | grep -c 'versioned.txt')" -ge 2
|
||||||
|
docker exec "${old_container}" mcli mb --with-lock local/locked >/dev/null
|
||||||
|
printf 'locked-by-old\n' | docker exec -i "${old_container}" mcli pipe local/locked/object.txt >/dev/null
|
||||||
|
dd if=/dev/zero bs=1048576 count=70 2>/dev/null | docker exec -i "${old_container}" mcli pipe local/compat/multipart.bin >/dev/null
|
||||||
|
test "$(docker exec "${old_container}" mcli stat --json local/compat/multipart.bin | jq -r '.size')" = 73400320
|
||||||
|
docker exec "${old_container}" mcli admin user add local migration-user migration-secret-123 >/dev/null
|
||||||
|
docker exec "${old_container}" mcli admin policy attach local readwrite --user migration-user >/dev/null
|
||||||
|
stop_server "${old_container}"
|
||||||
|
|
||||||
|
start_server "${new_container}" "${new_image}" silo server /data --address :9000
|
||||||
|
test "$(docker exec "${new_container}" mcli cat local/compat/versioned.txt)" = old-version-2
|
||||||
|
test "$(docker exec "${new_container}" mcli cat local/locked/object.txt)" = locked-by-old
|
||||||
|
test "$(docker exec "${new_container}" mcli stat --json local/compat/multipart.bin | jq -r '.size')" = 73400320
|
||||||
|
docker exec "${new_container}" mcli admin user info local migration-user >/dev/null
|
||||||
|
docker exec "${new_container}" mcli alias set migrated http://127.0.0.1:9000 migration-user migration-secret-123 >/dev/null
|
||||||
|
printf 'written-by-silo\n' | docker exec -i "${new_container}" mcli pipe migrated/compat/silo.txt >/dev/null
|
||||||
|
stop_server "${new_container}"
|
||||||
|
|
||||||
|
start_server "${rollback_container}" "${old_image}" minio server /data --address :9000
|
||||||
|
test "$(docker exec "${rollback_container}" mcli cat local/compat/versioned.txt)" = old-version-2
|
||||||
|
test "$(docker exec "${rollback_container}" mcli cat local/compat/silo.txt)" = written-by-silo
|
||||||
|
test "$(docker exec "${rollback_container}" mcli stat --json local/compat/multipart.bin | jq -r '.size')" = 73400320
|
||||||
|
stop_server "${rollback_container}"
|
||||||
|
|
||||||
|
echo "MinIO-to-Silo data upgrade and rollback checks passed"
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ -n "$TEST_DEBUG" ]; then
|
||||||
|
set -x
|
||||||
|
fi
|
||||||
|
|
||||||
|
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||||
|
SILO_CONFIG_DIR="$WORK_DIR/.silo"
|
||||||
|
SILO=("$PWD/silo" --config-dir "$SILO_CONFIG_DIR" server)
|
||||||
|
|
||||||
|
if [ ! -x "$PWD/silo" ]; then
|
||||||
|
echo "Silo executable binary not found in current directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -x "$PWD/silo" ]; then
|
||||||
|
echo "Silo executable binary not found in current directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
trap 'catch $LINENO' ERR
|
||||||
|
|
||||||
|
function purge() {
|
||||||
|
rm -rf "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# shellcheck disable=SC2120
|
||||||
|
catch() {
|
||||||
|
if [ $# -ne 0 ]; then
|
||||||
|
echo "error on line $1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Cleaning up instances of Silo"
|
||||||
|
pkill silo || true
|
||||||
|
pkill -9 silo || true
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
if [ $# -ne 0 ]; then
|
||||||
|
exit $#
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
catch
|
||||||
|
|
||||||
|
function start_silo_10drive() {
|
||||||
|
start_port=$1
|
||||||
|
|
||||||
|
export MINIO_ROOT_USER=silo
|
||||||
|
export MINIO_ROOT_PASSWORD=silo1234
|
||||||
|
export MC_HOST_silo="http://silo:silo1234@127.0.0.1:${start_port}/"
|
||||||
|
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||||
|
export MINIO_CI_CD=1
|
||||||
|
|
||||||
|
mkdir ${WORK_DIR}
|
||||||
|
if [ ! -x "$PWD/mc" ]; then
|
||||||
|
"$(git rev-parse --show-toplevel)/buildscripts/install-mcli.sh" "$PWD/mc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
"${SILO[@]}" --address ":$start_port" "${WORK_DIR}/disk{1...10}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||||
|
pid=$!
|
||||||
|
disown $pid
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||||
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
"${PWD}/mc" mb --with-versioning silo/bucket
|
||||||
|
|
||||||
|
export AWS_ACCESS_KEY_ID=silo
|
||||||
|
export AWS_SECRET_ACCESS_KEY=silo1234
|
||||||
|
aws --endpoint-url http://localhost:"$start_port" s3api create-multipart-upload --bucket bucket --key obj-1 >upload-id.json
|
||||||
|
uploadId=$(jq -r '.UploadId' upload-id.json)
|
||||||
|
|
||||||
|
truncate -s 5MiB file-5mib
|
||||||
|
for i in {1..2}; do
|
||||||
|
aws --endpoint-url http://localhost:"$start_port" s3api upload-part \
|
||||||
|
--upload-id "$uploadId" --bucket bucket --key obj-1 \
|
||||||
|
--part-number "$i" --body ./file-5mib
|
||||||
|
done
|
||||||
|
for i in {1..6}; do
|
||||||
|
find ${WORK_DIR}/disk${i}/.minio.sys/multipart/ -type f -name "part.1" -delete
|
||||||
|
done
|
||||||
|
cat <<EOF >parts.json
|
||||||
|
{
|
||||||
|
"Parts": [
|
||||||
|
{
|
||||||
|
"PartNumber": 1,
|
||||||
|
"ETag": "5f363e0e58a95f06cbe9bbc662c5dfb6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PartNumber": 2,
|
||||||
|
"ETag": "5f363e0e58a95f06cbe9bbc662c5dfb6"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
err=$(aws --endpoint-url http://localhost:"$start_port" s3api complete-multipart-upload --upload-id "$uploadId" --bucket bucket --key obj-1 --multipart-upload file://./parts.json 2>&1)
|
||||||
|
rv=$?
|
||||||
|
if [ $rv -eq 0 ]; then
|
||||||
|
echo "Failed to receive an error"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Received an error during complete-multipart as expected: $err"
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
|
start_silo_10drive ${start_port}
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Executable
+157
@@ -0,0 +1,157 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
repo_dir="$(cd "${script_dir}/.." && pwd)"
|
||||||
|
dist_dir="${DIST_DIR:-${repo_dir}/dist}"
|
||||||
|
nfpm_config="${NFPM_CONFIG:-${repo_dir}/.github/nfpm.yml}"
|
||||||
|
|
||||||
|
if [ -z "${PKG_VERSION:-}" ]; then
|
||||||
|
echo "PKG_VERSION is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Re-validate the shape release.yml derived from the tag. The packages are
|
||||||
|
# named from this, so a malformed value would ship under a name no repository
|
||||||
|
# can order against.
|
||||||
|
if ! [[ "${PKG_VERSION}" =~ ^[0-9]{14}\.0\.0$ ]]; then
|
||||||
|
echo "Invalid PKG_VERSION: ${PKG_VERSION}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The PGSTY release segment, PGDG-style. sign-release-rpms.sh declares the
|
||||||
|
# same value as expected_release, and test-release.yml asserts the two agree.
|
||||||
|
PKG_RELEASE="1PGSTY"
|
||||||
|
|
||||||
|
if ! command -v nfpm >/dev/null 2>&1; then
|
||||||
|
echo "nfpm is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "${nfpm_config}" ]; then
|
||||||
|
echo "Missing nFPM config: ${nfpm_config}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# nfpm resolves a relative content src against the current directory, not
|
||||||
|
# against the config file, so the unit path is passed in absolute. Otherwise
|
||||||
|
# this only works when invoked from the repository root and fails elsewhere on
|
||||||
|
# a message that names the file rather than the cause.
|
||||||
|
unit_file="${repo_dir}/silo.service"
|
||||||
|
defaults_file="${repo_dir}/silo.env"
|
||||||
|
sysusers_file="${repo_dir}/silo.sysusers"
|
||||||
|
license_file="${repo_dir}/LICENSE"
|
||||||
|
notice_file="${repo_dir}/NOTICE"
|
||||||
|
postinstall_file="${repo_dir}/buildscripts/package/postinstall.sh"
|
||||||
|
preremove_file="${repo_dir}/buildscripts/package/preremove.sh"
|
||||||
|
if [ ! -f "${unit_file}" ]; then
|
||||||
|
echo "Missing systemd unit: ${unit_file}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ ! -f "${defaults_file}" ]; then
|
||||||
|
echo "Missing defaults file: ${defaults_file}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ ! -f "${sysusers_file}" ]; then
|
||||||
|
echo "Missing sysusers file: ${sysusers_file}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
for distributed_doc in "${license_file}" "${notice_file}"; do
|
||||||
|
if [ ! -s "${distributed_doc}" ]; then
|
||||||
|
echo "Missing license material: ${distributed_doc}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
for lifecycle_script in "${postinstall_file}" "${preremove_file}"; do
|
||||||
|
if [ ! -x "${lifecycle_script}" ]; then
|
||||||
|
echo "Missing executable package lifecycle script: ${lifecycle_script}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
packages_dir="${dist_dir}/packages"
|
||||||
|
mkdir -p "${packages_dir}"
|
||||||
|
|
||||||
|
# Two spaces, no trailing newline: sign-release-rpms.sh parses these files to
|
||||||
|
# check download integrity before it signs, and regenerates them afterwards in
|
||||||
|
# the same shape.
|
||||||
|
sha256_file() {
|
||||||
|
local file="$1"
|
||||||
|
local digest
|
||||||
|
|
||||||
|
if command -v sha256sum >/dev/null 2>&1; then
|
||||||
|
digest="$(sha256sum "${file}" | awk '{print $1}')"
|
||||||
|
else
|
||||||
|
digest="$(shasum -a 256 "${file}" | awk '{print $1}')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s %s' "${digest}" "$(basename "${file}")" > "${file}.sha256sum"
|
||||||
|
}
|
||||||
|
|
||||||
|
find_binary() {
|
||||||
|
local goarch="$1"
|
||||||
|
local matches
|
||||||
|
local count
|
||||||
|
|
||||||
|
# Must resolve to exactly one binary. Picking the first of several build
|
||||||
|
# variants (an added goamd64 level, a stale dist entry) would silently ship a
|
||||||
|
# package whose contents do not match its name.
|
||||||
|
matches="$(find "${dist_dir}" -maxdepth 2 -type f \
|
||||||
|
-path "${dist_dir}/silo_linux_${goarch}*/silo" | sort)"
|
||||||
|
count="$(printf '%s' "${matches}" | grep -c . || true)"
|
||||||
|
|
||||||
|
if [ "${count}" -eq 0 ]; then
|
||||||
|
echo "Missing GoReleaser binary for linux/${goarch}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ "${count}" -ne 1 ]; then
|
||||||
|
echo "Expected exactly one GoReleaser binary for linux/${goarch}, found ${count}:" >&2
|
||||||
|
printf '%s\n' "${matches}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "${matches}"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_arch() {
|
||||||
|
local goarch="$1"
|
||||||
|
local rpm_arch="$2"
|
||||||
|
local deb_arch="$3"
|
||||||
|
local apk_arch="$4"
|
||||||
|
local source
|
||||||
|
local rpm_file
|
||||||
|
local deb_file
|
||||||
|
local apk_file
|
||||||
|
|
||||||
|
source="$(find_binary "${goarch}")"
|
||||||
|
|
||||||
|
# These names are the public download names and must not drift; RPM and DEB
|
||||||
|
# carry the PGSTY release number (nfpm renders it as the RPM Release tag and
|
||||||
|
# as the Debian revision after a dash). APK stays bare: Alpine pkgrel only
|
||||||
|
# admits -r<integer>, so a lettered release cannot ride along there.
|
||||||
|
rpm_file="${packages_dir}/silo-${PKG_VERSION}-${PKG_RELEASE}.${rpm_arch}.rpm"
|
||||||
|
deb_file="${packages_dir}/silo_${PKG_VERSION}-${PKG_RELEASE}_${deb_arch}.deb"
|
||||||
|
apk_file="${packages_dir}/silo_${PKG_VERSION}_${apk_arch}.apk"
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "${repo_dir}"
|
||||||
|
export NFPM_UNIT="${unit_file}" NFPM_DEFAULTS="${defaults_file}" NFPM_SYSUSERS="${sysusers_file}" \
|
||||||
|
NFPM_LICENSE="${license_file}" NFPM_NOTICE="${notice_file}"
|
||||||
|
PKG_VERSION="${PKG_VERSION}" NFPM_RELEASE="${PKG_RELEASE}" NFPM_ARCH="${goarch}" NFPM_SOURCE="${source}" \
|
||||||
|
nfpm package --config "${nfpm_config}" --packager rpm --target "${rpm_file}"
|
||||||
|
PKG_VERSION="${PKG_VERSION}" NFPM_RELEASE="${PKG_RELEASE}" NFPM_ARCH="${goarch}" NFPM_SOURCE="${source}" \
|
||||||
|
nfpm package --config "${nfpm_config}" --packager deb --target "${deb_file}"
|
||||||
|
PKG_VERSION="${PKG_VERSION}" NFPM_RELEASE='' NFPM_ARCH="${goarch}" NFPM_SOURCE="${source}" \
|
||||||
|
nfpm package --config "${nfpm_config}" --packager apk --target "${apk_file}"
|
||||||
|
)
|
||||||
|
|
||||||
|
sha256_file "${rpm_file}"
|
||||||
|
sha256_file "${deb_file}"
|
||||||
|
sha256_file "${apk_file}"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_arch amd64 x86_64 amd64 x86_64
|
||||||
|
build_arch arm64 aarch64 arm64 aarch64
|
||||||
|
|
||||||
|
find "${packages_dir}" -maxdepth 1 -type f | sort
|
||||||
Executable
+172
@@ -0,0 +1,172 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
repo_dir="$(cd "${script_dir}/../.." && pwd)"
|
||||||
|
postinstall="${script_dir}/postinstall.sh"
|
||||||
|
preremove="${script_dir}/preremove.sh"
|
||||||
|
test_dir="$(mktemp -d)"
|
||||||
|
fakebin="${test_dir}/bin"
|
||||||
|
log_file="${test_dir}/calls.log"
|
||||||
|
useradd_shell=/usr/sbin/nologin
|
||||||
|
[ -x "${useradd_shell}" ] || useradd_shell=/sbin/nologin
|
||||||
|
busybox_shell=/sbin/nologin
|
||||||
|
[ -x "${busybox_shell}" ] || busybox_shell=/bin/false
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -rf "${test_dir}"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
mkdir -p "${fakebin}"
|
||||||
|
touch "${log_file}"
|
||||||
|
|
||||||
|
# One dispatcher represents every external command used by the lifecycle
|
||||||
|
# scripts. The tested scripts run with no host utilities in PATH, so a green
|
||||||
|
# result cannot create a real account or touch the host service manager.
|
||||||
|
cat > "${fakebin}/fake-command" <<'EOF'
|
||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
command_name=${0##*/}
|
||||||
|
case "${command_name}" in
|
||||||
|
id)
|
||||||
|
[ "${PACKAGE_TEST_USER_EXISTS:-0}" = 1 ]
|
||||||
|
;;
|
||||||
|
getent)
|
||||||
|
[ "${PACKAGE_TEST_GROUP_EXISTS:-0}" = 1 ]
|
||||||
|
;;
|
||||||
|
systemd-sysusers|useradd|addgroup|adduser|systemctl)
|
||||||
|
{
|
||||||
|
printf '%s' "${command_name}"
|
||||||
|
for argument in "$@"; do
|
||||||
|
printf ' %s' "${argument}"
|
||||||
|
done
|
||||||
|
printf '\n'
|
||||||
|
} >> "${PACKAGE_TEST_LOG}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unexpected fake command: ${command_name}" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
EOF
|
||||||
|
chmod +x "${fakebin}/fake-command"
|
||||||
|
|
||||||
|
link_command() {
|
||||||
|
ln -sf fake-command "${fakebin}/$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
unlink_optional_commands() {
|
||||||
|
rm -f \
|
||||||
|
"${fakebin}/systemd-sysusers" \
|
||||||
|
"${fakebin}/useradd" \
|
||||||
|
"${fakebin}/adduser" \
|
||||||
|
"${fakebin}/addgroup"
|
||||||
|
}
|
||||||
|
|
||||||
|
reset_log() {
|
||||||
|
: > "${log_file}"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_postinstall() {
|
||||||
|
PACKAGE_TEST_LOG="${log_file}" \
|
||||||
|
PACKAGE_TEST_USER_EXISTS="${1}" \
|
||||||
|
PACKAGE_TEST_GROUP_EXISTS="${2}" \
|
||||||
|
PATH="${fakebin}" \
|
||||||
|
/bin/sh "${postinstall}"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_preremove() {
|
||||||
|
PACKAGE_TEST_LOG="${log_file}" PATH="${fakebin}" \
|
||||||
|
/bin/sh "${preremove}" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_log_line() {
|
||||||
|
grep -Fx -- "$1" "${log_file}" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
reject_log_text() {
|
||||||
|
if grep -F -- "$1" "${log_file}" >/dev/null; then
|
||||||
|
echo "unexpected lifecycle call containing '$1':" >&2
|
||||||
|
cat "${log_file}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
link_command id
|
||||||
|
link_command getent
|
||||||
|
link_command systemctl
|
||||||
|
|
||||||
|
# Clean install through systemd-sysusers. Side-by-side safety is represented
|
||||||
|
# by the fact that the only service-manager operation is daemon-reload: no old
|
||||||
|
# service is stopped, disabled, enabled, masked, or restarted.
|
||||||
|
unlink_optional_commands
|
||||||
|
link_command systemd-sysusers
|
||||||
|
reset_log
|
||||||
|
run_postinstall 0 0
|
||||||
|
assert_log_line "systemd-sysusers /usr/lib/sysusers.d/silo.conf"
|
||||||
|
assert_log_line "systemctl daemon-reload"
|
||||||
|
test "$(wc -l < "${log_file}" | tr -d ' ')" -eq 2
|
||||||
|
|
||||||
|
# An existing service account is preserved without modification.
|
||||||
|
reset_log
|
||||||
|
run_postinstall 1 0
|
||||||
|
test "$(cat "${log_file}")" = "systemctl daemon-reload"
|
||||||
|
|
||||||
|
# useradd creates a private group only when one does not already exist. An
|
||||||
|
# administrator may pre-create group silo with the legacy GID; that group must
|
||||||
|
# be reused rather than causing installation to fail.
|
||||||
|
unlink_optional_commands
|
||||||
|
link_command useradd
|
||||||
|
reset_log
|
||||||
|
run_postinstall 0 0
|
||||||
|
assert_log_line "useradd --system --user-group --no-create-home --shell ${useradd_shell} --comment Silo object storage service silo"
|
||||||
|
reject_log_text "--gid silo"
|
||||||
|
|
||||||
|
reset_log
|
||||||
|
run_postinstall 0 1
|
||||||
|
assert_log_line "useradd --system --gid silo --no-create-home --shell ${useradd_shell} --comment Silo object storage service silo"
|
||||||
|
reject_log_text "--user-group"
|
||||||
|
|
||||||
|
# BusyBox follows the same existing-group contract.
|
||||||
|
unlink_optional_commands
|
||||||
|
link_command adduser
|
||||||
|
link_command addgroup
|
||||||
|
reset_log
|
||||||
|
run_postinstall 0 0
|
||||||
|
assert_log_line "addgroup -S silo"
|
||||||
|
assert_log_line "adduser -S -D -H -G silo -s ${busybox_shell} silo"
|
||||||
|
|
||||||
|
reset_log
|
||||||
|
run_postinstall 0 1
|
||||||
|
reject_log_text "addgroup"
|
||||||
|
assert_log_line "adduser -S -D -H -G silo -s ${busybox_shell} silo"
|
||||||
|
|
||||||
|
# Debian remove, RPM erase, and Alpine deinstall stop the Silo unit. Upgrade
|
||||||
|
# arguments must leave the running service alone.
|
||||||
|
for removal_argument in remove 0 20260214120000.0.0-r0; do
|
||||||
|
reset_log
|
||||||
|
run_preremove "${removal_argument}"
|
||||||
|
test "$(cat "${log_file}")" = "systemctl disable --now silo.service"
|
||||||
|
done
|
||||||
|
|
||||||
|
for upgrade_argument in upgrade 1; do
|
||||||
|
reset_log
|
||||||
|
run_preremove "${upgrade_argument}"
|
||||||
|
test ! -s "${log_file}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# The package deliberately leaves legacy ownership changes to an explicit
|
||||||
|
# systemd drop-in. Lifecycle scripts must never rewrite ownership or touch the
|
||||||
|
# old unit, and the base unit must expose overridable User/Group directives.
|
||||||
|
grep -Fx 'User=silo' "${repo_dir}/silo.service" >/dev/null
|
||||||
|
grep -Fx 'Group=silo' "${repo_dir}/silo.service" >/dev/null
|
||||||
|
if grep -Ein '\b(chown|chgrp|usermod|groupmod)\b|minio\.service' \
|
||||||
|
"${postinstall}" "${preremove}"; then
|
||||||
|
echo "package lifecycle scripts must not mutate data ownership or the legacy service" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Silo package lifecycle checks passed"
|
||||||
Executable
+44
@@ -0,0 +1,44 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
sysusers_file=/usr/lib/sysusers.d/silo.conf
|
||||||
|
|
||||||
|
group_exists() {
|
||||||
|
if command -v getent >/dev/null 2>&1; then
|
||||||
|
getent group silo >/dev/null 2>&1
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ -r /etc/group ] || return 1
|
||||||
|
while IFS=: read -r group_name _; do
|
||||||
|
[ "${group_name}" = silo ] && return 0
|
||||||
|
done < /etc/group
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! id -u silo >/dev/null 2>&1; then
|
||||||
|
if command -v systemd-sysusers >/dev/null 2>&1; then
|
||||||
|
systemd-sysusers "${sysusers_file}"
|
||||||
|
elif command -v useradd >/dev/null 2>&1; then
|
||||||
|
nologin_shell=/usr/sbin/nologin
|
||||||
|
[ -x "${nologin_shell}" ] || nologin_shell=/sbin/nologin
|
||||||
|
if group_exists; then
|
||||||
|
useradd --system --gid silo --no-create-home --shell "${nologin_shell}" --comment "Silo object storage service" silo
|
||||||
|
else
|
||||||
|
useradd --system --user-group --no-create-home --shell "${nologin_shell}" --comment "Silo object storage service" silo
|
||||||
|
fi
|
||||||
|
elif command -v adduser >/dev/null 2>&1 && command -v addgroup >/dev/null 2>&1; then
|
||||||
|
nologin_shell=/sbin/nologin
|
||||||
|
[ -x "${nologin_shell}" ] || nologin_shell=/bin/false
|
||||||
|
group_exists || addgroup -S silo
|
||||||
|
adduser -S -D -H -G silo -s "${nologin_shell}" silo
|
||||||
|
else
|
||||||
|
echo "Unable to create the silo system account: systemd-sysusers, useradd, or BusyBox adduser is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v systemctl >/dev/null 2>&1; then
|
||||||
|
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
Executable
+14
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# Debian passes "remove" for an actual removal, RPM passes 0 to %preun, and
|
||||||
|
# Alpine runs pre-deinstall only for removal and passes the old dotted version.
|
||||||
|
# Upgrade paths deliberately leave the running service untouched.
|
||||||
|
case "${1:-}" in
|
||||||
|
remove|0|*.*)
|
||||||
|
if command -v systemctl >/dev/null 2>&1; then
|
||||||
|
systemctl disable --now silo.service >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
mQINBGaV5PwBEACbErI+7yOrsXTT3mR83O6Fw9WyHJqozhyNPF3dA1gAtWpfWqd4
|
||||||
|
S9x6vBjVwUbIRn21jYgov0hDiaLABNQhRzifvVr0r1IjBW8lhA8zJGaO42Uz0aBW
|
||||||
|
YIkajOklsXgYMX+gSmy5WXzM31sDQVMnzptHh9dwW067hMM5pJKDslu2pLMwSb9K
|
||||||
|
QgIFcYsaR0taBkcDg4dNu1gncriD/GcdXIS0/V4R82DIYeIqj2S0lt0jDTACbUz3
|
||||||
|
C6esrTw2XerCeHKHb9c/V+KMhqvLJOOpy/aJWLrTGBoaH7xw6v0qg32OYiBxlUj9
|
||||||
|
VEzoQbDfbRkR+jlxiuYP3scUs/ziKrSh+0mshVbeuLRSNfuHLa7C4xTEnATcgD1J
|
||||||
|
MZeMaJXIcDt+DN+1aHVQjY5YNvr5wA3ykxW51uReZf7/odgqVW3+1rhW5pd8NQKQ
|
||||||
|
qoVUHOtIrC9KaiGfrczEtJTNUxcNZV9eBgcKHYDXB2hmR2pIf7WvydgXTs/qIsXg
|
||||||
|
SIzfKjisi795Dd5GrvdLYXVnu9YzylWlkJ5rjod1wnSxkI/CcCJaoPLnXZA9KV7A
|
||||||
|
cpMWWaUEXP/XBIwIU+vxDd1taBIaPIOv1KIdzvG7QqAQtf5Lphi5HfaGvBud/CVt
|
||||||
|
mvWhRPJMr1J0ER2xAgU2iZR7dN0vSF6zDqc0W09RAoC0nDS3tupDX2BrOwARAQAB
|
||||||
|
tCRSdW9oYW5nIEZlbmcgKFBpZ3N0eSkgPHJoQHZvbm5nLmNvbT6JAlEEEwEIADsW
|
||||||
|
IQSVkqe8emguczM3bgnnk12Nub2LIAUCZpXk/AIbAwULCQgHAgIiAgYVCgkICwIE
|
||||||
|
FgIDAQIeBwIXgAAKCRDnk12Nub2LIOMuEACBLVc09O4icFwc45R3KMvOMu14Egpn
|
||||||
|
UkpmBKhErjup0TIunzI0zZH6HG8LGuf6XEdH4ItCJeLg5349UE00BUHNmxk2coo2
|
||||||
|
u4Wtu28LPqmxb6sqpuRAaefedU6vqfs7YN6WWp52pVF1KdOHkIOcgAQ9z3ZHdosM
|
||||||
|
I/Y/UxO2t4pjdCAfJHOmGPrbgLcHSMpoLLxjuf3YIwS5NSfjNDd0Y8sKFUcMGLCF
|
||||||
|
5P0lv5feLLdZvh2Una34UmHKhZlXC5E3vlY9bf/LgsRzXRFQosD0RsCXbz3Tk+zF
|
||||||
|
+j/eP3WhUvJshqIDuY6eJYCzMjiA8sM5gety+htVJuD0mewp+qAhjxE0d4bIr4qO
|
||||||
|
BKQzBt9tT2ackCPdgW42VPS+IZymm1oMET0hgZfKiVpwsKO6qxeWn4RW2jJ0zkUJ
|
||||||
|
MsrrxOPFdZQAtuFcLwa5PUAHHs6XQT2vzxDpeE9lInQ14lshofU5ZKIeb9sbvb/w
|
||||||
|
P+xnDqvZ1pcotEIBvDK0S0jHbHHqtioIUdDFvdCBlBlYP1TQRNPlJ7TJDBBvhj8i
|
||||||
|
fmjQsYSV1u36aHOJVGYNHv+SyJpVd3nHCZn97ADM9qHnDm7xljyHXPzIx4FMmBGJ
|
||||||
|
UTiLH5yxa1xhWr42Iv3TykaQJVbpydmBuegFR8WbWitAvVqI3HvRG+FalLsjJruc
|
||||||
|
8YDAf7gHdj/937kCDQRmleT8ARAAmJxscC76NZzqFBiaeq2+aJxOt1HGPqKb4pbz
|
||||||
|
jLKRX9sFkeXuzhfZaNDljnr2yrnQ75rit9Aah/loEhbSHanNUDCNmvOeSEISr9yA
|
||||||
|
yfOnqlcVOtcwWQK57n6MvlCSM8Js3jdoSmCFHVtdFFwxejE5ok0dk1VFYDIg6DRk
|
||||||
|
ZBMuxGO7ZJW7TzCxhK4AL+NNYA2wX6b+IVMn6CA9kwNwCNrrnGHR1sblSxZp7lPo
|
||||||
|
+GsqzYY0LXGR2eEicgKd4lk38gaO8Q4d1mlpX95vgdhGKxR+CM26y9QU0qrO1hXP
|
||||||
|
Fw6lX9HfIUkVNrqAa1mzgneYXivnLvcj8gc7bFAdweX4MyBHsmiPm32WqjUJFAmw
|
||||||
|
kcKYaiyfDJ+1wusa/b+7RCnshWc8B9udYbXfvcpOGgphpUuvomKT8at3ToJfEWmR
|
||||||
|
BzToYYTsgAAX8diY/X53BHCE/+MhLccglEUYNZyBRkTwDLrS9QgNkhrADaTwxsv1
|
||||||
|
8PwnVKve/ZxwOU0QGf4ZOhA2YQOE5hkRDR5uY2OHsOS5vHsd9Y6kNNnO8EBy99d1
|
||||||
|
QiBJOW3AP0nr4Cj1/NhdigAujsYRKiCAuPT7dgqART58VU4bZ3PgonMlziLe7+ht
|
||||||
|
YYxV+wyP6LVqicDd0MLLvG7r/JOiWuABOUxsFFaRecehoPJjeAEQxnWJjedokXKL
|
||||||
|
HVOFaEkAEQEAAYkCNgQYAQgAIBYhBJWSp7x6aC5zMzduCeeTXY25vYsgBQJmleT8
|
||||||
|
AhsMAAoJEOeTXY25vYsgG8sP/3UdsWuiwTsf/x4BTW82K+Uk9YwZDnUNH+4dUMED
|
||||||
|
bKT1C6CbuSZ7Mnbi2rVsmGzOMs9MehIx6Ko8/iCR2OCeWi8Q+wM+iffAfWuT1GK6
|
||||||
|
7f/VIfoYBUWEa+kvDcPgEbd5Tu7ZdUO/jROVBSlXRSjzK9LpIj7GozBTJ8Vqy5x7
|
||||||
|
oqbWPPEYtGDVHime8o6f5/wfhNgL3mFnoq6srK7KhwACwfTXlNqAlGiXGa30Yj+b
|
||||||
|
Cj6IvmxoII49E67/ovMEmzDCb3RXiaL6OATy25P+HQJvWvAam7Qq5Xn+bZg65Mup
|
||||||
|
vXq3zoX0a7EKXc5vsJVNtTlXO1ATdYszKP5uNzkHrNAN52VRYaowq1vPy/MVMbSI
|
||||||
|
rL/hTFKr7ZNhmC7jmS3OuJyCYQsfEerubtBUuc/W6JDc2oTI3xOG1S2Zj8f4PxLl
|
||||||
|
H7vMG4E+p6eOrUGw6VQXjFsH9GtwhkPh/ZGMKENb2+JztJ02674Cok4s5c/lZFKz
|
||||||
|
mmRUcNjX2bm2K0GfGG5/hAog/CHCeUZvwIh4hZLkdeJ1QsIYpN8xbvY7QP6yh4VB
|
||||||
|
XrL18+2sontZ45MsGResrRibB35x7IrCrxZsVtRJZthHqshiORPatgy+AiWcAtEv
|
||||||
|
UWEnnC1xBSasNebw4fSE8AJg9JMCRw+3GAetlotOeW9q7PN6yrXD9rGuV/QquQNd
|
||||||
|
/c7w
|
||||||
|
=4rRi
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
for d in $(go list ./... | grep -v browser); do
|
export GORACE="history_size=7"
|
||||||
CGO_ENABLED=1 go test -v -tags kqueue -race --timeout 100m "$d"
|
export MINIO_API_REQUESTS_MAX=10000
|
||||||
|
|
||||||
|
for d in $(go list ./...); do
|
||||||
|
CGO_ENABLED=1 go test -v -race --timeout 100m "$d"
|
||||||
done
|
done
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,406 @@
|
|||||||
|
// Copyright 2026 PGSTY contributors.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// rebrand-guard records the compatibility identifiers that a product rebrand
|
||||||
|
// must not accidentally rename. It intentionally excludes product branding and
|
||||||
|
// delivery names, which are validated by buildscripts/verify-rebrand.sh.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const manifestVersion = 4
|
||||||
|
|
||||||
|
var (
|
||||||
|
minioImportRE = regexp.MustCompile(`github\.com/minio/[A-Za-z0-9_./-]+`)
|
||||||
|
envRE = regexp.MustCompile(`\b_?MINIO_[A-Z0-9_]+\b`)
|
||||||
|
metricRE = regexp.MustCompile(`\bminio_[A-Za-z0-9_]+\b`)
|
||||||
|
headerRE = regexp.MustCompile(`(?i)\bx-minio-[a-z0-9_-]+\b`)
|
||||||
|
routeRE = regexp.MustCompile(`^/[A-Za-z0-9._~!$&'()*+,;=:@%/?{}=-]*`)
|
||||||
|
storageRE = regexp.MustCompile(`\.minio\.sys(?:/[A-Za-z0-9._${}-]+)*`)
|
||||||
|
policyRE = regexp.MustCompile(`(?:arn:minio|minio:s3)[A-Za-z0-9_:/.*${}-]*`)
|
||||||
|
brandRE = regexp.MustCompile(`(?i)(^|[^a-z0-9_])minio([^a-z0-9_]|$)`)
|
||||||
|
)
|
||||||
|
|
||||||
|
type manifest struct {
|
||||||
|
Version int `json:"version"`
|
||||||
|
ModulePath string `json:"module_path"`
|
||||||
|
MinioImports []string `json:"minio_imports"`
|
||||||
|
Environment []string `json:"environment"`
|
||||||
|
Metrics []string `json:"metrics"`
|
||||||
|
Headers []string `json:"headers"`
|
||||||
|
Routes []string `json:"routes"`
|
||||||
|
RouteRoots []string `json:"route_roots"`
|
||||||
|
GridRoutes []string `json:"grid_routes"`
|
||||||
|
StorageMarkers []string `json:"storage_markers"`
|
||||||
|
PolicyValues []string `json:"policy_values"`
|
||||||
|
BrandAllowlist []string `json:"brand_allowlist"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
write := flag.Bool("write", false, "replace the checked-in compatibility baseline")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
repo, err := gitOutput("rev-parse", "--show-toplevel")
|
||||||
|
if err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
repo = strings.TrimSpace(repo)
|
||||||
|
baselinePath := filepath.Join(repo, "buildscripts", "rebrand-guard", "compat-baseline.json")
|
||||||
|
|
||||||
|
current, err := collect(repo)
|
||||||
|
if err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
if *write {
|
||||||
|
if err := writeManifest(baselinePath, current); err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("wrote %s\n", baselinePath)
|
||||||
|
printSummary(current)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
want, err := readManifest(baselinePath)
|
||||||
|
if err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
if err := compare(want, current); err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
printSummary(current)
|
||||||
|
fmt.Println("Silo rebrand compatibility baseline is unchanged")
|
||||||
|
}
|
||||||
|
|
||||||
|
func collect(repo string) (manifest, error) {
|
||||||
|
files, err := trackedFiles(repo)
|
||||||
|
if err != nil {
|
||||||
|
return manifest{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sets := map[string]map[string]struct{}{
|
||||||
|
"imports": {},
|
||||||
|
"env": {},
|
||||||
|
"metrics": {},
|
||||||
|
"headers": {},
|
||||||
|
"routes": {},
|
||||||
|
"roots": {},
|
||||||
|
"grid": {},
|
||||||
|
"storage": {},
|
||||||
|
"policy": {},
|
||||||
|
"brand": {},
|
||||||
|
}
|
||||||
|
modulePath := ""
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
|
||||||
|
for _, rel := range files {
|
||||||
|
// Investigation artifacts contain synthetic routes and archived configurations.
|
||||||
|
if rel == "SILO_REBRANDING_MIGRATION.md" ||
|
||||||
|
strings.HasPrefix(rel, "docs/investigations/") ||
|
||||||
|
strings.HasPrefix(rel, "buildscripts/rebrand-guard/") ||
|
||||||
|
strings.HasPrefix(rel, "buildscripts/helm-migration-guard/") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
path := filepath.Join(repo, filepath.FromSlash(rel))
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return manifest{}, fmt.Errorf("read %s: %w", rel, err)
|
||||||
|
}
|
||||||
|
if bytes.IndexByte(data, 0) >= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
text := string(data)
|
||||||
|
|
||||||
|
addMatches(sets["env"], envRE, text, false)
|
||||||
|
addMatches(sets["headers"], headerRE, text, true)
|
||||||
|
addMatches(sets["storage"], storageRE, text, false)
|
||||||
|
addMatches(sets["policy"], policyRE, text, false)
|
||||||
|
if strings.HasSuffix(rel, ".go") && (strings.HasPrefix(rel, "cmd/") || strings.HasPrefix(rel, "internal/")) {
|
||||||
|
addMatches(sets["metrics"], metricRE, text, false)
|
||||||
|
}
|
||||||
|
if rel == "go.mod" {
|
||||||
|
addMatches(sets["imports"], minioImportRE, text, false)
|
||||||
|
for _, line := range strings.Split(text, "\n") {
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
if len(fields) == 2 && fields[0] == "module" {
|
||||||
|
modulePath = fields[1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(rel, ".go") {
|
||||||
|
file, err := parser.ParseFile(fset, path, data, parser.SkipObjectResolution)
|
||||||
|
if err != nil {
|
||||||
|
return manifest{}, fmt.Errorf("parse %s: %w", rel, err)
|
||||||
|
}
|
||||||
|
for _, spec := range file.Imports {
|
||||||
|
value, err := strconv.Unquote(spec.Path.Value)
|
||||||
|
if err == nil && strings.HasPrefix(value, "github.com/minio/") {
|
||||||
|
sets["imports"][value] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(rel, "_test.go") {
|
||||||
|
// Test files hold request paths for fixtures, not served routes.
|
||||||
|
collectStringMatches(sets["routes"], routeRE, file)
|
||||||
|
if strings.HasPrefix(rel, "cmd/") || strings.HasPrefix(rel, "internal/") {
|
||||||
|
collectBrandStrings(sets["brand"], rel, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
collectNamedStringValues(sets["roots"], rel, file, "minioReservedBucket")
|
||||||
|
if rel == "internal/grid/manager.go" {
|
||||||
|
collectStringMatches(sets["grid"], routeRE, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This was a shell-local PID variable in the generated inspect script,
|
||||||
|
// never a supported environment setting.
|
||||||
|
delete(sets["env"], "MINIO_SRVR_PID")
|
||||||
|
|
||||||
|
if modulePath == "" {
|
||||||
|
return manifest{}, errors.New("go.mod module path was not found")
|
||||||
|
}
|
||||||
|
return manifest{
|
||||||
|
Version: manifestVersion,
|
||||||
|
ModulePath: modulePath,
|
||||||
|
MinioImports: sorted(sets["imports"]),
|
||||||
|
Environment: sorted(sets["env"]),
|
||||||
|
Metrics: sorted(sets["metrics"]),
|
||||||
|
Headers: sorted(sets["headers"]),
|
||||||
|
Routes: sorted(sets["routes"]),
|
||||||
|
RouteRoots: sorted(sets["roots"]),
|
||||||
|
GridRoutes: sorted(sets["grid"]),
|
||||||
|
StorageMarkers: sorted(sets["storage"]),
|
||||||
|
PolicyValues: sorted(sets["policy"]),
|
||||||
|
BrandAllowlist: sorted(sets["brand"]),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectBrandStrings(dst map[string]struct{}, rel string, file *ast.File) {
|
||||||
|
ast.Inspect(file, func(node ast.Node) bool {
|
||||||
|
literal, ok := node.(*ast.BasicLit)
|
||||||
|
if !ok || literal.Kind != token.STRING {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
value, err := strconv.Unquote(literal.Value)
|
||||||
|
if err != nil || !brandRE.MatchString(value) || strings.HasPrefix(value, "github.com/minio/") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
dst[filepath.ToSlash(rel)+"="+strconv.Quote(value)] = struct{}{}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectNamedStringValues(dst map[string]struct{}, rel string, file *ast.File, names ...string) {
|
||||||
|
wanted := make(map[string]struct{}, len(names))
|
||||||
|
for _, name := range names {
|
||||||
|
wanted[name] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, decl := range file.Decls {
|
||||||
|
gen, ok := decl.(*ast.GenDecl)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, rawSpec := range gen.Specs {
|
||||||
|
spec, ok := rawSpec.(*ast.ValueSpec)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for i, name := range spec.Names {
|
||||||
|
if _, ok := wanted[name.Name]; !ok || i >= len(spec.Values) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
literal, ok := spec.Values[i].(*ast.BasicLit)
|
||||||
|
if !ok || literal.Kind != token.STRING {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
value, err := strconv.Unquote(literal.Value)
|
||||||
|
if err == nil {
|
||||||
|
dst[filepath.ToSlash(rel)+":"+name.Name+"="+value] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectStringMatches(dst map[string]struct{}, re *regexp.Regexp, file *ast.File) {
|
||||||
|
ast.Inspect(file, func(node ast.Node) bool {
|
||||||
|
literal, ok := node.(*ast.BasicLit)
|
||||||
|
if !ok || literal.Kind != token.STRING {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
value, err := strconv.Unquote(literal.Value)
|
||||||
|
if err == nil {
|
||||||
|
addMatches(dst, re, value, false)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func trackedFiles(repo string) ([]string, error) {
|
||||||
|
cmd := exec.Command("git", "-C", repo, "ls-files", "--cached", "-z")
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("git ls-files: %w", err)
|
||||||
|
}
|
||||||
|
parts := bytes.Split(out, []byte{0})
|
||||||
|
files := make([]string, 0, len(parts))
|
||||||
|
for _, part := range parts {
|
||||||
|
if len(part) > 0 {
|
||||||
|
files = append(files, string(part))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addMatches(dst map[string]struct{}, re *regexp.Regexp, text string, lower bool) {
|
||||||
|
for _, match := range re.FindAllString(text, -1) {
|
||||||
|
if lower {
|
||||||
|
match = strings.ToLower(match)
|
||||||
|
}
|
||||||
|
dst[match] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sorted(set map[string]struct{}) []string {
|
||||||
|
values := make([]string, 0, len(set))
|
||||||
|
for value := range set {
|
||||||
|
values = append(values, value)
|
||||||
|
}
|
||||||
|
sort.Strings(values)
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeManifest(path string, value manifest) error {
|
||||||
|
data, err := json.MarshalIndent(value, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data = append(data, '\n')
|
||||||
|
return os.WriteFile(path, data, 0o644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readManifest(path string) (manifest, error) {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return manifest{}, fmt.Errorf("read compatibility baseline (run go run ./buildscripts/rebrand-guard --write once): %w", err)
|
||||||
|
}
|
||||||
|
var value manifest
|
||||||
|
if err := json.Unmarshal(data, &value); err != nil {
|
||||||
|
return manifest{}, err
|
||||||
|
}
|
||||||
|
if value.Version != manifestVersion {
|
||||||
|
return manifest{}, fmt.Errorf("unsupported compatibility baseline version %d", value.Version)
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func compare(want, got manifest) error {
|
||||||
|
var failures []string
|
||||||
|
if want.ModulePath != got.ModulePath {
|
||||||
|
failures = append(failures, fmt.Sprintf("module_path: want %q, got %q", want.ModulePath, got.ModulePath))
|
||||||
|
}
|
||||||
|
checks := []struct {
|
||||||
|
name string
|
||||||
|
want, got []string
|
||||||
|
}{
|
||||||
|
{"minio_imports", want.MinioImports, got.MinioImports},
|
||||||
|
{"environment", want.Environment, got.Environment},
|
||||||
|
{"metrics", want.Metrics, got.Metrics},
|
||||||
|
{"headers", want.Headers, got.Headers},
|
||||||
|
{"routes", want.Routes, got.Routes},
|
||||||
|
{"route_roots", want.RouteRoots, got.RouteRoots},
|
||||||
|
{"grid_routes", want.GridRoutes, got.GridRoutes},
|
||||||
|
{"storage_markers", want.StorageMarkers, got.StorageMarkers},
|
||||||
|
{"policy_values", want.PolicyValues, got.PolicyValues},
|
||||||
|
{"brand_allowlist", want.BrandAllowlist, got.BrandAllowlist},
|
||||||
|
}
|
||||||
|
for _, check := range checks {
|
||||||
|
if missing, added := setDiff(check.want, check.got); len(missing) > 0 || len(added) > 0 {
|
||||||
|
var b strings.Builder
|
||||||
|
fmt.Fprintf(&b, "%s compatibility set changed", check.name)
|
||||||
|
for _, value := range missing {
|
||||||
|
fmt.Fprintf(&b, "\n - %s", value)
|
||||||
|
}
|
||||||
|
for _, value := range added {
|
||||||
|
fmt.Fprintf(&b, "\n + %s", value)
|
||||||
|
}
|
||||||
|
failures = append(failures, b.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(failures) > 0 {
|
||||||
|
return errors.New(strings.Join(failures, "\n"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setDiff(want, got []string) (missing, added []string) {
|
||||||
|
wantSet := make(map[string]struct{}, len(want))
|
||||||
|
gotSet := make(map[string]struct{}, len(got))
|
||||||
|
for _, value := range want {
|
||||||
|
wantSet[value] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, value := range got {
|
||||||
|
gotSet[value] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, value := range want {
|
||||||
|
if _, ok := gotSet[value]; !ok {
|
||||||
|
missing = append(missing, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, value := range got {
|
||||||
|
if _, ok := wantSet[value]; !ok {
|
||||||
|
added = append(added, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return missing, added
|
||||||
|
}
|
||||||
|
|
||||||
|
func printSummary(value manifest) {
|
||||||
|
fmt.Printf("compatibility manifest: imports=%d env=%d metrics=%d headers=%d routes=%d roots=%d grid=%d storage=%d policy=%d brand=%d sha256=%s\n",
|
||||||
|
len(value.MinioImports), len(value.Environment), len(value.Metrics), len(value.Headers),
|
||||||
|
len(value.Routes), len(value.RouteRoots), len(value.GridRoutes), len(value.StorageMarkers), len(value.PolicyValues),
|
||||||
|
len(value.BrandAllowlist), manifestDigest(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
func manifestDigest(value manifest) string {
|
||||||
|
data, _ := json.Marshal(value)
|
||||||
|
sum := sha256.Sum256(data)
|
||||||
|
return hex.EncodeToString(sum[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func gitOutput(args ...string) (string, error) {
|
||||||
|
out, err := exec.Command("git", args...).CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("git %s: %s: %w", strings.Join(args, " "), strings.TrimSpace(string(out)), err)
|
||||||
|
}
|
||||||
|
return string(out), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fatal(err error) {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
Executable
+63
@@ -0,0 +1,63 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
set -E
|
||||||
|
set -o pipefail
|
||||||
|
set -x
|
||||||
|
set -e
|
||||||
|
|
||||||
|
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||||
|
SILO_CONFIG_DIR="$WORK_DIR/.silo"
|
||||||
|
SILO=("$PWD/silo" --config-dir "$SILO_CONFIG_DIR" server)
|
||||||
|
|
||||||
|
if [ ! -x "$PWD/silo" ]; then
|
||||||
|
echo "Silo executable binary not found in current directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
function start_silo_5drive() {
|
||||||
|
start_port=$1
|
||||||
|
|
||||||
|
export MINIO_ROOT_USER=silo
|
||||||
|
export MINIO_ROOT_PASSWORD=silo1234
|
||||||
|
export MC_HOST_silo="http://silo:silo1234@127.0.0.1:${start_port}/"
|
||||||
|
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||||
|
export MINIO_CI_CD=1
|
||||||
|
|
||||||
|
mkdir -p "${WORK_DIR}"
|
||||||
|
"$(git rev-parse --show-toplevel)/buildscripts/install-mcli.sh" "${WORK_DIR}/mc"
|
||||||
|
|
||||||
|
"${WORK_DIR}/mc" cp --quiet -r "buildscripts/cicd-corpus/" "${WORK_DIR}/cicd-corpus/"
|
||||||
|
|
||||||
|
"${SILO[@]}" --address ":$start_port" "${WORK_DIR}/cicd-corpus/disk{1...5}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||||
|
pid=$!
|
||||||
|
disown $pid
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||||
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
"${WORK_DIR}/mc" stat silo/bucket/testobj
|
||||||
|
|
||||||
|
pkill silo
|
||||||
|
sleep 3
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
|
|
||||||
|
start_silo_5drive ${start_port}
|
||||||
|
}
|
||||||
|
|
||||||
|
function purge() {
|
||||||
|
rm -rf "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
(main "$@")
|
||||||
|
rv=$?
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit "$rv"
|
||||||
Executable
+151
@@ -0,0 +1,151 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
set -E
|
||||||
|
set -o pipefail
|
||||||
|
set -x
|
||||||
|
|
||||||
|
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||||
|
SILO_CONFIG_DIR="$WORK_DIR/.silo"
|
||||||
|
MINIO_OLD=("$PWD/minio.RELEASE.2020-10-28T08-16-50Z" --config-dir "$SILO_CONFIG_DIR" server)
|
||||||
|
SILO=("$PWD/silo" --config-dir "$SILO_CONFIG_DIR" server)
|
||||||
|
|
||||||
|
if [ ! -x "$PWD/silo" ]; then
|
||||||
|
echo "Silo executable binary not found in current directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
function download_old_release() {
|
||||||
|
if [ ! -x minio.RELEASE.2020-10-28T08-16-50Z ]; then
|
||||||
|
: "${SILO_LEGACY_FIXTURE_2020:?set SILO_LEGACY_FIXTURE_2020 to the audited legacy binary}"
|
||||||
|
: "${SILO_LEGACY_SHA256_2020:?set SILO_LEGACY_SHA256_2020 to its audited SHA-256}"
|
||||||
|
"$(git rev-parse --show-toplevel)/buildscripts/install-verified-fixture.sh" \
|
||||||
|
"${SILO_LEGACY_FIXTURE_2020}" "${SILO_LEGACY_SHA256_2020}" \
|
||||||
|
"minio.RELEASE.2020-10-28T08-16-50Z"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function verify_rewrite() {
|
||||||
|
start_port=$1
|
||||||
|
|
||||||
|
export MINIO_ACCESS_KEY=silo
|
||||||
|
export MINIO_SECRET_KEY=silo1234
|
||||||
|
export MC_HOST_silo="http://silo:silo1234@127.0.0.1:${start_port}/"
|
||||||
|
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||||
|
export MINIO_CI_CD=1
|
||||||
|
|
||||||
|
mkdir -p "${WORK_DIR}"
|
||||||
|
"$(git rev-parse --show-toplevel)/buildscripts/install-mcli.sh" "${WORK_DIR}/mc"
|
||||||
|
|
||||||
|
"${MINIO_OLD[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||||
|
pid=$!
|
||||||
|
disown $pid
|
||||||
|
|
||||||
|
"${WORK_DIR}/mc" ready silo/
|
||||||
|
|
||||||
|
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||||
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
"${WORK_DIR}/mc" mb silo/healing-rewrite-bucket --quiet --with-lock
|
||||||
|
"${WORK_DIR}/mc" cp \
|
||||||
|
buildscripts/verify-build.sh \
|
||||||
|
silo/healing-rewrite-bucket/ \
|
||||||
|
--disable-multipart --quiet
|
||||||
|
|
||||||
|
"${WORK_DIR}/mc" cp \
|
||||||
|
buildscripts/verify-build.sh \
|
||||||
|
silo/healing-rewrite-bucket/ \
|
||||||
|
--disable-multipart --quiet
|
||||||
|
|
||||||
|
"${WORK_DIR}/mc" cp \
|
||||||
|
buildscripts/verify-build.sh \
|
||||||
|
silo/healing-rewrite-bucket/ \
|
||||||
|
--disable-multipart --quiet
|
||||||
|
|
||||||
|
kill ${pid}
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
"${SILO[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||||
|
pid=$!
|
||||||
|
disown $pid
|
||||||
|
|
||||||
|
"${WORK_DIR}/mc" ready silo/
|
||||||
|
|
||||||
|
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||||
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! ./s3-check-md5 \
|
||||||
|
-debug \
|
||||||
|
-versions \
|
||||||
|
-access-key silo \
|
||||||
|
-secret-key silo1234 \
|
||||||
|
-endpoint "http://127.0.0.1:${start_port}/" 2>&1 | grep INTACT; then
|
||||||
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
mkdir -p inspects
|
||||||
|
(
|
||||||
|
cd inspects
|
||||||
|
"${WORK_DIR}/mc" admin inspect silo/healing-rewrite-bucket/verify-build.sh/**
|
||||||
|
)
|
||||||
|
|
||||||
|
"${WORK_DIR}/mc" mb play/inspects
|
||||||
|
"${WORK_DIR}/mc" mirror inspects play/inspects
|
||||||
|
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
go run ./buildscripts/heal-manual.go "127.0.0.1:${start_port}" "silo" "silo1234"
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
if ! ./s3-check-md5 \
|
||||||
|
-debug \
|
||||||
|
-versions \
|
||||||
|
-access-key silo \
|
||||||
|
-secret-key silo1234 \
|
||||||
|
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
||||||
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
mkdir -p inspects
|
||||||
|
(
|
||||||
|
cd inspects
|
||||||
|
"${WORK_DIR}/mc" admin inspect silo/healing-rewrite-bucket/verify-build.sh/**
|
||||||
|
)
|
||||||
|
|
||||||
|
"${WORK_DIR}/mc" mb play/inspects
|
||||||
|
"${WORK_DIR}/mc" mirror inspects play/inspects
|
||||||
|
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
kill ${pid}
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
download_old_release
|
||||||
|
|
||||||
|
start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
|
|
||||||
|
verify_rewrite ${start_port}
|
||||||
|
}
|
||||||
|
|
||||||
|
function purge() {
|
||||||
|
rm -rf "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
(main "$@")
|
||||||
|
rv=$?
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit "$rv"
|
||||||
Executable
+287
@@ -0,0 +1,287 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# These are the single source of truth for the package identity: .github/nfpm.yml
|
||||||
|
# must agree with them, and test-release.yml asserts that it does. Drift the
|
||||||
|
# other way round would only surface here, on the maintainer's machine, after
|
||||||
|
# the build has already run and uploaded.
|
||||||
|
expected_fingerprint="9592A7BC7A682E7333376E09E7935D8DB9BD8B20"
|
||||||
|
expected_release="1PGSTY"
|
||||||
|
expected_vendor="PGSTY"
|
||||||
|
expected_packager="Ruohang Feng (@Vonng) <rh@vonng.com>"
|
||||||
|
expected_url="https://silo.pgsty.com"
|
||||||
|
expected_summary="S3-Interface Libre Object Storage, a community-maintained S3-compatible server."
|
||||||
|
expected_description="S3-Interface Libre Object Storage, a community-maintained S3-compatible server."
|
||||||
|
expected_license="AGPL-3.0-or-later"
|
||||||
|
expected_group="Applications/File"
|
||||||
|
expected_payload="/etc/default/silo
|
||||||
|
/usr/bin/silo
|
||||||
|
/usr/lib/systemd/system/silo.service
|
||||||
|
/usr/lib/sysusers.d/silo.conf
|
||||||
|
/usr/share/doc/silo/LICENSE
|
||||||
|
/usr/share/doc/silo/NOTICE"
|
||||||
|
repository="${GH_REPO:-pgsty/silo}"
|
||||||
|
container="${DNFUPDATE_CONTAINER:-dnfupdate}"
|
||||||
|
upload=false
|
||||||
|
release_tag=""
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage: buildscripts/sign-release-rpms.sh RELEASE.TAG [--upload] [--repo OWNER/REPO] [--container NAME]
|
||||||
|
|
||||||
|
Downloads the two unsigned RPMs from a Draft GitHub Release, signs them with
|
||||||
|
the expected Pigsty key in the local dnfupdate container, verifies the result,
|
||||||
|
and regenerates their .sha256sum files. Nothing is uploaded unless --upload is
|
||||||
|
provided.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--upload)
|
||||||
|
upload=true
|
||||||
|
;;
|
||||||
|
--repo)
|
||||||
|
shift
|
||||||
|
if [ "$#" -eq 0 ]; then
|
||||||
|
echo "--repo requires OWNER/REPO" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
repository="$1"
|
||||||
|
;;
|
||||||
|
--container)
|
||||||
|
shift
|
||||||
|
if [ "$#" -eq 0 ]; then
|
||||||
|
echo "--container requires a name" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
container="$1"
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
echo "Unknown option: $1" >&2
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [ -n "${release_tag}" ]; then
|
||||||
|
echo "Only one release tag may be specified" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
release_tag="$1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "${release_tag}" ]; then
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for command in docker gh; do
|
||||||
|
if ! command -v "${command}" >/dev/null 2>&1; then
|
||||||
|
echo "${command} is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
version_hyphen="${release_tag#RELEASE.}"
|
||||||
|
package_version="$(printf '%s\n' "${version_hyphen}" | sed -E \
|
||||||
|
's/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2})-([0-9]{2})-([0-9]{2})Z$/\1\2\3\4\5\6.0.0/')"
|
||||||
|
if [ "${package_version}" = "${version_hyphen}" ]; then
|
||||||
|
echo "Invalid release tag: ${release_tag}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$(gh release view "${release_tag}" --repo "${repository}" --json isDraft --jq .isDraft)" != "true" ]; then
|
||||||
|
echo "Refusing to sign: ${release_tag} is not a Draft release" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$(docker inspect --format '{{.State.Running}}' "${container}" 2>/dev/null || true)" != "true" ]; then
|
||||||
|
echo "Signing container is not running: ${container}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
secret_fingerprints="$(docker exec "${container}" \
|
||||||
|
gpg --batch --with-colons --list-secret-keys 2>/dev/null |
|
||||||
|
awk -F: '$1 == "fpr" { print toupper($10) }')"
|
||||||
|
if ! printf '%s\n' "${secret_fingerprints}" | grep -Fxq "${expected_fingerprint}"; then
|
||||||
|
echo "Expected signing key is not available in ${container}: ${expected_fingerprint}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
repo_dir="$(cd "${script_dir}/.." && pwd)"
|
||||||
|
work_root="${SIGN_WORKDIR:-${repo_dir}/.release-sign}"
|
||||||
|
mkdir -p "${work_root}"
|
||||||
|
work_dir="$(mktemp -d "${work_root}/${release_tag}.XXXXXX")"
|
||||||
|
unsigned_dir="${work_dir}/unsigned"
|
||||||
|
signed_dir="${work_dir}/signed"
|
||||||
|
mkdir -p "${unsigned_dir}" "${signed_dir}"
|
||||||
|
chmod 700 "${work_dir}" "${unsigned_dir}" "${signed_dir}"
|
||||||
|
|
||||||
|
rpm_files=(
|
||||||
|
"silo-${package_version}-${expected_release}.x86_64.rpm"
|
||||||
|
"silo-${package_version}-${expected_release}.aarch64.rpm"
|
||||||
|
)
|
||||||
|
|
||||||
|
download_patterns=()
|
||||||
|
for rpm_file in "${rpm_files[@]}"; do
|
||||||
|
download_patterns+=(--pattern "${rpm_file}" --pattern "${rpm_file}.sha256sum")
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Downloading RPMs from Draft release ${repository}@${release_tag}"
|
||||||
|
gh release download "${release_tag}" --repo "${repository}" \
|
||||||
|
--dir "${unsigned_dir}" "${download_patterns[@]}"
|
||||||
|
|
||||||
|
sha256_digest() {
|
||||||
|
local file="$1"
|
||||||
|
if command -v sha256sum >/dev/null 2>&1; then
|
||||||
|
sha256sum "${file}" | awk '{print $1}'
|
||||||
|
else
|
||||||
|
shasum -a 256 "${file}" | awk '{print $1}'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
for rpm_file in "${rpm_files[@]}"; do
|
||||||
|
rpm_path="${unsigned_dir}/${rpm_file}"
|
||||||
|
checksum_path="${rpm_path}.sha256sum"
|
||||||
|
test -s "${rpm_path}"
|
||||||
|
test -s "${checksum_path}"
|
||||||
|
|
||||||
|
actual_line="$(sha256_digest "${rpm_path}") ${rpm_file}"
|
||||||
|
published_line="$(tr -d '\n' < "${checksum_path}")"
|
||||||
|
if [ "${actual_line}" != "${published_line}" ]; then
|
||||||
|
echo "Checksum mismatch for ${rpm_file}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
safe_tag="$(printf '%s' "${release_tag}" | tr -c 'A-Za-z0-9._-' '_')"
|
||||||
|
container_dir="/tmp/silo-sign-${safe_tag}-$$"
|
||||||
|
docker exec "${container}" mkdir -p "${container_dir}"
|
||||||
|
|
||||||
|
cleanup_container() {
|
||||||
|
local rpm_file
|
||||||
|
for rpm_file in "${rpm_files[@]}"; do
|
||||||
|
docker exec "${container}" rm -f "${container_dir}/${rpm_file}" >/dev/null 2>&1 || true
|
||||||
|
done
|
||||||
|
docker exec "${container}" rmdir "${container_dir}" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
trap cleanup_container EXIT
|
||||||
|
|
||||||
|
assert_rpm_tag() {
|
||||||
|
local rpm_path="$1"
|
||||||
|
local tag="$2"
|
||||||
|
local expected="$3"
|
||||||
|
local actual
|
||||||
|
|
||||||
|
actual="$(docker exec "${container}" rpm -qp --queryformat "%{${tag}}" "${rpm_path}")"
|
||||||
|
if [ "${actual}" != "${expected}" ]; then
|
||||||
|
echo "Unexpected RPM ${tag}: ${actual}" >&2
|
||||||
|
echo "Expected RPM ${tag}: ${expected}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
for rpm_file in "${rpm_files[@]}"; do
|
||||||
|
case "${rpm_file}" in
|
||||||
|
*.x86_64.rpm)
|
||||||
|
expected_arch="x86_64"
|
||||||
|
;;
|
||||||
|
*.aarch64.rpm)
|
||||||
|
expected_arch="aarch64"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unexpected RPM filename: ${rpm_file}" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "Signing ${rpm_file} with ${expected_fingerprint}"
|
||||||
|
docker cp "${unsigned_dir}/${rpm_file}" "${container}:${container_dir}/${rpm_file}" >/dev/null
|
||||||
|
container_rpm="${container_dir}/${rpm_file}"
|
||||||
|
|
||||||
|
assert_rpm_tag "${container_rpm}" NAME silo
|
||||||
|
assert_rpm_tag "${container_rpm}" VERSION "${package_version}"
|
||||||
|
assert_rpm_tag "${container_rpm}" RELEASE "${expected_release}"
|
||||||
|
assert_rpm_tag "${container_rpm}" ARCH "${expected_arch}"
|
||||||
|
assert_rpm_tag "${container_rpm}" VENDOR "${expected_vendor}"
|
||||||
|
assert_rpm_tag "${container_rpm}" PACKAGER "${expected_packager}"
|
||||||
|
assert_rpm_tag "${container_rpm}" URL "${expected_url}"
|
||||||
|
assert_rpm_tag "${container_rpm}" LICENSE "${expected_license}"
|
||||||
|
assert_rpm_tag "${container_rpm}" GROUP "${expected_group}"
|
||||||
|
assert_rpm_tag "${container_rpm}" SUMMARY "${expected_summary}"
|
||||||
|
assert_rpm_tag "${container_rpm}" DESCRIPTION "${expected_description}"
|
||||||
|
|
||||||
|
rpm_payload="$(docker exec "${container}" rpm -qpl "${container_rpm}")"
|
||||||
|
if [ "${rpm_payload}" != "${expected_payload}" ]; then
|
||||||
|
echo "Unexpected RPM payload for ${rpm_file}:" >&2
|
||||||
|
printf '%s\n' "${rpm_payload}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker exec "${container}" rpmsign \
|
||||||
|
--define "_gpg_name ${expected_fingerprint}" \
|
||||||
|
--addsign "${container_rpm}"
|
||||||
|
|
||||||
|
signature_output="$(docker exec "${container}" rpmkeys --checksig --verbose "${container_rpm}")"
|
||||||
|
printf '%s\n' "${signature_output}"
|
||||||
|
if ! printf '%s\n' "${signature_output}" | tr '[:upper:]' '[:lower:]' | grep -q 'key id b9bd8b20: ok'; then
|
||||||
|
echo "Signature verification failed for ${rpm_file}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker cp "${container}:${container_dir}/${rpm_file}" "${signed_dir}/${rpm_file}" >/dev/null
|
||||||
|
signed_digest="$(sha256_digest "${signed_dir}/${rpm_file}")"
|
||||||
|
printf '%s %s' "${signed_digest}" "${rpm_file}" > "${signed_dir}/${rpm_file}.sha256sum"
|
||||||
|
|
||||||
|
docker exec "${container}" rpm -qp --queryformat \
|
||||||
|
$'Name: %{NAME}\nVersion: %{VERSION}-%{RELEASE}\nArch: %{ARCH}\nVendor: %{VENDOR}\nPackager: %{PACKAGER}\nURL: %{URL}\n' \
|
||||||
|
"${container_rpm}"
|
||||||
|
echo "SHA256: ${signed_digest}"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "${upload}" = true ]; then
|
||||||
|
upload_files=()
|
||||||
|
for rpm_file in "${rpm_files[@]}"; do
|
||||||
|
upload_files+=("${signed_dir}/${rpm_file}" "${signed_dir}/${rpm_file}.sha256sum")
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Replacing RPMs in Draft release ${release_tag}"
|
||||||
|
gh release upload "${release_tag}" --repo "${repository}" --clobber "${upload_files[@]}"
|
||||||
|
|
||||||
|
for rpm_file in "${rpm_files[@]}"; do
|
||||||
|
for asset in "${rpm_file}" "${rpm_file}.sha256sum"; do
|
||||||
|
local_digest="sha256:$(sha256_digest "${signed_dir}/${asset}")"
|
||||||
|
remote_digest=""
|
||||||
|
for attempt in 1 2 3 4 5; do
|
||||||
|
remote_digest="$(gh release view "${release_tag}" --repo "${repository}" --json assets \
|
||||||
|
--jq ".assets[] | select(.name == \"${asset}\") | .digest")"
|
||||||
|
if [ "${local_digest}" = "${remote_digest}" ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if [ "${attempt}" -lt 5 ]; then
|
||||||
|
sleep 2
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ "${local_digest}" != "${remote_digest}" ]; then
|
||||||
|
echo "GitHub asset digest mismatch for ${asset}" >&2
|
||||||
|
echo "Local: ${local_digest}" >&2
|
||||||
|
echo "Remote: ${remote_digest}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Verified GitHub asset: ${asset} ${remote_digest}"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo
|
||||||
|
echo "Signed RPMs are ready for review in: ${signed_dir}"
|
||||||
|
echo "Re-run with --upload to replace the RPM assets in the Draft release."
|
||||||
|
fi
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ -n "$TEST_DEBUG" ]; then
|
||||||
|
set -x
|
||||||
|
fi
|
||||||
|
|
||||||
|
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||||
|
SILO_CONFIG_DIR="$WORK_DIR/.silo"
|
||||||
|
SILO=("$PWD/silo" --config-dir "$SILO_CONFIG_DIR" server)
|
||||||
|
|
||||||
|
if [ ! -x "$PWD/silo" ]; then
|
||||||
|
echo "Silo executable binary not found in current directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -x "$PWD/silo" ]; then
|
||||||
|
echo "Silo executable binary not found in current directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
trap 'catch $LINENO' ERR
|
||||||
|
|
||||||
|
function purge() {
|
||||||
|
rm -rf "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# shellcheck disable=SC2120
|
||||||
|
catch() {
|
||||||
|
if [ $# -ne 0 ]; then
|
||||||
|
echo "error on line $1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Cleaning up instances of Silo"
|
||||||
|
pkill silo || true
|
||||||
|
pkill -9 silo || true
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
if [ $# -ne 0 ]; then
|
||||||
|
exit $#
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
catch
|
||||||
|
|
||||||
|
function gen_put_request() {
|
||||||
|
hdr_sleep=$1
|
||||||
|
body_sleep=$2
|
||||||
|
|
||||||
|
echo "PUT /testbucket/testobject HTTP/1.1"
|
||||||
|
sleep $hdr_sleep
|
||||||
|
echo "Host: foo-header"
|
||||||
|
echo "User-Agent: curl/8.2.1"
|
||||||
|
echo "Accept: */*"
|
||||||
|
echo "Content-Length: 30"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
sleep $body_sleep
|
||||||
|
echo "random line 0"
|
||||||
|
echo "random line 1"
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function send_put_object_request() {
|
||||||
|
hdr_timeout=$1
|
||||||
|
body_timeout=$2
|
||||||
|
|
||||||
|
start=$(date +%s)
|
||||||
|
timeout 5m bash -c "gen_put_request $hdr_timeout $body_timeout | netcat 127.0.0.1 $start_port | read" || return -1
|
||||||
|
[ $(($(date +%s) - start)) -gt $((srv_hdr_timeout + srv_idle_timeout + 1)) ] && return -1
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_silo_with_timeout() {
|
||||||
|
start_port=$1
|
||||||
|
|
||||||
|
export MINIO_ROOT_USER=silo
|
||||||
|
export MINIO_ROOT_PASSWORD=silo1234
|
||||||
|
export MC_HOST_silo="http://silo:silo1234@127.0.0.1:${start_port}/"
|
||||||
|
export MINIO_CI_CD=1
|
||||||
|
|
||||||
|
mkdir ${WORK_DIR}
|
||||||
|
if [ ! -x "$PWD/mc" ]; then
|
||||||
|
"$(git rev-parse --show-toplevel)/buildscripts/install-mcli.sh" "$PWD/mc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
"${SILO[@]}" --address ":$start_port" --read-header-timeout ${srv_hdr_timeout}s --idle-timeout ${srv_idle_timeout}s "${WORK_DIR}/disk/" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||||
|
pid=$!
|
||||||
|
disown $pid
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||||
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
"${PWD}/mc" mb silo/testbucket
|
||||||
|
"${PWD}/mc" anonymous set public silo/testbucket
|
||||||
|
|
||||||
|
# slow header writing
|
||||||
|
send_put_object_request 20 0 && exit -1
|
||||||
|
"${PWD}/mc" stat silo/testbucket/testobject && exit -1
|
||||||
|
|
||||||
|
# quick header write and slow bodywrite
|
||||||
|
send_put_object_request 0 40 && exit -1
|
||||||
|
"${PWD}/mc" stat silo/testbucket/testobject && exit -1
|
||||||
|
|
||||||
|
# quick header and body write
|
||||||
|
send_put_object_request 1 1 || exit -1
|
||||||
|
"${PWD}/mc" stat silo/testbucket/testobject || exit -1
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
export start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
|
export srv_hdr_timeout=5
|
||||||
|
export srv_idle_timeout=5
|
||||||
|
export -f gen_put_request
|
||||||
|
|
||||||
|
test_silo_with_timeout ${start_port}
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Executable
+51
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Asserts that every binary GoReleaser produced is stamped by the Go toolchain
|
||||||
|
# as built from this exact commit with a clean working tree. A stray untracked
|
||||||
|
# file (for example an un-ignored dist/) silently turns every release binary
|
||||||
|
# into a "+dirty" pseudo-version, which destroys the link between a published
|
||||||
|
# artifact and its tag. Catch that here instead of after publishing.
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
repo_dir="$(cd "${script_dir}/.." && pwd)"
|
||||||
|
dist_dir="${DIST_DIR:-${repo_dir}/dist}"
|
||||||
|
expected_count="${EXPECTED_BINARY_COUNT:-6}"
|
||||||
|
|
||||||
|
if ! command -v go >/dev/null 2>&1; then
|
||||||
|
echo "go is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "${dist_dir}" ]; then
|
||||||
|
echo "Missing GoReleaser dist directory: ${dist_dir}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
revision="$(git -C "${repo_dir}" rev-parse HEAD)"
|
||||||
|
|
||||||
|
count=0
|
||||||
|
while IFS= read -r binary; do
|
||||||
|
count=$((count + 1))
|
||||||
|
info="$(go version -m "${binary}")"
|
||||||
|
|
||||||
|
if ! grep -qF "vcs.revision=${revision}" <<< "${info}"; then
|
||||||
|
echo "Unexpected vcs.revision in ${binary} (expected ${revision})" >&2
|
||||||
|
grep -F 'vcs.' <<< "${info}" >&2 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -qF 'vcs.modified=false' <<< "${info}"; then
|
||||||
|
echo "Binary was built from a dirty working tree: ${binary}" >&2
|
||||||
|
grep -F 'vcs.' <<< "${info}" >&2 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done < <(find "${dist_dir}" -maxdepth 2 -type f \( -name 'silo' -o -name 'silo.exe' \) | sort)
|
||||||
|
|
||||||
|
if [ "${count}" -ne "${expected_count}" ]; then
|
||||||
|
echo "Expected ${expected_count} release binaries, found ${count}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Verified ${count} binaries built from ${revision} with a clean tree"
|
||||||
+191
-210
@@ -5,9 +5,9 @@ set -e
|
|||||||
set -E
|
set -E
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
||||||
if [ ! -x "$PWD/minio" ]; then
|
if [ ! -x "$PWD/silo" ]; then
|
||||||
echo "minio executable binary not found in current directory"
|
echo "Silo executable binary not found in current directory"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
WORK_DIR="$PWD/.verify-$RANDOM"
|
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||||
@@ -15,292 +15,273 @@ WORK_DIR="$PWD/.verify-$RANDOM"
|
|||||||
export MINT_MODE=core
|
export MINT_MODE=core
|
||||||
export MINT_DATA_DIR="$WORK_DIR/data"
|
export MINT_DATA_DIR="$WORK_DIR/data"
|
||||||
export SERVER_ENDPOINT="127.0.0.1:9000"
|
export SERVER_ENDPOINT="127.0.0.1:9000"
|
||||||
export ACCESS_KEY="minio"
|
export MC_HOST_verify="http://silo:silo1234@${SERVER_ENDPOINT}/"
|
||||||
export SECRET_KEY="minio123"
|
export MC_HOST_verify_ipv6="http://silo:silo1234@[::1]:9000/"
|
||||||
|
export ACCESS_KEY="silo"
|
||||||
|
export SECRET_KEY="silo1234"
|
||||||
export ENABLE_HTTPS=0
|
export ENABLE_HTTPS=0
|
||||||
export GO111MODULE=on
|
export GO111MODULE=on
|
||||||
export GOGC=25
|
export GOGC=25
|
||||||
|
export ENABLE_ADMIN=1
|
||||||
|
export MINIO_CI_CD=1
|
||||||
|
|
||||||
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
SILO_CONFIG_DIR="$WORK_DIR/.silo"
|
||||||
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" )
|
SILO=("$PWD/silo" --config-dir "$SILO_CONFIG_DIR")
|
||||||
|
|
||||||
FILE_1_MB="$MINT_DATA_DIR/datafile-1-MB"
|
FILE_1_MB="$MINT_DATA_DIR/datafile-1-MB"
|
||||||
FILE_65_MB="$MINT_DATA_DIR/datafile-65-MB"
|
FILE_65_MB="$MINT_DATA_DIR/datafile-65-MB"
|
||||||
|
|
||||||
FUNCTIONAL_TESTS="$WORK_DIR/functional-tests.sh"
|
FUNCTIONAL_TESTS="$WORK_DIR/functional-tests.sh"
|
||||||
|
|
||||||
function start_minio_fs()
|
function start_silo_fs() {
|
||||||
{
|
export MINIO_ROOT_USER=$ACCESS_KEY
|
||||||
export MINIO_ROOT_USER=$ACCESS_KEY
|
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
||||||
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
"${SILO[@]}" server "${WORK_DIR}/fs-disk" >"$WORK_DIR/fs-silo.log" 2>&1 &
|
||||||
"${MINIO[@]}" server "${WORK_DIR}/fs-disk" >"$WORK_DIR/fs-minio.log" 2>&1 &
|
|
||||||
sleep 10
|
"${WORK_DIR}/mc" ready verify
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_erasure()
|
function start_silo_erasure() {
|
||||||
{
|
"${SILO[@]}" server "${WORK_DIR}/erasure-disk1" "${WORK_DIR}/erasure-disk2" "${WORK_DIR}/erasure-disk3" "${WORK_DIR}/erasure-disk4" >"$WORK_DIR/erasure-silo.log" 2>&1 &
|
||||||
"${MINIO[@]}" server "${WORK_DIR}/erasure-disk1" "${WORK_DIR}/erasure-disk2" "${WORK_DIR}/erasure-disk3" "${WORK_DIR}/erasure-disk4" >"$WORK_DIR/erasure-minio.log" 2>&1 &
|
|
||||||
sleep 15
|
"${WORK_DIR}/mc" ready verify
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_erasure_sets()
|
function start_silo_erasure_sets() {
|
||||||
{
|
export MINIO_ENDPOINTS="${WORK_DIR}/erasure-disk-sets{1...32}"
|
||||||
export MINIO_ENDPOINTS="${WORK_DIR}/erasure-disk-sets{1...32}"
|
"${SILO[@]}" server >"$WORK_DIR/erasure-silo-sets.log" 2>&1 &
|
||||||
"${MINIO[@]}" server > "$WORK_DIR/erasure-minio-sets.log" 2>&1 &
|
|
||||||
sleep 15
|
"${WORK_DIR}/mc" ready verify
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_pool_erasure_sets()
|
function start_silo_pool_erasure_sets() {
|
||||||
{
|
export MINIO_ROOT_USER=$ACCESS_KEY
|
||||||
export MINIO_ROOT_USER=$ACCESS_KEY
|
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
||||||
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/pool-disk-sets{1...4} http://127.0.0.1:9001${WORK_DIR}/pool-disk-sets{5...8}"
|
||||||
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/pool-disk-sets{1...4} http://127.0.0.1:9001${WORK_DIR}/pool-disk-sets{5...8}"
|
"${SILO[@]}" server --address ":9000" >"$WORK_DIR/pool-silo-9000.log" 2>&1 &
|
||||||
"${MINIO[@]}" server --address ":9000" > "$WORK_DIR/pool-minio-9000.log" 2>&1 &
|
"${SILO[@]}" server --address ":9001" >"$WORK_DIR/pool-silo-9001.log" 2>&1 &
|
||||||
"${MINIO[@]}" server --address ":9001" > "$WORK_DIR/pool-minio-9001.log" 2>&1 &
|
|
||||||
|
|
||||||
sleep 40
|
"${WORK_DIR}/mc" ready verify
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_pool_erasure_sets_ipv6()
|
function start_silo_pool_erasure_sets_ipv6() {
|
||||||
{
|
export MINIO_ROOT_USER=$ACCESS_KEY
|
||||||
export MINIO_ROOT_USER=$ACCESS_KEY
|
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
||||||
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
export MINIO_ENDPOINTS="http://[::1]:9000${WORK_DIR}/pool-disk-sets-ipv6{1...4} http://[::1]:9001${WORK_DIR}/pool-disk-sets-ipv6{5...8}"
|
||||||
export MINIO_ENDPOINTS="http://[::1]:9000${WORK_DIR}/pool-disk-sets{1...4} http://[::1]:9001${WORK_DIR}/pool-disk-sets{5...8}"
|
"${SILO[@]}" server --address="[::1]:9000" >"$WORK_DIR/pool-silo-ipv6-9000.log" 2>&1 &
|
||||||
"${MINIO[@]}" server --address="[::1]:9000" > "$WORK_DIR/pool-minio-ipv6-9000.log" 2>&1 &
|
"${SILO[@]}" server --address="[::1]:9001" >"$WORK_DIR/pool-silo-ipv6-9001.log" 2>&1 &
|
||||||
"${MINIO[@]}" server --address="[::1]:9001" > "$WORK_DIR/pool-minio-ipv6-9001.log" 2>&1 &
|
|
||||||
|
|
||||||
sleep 40
|
"${WORK_DIR}/mc" ready verify_ipv6
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_dist_erasure()
|
function start_silo_dist_erasure() {
|
||||||
{
|
export MINIO_ROOT_USER=$ACCESS_KEY
|
||||||
export MINIO_ROOT_USER=$ACCESS_KEY
|
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
||||||
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/dist-disk1 http://127.0.0.1:9001${WORK_DIR}/dist-disk2 http://127.0.0.1:9002${WORK_DIR}/dist-disk3 http://127.0.0.1:9003${WORK_DIR}/dist-disk4"
|
||||||
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/dist-disk1 http://127.0.0.1:9001${WORK_DIR}/dist-disk2 http://127.0.0.1:9002${WORK_DIR}/dist-disk3 http://127.0.0.1:9003${WORK_DIR}/dist-disk4"
|
for i in $(seq 0 3); do
|
||||||
for i in $(seq 0 3); do
|
"${SILO[@]}" server --address ":900${i}" >"$WORK_DIR/dist-silo-900${i}.log" 2>&1 &
|
||||||
"${MINIO[@]}" server --address ":900${i}" > "$WORK_DIR/dist-minio-900${i}.log" 2>&1 &
|
done
|
||||||
done
|
|
||||||
|
|
||||||
sleep 40
|
"${WORK_DIR}/mc" ready verify
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test_fs()
|
function run_test_fs() {
|
||||||
{
|
start_silo_fs
|
||||||
start_minio_fs
|
|
||||||
|
|
||||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||||
rv=$?
|
rv=$?
|
||||||
|
|
||||||
pkill minio
|
pkill silo
|
||||||
sleep 3
|
sleep 3
|
||||||
|
|
||||||
if [ "$rv" -ne 0 ]; then
|
if [ "$rv" -ne 0 ]; then
|
||||||
cat "$WORK_DIR/fs-minio.log"
|
cat "$WORK_DIR/fs-silo.log"
|
||||||
fi
|
fi
|
||||||
rm -f "$WORK_DIR/fs-minio.log"
|
rm -f "$WORK_DIR/fs-silo.log"
|
||||||
|
|
||||||
return "$rv"
|
return "$rv"
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test_erasure_sets()
|
function run_test_erasure_sets() {
|
||||||
{
|
start_silo_erasure_sets
|
||||||
start_minio_erasure_sets
|
|
||||||
|
|
||||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||||
rv=$?
|
rv=$?
|
||||||
|
|
||||||
pkill minio
|
pkill silo
|
||||||
sleep 3
|
sleep 3
|
||||||
|
|
||||||
if [ "$rv" -ne 0 ]; then
|
if [ "$rv" -ne 0 ]; then
|
||||||
cat "$WORK_DIR/erasure-minio-sets.log"
|
cat "$WORK_DIR/erasure-silo-sets.log"
|
||||||
fi
|
fi
|
||||||
rm -f "$WORK_DIR/erasure-minio-sets.log"
|
rm -f "$WORK_DIR/erasure-silo-sets.log"
|
||||||
|
|
||||||
return "$rv"
|
return "$rv"
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test_pool_erasure_sets()
|
function run_test_pool_erasure_sets() {
|
||||||
{
|
start_silo_pool_erasure_sets
|
||||||
start_minio_pool_erasure_sets
|
|
||||||
|
|
||||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||||
rv=$?
|
rv=$?
|
||||||
|
|
||||||
pkill minio
|
pkill silo
|
||||||
sleep 3
|
sleep 3
|
||||||
|
|
||||||
if [ "$rv" -ne 0 ]; then
|
if [ "$rv" -ne 0 ]; then
|
||||||
for i in $(seq 0 1); do
|
for i in $(seq 0 1); do
|
||||||
echo "server$i log:"
|
echo "server$i log:"
|
||||||
cat "$WORK_DIR/pool-minio-900$i.log"
|
cat "$WORK_DIR/pool-silo-900$i.log"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for i in $(seq 0 1); do
|
for i in $(seq 0 1); do
|
||||||
rm -f "$WORK_DIR/pool-minio-900$i.log"
|
rm -f "$WORK_DIR/pool-silo-900$i.log"
|
||||||
done
|
done
|
||||||
|
|
||||||
return "$rv"
|
return "$rv"
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test_pool_erasure_sets_ipv6()
|
function run_test_pool_erasure_sets_ipv6() {
|
||||||
{
|
start_silo_pool_erasure_sets_ipv6
|
||||||
start_minio_pool_erasure_sets_ipv6
|
|
||||||
|
|
||||||
export SERVER_ENDPOINT="[::1]:9000"
|
export SERVER_ENDPOINT="[::1]:9000"
|
||||||
|
|
||||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||||
rv=$?
|
rv=$?
|
||||||
|
|
||||||
pkill minio
|
pkill silo
|
||||||
sleep 3
|
sleep 3
|
||||||
|
|
||||||
if [ "$rv" -ne 0 ]; then
|
if [ "$rv" -ne 0 ]; then
|
||||||
for i in $(seq 0 1); do
|
for i in $(seq 0 1); do
|
||||||
echo "server$i log:"
|
echo "server$i log:"
|
||||||
cat "$WORK_DIR/pool-minio-ipv6-900$i.log"
|
cat "$WORK_DIR/pool-silo-ipv6-900$i.log"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for i in $(seq 0 1); do
|
for i in $(seq 0 1); do
|
||||||
rm -f "$WORK_DIR/pool-minio-ipv6-900$i.log"
|
rm -f "$WORK_DIR/pool-silo-ipv6-900$i.log"
|
||||||
done
|
done
|
||||||
|
|
||||||
return "$rv"
|
return "$rv"
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test_erasure()
|
function run_test_erasure() {
|
||||||
{
|
start_silo_erasure
|
||||||
start_minio_erasure
|
|
||||||
|
|
||||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||||
rv=$?
|
rv=$?
|
||||||
|
|
||||||
pkill minio
|
pkill silo
|
||||||
sleep 3
|
sleep 3
|
||||||
|
|
||||||
if [ "$rv" -ne 0 ]; then
|
if [ "$rv" -ne 0 ]; then
|
||||||
cat "$WORK_DIR/erasure-minio.log"
|
cat "$WORK_DIR/erasure-silo.log"
|
||||||
fi
|
fi
|
||||||
rm -f "$WORK_DIR/erasure-minio.log"
|
rm -f "$WORK_DIR/erasure-silo.log"
|
||||||
|
|
||||||
return "$rv"
|
return "$rv"
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test_dist_erasure()
|
function run_test_dist_erasure() {
|
||||||
{
|
start_silo_dist_erasure
|
||||||
start_minio_dist_erasure
|
|
||||||
|
|
||||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||||
rv=$?
|
rv=$?
|
||||||
|
|
||||||
pkill minio
|
pkill silo
|
||||||
sleep 3
|
sleep 3
|
||||||
|
|
||||||
if [ "$rv" -ne 0 ]; then
|
if [ "$rv" -ne 0 ]; then
|
||||||
echo "server1 log:"
|
echo "server1 log:"
|
||||||
cat "$WORK_DIR/dist-minio-9000.log"
|
cat "$WORK_DIR/dist-silo-9000.log"
|
||||||
echo "server2 log:"
|
echo "server2 log:"
|
||||||
cat "$WORK_DIR/dist-minio-9001.log"
|
cat "$WORK_DIR/dist-silo-9001.log"
|
||||||
echo "server3 log:"
|
echo "server3 log:"
|
||||||
cat "$WORK_DIR/dist-minio-9002.log"
|
cat "$WORK_DIR/dist-silo-9002.log"
|
||||||
echo "server4 log:"
|
echo "server4 log:"
|
||||||
cat "$WORK_DIR/dist-minio-9003.log"
|
cat "$WORK_DIR/dist-silo-9003.log"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f "$WORK_DIR/dist-minio-9000.log" "$WORK_DIR/dist-minio-9001.log" "$WORK_DIR/dist-minio-9002.log" "$WORK_DIR/dist-minio-9003.log"
|
rm -f "$WORK_DIR/dist-silo-9000.log" "$WORK_DIR/dist-silo-9001.log" "$WORK_DIR/dist-silo-9002.log" "$WORK_DIR/dist-silo-9003.log"
|
||||||
|
|
||||||
return "$rv"
|
return "$rv"
|
||||||
}
|
}
|
||||||
|
|
||||||
function purge()
|
function purge() {
|
||||||
{
|
rm -rf "$1"
|
||||||
rm -rf "$1"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function __init__()
|
function __init__() {
|
||||||
{
|
echo "Initializing environment"
|
||||||
echo "Initializing environment"
|
mkdir -p "$WORK_DIR"
|
||||||
mkdir -p "$WORK_DIR"
|
mkdir -p "$SILO_CONFIG_DIR"
|
||||||
mkdir -p "$MINIO_CONFIG_DIR"
|
mkdir -p "$MINT_DATA_DIR"
|
||||||
mkdir -p "$MINT_DATA_DIR"
|
|
||||||
|
|
||||||
MC_BUILD_DIR="mc-$RANDOM"
|
"$(git rev-parse --show-toplevel)/buildscripts/install-mcli.sh" "${WORK_DIR}/mc"
|
||||||
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
|
||||||
echo "failed to download https://github.com/minio/mc"
|
|
||||||
purge "${MC_BUILD_DIR}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
|
shred -n 1 -s 1M - 1>"$FILE_1_MB" 2>/dev/null
|
||||||
|
shred -n 1 -s 65M - 1>"$FILE_65_MB" 2>/dev/null
|
||||||
|
|
||||||
# remove mc source.
|
## version is purposefully set to '3' for minio to migrate configuration file
|
||||||
purge "${MC_BUILD_DIR}"
|
echo '{"version": "3", "credential": {"accessKey": "silo", "secretKey": "silo1234"}, "region": "us-east-1"}' >"$SILO_CONFIG_DIR/config.json"
|
||||||
|
|
||||||
shred -n 1 -s 1M - 1>"$FILE_1_MB" 2>/dev/null
|
"$(git rev-parse --show-toplevel)/buildscripts/install-verified-fixture.sh" \
|
||||||
shred -n 1 -s 65M - 1>"$FILE_65_MB" 2>/dev/null
|
https://raw.githubusercontent.com/pgsty/mc/4c4dcc4b55baf238cd0c81030d77945b3828f157/functional-tests.sh \
|
||||||
|
9b98c8152b294d567b9bc732869226dd4f65d0f4d84092dc066a900a66a9e22c \
|
||||||
|
"$FUNCTIONAL_TESTS"
|
||||||
|
|
||||||
## version is purposefully set to '3' for minio to migrate configuration file
|
sed -i 's|-sS|-sSg|g' "$FUNCTIONAL_TESTS"
|
||||||
echo '{"version": "3", "credential": {"accessKey": "minio", "secretKey": "minio123"}, "region": "us-east-1"}' > "$MINIO_CONFIG_DIR/config.json"
|
chmod a+x "$FUNCTIONAL_TESTS"
|
||||||
|
|
||||||
if ! wget -q -O "$FUNCTIONAL_TESTS" https://raw.githubusercontent.com/minio/mc/master/functional-tests.sh; then
|
|
||||||
echo "failed to download https://raw.githubusercontent.com/minio/mc/master/functional-tests.sh"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
sed -i 's|-sS|-sSg|g' "$FUNCTIONAL_TESTS"
|
|
||||||
chmod a+x "$FUNCTIONAL_TESTS"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function main()
|
function main() {
|
||||||
{
|
echo "Testing in FS setup"
|
||||||
echo "Testing in FS setup"
|
if ! run_test_fs; then
|
||||||
if ! run_test_fs; then
|
echo "FAILED"
|
||||||
echo "FAILED"
|
purge "$WORK_DIR"
|
||||||
purge "$WORK_DIR"
|
exit 1
|
||||||
exit 1
|
fi
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Testing in Erasure setup"
|
echo "Testing in Erasure setup"
|
||||||
if ! run_test_erasure; then
|
if ! run_test_erasure; then
|
||||||
echo "FAILED"
|
echo "FAILED"
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Testing in Distributed Erasure setup"
|
echo "Testing in Distributed Erasure setup"
|
||||||
if ! run_test_dist_erasure; then
|
if ! run_test_dist_erasure; then
|
||||||
echo "FAILED"
|
echo "FAILED"
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Testing in Erasure setup as sets"
|
echo "Testing in Erasure setup as sets"
|
||||||
if ! run_test_erasure_sets; then
|
if ! run_test_erasure_sets; then
|
||||||
echo "FAILED"
|
echo "FAILED"
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Testing in Distributed Eraure expanded setup"
|
echo "Testing in Distributed Eraure expanded setup"
|
||||||
if ! run_test_pool_erasure_sets; then
|
if ! run_test_pool_erasure_sets; then
|
||||||
echo "FAILED"
|
echo "FAILED"
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Testing in Distributed Erasure expanded setup with ipv6"
|
echo "Testing in Distributed Erasure expanded setup with ipv6"
|
||||||
if ! run_test_pool_erasure_sets_ipv6; then
|
if ! run_test_pool_erasure_sets_ipv6; then
|
||||||
echo "FAILED"
|
echo "FAILED"
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
}
|
}
|
||||||
|
|
||||||
( __init__ "$@" && main "$@" )
|
trap 'purge "$WORK_DIR"' EXIT
|
||||||
rv=$?
|
__init__ "$@"
|
||||||
purge "$WORK_DIR"
|
main "$@"
|
||||||
exit "$rv"
|
|
||||||
|
|||||||
+150
@@ -0,0 +1,150 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
#
|
||||||
|
|
||||||
|
set -E
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
if [ ! -x "$PWD/silo" ]; then
|
||||||
|
echo "Silo executable binary not found in current directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||||
|
SILO_CONFIG_DIR="$WORK_DIR/.silo"
|
||||||
|
SILO=("$PWD/silo" --config-dir "$SILO_CONFIG_DIR" server)
|
||||||
|
|
||||||
|
function start_silo_3_node() {
|
||||||
|
export MINIO_ROOT_USER=silo
|
||||||
|
export MINIO_ROOT_PASSWORD=silo1234
|
||||||
|
export MINIO_ERASURE_SET_DRIVE_COUNT=6
|
||||||
|
export MINIO_CI_CD=1
|
||||||
|
|
||||||
|
start_port=$1
|
||||||
|
args=""
|
||||||
|
for i in $(seq 1 3); do
|
||||||
|
args="$args http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/1/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/2/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/3/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/4/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/5/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/6/"
|
||||||
|
done
|
||||||
|
|
||||||
|
"${SILO[@]}" --address ":$((start_port + 1))" $args >"${WORK_DIR}/dist-silo-server1.log" 2>&1 &
|
||||||
|
pid1=$!
|
||||||
|
disown ${pid1}
|
||||||
|
|
||||||
|
"${SILO[@]}" --address ":$((start_port + 2))" $args >"${WORK_DIR}/dist-silo-server2.log" 2>&1 &
|
||||||
|
pid2=$!
|
||||||
|
disown $pid2
|
||||||
|
|
||||||
|
"${SILO[@]}" --address ":$((start_port + 3))" $args >"${WORK_DIR}/dist-silo-server3.log" 2>&1 &
|
||||||
|
pid3=$!
|
||||||
|
disown $pid3
|
||||||
|
|
||||||
|
export MC_HOST_mysilo="http://silo:silo1234@127.0.0.1:$((start_port + 1))"
|
||||||
|
|
||||||
|
timeout 15m /tmp/mc ready mysilo || fail
|
||||||
|
|
||||||
|
# Wait for all drives to be online and formatted
|
||||||
|
while [ $(/tmp/mc admin info --json mysilo | jq '.info.servers[].drives[].state | select(. != "ok")' | wc -l) -gt 0 ]; do sleep 1; done
|
||||||
|
# Wait for all drives to be healed
|
||||||
|
while [ $(/tmp/mc admin info --json mysilo | jq '.info.servers[].drives[].healing | select(. != null) | select(. == true)' | wc -l) -gt 0 ]; do sleep 1; done
|
||||||
|
|
||||||
|
# Wait for Status: in MinIO output
|
||||||
|
while true; do
|
||||||
|
rv=$(check_online)
|
||||||
|
if [ "$rv" != "1" ]; then
|
||||||
|
# success
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if we should retry
|
||||||
|
retry=$((retry + 1))
|
||||||
|
if [ $retry -le 20 ]; then
|
||||||
|
sleep 5
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Failure
|
||||||
|
fail
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! ps -p $pid1 1>&2 >/dev/null; then
|
||||||
|
echo "silo-server-1 is not running." && fail
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! ps -p $pid2 1>&2 >/dev/null; then
|
||||||
|
echo "silo-server-2 is not running." && fail
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! ps -p $pid3 1>&2 >/dev/null; then
|
||||||
|
echo "silo-server-3 is not running." && fail
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! pkill silo; then
|
||||||
|
fail
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
if pgrep silo; then
|
||||||
|
# forcibly killing, to proceed further properly.
|
||||||
|
if ! pkill -9 silo; then
|
||||||
|
echo "no Silo process running anymore, proceed."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function fail() {
|
||||||
|
for i in $(seq 1 3); do
|
||||||
|
echo "server$i log:"
|
||||||
|
cat "${WORK_DIR}/dist-silo-server$i.log"
|
||||||
|
done
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_online() {
|
||||||
|
if ! grep -q 'API:' ${WORK_DIR}/dist-silo-*.log; then
|
||||||
|
echo "1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function purge() {
|
||||||
|
echo rm -rf "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
function __init__() {
|
||||||
|
echo "Initializing environment"
|
||||||
|
mkdir -p "$WORK_DIR"
|
||||||
|
mkdir -p "$SILO_CONFIG_DIR"
|
||||||
|
|
||||||
|
## version is purposefully set to '3' for minio to migrate configuration file
|
||||||
|
echo '{"version": "3", "credential": {"accessKey": "silo", "secretKey": "silo1234"}, "region": "us-east-1"}' >"$SILO_CONFIG_DIR/config.json"
|
||||||
|
|
||||||
|
if [ ! -f /tmp/mc ]; then
|
||||||
|
"$(git rev-parse --show-toplevel)/buildscripts/install-mcli.sh" /tmp/mc
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function perform_test() {
|
||||||
|
start_silo_3_node $2
|
||||||
|
|
||||||
|
echo "Testing Distributed Erasure setup healing of drives"
|
||||||
|
echo "Remove the contents of the disks belonging to '${1}' erasure set"
|
||||||
|
|
||||||
|
rm -rf ${WORK_DIR}/${1}/*/
|
||||||
|
|
||||||
|
set -x
|
||||||
|
start_silo_3_node $2
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
# use same ports for all tests
|
||||||
|
start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
|
|
||||||
|
perform_test "2" ${start_port}
|
||||||
|
perform_test "1" ${start_port}
|
||||||
|
perform_test "3" ${start_port}
|
||||||
|
}
|
||||||
|
|
||||||
|
(__init__ "$@" && main "$@")
|
||||||
|
rv=$?
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit "$rv"
|
||||||
+96
@@ -0,0 +1,96 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
set -E
|
||||||
|
set -o pipefail
|
||||||
|
set -x
|
||||||
|
|
||||||
|
if [ ! -x "$PWD/silo" ]; then
|
||||||
|
echo "Silo executable binary not found in current directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
WORK_DIR="$(mktemp -d)"
|
||||||
|
SILO_CONFIG_DIR="$WORK_DIR/.silo"
|
||||||
|
SILO=("$PWD/silo" --config-dir "$SILO_CONFIG_DIR" server)
|
||||||
|
|
||||||
|
function start_silo() {
|
||||||
|
start_port=$1
|
||||||
|
|
||||||
|
export MINIO_ROOT_USER=silo
|
||||||
|
export MINIO_ROOT_PASSWORD=silo1234
|
||||||
|
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||||
|
unset MINIO_CI_CD
|
||||||
|
unset CI
|
||||||
|
|
||||||
|
args=()
|
||||||
|
for i in $(seq 1 4); do
|
||||||
|
args+=("http://localhost:$((start_port + i))${WORK_DIR}/mnt/disk$i/ ")
|
||||||
|
done
|
||||||
|
|
||||||
|
for i in $(seq 1 4); do
|
||||||
|
"${SILO[@]}" --address ":$((start_port + i))" ${args[@]} 2>&1 >"${WORK_DIR}/server$i.log" &
|
||||||
|
done
|
||||||
|
|
||||||
|
# Wait until all nodes return 403
|
||||||
|
for i in $(seq 1 4); do
|
||||||
|
while [ "$(curl -m 1 -s -o /dev/null -w "%{http_code}" http://localhost:$((start_port + i)))" -ne "403" ]; do
|
||||||
|
echo -n "."
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prepare fake disks with losetup
|
||||||
|
function prepare_block_devices() {
|
||||||
|
set -e
|
||||||
|
mkdir -p ${WORK_DIR}/disks/ ${WORK_DIR}/mnt/
|
||||||
|
sudo modprobe loop
|
||||||
|
for i in 1 2 3 4; do
|
||||||
|
dd if=/dev/zero of=${WORK_DIR}/disks/img.${i} bs=1M count=2000
|
||||||
|
device=$(sudo losetup --find --show ${WORK_DIR}/disks/img.${i})
|
||||||
|
sudo mkfs.ext4 -F ${device}
|
||||||
|
mkdir -p ${WORK_DIR}/mnt/disk${i}/
|
||||||
|
sudo mount ${device} ${WORK_DIR}/mnt/disk${i}/
|
||||||
|
sudo chown "$(id -u):$(id -g)" ${device} ${WORK_DIR}/mnt/disk${i}/
|
||||||
|
done
|
||||||
|
set +e
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start a distributed MinIO setup, unmount one disk and check if it is formatted
|
||||||
|
function main() {
|
||||||
|
start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
|
start_silo ${start_port}
|
||||||
|
|
||||||
|
# Unmount the disk, after the unmount the device id
|
||||||
|
# /tmp/xxx/mnt/disk4 will be the same as '/' and it
|
||||||
|
# will be detected as root disk
|
||||||
|
while [ "$u" != "0" ]; do
|
||||||
|
sudo umount ${WORK_DIR}/mnt/disk4/
|
||||||
|
u=$?
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
# Wait until MinIO self heal kicks in
|
||||||
|
sleep 60
|
||||||
|
|
||||||
|
if [ -f ${WORK_DIR}/mnt/disk4/.minio.sys/format.json ]; then
|
||||||
|
echo "A root disk is formatted unexpectedely"
|
||||||
|
cat "${WORK_DIR}/server4.log"
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
pkill silo
|
||||||
|
sudo umount ${WORK_DIR}/mnt/disk{1..3}/
|
||||||
|
sudo rm /dev/minio-loopdisk*
|
||||||
|
rm -rf "$WORK_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
(prepare_block_devices)
|
||||||
|
(main "$@")
|
||||||
|
rv=$?
|
||||||
|
|
||||||
|
cleanup
|
||||||
|
exit "$rv"
|
||||||
+128
-82
@@ -4,117 +4,163 @@
|
|||||||
set -E
|
set -E
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
||||||
if [ ! -x "$PWD/minio" ]; then
|
if [ ! -x "$PWD/silo" ]; then
|
||||||
echo "minio executable binary not found in current directory"
|
echo "Silo executable binary not found in current directory"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
WORK_DIR="$PWD/.verify-$RANDOM"
|
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||||
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
SILO_CONFIG_DIR="$WORK_DIR/.silo"
|
||||||
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
SILO=("$PWD/silo" --config-dir "$SILO_CONFIG_DIR" server)
|
||||||
|
GOPATH=/tmp/gopath
|
||||||
|
|
||||||
function start_minio_3_node() {
|
function start_silo_3_node() {
|
||||||
export MINIO_ROOT_USER=minio
|
for i in $(seq 1 3); do
|
||||||
export MINIO_ROOT_PASSWORD=minio123
|
rm "${WORK_DIR}/dist-silo-server$i.log"
|
||||||
export MINIO_ERASURE_SET_DRIVE_COUNT=6
|
done
|
||||||
|
|
||||||
start_port=$(shuf -i 10000-65000 -n 1)
|
export MINIO_ROOT_USER=silo
|
||||||
args=""
|
export MINIO_ROOT_PASSWORD=silo1234
|
||||||
for i in $(seq 1 3); do
|
export MINIO_ERASURE_SET_DRIVE_COUNT=6
|
||||||
args="$args http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/1/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/2/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/3/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/4/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/5/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/6/"
|
export MINIO_CI_CD=1
|
||||||
done
|
|
||||||
|
|
||||||
"${MINIO[@]}" --address ":$[$start_port+1]" $args > "${WORK_DIR}/dist-minio-server1.log" 2>&1 &
|
first_time=$(find ${WORK_DIR}/ | grep format.json | wc -l)
|
||||||
disown $!
|
|
||||||
|
|
||||||
"${MINIO[@]}" --address ":$[$start_port+2]" $args > "${WORK_DIR}/dist-minio-server2.log" 2>&1 &
|
start_port=$1
|
||||||
disown $!
|
args=""
|
||||||
|
for d in $(seq 1 3 5); do
|
||||||
|
args="$args http://127.0.0.1:$((start_port + 1))${WORK_DIR}/1/${d}/ http://127.0.0.1:$((start_port + 2))${WORK_DIR}/2/${d}/ http://127.0.0.1:$((start_port + 3))${WORK_DIR}/3/${d}/ "
|
||||||
|
d=$((d + 1))
|
||||||
|
args="$args http://127.0.0.1:$((start_port + 1))${WORK_DIR}/1/${d}/ http://127.0.0.1:$((start_port + 2))${WORK_DIR}/2/${d}/ http://127.0.0.1:$((start_port + 3))${WORK_DIR}/3/${d}/ "
|
||||||
|
done
|
||||||
|
|
||||||
"${MINIO[@]}" --address ":$[$start_port+3]" $args > "${WORK_DIR}/dist-minio-server3.log" 2>&1 &
|
"${SILO[@]}" --address ":$((start_port + 1))" $args >"${WORK_DIR}/dist-silo-server1.log" 2>&1 &
|
||||||
disown $!
|
pid1=$!
|
||||||
|
disown ${pid1}
|
||||||
|
|
||||||
sleep "$1"
|
"${SILO[@]}" --address ":$((start_port + 2))" $args >"${WORK_DIR}/dist-silo-server2.log" 2>&1 &
|
||||||
if [ "$(pgrep -c minio)" -ne 3 ]; then
|
pid2=$!
|
||||||
for i in $(seq 1 3); do
|
disown $pid2
|
||||||
echo "server$i log:"
|
|
||||||
cat "${WORK_DIR}/dist-minio-server$i.log"
|
|
||||||
done
|
|
||||||
echo "FAILED"
|
|
||||||
purge "$WORK_DIR"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if ! pkill minio; then
|
|
||||||
for i in $(seq 1 3); do
|
|
||||||
echo "server$i log:"
|
|
||||||
cat "${WORK_DIR}/dist-minio-server$i.log"
|
|
||||||
done
|
|
||||||
echo "FAILED"
|
|
||||||
purge "$WORK_DIR"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
sleep 1;
|
"${SILO[@]}" --address ":$((start_port + 3))" $args >"${WORK_DIR}/dist-silo-server3.log" 2>&1 &
|
||||||
if pgrep minio; then
|
pid3=$!
|
||||||
# forcibly killing, to proceed further properly.
|
disown $pid3
|
||||||
if ! pkill -9 minio; then
|
|
||||||
echo "no minio process running anymore, proceed."
|
export MC_HOST_mysilo="http://silo:silo1234@127.0.0.1:$((start_port + 1))"
|
||||||
fi
|
timeout 15m /tmp/mc ready mysilo || fail
|
||||||
fi
|
|
||||||
|
[ ${first_time} -eq 0 ] && upload_objects
|
||||||
|
[ ${first_time} -ne 0 ] && sleep 120
|
||||||
|
|
||||||
|
if ! ps -p $pid1 1>&2 >/dev/null; then
|
||||||
|
echo "silo server 1 is not running" && fail
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! ps -p $pid2 1>&2 >/dev/null; then
|
||||||
|
echo "silo server 2 is not running" && fail
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! ps -p $pid3 1>&2 >/dev/null; then
|
||||||
|
echo "silo server 3 is not running" && fail
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! pkill silo; then
|
||||||
|
fail
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
if pgrep silo; then
|
||||||
|
# forcibly killing, to proceed further properly.
|
||||||
|
if ! pkill -9 silo; then
|
||||||
|
echo "no Silo process running anymore, proceed."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function check_heal() {
|
||||||
|
if ! grep -q 'API:' ${WORK_DIR}/dist-silo-*.log; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
function check_online() {
|
for ((i = 0; i < 20; i++)); do
|
||||||
if grep -q 'Unable to initialize sub-systems' ${WORK_DIR}/dist-minio-*.log; then
|
test -f ${WORK_DIR}/$1/1/.minio.sys/format.json
|
||||||
echo "1"
|
v1=$?
|
||||||
fi
|
nextInES=$(($1 + 1)) && [ $nextInES -gt 3 ] && nextInES=1
|
||||||
|
foundFiles1=$(find ${WORK_DIR}/$1/1/ | grep -v .minio.sys | grep xl.meta | wc -l)
|
||||||
|
foundFiles2=$(find ${WORK_DIR}/$nextInES/1/ | grep -v .minio.sys | grep xl.meta | wc -l)
|
||||||
|
test $foundFiles1 -eq $foundFiles2
|
||||||
|
v2=$?
|
||||||
|
[ $v1 == 0 -a $v2 == 0 ] && return 0
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
function purge()
|
function purge() {
|
||||||
{
|
rm -rf "$1"
|
||||||
rm -rf "$1"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function __init__()
|
function fail() {
|
||||||
{
|
for i in $(seq 1 3); do
|
||||||
echo "Initializing environment"
|
echo "server$i log:"
|
||||||
mkdir -p "$WORK_DIR"
|
cat "${WORK_DIR}/dist-silo-server$i.log"
|
||||||
mkdir -p "$MINIO_CONFIG_DIR"
|
done
|
||||||
|
pkill -9 silo
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
## version is purposefully set to '3' for minio to migrate configuration file
|
function __init__() {
|
||||||
echo '{"version": "3", "credential": {"accessKey": "minio", "secretKey": "minio123"}, "region": "us-east-1"}' > "$MINIO_CONFIG_DIR/config.json"
|
echo "Initializing environment"
|
||||||
|
mkdir -p "$WORK_DIR"
|
||||||
|
mkdir -p "$SILO_CONFIG_DIR"
|
||||||
|
|
||||||
|
## version is purposefully set to '3' for minio to migrate configuration file
|
||||||
|
echo '{"version": "3", "credential": {"accessKey": "silo", "secretKey": "silo1234"}, "region": "us-east-1"}' >"$SILO_CONFIG_DIR/config.json"
|
||||||
|
|
||||||
|
if [ ! -f /tmp/mc ]; then
|
||||||
|
"$(git rev-parse --show-toplevel)/buildscripts/install-mcli.sh" /tmp/mc
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function upload_objects() {
|
||||||
|
/tmp/mc mb mysilo/testbucket/
|
||||||
|
for ((i = 0; i < 20; i++)); do
|
||||||
|
echo "my content" | /tmp/mc pipe mysilo/testbucket/file-$i
|
||||||
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
function perform_test() {
|
function perform_test() {
|
||||||
start_minio_3_node 120
|
start_port=$2
|
||||||
|
|
||||||
echo "Testing Distributed Erasure setup healing of drives"
|
start_silo_3_node $start_port
|
||||||
echo "Remove the contents of the disks belonging to '${1}' erasure set"
|
|
||||||
|
|
||||||
rm -rf ${WORK_DIR}/${1}/*/
|
echo "Testing Distributed Erasure setup healing of drives"
|
||||||
|
echo "Remove the contents of the disks belonging to '${1}' node"
|
||||||
|
|
||||||
start_minio_3_node 120
|
rm -rf ${WORK_DIR}/${1}/*/
|
||||||
|
|
||||||
rv=$(check_online)
|
set -x
|
||||||
if [ "$rv" == "1" ]; then
|
start_silo_3_node $start_port
|
||||||
for i in $(seq 1 3); do
|
|
||||||
echo "server$i log:"
|
check_heal ${1}
|
||||||
cat "${WORK_DIR}/dist-minio-server$i.log"
|
rv=$?
|
||||||
done
|
if [ "$rv" == "1" ]; then
|
||||||
pkill -9 minio
|
fail
|
||||||
echo "FAILED"
|
fi
|
||||||
purge "$WORK_DIR"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function main()
|
function main() {
|
||||||
{
|
# use same ports for all tests
|
||||||
perform_test "2"
|
start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
perform_test "1"
|
|
||||||
perform_test "3"
|
perform_test "2" ${start_port}
|
||||||
|
perform_test "1" ${start_port}
|
||||||
|
perform_test "3" ${start_port}
|
||||||
}
|
}
|
||||||
|
|
||||||
( __init__ "$@" && main "$@" )
|
(__init__ "$@" && main "$@")
|
||||||
rv=$?
|
rv=$?
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit "$rv"
|
exit "$rv"
|
||||||
|
|||||||
Executable
+122
@@ -0,0 +1,122 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
repo_dir="$(cd "${script_dir}/.." && pwd)"
|
||||||
|
baseline_commit="${HELM_LEGACY_COMMIT:-d88f46cce}"
|
||||||
|
helm_image="${HELM_IMAGE:-alpine/helm:3.18.6@sha256:c6d8088ddb279625a2e1ca3b08b22c18c946d1f65c8b810f28f1597435a1134c}"
|
||||||
|
work_dir="$(mktemp -d "${TMPDIR:-/tmp}/silo-helm.XXXXXX")"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -rf "${work_dir}"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
cd "${repo_dir}"
|
||||||
|
git cat-file -e "${baseline_commit}^{commit}"
|
||||||
|
git archive "${baseline_commit}" helm/minio | tar -x -C "${work_dir}"
|
||||||
|
|
||||||
|
if command -v helm >/dev/null 2>&1; then
|
||||||
|
new_chart="${repo_dir}/helm/silo"
|
||||||
|
old_chart="${work_dir}/helm/minio"
|
||||||
|
output_dir="${work_dir}"
|
||||||
|
helm_run() {
|
||||||
|
helm "$@"
|
||||||
|
}
|
||||||
|
else
|
||||||
|
command -v docker >/dev/null 2>&1 || {
|
||||||
|
echo "helm or docker is required" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
new_chart=/repo/helm/silo
|
||||||
|
old_chart=/check/helm/minio
|
||||||
|
output_dir=/check
|
||||||
|
helm_run() {
|
||||||
|
docker run --rm \
|
||||||
|
-v "${repo_dir}:/repo:ro" \
|
||||||
|
-v "${work_dir}:/check" \
|
||||||
|
"${helm_image}" "$@"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
helm_run lint "${new_chart}"
|
||||||
|
helm_run template silo "${new_chart}" \
|
||||||
|
--namespace silo \
|
||||||
|
--set rootUser=silo-admin \
|
||||||
|
--set rootPassword=test-password-123456 >/dev/null
|
||||||
|
helm_run template silo "${new_chart}" \
|
||||||
|
--namespace silo \
|
||||||
|
--set mode=standalone \
|
||||||
|
--set replicas=1 \
|
||||||
|
--set persistence.enabled=false \
|
||||||
|
--set rootUser=silo-admin \
|
||||||
|
--set rootPassword=test-password-123456 >/dev/null
|
||||||
|
|
||||||
|
# Exercise optional templates that the default render leaves dormant.
|
||||||
|
helm_run template silo-all "${new_chart}" \
|
||||||
|
--namespace silo \
|
||||||
|
--set rootUser=silo-admin \
|
||||||
|
--set rootPassword=test-password-123456 \
|
||||||
|
--set tls.enabled=true \
|
||||||
|
--set tls.certSecret=silo-tls \
|
||||||
|
--set trustedCertsSecret=silo-trusted-ca \
|
||||||
|
--set ingress.enabled=true \
|
||||||
|
--set consoleIngress.enabled=true \
|
||||||
|
--set networkPolicy.enabled=true \
|
||||||
|
--set podDisruptionBudget.enabled=true \
|
||||||
|
--set metrics.serviceMonitor.enabled=true \
|
||||||
|
--set metrics.serviceMonitor.includeNode=true \
|
||||||
|
--set 'buckets[0].name=chart-test' \
|
||||||
|
--set 'buckets[0].policy=none' \
|
||||||
|
--set 'buckets[0].purge=false' >/dev/null
|
||||||
|
|
||||||
|
# Existing values commonly address the historical myminio target. Render the
|
||||||
|
# custom-command path explicitly so both the new and compatibility aliases are
|
||||||
|
# protected by the release gate rather than only by a source-text assertion.
|
||||||
|
custom_render="${work_dir}/custom-command.yaml"
|
||||||
|
helm_run template silo-custom "${new_chart}" \
|
||||||
|
--namespace silo \
|
||||||
|
--set rootUser=silo-admin \
|
||||||
|
--set rootPassword=test-password-123456 \
|
||||||
|
--set-string 'customCommands[0].command=admin info myminio' \
|
||||||
|
--show-only templates/configmap.yaml >"${custom_render}"
|
||||||
|
for expected in \
|
||||||
|
'alias set mysilo' \
|
||||||
|
'alias set myminio' \
|
||||||
|
'runCommand admin info myminio'; do
|
||||||
|
grep -F -- "${expected}" "${custom_render}" >/dev/null || {
|
||||||
|
echo "rendered custom command is missing: ${expected}" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
done
|
||||||
|
|
||||||
|
old_render="${work_dir}/legacy.yaml"
|
||||||
|
new_render="${work_dir}/candidate.yaml"
|
||||||
|
helm_run template my-release "${old_chart}" \
|
||||||
|
--namespace my-namespace \
|
||||||
|
--set rootUser=legacy-admin \
|
||||||
|
--set rootPassword=legacy-password-123456 >"${old_render}"
|
||||||
|
helm_run template my-release "${new_chart}" \
|
||||||
|
--namespace my-namespace \
|
||||||
|
-f "${old_chart}/values.yaml" \
|
||||||
|
--set rootUser=legacy-admin \
|
||||||
|
--set rootPassword=legacy-password-123456 \
|
||||||
|
--set nameOverride=minio \
|
||||||
|
--set fullnameOverride=my-release-minio \
|
||||||
|
--set serviceAccount.name=minio-sa \
|
||||||
|
--set image.repository=pgsty/silo \
|
||||||
|
--set mcImage.repository=pgsty/silo \
|
||||||
|
--set-string image.tag=RELEASE.2026-08-04T00-00-00Z \
|
||||||
|
--set-string mcImage.tag=RELEASE.2026-08-04T00-00-00Z >"${new_render}"
|
||||||
|
|
||||||
|
go run ./buildscripts/helm-migration-guard "${old_render}" "${new_render}"
|
||||||
|
|
||||||
|
helm_run package "${new_chart}" --destination "${output_dir}" >/dev/null
|
||||||
|
test -s "${work_dir}/silo-7.0.2.tgz"
|
||||||
|
if find "${work_dir}" -maxdepth 1 -type f -name 'minio-*.tgz' | grep -q .; then
|
||||||
|
echo "Helm packaging emitted a legacy MinIO chart name" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Silo Helm lint, render, legacy-upgrade, and package checks passed"
|
||||||
Executable
+220
@@ -0,0 +1,220 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
repo_dir="$(cd "${script_dir}/.." && pwd)"
|
||||||
|
cd "${repo_dir}"
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "Silo rebrand verification failed: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
require_file() {
|
||||||
|
[ -f "$1" ] || fail "missing required file: $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_text() {
|
||||||
|
local file="$1"
|
||||||
|
local text="$2"
|
||||||
|
grep -Fq -- "${text}" "${file}" || fail "${file} does not contain: ${text}"
|
||||||
|
}
|
||||||
|
|
||||||
|
reject_text() {
|
||||||
|
local file="$1"
|
||||||
|
local text="$2"
|
||||||
|
if grep -Fq -- "${text}" "${file}"; then
|
||||||
|
fail "${file} still contains forbidden delivery text: ${text}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
for file in \
|
||||||
|
.github/goreleaser.yml \
|
||||||
|
.github/nfpm.yml \
|
||||||
|
buildscripts/package/lifecycle_test.sh \
|
||||||
|
buildscripts/verify-helm-migration.sh \
|
||||||
|
Dockerfile.goreleaser \
|
||||||
|
Dockerfile.distroless \
|
||||||
|
dockerscripts/build-static-curl.sh \
|
||||||
|
dockerscripts/docker-entrypoint.sh \
|
||||||
|
helm/silo/Chart.yaml \
|
||||||
|
helm/silo/values.yaml \
|
||||||
|
silo.service \
|
||||||
|
silo.env \
|
||||||
|
silo.sysusers; do
|
||||||
|
require_file "${file}"
|
||||||
|
done
|
||||||
|
|
||||||
|
for retired_path in \
|
||||||
|
CNAME _config.yml index.yaml helm-reindex.sh helm/minio helm-releases \
|
||||||
|
Dockerfile Dockerfile.cicd Dockerfile.hotfix Dockerfile.release \
|
||||||
|
Dockerfile.release.old_cpu Dockerfile.scratch docker-buildx.sh \
|
||||||
|
minio.service cmd/callhome.go buildscripts/upgrade-tests .github/logo.svg \
|
||||||
|
docs/federation/lookup/bucket-lookup.png \
|
||||||
|
docs/screenshots/Minio_Cloud_Native_Arch.jpg \
|
||||||
|
docs/screenshots/Minio_Cloud_Native_Arch.png \
|
||||||
|
docs/screenshots/Minio_Cloud_Native_Arch.svg \
|
||||||
|
docs/screenshots/Architecture-diagram_distributed_8.jpg \
|
||||||
|
docs/screenshots/Architecture-diagram_distributed_8.png \
|
||||||
|
docs/screenshots/Architecture-diagram_distributed_8.svg \
|
||||||
|
docs/screenshots/Architecture-diagram_distributed_16.jpg \
|
||||||
|
docs/screenshots/Architecture-diagram_distributed_16.png \
|
||||||
|
docs/screenshots/Architecture-diagram_distributed_16.svg \
|
||||||
|
docs/screenshots/Architecture-diagram_distributed_nm.png \
|
||||||
|
docs/screenshots/Example-1.jpg docs/screenshots/Example-1.png \
|
||||||
|
docs/screenshots/Example-2.jpg docs/screenshots/Example-2.png \
|
||||||
|
docs/screenshots/Example-3.jpg docs/screenshots/Example-3.png \
|
||||||
|
docs/screenshots/pic1.png docs/screenshots/pic2.png \
|
||||||
|
docs/metrics/prometheus/grafana/grafana-minio.png \
|
||||||
|
docs/metrics/prometheus/grafana/bucket/grafana-bucket.png \
|
||||||
|
docs/metrics/prometheus/grafana/node/grafana-node.png \
|
||||||
|
docs/metrics/prometheus/grafana/replication/grafana-replication-cluster.png \
|
||||||
|
docs/metrics/prometheus/grafana/replication/grafana-replication-node.png; do
|
||||||
|
[ ! -e "${retired_path}" ] || fail "retired upstream delivery path remains: ${retired_path}"
|
||||||
|
done
|
||||||
|
|
||||||
|
require_text .github/goreleaser.yml "binary: silo"
|
||||||
|
require_text .github/goreleaser.yml 'name_template: "silo_{{ .Env.PKG_VERSION }}_{{ .Os }}_{{ .Arch }}"'
|
||||||
|
require_text .github/goreleaser.yml "sboms:"
|
||||||
|
require_text .github/goreleaser.yml "artifacts: archive"
|
||||||
|
require_text .github/goreleaser.yml "cmd: cosign"
|
||||||
|
# shellcheck disable=SC2016 # Match the literal GoReleaser template variable.
|
||||||
|
require_text .github/goreleaser.yml 'signature: "${artifact}.sigstore.json"'
|
||||||
|
require_text .github/nfpm.yml "name: silo"
|
||||||
|
require_text .github/nfpm.yml "dst: /usr/bin/silo"
|
||||||
|
require_text .github/nfpm.yml "dst: /etc/default/silo"
|
||||||
|
require_text .github/nfpm.yml "dst: /usr/lib/sysusers.d/silo.conf"
|
||||||
|
require_text buildscripts/package/lifecycle_test.sh "Silo package lifecycle checks passed"
|
||||||
|
require_text buildscripts/verify-helm-migration.sh "Silo Helm lint, render, legacy-upgrade, and package checks passed"
|
||||||
|
require_text silo.service "Conflicts=minio.service"
|
||||||
|
require_text silo.service "EnvironmentFile=-/etc/default/minio"
|
||||||
|
require_text silo.service "EnvironmentFile=-/etc/default/silo"
|
||||||
|
# shellcheck disable=SC2016 # Match the literal service environment variables.
|
||||||
|
require_text silo.service 'ExecStart=/usr/bin/silo server $MINIO_OPTS $MINIO_VOLUMES'
|
||||||
|
require_text README.md "/etc/systemd/system/silo.service.d/10-legacy-user.conf"
|
||||||
|
require_text README_ZH.md "/etc/systemd/system/silo.service.d/10-legacy-user.conf"
|
||||||
|
require_text Dockerfile.goreleaser "COPY silo /usr/bin/silo"
|
||||||
|
require_text Dockerfile.goreleaser 'CMD ["silo"]'
|
||||||
|
require_text Dockerfile.goreleaser "MC_AMD64_SHA256="
|
||||||
|
require_text Dockerfile.goreleaser "Published checksum drift"
|
||||||
|
require_text Dockerfile.distroless 'COPY --chmod=0755 silo /usr/bin/silo'
|
||||||
|
require_text Dockerfile.distroless 'ENTRYPOINT ["/usr/bin/silo"]'
|
||||||
|
require_text Dockerfile.distroless '"/usr/bin/silo", "healthcheck", "ready"'
|
||||||
|
require_text dockerscripts/build-static-curl.sh "sha256sum -c"
|
||||||
|
require_text helm/silo/Chart.yaml "name: silo"
|
||||||
|
require_text helm/silo/values.yaml "repository: pgsty/silo"
|
||||||
|
require_text helm/silo/templates/deployment.yaml "/usr/bin/docker-entrypoint.sh silo server"
|
||||||
|
require_text helm/silo/templates/statefulset.yaml "/usr/bin/docker-entrypoint.sh silo server"
|
||||||
|
require_text docs/orchestration/docker-compose/docker-compose.yaml 'http://silo{1...4}/data{1...2}'
|
||||||
|
require_text docs/resiliency/docker-compose.yaml 'http://silo{1...4}/data{1...8}'
|
||||||
|
require_text docs/distributed/DECOMMISSION.md 'systemctl restart silo'
|
||||||
|
# shellcheck disable=SC2016 # Match the literal shell variable.
|
||||||
|
require_text docs/resiliency/resiliency-tests.sh 'docker exec resiliency-silo$NODE-1'
|
||||||
|
require_text .github/workflows/release.yml "Attest downloadable release artifacts"
|
||||||
|
require_text .github/workflows/release.yml "packages_checksums.txt"
|
||||||
|
require_text .github/workflows/docker-release.yml "Attest multi-architecture image provenance"
|
||||||
|
require_text .github/workflows/docker-release.yml "index.docker.io/pgsty/silo"
|
||||||
|
|
||||||
|
# Copyright notices credit both parties with fixed terms: upstream MinIO
|
||||||
|
# development ends at its own last year, and the fork's own term starts when
|
||||||
|
# the fork did. Deriving the upstream end year from the clock would extend
|
||||||
|
# MinIO's copyright term every January.
|
||||||
|
require_text cmd/build-constants.go 'upstreamCopyrightEndYear = "2025"'
|
||||||
|
require_text cmd/build-constants.go 'forkCopyrightStartYear = "2025"'
|
||||||
|
require_text cmd/main.go 'upstreamCopyrightEndYear'
|
||||||
|
reject_text cmd/main.go 'CopyrightYear = strconv.Itoa(time.Now().Year())'
|
||||||
|
require_text NOTICE 'MinIO Project, (C) 2015-2025 MinIO, Inc.'
|
||||||
|
require_text NOTICE 'Silo Project modifications, (C) 2025-2026 PGSTY.'
|
||||||
|
|
||||||
|
# Contribution policy: no CLA, inbound=outbound, DCO sign-off enforced in CI.
|
||||||
|
require_file .github/workflows/dco.yml
|
||||||
|
require_text .github/workflows/dco.yml "Signed-off-by"
|
||||||
|
require_text CONTRIBUTING.md "developercertificate.org"
|
||||||
|
require_text CONTRIBUTING.md "No CLA"
|
||||||
|
|
||||||
|
for file in .github/nfpm.yml Dockerfile.goreleaser silo.service; do
|
||||||
|
reject_text "${file}" "/usr/bin/minio"
|
||||||
|
reject_text "${file}" "/usr/local/bin/minio"
|
||||||
|
done
|
||||||
|
reject_text Dockerfile.goreleaser "MINIO_UPDATE_MINISIGN_PUBKEY"
|
||||||
|
reject_text buildscripts/minio-upgrade.sh "docker system prune"
|
||||||
|
reject_text buildscripts/minio-upgrade.sh "docker volume prune"
|
||||||
|
reject_text docs/orchestration/docker-compose/docker-compose.yaml 'http://minio{1...4}'
|
||||||
|
reject_text docs/resiliency/docker-compose.yaml 'http://minio{1...4}'
|
||||||
|
reject_text docs/distributed/DECOMMISSION.md 'systemctl restart minio'
|
||||||
|
reject_text docs/resiliency/resiliency-tests.sh 'resiliency-minio'
|
||||||
|
reject_text docs/resiliency/resiliency-tests.sh 'docker system prune'
|
||||||
|
reject_text docs/resiliency/resiliency-tests.sh 'docker image prune'
|
||||||
|
reject_text docs/resiliency/resiliency-tests.sh 'docker ps -q'
|
||||||
|
|
||||||
|
if grep -Ev '^[[:space:]]*(#|$)' silo.env | grep -q '='; then
|
||||||
|
fail "silo.env must not contain active assignments that shadow /etc/default/minio"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if rg -n 'pgsty/minio:' .github/workflows Dockerfile.goreleaser helm/silo; then
|
||||||
|
fail "an active delivery surface still publishes the frozen pgsty/minio image"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The repository and its default branch are pgsty/silo and main. The invariant
|
||||||
|
# is that the old name is never a live target, not that it is never spoken: the
|
||||||
|
# READMEs have to name it to explain the rename and to point at the archived
|
||||||
|
# artifacts, which is the opposite of stranding a reader on it. CONTRIBUTORS.md
|
||||||
|
# also quotes historical issue titles.
|
||||||
|
#
|
||||||
|
# So two rules. First, no live URL may resolve to the old repository anywhere,
|
||||||
|
# READMEs and CONTRIBUTORS.md included.
|
||||||
|
old_repo_pattern='pgsty/minio(\.git)?([^[:alnum:]_.-]|$)'
|
||||||
|
stale_repo_url="$(rg -n -e "github\.com/${old_repo_pattern}" -e "hub\.docker\.com/r/${old_repo_pattern}" \
|
||||||
|
--glob '!.git/**' --glob '!dist/**' \
|
||||||
|
--glob '!SILO_REBRANDING_MIGRATION.md' \
|
||||||
|
--glob '!buildscripts/rebrand-guard/compat-baseline.json' . |
|
||||||
|
sed 's#^\./##' | grep -v '^buildscripts/verify-rebrand\.sh:' || true)"
|
||||||
|
if [ -n "${stale_repo_url}" ]; then
|
||||||
|
printf '%s\n' "${stale_repo_url}" >&2
|
||||||
|
fail "a link still resolves to the pre-rename pgsty/minio repository"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Second, the bare name may only appear where it is deliberate: the pinned
|
||||||
|
# pre-rebrand image digest in the upgrade test, the two guards that refuse a
|
||||||
|
# legacy image, the two READMEs that document the rename and the archived
|
||||||
|
# minio branch, and historical issue titles in CONTRIBUTORS.md.
|
||||||
|
repo_guard_allowlist='^(buildscripts/minio-upgrade\.sh|buildscripts/verify-rebrand\.sh|buildscripts/helm-migration-guard/main\.go|README\.md|README_ZH\.md|CONTRIBUTORS\.md):'
|
||||||
|
stale_repo="$(rg -n "${old_repo_pattern}" --glob '!.git/**' --glob '!dist/**' \
|
||||||
|
--glob '!SILO_REBRANDING_MIGRATION.md' \
|
||||||
|
--glob '!buildscripts/rebrand-guard/compat-baseline.json' . |
|
||||||
|
sed 's#^\./##' | grep -Ev "${repo_guard_allowlist}" || true)"
|
||||||
|
if [ -n "${stale_repo}" ]; then
|
||||||
|
printf '%s\n' "${stale_repo}" >&2
|
||||||
|
fail "a source reference still names the pre-rename pgsty/minio repository"
|
||||||
|
fi
|
||||||
|
|
||||||
|
stale_branch="$(rg -n 'pgsty/silo/(blob/|tree/|raw/)?master' \
|
||||||
|
--glob '!.git/**' --glob '!dist/**' . || true)"
|
||||||
|
if [ -n "${stale_branch}" ]; then
|
||||||
|
printf '%s\n' "${stale_branch}" >&2
|
||||||
|
fail "a link still targets the retired master branch; raw and Actions URLs do not follow a branch rename"
|
||||||
|
fi
|
||||||
|
|
||||||
|
for workflow in .github/workflows/go.yml .github/workflows/vulncheck.yml; do
|
||||||
|
if rg -q '^\s+- master$' "${workflow}"; then
|
||||||
|
fail "${workflow} still filters on master and would go silently dormant on main"
|
||||||
|
fi
|
||||||
|
require_text "${workflow}" " - main"
|
||||||
|
done
|
||||||
|
|
||||||
|
network_hits="$(rg -n --glob '*.go' --glob '!**/*_test.go' \
|
||||||
|
'https?://[^"`[:space:]]*(dl\.min\.io|subnet\.min\.io|api\.min\.io|slack\.min\.io|play\.min\.io)' \
|
||||||
|
cmd internal || true)"
|
||||||
|
network_hits="$(printf '%s\n' "${network_hits}" | grep -Ev '^[^:]+:[0-9]+:[[:space:]]*//' || true)"
|
||||||
|
if [ -n "${network_hits}" ]; then
|
||||||
|
printf '%s\n' "${network_hits}" >&2
|
||||||
|
fail "runtime code still contains an upstream MinIO service endpoint"
|
||||||
|
fi
|
||||||
|
|
||||||
|
require_text cmd/build-constants.go 'MinioReleaseBaseURL = ""'
|
||||||
|
require_text cmd/globals.go "globalInplaceUpdateDisabled = true"
|
||||||
|
reject_text cmd/globals.go "subnetAdminPublicKey"
|
||||||
|
reject_text cmd/admin-handlers.go "getSubnetAdminPublicKey"
|
||||||
|
|
||||||
|
echo "Silo delivery and runtime rebrand checks passed"
|
||||||
+6
-14
@@ -22,10 +22,10 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/mux"
|
||||||
|
"github.com/pgsty/silo-pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Data types used for returning dummy access control
|
// Data types used for returning dummy access control
|
||||||
@@ -80,7 +80,7 @@ func (api objectAPIHandlers) PutBucketACLHandler(w http.ResponseWriter, r *http.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Before proceeding validate if bucket exists.
|
// Before proceeding validate if bucket exists.
|
||||||
_, err := objAPI.GetBucketInfo(ctx, bucket)
|
_, err := objAPI.GetBucketInfo(ctx, bucket, BucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -90,8 +90,8 @@ func (api objectAPIHandlers) PutBucketACLHandler(w http.ResponseWriter, r *http.
|
|||||||
if aclHeader == "" {
|
if aclHeader == "" {
|
||||||
acl := &accessControlPolicy{}
|
acl := &accessControlPolicy{}
|
||||||
if err = xmlDecoder(r.Body, acl, r.ContentLength); err != nil {
|
if err = xmlDecoder(r.Body, acl, r.ContentLength); err != nil {
|
||||||
if err == io.EOF {
|
if terr, ok := err.(*xml.SyntaxError); ok && terr.Msg == io.EOF.Error() {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingSecurityHeader),
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedXML),
|
||||||
r.URL)
|
r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -114,8 +114,6 @@ func (api objectAPIHandlers) PutBucketACLHandler(w http.ResponseWriter, r *http.
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, NotImplemented{}), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, NotImplemented{}), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.(http.Flusher).Flush()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBucketACLHandler - GET Bucket ACL
|
// GetBucketACLHandler - GET Bucket ACL
|
||||||
@@ -144,7 +142,7 @@ func (api objectAPIHandlers) GetBucketACLHandler(w http.ResponseWriter, r *http.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Before proceeding validate if bucket exists.
|
// Before proceeding validate if bucket exists.
|
||||||
_, err := objAPI.GetBucketInfo(ctx, bucket)
|
_, err := objAPI.GetBucketInfo(ctx, bucket, BucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -164,8 +162,6 @@ func (api objectAPIHandlers) GetBucketACLHandler(w http.ResponseWriter, r *http.
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.(http.Flusher).Flush()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutObjectACLHandler - PUT Object ACL
|
// PutObjectACLHandler - PUT Object ACL
|
||||||
@@ -229,8 +225,6 @@ func (api objectAPIHandlers) PutObjectACLHandler(w http.ResponseWriter, r *http.
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, NotImplemented{}), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, NotImplemented{}), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.(http.Flusher).Flush()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetObjectACLHandler - GET Object ACL
|
// GetObjectACLHandler - GET Object ACL
|
||||||
@@ -283,6 +277,4 @@ func (api objectAPIHandlers) GetObjectACLHandler(w http.ResponseWriter, r *http.
|
|||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.(http.Flusher).Flush()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,348 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/madmin-go/v3"
|
||||||
|
"github.com/minio/minio/internal/auth"
|
||||||
|
"github.com/minio/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func corsAdminRequest(t *testing.T, cred auth.Credentials, method, path string, body []byte) *httptest.ResponseRecorder {
|
||||||
|
t.Helper()
|
||||||
|
router := mux.NewRouter()
|
||||||
|
registerAdminRouter(router, true)
|
||||||
|
req, err := newTestSignedRequestV4(method, adminPathPrefix+adminAPIVersionPrefix+path,
|
||||||
|
int64(len(body)), bytes.NewReader(body), cred.AccessKey, cred.SecretKey, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("admin %s: %d: %s", path, rec.Code, rec.Body.String())
|
||||||
|
}
|
||||||
|
return rec
|
||||||
|
}
|
||||||
|
|
||||||
|
func corsImportReport(t *testing.T, rec *httptest.ResponseRecorder) madmin.BucketMetaImportErrs {
|
||||||
|
t.Helper()
|
||||||
|
var rpt madmin.BucketMetaImportErrs
|
||||||
|
if err := json.Unmarshal(rec.Body.Bytes(), &rpt); err != nil {
|
||||||
|
t.Fatalf("import report %q: %v", rec.Body.String(), err)
|
||||||
|
}
|
||||||
|
return rpt
|
||||||
|
}
|
||||||
|
|
||||||
|
func corsZip(t *testing.T, entries map[string][]byte) []byte {
|
||||||
|
t.Helper()
|
||||||
|
var buf bytes.Buffer
|
||||||
|
zw := zip.NewWriter(&buf)
|
||||||
|
for name, data := range entries {
|
||||||
|
w, err := zw.Create(name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err = w.Write(data); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := zw.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// corsCorruptedZip builds an archive holding a stored (uncompressed) cors.xml
|
||||||
|
// whose payload is altered after the checksum is computed, plus the given
|
||||||
|
// companion entries. The altered document stays well formed, so only the zip
|
||||||
|
// checksum tells the two apart.
|
||||||
|
func corsCorruptedZip(t *testing.T, name string, doc []byte, others map[string][]byte) []byte {
|
||||||
|
t.Helper()
|
||||||
|
var buf bytes.Buffer
|
||||||
|
zw := zip.NewWriter(&buf)
|
||||||
|
w, err := zw.CreateHeader(&zip.FileHeader{Name: name, Method: zip.Store})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err = w.Write(doc); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for other, data := range others {
|
||||||
|
ow, err := zw.Create(other)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err = ow.Write(data); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = zw.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
raw := buf.Bytes()
|
||||||
|
at := bytes.Index(raw, []byte("app.example.com"))
|
||||||
|
if at < 0 {
|
||||||
|
t.Fatalf("stored CORS payload not found in archive")
|
||||||
|
}
|
||||||
|
raw[at] = 'A'
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAdminBucketMetadataCORSRoundTrip covers the export/import round trip for
|
||||||
|
// per-bucket CORS, per-file error reporting for an invalid document, and that
|
||||||
|
// an archive without cors.xml leaves an existing configuration alone.
|
||||||
|
func TestAdminBucketMetadataCORSRoundTrip(t *testing.T) {
|
||||||
|
ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: func(obj ObjectLayer, instanceType, bucket string, _ http.Handler, cred auth.Credentials, t *testing.T) {
|
||||||
|
corsXML := []byte(testSiteReplicationCORSDoc)
|
||||||
|
if _, err := updateLocalBucketCORSMetadata(t.Context(), obj, bucket, corsXML); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export must carry the stored document verbatim.
|
||||||
|
rec := corsAdminRequest(t, cred, http.MethodGet, "/export-bucket-metadata?bucket="+bucket, nil)
|
||||||
|
archive := rec.Body.Bytes()
|
||||||
|
zr, err := zip.NewReader(bytes.NewReader(archive), int64(len(archive)))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var exported []byte
|
||||||
|
for _, f := range zr.File {
|
||||||
|
if f.Name != bucket+"/"+bucketCorsConfig {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r, err := f.Open()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
exported, err = io.ReadAll(r)
|
||||||
|
r.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !bytes.Equal(exported, corsXML) {
|
||||||
|
t.Fatalf("%s: exported CORS = %q, want %q", instanceType, exported, corsXML)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop the configuration: the archive must then omit the entry.
|
||||||
|
if _, err = updateLocalBucketCORSMetadata(t.Context(), obj, bucket, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, _, err = globalBucketMetadataSys.GetCorsConfigXML(bucket); err == nil {
|
||||||
|
t.Fatalf("%s: CORS still present before restore", instanceType)
|
||||||
|
}
|
||||||
|
rec = corsAdminRequest(t, cred, http.MethodGet, "/export-bucket-metadata?bucket="+bucket, nil)
|
||||||
|
empty := rec.Body.Bytes()
|
||||||
|
zr, err = zip.NewReader(bytes.NewReader(empty), int64(len(empty)))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, f := range zr.File {
|
||||||
|
if f.Name == bucket+"/"+bucketCorsConfig {
|
||||||
|
t.Fatalf("%s: export emitted %s for a bucket without CORS", instanceType, f.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rec = corsAdminRequest(t, cred, http.MethodPut, "/import-bucket-metadata", archive)
|
||||||
|
if st := corsImportReport(t, rec).Buckets[bucket]; !st.Cors.IsSet || st.Cors.Err != "" {
|
||||||
|
t.Fatalf("%s: import report cors = %+v", instanceType, st.Cors)
|
||||||
|
}
|
||||||
|
stored, storedAt, err := globalBucketMetadataSys.GetCorsConfigXML(bucket)
|
||||||
|
if err != nil || !bytes.Equal(stored, corsXML) {
|
||||||
|
t.Fatalf("%s: restored CORS = %q, err = %v", instanceType, stored, err)
|
||||||
|
}
|
||||||
|
created, err := globalBucketMetadataSys.CreatedAt(bucket)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !storedAt.After(created) {
|
||||||
|
t.Fatalf("%s: restored CORS timestamp %v is not after bucket creation %v", instanceType, storedAt, created)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An archive without cors.xml must not remove the configuration.
|
||||||
|
corsAdminRequest(t, cred, http.MethodPut, "/import-bucket-metadata",
|
||||||
|
corsZip(t, map[string][]byte{bucket + "/quota.json": []byte(`{"quota":0}`)}))
|
||||||
|
if stored, _, err = globalBucketMetadataSys.GetCorsConfigXML(bucket); err != nil || !bytes.Equal(stored, corsXML) {
|
||||||
|
t.Fatalf("%s: import without cors.xml changed CORS: %q, err = %v", instanceType, stored, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A bucket the import itself creates must still land above its own
|
||||||
|
// creation time, otherwise CORS replication would drop the restore.
|
||||||
|
fresh := "cors-import-created-bucket"
|
||||||
|
rec = corsAdminRequest(t, cred, http.MethodPut, "/import-bucket-metadata",
|
||||||
|
corsZip(t, map[string][]byte{fresh + "/" + bucketCorsConfig: corsXML}))
|
||||||
|
if st := corsImportReport(t, rec).Buckets[fresh]; !st.Cors.IsSet || st.Cors.Err != "" {
|
||||||
|
t.Fatalf("%s: fresh bucket import report cors = %+v", instanceType, st.Cors)
|
||||||
|
}
|
||||||
|
freshStored, freshAt, err := globalBucketMetadataSys.GetCorsConfigXML(fresh)
|
||||||
|
if err != nil || !bytes.Equal(freshStored, corsXML) {
|
||||||
|
t.Fatalf("%s: fresh bucket CORS = %q, err = %v", instanceType, freshStored, err)
|
||||||
|
}
|
||||||
|
freshCreated, err := globalBucketMetadataSys.CreatedAt(fresh)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !freshAt.After(freshCreated) {
|
||||||
|
t.Fatalf("%s: fresh bucket CORS timestamp %v is not after creation %v", instanceType, freshAt, freshCreated)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An invalid document must fail loudly for that bucket and change nothing.
|
||||||
|
rec = corsAdminRequest(t, cred, http.MethodPut, "/import-bucket-metadata",
|
||||||
|
corsZip(t, map[string][]byte{bucket + "/" + bucketCorsConfig: []byte("<CORSConfiguration><CORSRule>")}))
|
||||||
|
if st := corsImportReport(t, rec).Buckets[bucket]; st.Cors.Err == "" {
|
||||||
|
t.Fatalf("%s: invalid CORS import reported no error: %+v", instanceType, st)
|
||||||
|
}
|
||||||
|
if stored, _, err = globalBucketMetadataSys.GetCorsConfigXML(bucket); err != nil || !bytes.Equal(stored, corsXML) {
|
||||||
|
t.Fatalf("%s: invalid CORS import changed stored config: %q, err = %v", instanceType, stored, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A well formed document carried by a corrupt zip entry must be
|
||||||
|
// rejected too, leaving the stored document and its timestamp alone
|
||||||
|
// while the other configs in the same archive still apply.
|
||||||
|
_, corsAt, err := globalBucketMetadataSys.GetCorsConfigXML(bucket)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rec = corsAdminRequest(t, cred, http.MethodPut, "/import-bucket-metadata",
|
||||||
|
corsCorruptedZip(t, bucket+"/"+bucketCorsConfig, corsXML,
|
||||||
|
map[string][]byte{bucket + "/quota.json": []byte(`{"quota":4096,"quotatype":"hard"}`)}))
|
||||||
|
st := corsImportReport(t, rec).Buckets[bucket]
|
||||||
|
if st.Cors.Err == "" {
|
||||||
|
t.Fatalf("%s: corrupt CORS entry reported no error: %+v", instanceType, st)
|
||||||
|
}
|
||||||
|
if !st.Quota.IsSet || st.Quota.Err != "" {
|
||||||
|
t.Fatalf("%s: corrupt CORS entry blocked the neighboring quota: %+v", instanceType, st.Quota)
|
||||||
|
}
|
||||||
|
stored, storedAt, err = globalBucketMetadataSys.GetCorsConfigXML(bucket)
|
||||||
|
if err != nil || !bytes.Equal(stored, corsXML) || !storedAt.Equal(corsAt) {
|
||||||
|
t.Fatalf("%s: corrupt CORS entry changed stored config: %q at %v (was %v), err = %v", instanceType, stored, storedAt, corsAt, err)
|
||||||
|
}
|
||||||
|
quota, _, err := globalBucketMetadataSys.GetQuotaConfig(t.Context(), bucket)
|
||||||
|
if err != nil || quota == nil || quota.Quota != 4096 {
|
||||||
|
t.Fatalf("%s: neighboring quota not applied: %+v, err = %v", instanceType, quota, err)
|
||||||
|
}
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
|
||||||
|
// corsPeerStub is a stand-in site-replication peer. It records every
|
||||||
|
// SRBucketMeta it is asked to apply and answers with status.
|
||||||
|
func corsPeerStub(t *testing.T, applied chan<- madmin.SRBucketMeta, status int) *httptest.Server {
|
||||||
|
t.Helper()
|
||||||
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == http.MethodPut && applied != nil {
|
||||||
|
var item madmin.SRBucketMeta
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&item); err != nil {
|
||||||
|
t.Errorf("decode peer apply: %v", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
applied <- item
|
||||||
|
}
|
||||||
|
w.WriteHeader(status)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAdminBucketMetadataCORSImportReplicatesPastPeerFailure pins that an
|
||||||
|
// imported CORS document reaches the reachable peers even when the shared
|
||||||
|
// bucket metadata hook failed against an unreachable one, and that both
|
||||||
|
// failures are still reported for the bucket.
|
||||||
|
func TestAdminBucketMetadataCORSImportReplicatesPastPeerFailure(t *testing.T) {
|
||||||
|
ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: func(obj ObjectLayer, instanceType, bucket string, _ http.Handler, cred auth.Credentials, t *testing.T) {
|
||||||
|
ctx := t.Context()
|
||||||
|
corsXML := []byte(testSiteReplicationCORSDoc)
|
||||||
|
|
||||||
|
healthyApplies := make(chan madmin.SRBucketMeta, 4)
|
||||||
|
healthy := corsPeerStub(t, healthyApplies, http.StatusOK)
|
||||||
|
defer healthy.Close()
|
||||||
|
broken := corsPeerStub(t, nil, http.StatusBadRequest)
|
||||||
|
defer broken.Close()
|
||||||
|
|
||||||
|
// With site replication on, admin requests resolve their token signing
|
||||||
|
// key through the site replicator account, so it has to exist.
|
||||||
|
serviceCred, err := auth.CreateCredentials(siteReplicatorSvcAcc, "cors-import-service-secret")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
serviceCred.ParentUser = cred.AccessKey
|
||||||
|
if _, err = globalIAMSys.store.AddServiceAccount(ctx, serviceCred); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer globalIAMSys.DeleteServiceAccount(ctx, serviceCred.AccessKey, false)
|
||||||
|
globalSiteReplicatorCred.Set(serviceCred.SecretKey)
|
||||||
|
defer globalSiteReplicatorCred.Set("")
|
||||||
|
|
||||||
|
globalSiteReplicationSys.Lock()
|
||||||
|
oldEnabled, oldState := globalSiteReplicationSys.enabled, globalSiteReplicationSys.state
|
||||||
|
globalSiteReplicationSys.enabled = true
|
||||||
|
globalSiteReplicationSys.state = srState{
|
||||||
|
Name: "cors-import-test",
|
||||||
|
ServiceAccountAccessKey: serviceCred.AccessKey,
|
||||||
|
Peers: map[string]madmin.PeerInfo{
|
||||||
|
globalDeploymentID(): {Name: "local", DeploymentID: globalDeploymentID()},
|
||||||
|
"peer-healthy": {Name: "healthy", DeploymentID: "peer-healthy", Endpoint: healthy.URL},
|
||||||
|
"peer-broken": {Name: "broken", DeploymentID: "peer-broken", Endpoint: broken.URL},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
globalSiteReplicationSys.Unlock()
|
||||||
|
defer func() {
|
||||||
|
globalSiteReplicationSys.Lock()
|
||||||
|
globalSiteReplicationSys.enabled, globalSiteReplicationSys.state = oldEnabled, oldState
|
||||||
|
globalSiteReplicationSys.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
rec := corsAdminRequest(t, cred, http.MethodPut, "/import-bucket-metadata",
|
||||||
|
corsZip(t, map[string][]byte{
|
||||||
|
bucket + "/" + bucketCorsConfig: corsXML,
|
||||||
|
bucket + "/quota.json": []byte(`{"quota":8192,"quotatype":"hard"}`),
|
||||||
|
}))
|
||||||
|
st := corsImportReport(t, rec).Buckets[bucket]
|
||||||
|
if !st.Cors.IsSet || st.Cors.Err != "" {
|
||||||
|
t.Fatalf("%s: import report cors = %+v", instanceType, st.Cors)
|
||||||
|
}
|
||||||
|
stored, storedAt, err := globalBucketMetadataSys.GetCorsConfigXML(bucket)
|
||||||
|
if err != nil || !bytes.Equal(stored, corsXML) {
|
||||||
|
t.Fatalf("%s: stored CORS = %q, err = %v", instanceType, stored, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The reachable peer must have been told about the CORS document,
|
||||||
|
// carrying exactly the timestamp that was saved locally.
|
||||||
|
var corsSeen, sharedSeen bool
|
||||||
|
for range 2 {
|
||||||
|
select {
|
||||||
|
case item := <-healthyApplies:
|
||||||
|
if item.Type != madmin.SRBucketMetaTypeCorsConfig {
|
||||||
|
sharedSeen = item.Bucket == bucket && item.Quota != nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if item.Bucket != bucket || item.Cors == nil || !item.UpdatedAt.Equal(storedAt) {
|
||||||
|
t.Fatalf("%s: peer CORS event = %#v, want %s at %v", instanceType, item, bucket, storedAt)
|
||||||
|
}
|
||||||
|
payload, decErr := base64.StdEncoding.Strict().DecodeString(*item.Cors)
|
||||||
|
if decErr != nil || !bytes.Equal(payload, corsXML) {
|
||||||
|
t.Fatalf("%s: peer CORS payload = %q, err = %v", instanceType, payload, decErr)
|
||||||
|
}
|
||||||
|
corsSeen = true
|
||||||
|
case <-time.After(10 * time.Second):
|
||||||
|
t.Fatalf("%s: healthy peer received no further events (shared=%v cors=%v)", instanceType, sharedSeen, corsSeen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !sharedSeen || !corsSeen {
|
||||||
|
t.Fatalf("%s: healthy peer events shared=%v cors=%v, want both", instanceType, sharedSeen, corsSeen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both hook failures against the unreachable peer stay reported.
|
||||||
|
if got := strings.Count(st.Err, "->broken:"); got != 2 {
|
||||||
|
t.Fatalf("%s: bucket error mentions the broken peer %d times, want 2: %q", instanceType, got, st.Err)
|
||||||
|
}
|
||||||
|
}})
|
||||||
|
}
|
||||||
+1026
-59
File diff suppressed because it is too large
Load Diff
+120
-29
@@ -20,16 +20,21 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/minio/kes"
|
"github.com/minio/kms-go/kes"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/pgsty/silo-pkg/v3/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Request, action iampolicy.AdminAction) (ObjectLayer, auth.Credentials) {
|
// validateAdminReq will validate request against and return whether it is allowed.
|
||||||
|
// If any of the supplied actions are allowed it will be successful.
|
||||||
|
// If nil ObjectLayer is returned, the operation is not permitted.
|
||||||
|
// When nil ObjectLayer has been returned an error has always been sent to w.
|
||||||
|
func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Request, actions ...policy.AdminAction) (ObjectLayer, auth.Credentials) {
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
if objectAPI == nil || globalNotificationSys == nil {
|
if objectAPI == nil || globalNotificationSys == nil {
|
||||||
@@ -37,14 +42,22 @@ func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Reques
|
|||||||
return nil, auth.Credentials{}
|
return nil, auth.Credentials{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate request signature.
|
for _, action := range actions {
|
||||||
cred, adminAPIErr := checkAdminRequestAuth(ctx, r, action, "")
|
// Validate request signature.
|
||||||
if adminAPIErr != ErrNone {
|
cred, adminAPIErr := checkAdminRequestAuth(ctx, r, action, "")
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
switch adminAPIErr {
|
||||||
return nil, cred
|
case ErrNone:
|
||||||
|
return objectAPI, cred
|
||||||
|
case ErrAccessDenied:
|
||||||
|
// Try another
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||||
|
return nil, cred
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||||
return objectAPI, cred
|
return nil, auth.Credentials{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminError - is a generic error for all admin APIs.
|
// AdminError - is a generic error for all admin APIs.
|
||||||
@@ -65,13 +78,19 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
|||||||
|
|
||||||
var apiErr APIError
|
var apiErr APIError
|
||||||
switch e := err.(type) {
|
switch e := err.(type) {
|
||||||
case iampolicy.Error:
|
case policy.Error:
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinioMalformedIAMPolicy",
|
Code: "XMinioMalformedIAMPolicy",
|
||||||
Description: e.Error(),
|
Description: e.Error(),
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
}
|
}
|
||||||
case config.Error:
|
case config.ErrConfigNotFound:
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: "XMinioConfigNotFoundError",
|
||||||
|
Description: e.Error(),
|
||||||
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
|
}
|
||||||
|
case config.ErrConfigGeneric:
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinioConfigError",
|
Code: "XMinioConfigError",
|
||||||
Description: e.Error(),
|
Description: e.Error(),
|
||||||
@@ -83,8 +102,46 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
|||||||
Description: e.Message,
|
Description: e.Message,
|
||||||
HTTPStatusCode: e.StatusCode,
|
HTTPStatusCode: e.StatusCode,
|
||||||
}
|
}
|
||||||
|
case SRError:
|
||||||
|
apiErr = errorCodes.ToAPIErrWithErr(e.Code, e.Cause)
|
||||||
|
case decomError:
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: "XMinioDecommissionNotAllowed",
|
||||||
|
Description: e.Err,
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
switch {
|
switch {
|
||||||
|
case errors.Is(err, errTooManyPolicies):
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: "XMinioAdminInvalidRequest",
|
||||||
|
Description: err.Error(),
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
|
case errors.Is(err, errDecommissionAlreadyRunning):
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: "XMinioDecommissionNotAllowed",
|
||||||
|
Description: err.Error(),
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
|
case errors.Is(err, errDecommissionComplete):
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: "XMinioDecommissionNotAllowed",
|
||||||
|
Description: err.Error(),
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
|
case errors.Is(err, errDecommissionRebalanceAlreadyRunning):
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: "XMinioDecommissionNotAllowed",
|
||||||
|
Description: err.Error(),
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
|
case errors.Is(err, errRebalanceDecommissionAlreadyRunning):
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: "XMinioRebalanceNotAllowed",
|
||||||
|
Description: err.Error(),
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
case errors.Is(err, errConfigNotFound):
|
case errors.Is(err, errConfigNotFound):
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinioConfigError",
|
Code: "XMinioConfigError",
|
||||||
@@ -97,12 +154,30 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
|||||||
Description: err.Error(),
|
Description: err.Error(),
|
||||||
HTTPStatusCode: http.StatusForbidden,
|
HTTPStatusCode: http.StatusForbidden,
|
||||||
}
|
}
|
||||||
|
case errors.Is(err, errIAMServiceAccountNotAllowed):
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: "XMinioIAMServiceAccountNotAllowed",
|
||||||
|
Description: err.Error(),
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
case errors.Is(err, errIAMNotInitialized):
|
case errors.Is(err, errIAMNotInitialized):
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinioIAMNotInitialized",
|
Code: "XMinioIAMNotInitialized",
|
||||||
Description: err.Error(),
|
Description: err.Error(),
|
||||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||||
}
|
}
|
||||||
|
case errors.Is(err, errPolicyInUse):
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: "XMinioIAMPolicyInUse",
|
||||||
|
Description: "The policy cannot be removed, as it is in use",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
|
case errors.Is(err, errSessionPolicyTooLarge):
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: "XMinioIAMServiceAccountSessionPolicyTooLarge",
|
||||||
|
Description: err.Error(),
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
case errors.Is(err, kes.ErrKeyExists):
|
case errors.Is(err, kes.ErrKeyExists):
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinioKMSKeyExists",
|
Code: "XMinioKMSKeyExists",
|
||||||
@@ -135,24 +210,18 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
|||||||
Description: err.Error(),
|
Description: err.Error(),
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
}
|
}
|
||||||
case errors.Is(err, errTierBackendInUse):
|
|
||||||
apiErr = APIError{
|
|
||||||
Code: "XMinioAdminTierBackendInUse",
|
|
||||||
Description: err.Error(),
|
|
||||||
HTTPStatusCode: http.StatusConflict,
|
|
||||||
}
|
|
||||||
case errors.Is(err, errTierInsufficientCreds):
|
|
||||||
apiErr = APIError{
|
|
||||||
Code: "XMinioAdminTierInsufficientCreds",
|
|
||||||
Description: err.Error(),
|
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
|
||||||
}
|
|
||||||
case errIsTierPermError(err):
|
case errIsTierPermError(err):
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinioAdminTierInsufficientPermissions",
|
Code: "XMinioAdminTierInsufficientPermissions",
|
||||||
Description: err.Error(),
|
Description: err.Error(),
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
}
|
}
|
||||||
|
case errors.Is(err, errTierInvalidConfig):
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: "XMinioAdminTierInvalidConfig",
|
||||||
|
Description: err.Error(),
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
apiErr = errorCodes.ToAPIErrWithErr(toAdminAPIErrCode(ctx, err), err)
|
apiErr = errorCodes.ToAPIErrWithErr(toAdminAPIErrCode(ctx, err), err)
|
||||||
}
|
}
|
||||||
@@ -163,10 +232,32 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
|||||||
// toAdminAPIErrCode - converts errErasureWriteQuorum error to admin API
|
// toAdminAPIErrCode - converts errErasureWriteQuorum error to admin API
|
||||||
// specific error.
|
// specific error.
|
||||||
func toAdminAPIErrCode(ctx context.Context, err error) APIErrorCode {
|
func toAdminAPIErrCode(ctx context.Context, err error) APIErrorCode {
|
||||||
switch err {
|
if errors.Is(err, errErasureWriteQuorum) {
|
||||||
case errErasureWriteQuorum:
|
|
||||||
return ErrAdminConfigNoQuorum
|
return ErrAdminConfigNoQuorum
|
||||||
default:
|
|
||||||
return toAPIErrorCode(ctx, err)
|
|
||||||
}
|
}
|
||||||
|
return toAPIErrorCode(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// wraps export error for more context
|
||||||
|
func exportError(ctx context.Context, err error, fname, entity string) APIError {
|
||||||
|
if entity == "" {
|
||||||
|
return toAPIError(ctx, fmt.Errorf("error exporting %s with: %w", fname, err))
|
||||||
|
}
|
||||||
|
return toAPIError(ctx, fmt.Errorf("error exporting %s from %s with: %w", entity, fname, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// wraps import error for more context
|
||||||
|
func importError(ctx context.Context, err error, fname, entity string) APIError {
|
||||||
|
if entity == "" {
|
||||||
|
return toAPIError(ctx, fmt.Errorf("error importing %s with: %w", fname, err))
|
||||||
|
}
|
||||||
|
return toAPIError(ctx, fmt.Errorf("error importing %s from %s with: %w", entity, fname, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// wraps import error for more context
|
||||||
|
func importErrorWithAPIErr(ctx context.Context, apiErr APIErrorCode, err error, fname, entity string) APIError {
|
||||||
|
if entity == "" {
|
||||||
|
return errorCodes.ToAPIErrWithErr(apiErr, fmt.Errorf("error importing %s with: %w", fname, err))
|
||||||
|
}
|
||||||
|
return errorCodes.ToAPIErrWithErr(apiErr, fmt.Errorf("error importing %s from %s with: %w", entity, fname, err))
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user