feat(server): present Silo identity and close the inherited upstream services

Two coupled changes that must land together, because the same files carry both:
the server now identifies itself as Silo, and every path that would have called
home to a MinIO-operated service is closed.

Product identity
- build-constants.go: store name, UA name and startup banner become Silo. The
  Go identifiers (MinioStoreName, MinioBannerName, ...) keep their names on
  purpose - renaming exported symbols would churn the compatibility surface for
  a cosmetic gain, and the rebrand guard freezes that surface.
- main.go, server-startup-msg.go, ftp-server.go and the user-visible log, help
  and error strings across cmd/ and internal/ switch to Silo. Original MinIO
  copyright, LICENSE, NOTICE and CREDITS are untouched; --version now prints
  the upstream copyright, the pgsty modification notice, and the trademark
  policy's approved "based on MinIO technology" attribution.
- api-headers.go: the HTTP Server header becomes "Silo". This is the one
  externally observable identity change, so TestCommonHeadersUseSiloProductName
  pins it - probes that sniff for "MinIO" must move to capability detection.
- Prometheus metric HELP strings keep their MinIO wording. They are part of the
  metrics contract the guard protects, not product copy.

Configuration directory
- config-dir.go: new installs use ~/.silo. If only ~/.minio exists it is still
  read, with a one-time notice and no files moved. If both exist ~/.silo wins
  and an ambiguity warning is emitted; an explicit --config-dir always wins.
  Covered by TestSelectDefaultConfigDir. The internal .minio.sys layout is
  never renamed - this rule applies to the user config directory only.

Upstream service lockdown
- globalInplaceUpdateDisabled is now true at initialization rather than being
  set from MINIO_UPDATE. common-main.go still parses MINIO_UPDATE so upgrading
  nodes do not fail on an unknown key, but warns that the value is ignored;
  there is no way to re-enable the updater. TestInplaceUpdateCannotBeEnabled
  guards that. Without this, an admin with mc could have overwritten
  /usr/bin/silo with an upstream MinIO binary.
- verifyBinary and commitBinary refuse early; the ServerUpdate v1/v2 admin
  routes and the peer-rest update endpoints stay registered and keep returning
  the existing programmatic error, so clients see a stable failure rather than
  a 404.
- MinioReleaseBaseURL and defaultMinisignPubkey are emptied: no dl.min.io
  download root, and upstream's minisign key is no longer a trust root for
  anything this fork ships.
- cmd/callhome.go is deleted and internal/config/subnet/ is reduced to parsing
  its old keys and reporting that the integration is disabled. config-current.go
  warns instead of failing when callhome or SUBNET settings are present, so an
  upgraded node with those keys still starts.
- internal/config/errors.go replaces the MinIO Slack and support entry points
  with Silo documentation and issue links. Error codes and programmatic fields
  are unchanged.

Verified: the compatibility baseline is unchanged except for the deliberate
removal of the /api/health/upload SUBNET route; go build, go vet and the full
cmd/ and internal/ unit suites pass; a locally built binary starts, serves
S3/Admin/metrics on the unchanged /minio/* routes, answers with Server: Silo,
and falls back to a pre-existing ~/.minio with the expected notice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Feng Ruohang
2026-08-06 08:46:58 +08:00
parent 15def34dce
commit 77bdc4c0cd
50 changed files with 469 additions and 581 deletions
+33 -41
View File
@@ -84,7 +84,7 @@ const (
// ServerUpdateV2Handler - POST /minio/admin/v3/update?updateURL={updateURL}&type=2 // ServerUpdateV2Handler - POST /minio/admin/v3/update?updateURL={updateURL}&type=2
// ---------- // ----------
// updates all minio servers and restarts them gracefully. // Retained for Admin API compatibility. Silo always returns MethodNotAllowed.
func (a adminAPIHandlers) ServerUpdateV2Handler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ServerUpdateV2Handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
@@ -320,7 +320,7 @@ func (a adminAPIHandlers) ServerUpdateV2Handler(w http.ResponseWriter, r *http.R
// ServerUpdateHandler - POST /minio/admin/v3/update?updateURL={updateURL} // ServerUpdateHandler - POST /minio/admin/v3/update?updateURL={updateURL}
// ---------- // ----------
// updates all minio servers and restarts them gracefully. // Retained for Admin API compatibility. Silo always returns MethodNotAllowed.
func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
@@ -330,7 +330,7 @@ func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Req
} }
if globalInplaceUpdateDisabled || currentReleaseTime.IsZero() { if globalInplaceUpdateDisabled || currentReleaseTime.IsZero() {
// if MINIO_UPDATE=off - inplace update is disabled, mostly in containers. // MINIO_UPDATE is retained, but Silo permanently disables in-place updates.
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return return
} }
@@ -2708,10 +2708,10 @@ func fetchHealthInfo(healthCtx context.Context, objectAPI ObjectLayer, query *ur
} }
// Server start command regex groups: // Server start command regex groups:
// 1 - minio server // 1 - silo server (or the legacy minio command)
// 2 - flags e.g. `--address :9000 --certs-dir /etc/minio/certs` // 2 - flags e.g. `--address :9000 --certs-dir /etc/silo/certs`
// 3 - pool args e.g. `https://node{01...16}.domain/data/disk{001...204} https://node{17...32}.domain/data/disk{001...204}` // 3 - pool args e.g. `https://node{01...16}.domain/data/disk{001...204} https://node{17...32}.domain/data/disk{001...204}`
re := regexp.MustCompile(`^(.*minio\s+server\s+)(--[^\s]+\s+[^\s]+\s+)*(.*)`) re := regexp.MustCompile(`^(.*silo\s+server\s+|.*minio\s+server\s+)(--[^\s]+\s+[^\s]+\s+)*(.*)`)
// stays unchanged in the anonymized version // stays unchanged in the anonymized version
cmdLineWithoutPools := re.ReplaceAllString(cmdLine, `$1$2`) cmdLineWithoutPools := re.ReplaceAllString(cmdLine, `$1$2`)
@@ -3282,28 +3282,7 @@ func (a adminAPIHandlers) InspectDataHandler(w http.ResponseWriter, r *http.Requ
stream := estream.NewWriter(w) stream := estream.NewWriter(w)
defer stream.Close() defer stream.Close()
clusterKey, err := bytesToPublicKey(getSubnetAdminPublicKey()) if err := addInspectDataKey(stream, publicKey, getClusterMetaInfo(ctx)); err != nil {
if err != nil {
bugLogIf(ctx, stream.AddError(err.Error()))
return
}
err = stream.AddKeyEncrypted(clusterKey)
if err != nil {
bugLogIf(ctx, stream.AddError(err.Error()))
return
}
if b := getClusterMetaInfo(ctx); len(b) > 0 {
w, err := stream.AddEncryptedStream("cluster.info", nil)
if err != nil {
bugLogIf(ctx, err)
return
}
w.Write(b)
w.Close()
}
// Add new key for inspect data.
if err := stream.AddKeyEncrypted(publicKey); err != nil {
bugLogIf(ctx, stream.AddError(err.Error())) bugLogIf(ctx, stream.AddError(err.Error()))
return return
} }
@@ -3432,7 +3411,7 @@ func (a adminAPIHandlers) InspectDataHandler(w http.ResponseWriter, r *http.Requ
scheme = "http" scheme = "http"
} }
// save MinIO start script to inspect command // Save a Silo start script to inspect command.
var scrb bytes.Buffer var scrb bytes.Buffer
fmt.Fprintf(&scrb, `#!/usr/bin/env bash fmt.Fprintf(&scrb, `#!/usr/bin/env bash
@@ -3443,30 +3422,43 @@ function main() {
done done
# Read content of inspect-input.txt # Read content of inspect-input.txt
MINIO_OPTS=$(grep "Server command line args" <./inspect-input.txt | sed "s/Server command line args: //g" | sed -r "s#%s:\/\/#\.\/#g") SILO_OPTS=$(grep "Server command line args" <./inspect-input.txt | sed "s/Server command line args: //g" | sed -r "s#%s:\/\/#\.\/#g")
# Start MinIO instance using the options # Start Silo using the options
START_CMD="CI=on _MINIO_AUTO_DRIVE_HEALING=off minio server ${MINIO_OPTS} &" START_CMD="CI=on _MINIO_AUTO_DRIVE_HEALING=off silo server ${SILO_OPTS} &"
echo echo
echo "Starting MinIO instance: ${START_CMD}" echo "Starting Silo: ${START_CMD}"
echo echo
eval "$START_CMD" eval "$START_CMD"
MINIO_SRVR_PID="$!" SILO_SRVR_PID="$!"
echo "MinIO Server PID: ${MINIO_SRVR_PID}" echo "Silo Server PID: ${SILO_SRVR_PID}"
echo echo
echo "Waiting for MinIO instance to get ready!" echo "Waiting for Silo to get ready!"
sleep 10 sleep 10
} }
main "$@"`, scheme) main "$@"`, scheme)
adminLogIf(ctx, embedFileInZip(inspectZipW, "start-minio.sh", scrb.Bytes(), 0o755)) adminLogIf(ctx, embedFileInZip(inspectZipW, "start-silo.sh", scrb.Bytes(), 0o755))
} }
func getSubnetAdminPublicKey() []byte { // addInspectDataKey makes the requester the only recipient of encrypted
if globalIsCICD { // diagnostic data. Silo has no built-in vendor or support-service recipient.
return subnetAdminPublicKeyDev func addInspectDataKey(stream *estream.Writer, publicKey *rsa.PublicKey, clusterInfo []byte) error {
if err := stream.AddKeyEncrypted(publicKey); err != nil {
return err
} }
return subnetAdminPublicKey if len(clusterInfo) == 0 {
return nil
}
w, err := stream.AddEncryptedStream("cluster.info", nil)
if err != nil {
return err
}
if _, err = w.Write(clusterInfo); err != nil {
_ = w.Close()
return err
}
return w.Close()
} }
func createHostAnonymizerForFSMode() map[string]string { func createHostAnonymizerForFSMode() map[string]string {
+1 -1
View File
@@ -64,7 +64,7 @@ func prepareAdminErasureTestBed(ctx context.Context) (*adminErasureTestBed, erro
return nil, xlErr return nil, xlErr
} }
// Initialize minio server config. // Initialize Silo server config.
if err := newTestConfig(globalMinioDefaultRegion, objLayer); err != nil { if err := newTestConfig(globalMinioDefaultRegion, objLayer); err != nil {
cancel() cancel()
return nil, err return nil, err
+1 -1
View File
@@ -1154,7 +1154,7 @@ var errorCodes = errorCodeMap{
}, },
ErrUnsupportedNotification: { ErrUnsupportedNotification: {
Code: "UnsupportedNotification", Code: "UnsupportedNotification",
Description: "MinIO server does not support Topic or Cloud Function based notifications.", Description: "Silo does not support Topic or Cloud Function based notifications.",
HTTPStatusCode: http.StatusBadRequest, HTTPStatusCode: http.StatusBadRequest,
}, },
ErrInvalidCopyPartRange: { ErrInvalidCopyPartRange: {
+9
View File
@@ -18,9 +18,18 @@
package cmd package cmd
import ( import (
"net/http/httptest"
"testing" "testing"
) )
func TestCommonHeadersUseSiloProductName(t *testing.T) {
recorder := httptest.NewRecorder()
setCommonHeaders(recorder)
if got := recorder.Header().Get("Server"); got != "Silo" {
t.Fatalf("Server header = %q, want Silo", got)
}
}
func TestNewRequestID(t *testing.T) { func TestNewRequestID(t *testing.T) {
// Ensure that it returns an alphanumeric result of length 16. // Ensure that it returns an alphanumeric result of length 16.
id := mustGetRequestID(UTCNow()) id := mustGetRequestID(UTCNow())
+3 -3
View File
@@ -391,7 +391,7 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
return err return err
} }
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID) c.SetAppInfo("silo-"+batchJobPrefix, r.APIVersion+" "+job.ID)
core := &minio.Core{Client: c} core := &minio.Core{Client: c}
workerSize, err := strconv.Atoi(env.Get("_MINIO_BATCH_REPLICATION_WORKERS", strconv.Itoa(runtime.GOMAXPROCS(0)/2))) workerSize, err := strconv.Atoi(env.Get("_MINIO_BATCH_REPLICATION_WORKERS", strconv.Itoa(runtime.GOMAXPROCS(0)/2)))
@@ -1155,7 +1155,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
return err return err
} }
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID) c.SetAppInfo("silo-"+batchJobPrefix, r.APIVersion+" "+job.ID)
retry := false retry := false
for attempts := 1; attempts <= retryAttempts; attempts++ { for attempts := 1; attempts <= retryAttempts; attempts++ {
@@ -1477,7 +1477,7 @@ func (r *BatchJobReplicateV1) Validate(ctx context.Context, job BatchJobRequest,
if err != nil { if err != nil {
return err return err
} }
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID) c.SetAppInfo("silo-"+batchJobPrefix, r.APIVersion+" "+job.ID)
vcfg, err := c.GetBucketVersioning(ctx, remoteBkt) vcfg, err := c.GetBucketVersioning(ctx, remoteBkt)
if err != nil { if err != nil {
+1 -1
View File
@@ -53,7 +53,7 @@ type ServerSystemConfig struct {
// Diff - returns error on first difference found in two configs. // Diff - returns error on first difference found in two configs.
func (s1 *ServerSystemConfig) Diff(s2 *ServerSystemConfig) error { func (s1 *ServerSystemConfig) Diff(s2 *ServerSystemConfig) error {
if s1.Checksum != s2.Checksum { if s1.Checksum != s2.Checksum {
return fmt.Errorf("Expected MinIO binary checksum: %s, seen: %s", s1.Checksum, s2.Checksum) return fmt.Errorf("Expected Silo binary checksum: %s, seen: %s", s1.Checksum, s2.Checksum)
} }
ns1 := s1.NEndpoints ns1 := s1.NEndpoints
+4 -4
View File
@@ -1415,7 +1415,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
Object: ObjectInfo{Name: objInfo.Name}, Object: ObjectInfo{Name: objInfo.Name},
ReqParams: extractReqParams(r), ReqParams: extractReqParams(r),
RespElements: extractRespElements(w), RespElements: extractRespElements(w),
UserAgent: fmt.Sprintf("%s MinIO-Fan-Out (failed: %v)", r.UserAgent(), errs[i]), UserAgent: fmt.Sprintf("%s Silo-Fan-Out (failed: %v)", r.UserAgent(), errs[i]),
Host: handlers.GetSourceIP(r), Host: handlers.GetSourceIP(r),
}) })
continue continue
@@ -1434,7 +1434,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
Object: objInfo, Object: objInfo,
ReqParams: extractReqParams(r), ReqParams: extractReqParams(r),
RespElements: extractRespElements(w), RespElements: extractRespElements(w),
UserAgent: r.UserAgent() + " " + "MinIO-Fan-Out", UserAgent: r.UserAgent() + " " + "Silo-Fan-Out",
Host: handlers.GetSourceIP(r), Host: handlers.GetSourceIP(r),
}) })
} }
@@ -1462,7 +1462,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
Object: eventArgsList[i].Object, Object: eventArgsList[i].Object,
ReqParams: extractReqParams(r), ReqParams: extractReqParams(r),
RespElements: extractRespElements(w), RespElements: extractRespElements(w),
UserAgent: r.UserAgent() + " " + "MinIO-Fan-Out", UserAgent: r.UserAgent() + " " + "Silo-Fan-Out",
Host: handlers.GetSourceIP(r), Host: handlers.GetSourceIP(r),
}) })
@@ -1757,7 +1757,7 @@ func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.
if globalDNSConfig != nil { if globalDNSConfig != nil {
if err := globalDNSConfig.Delete(bucket); err != nil { if err := globalDNSConfig.Delete(bucket); err != nil {
dnsLogIf(ctx, fmt.Errorf("Unable to delete bucket DNS entry %w, please delete it manually, bucket on MinIO no longer exists", err)) dnsLogIf(ctx, fmt.Errorf("Unable to delete bucket DNS entry %w; please delete it manually because the bucket no longer exists on Silo", err))
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return return
} }
+10 -8
View File
@@ -49,20 +49,22 @@ var (
// MinioOSARCH - OS and ARCH. // MinioOSARCH - OS and ARCH.
minioOSARCH = runtime.GOOS + "-" + runtime.GOARCH minioOSARCH = runtime.GOOS + "-" + runtime.GOARCH
// MinioReleaseBaseURL - release url without os and arch. // MinioReleaseBaseURL is retained for source compatibility. Silo does not
MinioReleaseBaseURL = "https://dl.min.io/server/minio/release/" // provide an in-place update endpoint.
MinioReleaseBaseURL = ""
// MinioReleaseURL - release URL. // MinioReleaseURL - release URL.
MinioReleaseURL = MinioReleaseBaseURL + minioOSARCH + SlashSeparator MinioReleaseURL = MinioReleaseBaseURL + minioOSARCH + SlashSeparator
// MinioStoreName - MinIO store name. // MinioStoreName - Silo product name. The identifier is retained to avoid a
MinioStoreName = "MinIO" // source-only rename across compatibility-sensitive code.
MinioStoreName = "Silo"
// MinioUAName - MinIO user agent name. // MinioUAName - Silo user agent name.
MinioUAName = "MinIO" MinioUAName = "Silo"
// MinioBannerName - MinIO banner name for startup message. // MinioBannerName - Silo banner name for startup message.
MinioBannerName = "MinIO Object Storage Server" MinioBannerName = "Silo Object Storage Server"
// MinioLicense - MinIO server license. // MinioLicense - MinIO server license.
MinioLicense = "GNU AGPLv3 - https://www.gnu.org/licenses/agpl-3.0.html" MinioLicense = "GNU AGPLv3 - https://www.gnu.org/licenses/agpl-3.0.html"
-199
View File
@@ -1,199 +0,0 @@
// Copyright (c) 2015-2022 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cmd
import (
"bytes"
"compress/gzip"
"context"
"encoding/json"
"errors"
"fmt"
"math/rand"
"net/url"
"time"
"github.com/minio/madmin-go/v3"
)
var callhomeLeaderLockTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
// initCallhome will start the callhome task in the background.
func initCallhome(ctx context.Context, objAPI ObjectLayer) {
if !globalCallhomeConfig.Enabled() {
return
}
go func() {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
// Leader node (that successfully acquires the lock inside runCallhome)
// will keep performing the callhome. If the leader goes down for some reason,
// the lock will be released and another node will acquire it and take over
// because of this loop.
for {
if !globalCallhomeConfig.Enabled() {
return
}
if !runCallhome(ctx, objAPI) {
// callhome was disabled or context was canceled
return
}
// callhome running on a different node.
// sleep for some time and try again.
duration := max(time.Duration(r.Float64()*float64(globalCallhomeConfig.FrequencyDur())),
// Make sure to sleep at least a second to avoid high CPU ticks.
time.Second)
time.Sleep(duration)
}
}()
}
func runCallhome(ctx context.Context, objAPI ObjectLayer) bool {
// Make sure only 1 callhome is running on the cluster.
locker := objAPI.NewNSLock(minioMetaBucket, "callhome/runCallhome.lock")
lkctx, err := locker.GetLock(ctx, callhomeLeaderLockTimeout)
if err != nil {
// lock timedout means some other node is the leader,
// cycle back return 'true'
return true
}
ctx = lkctx.Context()
defer locker.Unlock(lkctx)
// Perform callhome once and then keep running it at regular intervals.
performCallhome(ctx)
callhomeTimer := time.NewTimer(globalCallhomeConfig.FrequencyDur())
defer callhomeTimer.Stop()
for {
if !globalCallhomeConfig.Enabled() {
// Stop the processing as callhome got disabled
return false
}
select {
case <-ctx.Done():
// indicates that we do not need to run callhome anymore
return false
case <-callhomeTimer.C:
if !globalCallhomeConfig.Enabled() {
// Stop the processing as callhome got disabled
return false
}
performCallhome(ctx)
// Reset the timer for next cycle.
callhomeTimer.Reset(globalCallhomeConfig.FrequencyDur())
}
}
}
func performCallhome(ctx context.Context) {
deadline := 10 * time.Second // Default deadline is 10secs for callhome
objectAPI := newObjectLayerFn()
if objectAPI == nil {
internalLogIf(ctx, errors.New("Callhome: object layer not ready"))
return
}
healthCtx, healthCancel := context.WithTimeout(ctx, deadline)
defer healthCancel()
healthInfoCh := make(chan madmin.HealthInfo)
query := url.Values{}
for _, k := range madmin.HealthDataTypesList {
query.Set(string(k), "true")
}
healthInfo := madmin.HealthInfo{
TimeStamp: time.Now().UTC(),
Version: madmin.HealthInfoVersion,
Minio: madmin.MinioHealthInfo{
Info: madmin.MinioInfo{
DeploymentID: globalDeploymentID(),
},
},
}
go fetchHealthInfo(healthCtx, objectAPI, &query, healthInfoCh, healthInfo)
for {
select {
case hi, hasMore := <-healthInfoCh:
if !hasMore {
auditOptions := AuditLogOptions{Event: "callhome:diagnostics"}
// Received all data. Send to SUBNET and return
err := sendHealthInfo(ctx, healthInfo)
if err != nil {
internalLogIf(ctx, fmt.Errorf("Unable to perform callhome: %w", err))
auditOptions.Error = err.Error()
}
auditLogInternal(ctx, auditOptions)
return
}
healthInfo = hi
case <-healthCtx.Done():
return
}
}
}
const (
subnetHealthPath = "/api/health/upload"
)
func sendHealthInfo(ctx context.Context, healthInfo madmin.HealthInfo) error {
url := globalSubnetConfig.BaseURL + subnetHealthPath
filename := fmt.Sprintf("health_%s.json.gz", UTCNow().Format("20060102150405"))
url += "?filename=" + filename
_, err := globalSubnetConfig.Upload(url, filename, createHealthJSONGzip(ctx, healthInfo))
return err
}
func createHealthJSONGzip(ctx context.Context, healthInfo madmin.HealthInfo) []byte {
var b bytes.Buffer
gzWriter := gzip.NewWriter(&b)
header := struct {
Version string `json:"version"`
}{Version: healthInfo.Version}
enc := json.NewEncoder(gzWriter)
if e := enc.Encode(header); e != nil {
internalLogIf(ctx, fmt.Errorf("Could not encode health info header: %w", e))
return nil
}
if e := enc.Encode(healthInfo); e != nil {
internalLogIf(ctx, fmt.Errorf("Could not encode health info: %w", e))
return nil
}
gzWriter.Flush()
gzWriter.Close()
return b.Bytes()
}
+25 -48
View File
@@ -83,7 +83,7 @@ func init() {
if mousetrap.StartedByExplorer() { if mousetrap.StartedByExplorer() {
fmt.Printf("Don't double-click %s\n", os.Args[0]) fmt.Printf("Don't double-click %s\n", os.Args[0])
fmt.Println("You need to open cmd.exe/PowerShell and run it from the command line") fmt.Println("You need to open cmd.exe/PowerShell and run it from the command line")
fmt.Println("Refer to the docs here on how to run it as a Windows Service https://github.com/minio/minio-service/tree/master/windows") fmt.Println("See Silo deployment documentation: https://silo.pgsty.com/operations/deployments/")
fmt.Println("Press the Enter Key to Exit") fmt.Println("Press the Enter Key to Exit")
fmt.Scanln() fmt.Scanln()
os.Exit(1) os.Exit(1)
@@ -295,42 +295,6 @@ func initConsoleServer() (*consoleapi.Server, error) {
return server, nil return server, nil
} }
// Check for updates and print a notification message
func checkUpdate(mode string) {
updateURL := minioReleaseInfoURL
if runtime.GOOS == globalWindowsOSName {
updateURL = minioReleaseWindowsInfoURL
}
u, err := url.Parse(updateURL)
if err != nil {
return
}
if currentReleaseTime.IsZero() {
return
}
_, lrTime, err := getLatestReleaseTime(u, 2*time.Second, mode)
if err != nil {
return
}
var older time.Duration
var downloadURL string
if lrTime.After(currentReleaseTime) {
older = lrTime.Sub(currentReleaseTime)
downloadURL = getDownloadURL(releaseTimeToReleaseTag(lrTime))
}
updateMsg := prepareUpdateMessage(downloadURL, older)
if updateMsg == "" {
return
}
logger.Info(prepareUpdateMessage("Run `mc admin update ALIAS`", lrTime.Sub(currentReleaseTime)))
}
func newConfigDir(dir string, dirSet bool, getDefaultDir func() string) (*ConfigDir, error) { func newConfigDir(dir string, dirSet bool, getDefaultDir func() string) (*ConfigDir, error) {
if dir == "" { if dir == "" {
dir = getDefaultDir() dir = getDefaultDir()
@@ -510,6 +474,16 @@ func handleCommonArgs(ctxt serverCtxt) {
var err error var err error
globalConfigDir, err = newConfigDir(configDir, configSet, defaultConfigDir.Get) globalConfigDir, err = newConfigDir(configDir, configSet, defaultConfigDir.Get)
logger.FatalIf(err, "Unable to initialize the (deprecated) config directory") logger.FatalIf(err, "Unable to initialize the (deprecated) config directory")
if !configSet {
defaultConfigDirWarningOnce.Do(func() {
switch defaultConfigDirSelection {
case defaultConfigDirLegacy:
logger.Info("Using legacy MinIO configuration directory %s because %s does not exist; no files were moved", defaultConfigDir.Get(), filepath.Join(filepath.Dir(defaultConfigDir.Get()), defaultSiloConfigDir))
case defaultConfigDirAmbiguous:
logger.Warning("Both Silo and legacy MinIO configuration directories exist; using %s. Set --config-dir explicitly before changing either directory", defaultConfigDir.Get())
}
})
}
globalCertsDir, err = newConfigDir(certsDir, certsSet, defaultCertsDir.Get) globalCertsDir, err = newConfigDir(certsDir, certsSet, defaultCertsDir.Get)
logger.FatalIf(err, "Unable to initialize the certs directory") logger.FatalIf(err, "Unable to initialize the certs directory")
@@ -730,7 +704,7 @@ func serverHandleEnvVars() {
} }
// Look for if URL has invalid values and return error. // Look for if URL has invalid values and return error.
if !isValidURLEndpoint((*url.URL)(u)) { if !isValidURLEndpoint((*url.URL)(u)) {
err := fmt.Errorf("URL contains unexpected resources, expected URL to be one of http(s)://console.example.com or as a subpath via API endpoint http(s)://minio.example.com/minio format: %v", u) err := fmt.Errorf("URL contains unexpected resources, expected URL to be one of http(s)://console.example.com or a /minio subpath on an API endpoint such as http(s)://silo.example.com/minio: %v", u)
logger.Fatal(err, "Invalid MINIO_BROWSER_REDIRECT_URL value is environment variable") logger.Fatal(err, "Invalid MINIO_BROWSER_REDIRECT_URL value is environment variable")
} }
globalBrowserRedirectURL = u globalBrowserRedirectURL = u
@@ -745,7 +719,7 @@ func serverHandleEnvVars() {
} }
// Look for if URL has invalid values and return error. // Look for if URL has invalid values and return error.
if !isValidURLEndpoint((*url.URL)(u)) { if !isValidURLEndpoint((*url.URL)(u)) {
err := fmt.Errorf("URL contains unexpected resources, expected URL to be of http(s)://minio.example.com format: %v", u) err := fmt.Errorf("URL contains unexpected resources, expected a URL such as http(s)://silo.example.com: %v", u)
logger.Fatal(err, "Invalid MINIO_SERVER_URL value is environment variable") logger.Fatal(err, "Invalid MINIO_SERVER_URL value is environment variable")
} }
u.Path = "" // remove any path component such as `/` u.Path = "" // remove any path component such as `/`
@@ -798,7 +772,7 @@ func serverHandleEnvVars() {
// Checking if the IP is a DNS entry. // Checking if the IP is a DNS entry.
addrs, err := globalDNSCache.LookupHost(GlobalContext, endpoint) addrs, err := globalDNSCache.LookupHost(GlobalContext, endpoint)
if err != nil { if err != nil {
logger.FatalIf(err, "Unable to initialize MinIO server with [%s] invalid entry found in MINIO_PUBLIC_IPS", endpoint) logger.FatalIf(err, "Unable to initialize Silo server with [%s] invalid entry found in MINIO_PUBLIC_IPS", endpoint)
} }
for _, addr := range addrs { for _, addr := range addrs {
domainIPs.Add(addr) domainIPs.Add(addr)
@@ -817,10 +791,13 @@ func serverHandleEnvVars() {
updateDomainIPs(domainIPs) updateDomainIPs(domainIPs)
} }
// In place update is true by default if the MINIO_UPDATE is not set // MINIO_UPDATE remains accepted for configuration compatibility, but Silo is
// or is not set to 'off', if MINIO_UPDATE is set to 'off' then // upgraded only through packages, images, or an orchestrator. It cannot
// in-place update is off. // re-enable the inherited in-place updater.
globalInplaceUpdateDisabled = strings.EqualFold(env.Get(config.EnvUpdate, config.EnableOn), config.EnableOff) if updateSetting := env.Get(config.EnvUpdate, config.EnableOff); !strings.EqualFold(updateSetting, config.EnableOff) {
logger.Warning("%s=%s is ignored: Silo in-place updates are permanently disabled", config.EnvUpdate, updateSetting)
}
globalInplaceUpdateDisabled = true
// Check if the supported credential env vars, // Check if the supported credential env vars,
// "MINIO_ROOT_USER" and "MINIO_ROOT_PASSWORD" are provided // "MINIO_ROOT_USER" and "MINIO_ROOT_PASSWORD" are provided
@@ -829,14 +806,14 @@ func serverHandleEnvVars() {
// Check all error conditions first // Check all error conditions first
//nolint:gocritic //nolint:gocritic
if !env.IsSet(config.EnvRootUser) && env.IsSet(config.EnvRootPassword) { if !env.IsSet(config.EnvRootUser) && env.IsSet(config.EnvRootPassword) {
logger.Fatal(config.ErrMissingEnvCredentialRootUser(nil), "Unable to start MinIO") logger.Fatal(config.ErrMissingEnvCredentialRootUser(nil), "Unable to start Silo")
} else if env.IsSet(config.EnvRootUser) && !env.IsSet(config.EnvRootPassword) { } else if env.IsSet(config.EnvRootUser) && !env.IsSet(config.EnvRootPassword) {
logger.Fatal(config.ErrMissingEnvCredentialRootPassword(nil), "Unable to start MinIO") logger.Fatal(config.ErrMissingEnvCredentialRootPassword(nil), "Unable to start Silo")
} else if !env.IsSet(config.EnvRootUser) && !env.IsSet(config.EnvRootPassword) { } else if !env.IsSet(config.EnvRootUser) && !env.IsSet(config.EnvRootPassword) {
if !env.IsSet(config.EnvAccessKey) && env.IsSet(config.EnvSecretKey) { if !env.IsSet(config.EnvAccessKey) && env.IsSet(config.EnvSecretKey) {
logger.Fatal(config.ErrMissingEnvCredentialAccessKey(nil), "Unable to start MinIO") logger.Fatal(config.ErrMissingEnvCredentialAccessKey(nil), "Unable to start Silo")
} else if env.IsSet(config.EnvAccessKey) && !env.IsSet(config.EnvSecretKey) { } else if env.IsSet(config.EnvAccessKey) && !env.IsSet(config.EnvSecretKey) {
logger.Fatal(config.ErrMissingEnvCredentialSecretKey(nil), "Unable to start MinIO") logger.Fatal(config.ErrMissingEnvCredentialSecretKey(nil), "Unable to start Silo")
} }
} }
+7 -12
View File
@@ -92,13 +92,13 @@ func initHelp() {
config.HelpKV{ config.HelpKV{
Key: config.SubnetSubSys, Key: config.SubnetSubSys,
Type: "string", Type: "string",
Description: "register Enterprise license for the cluster", Description: "legacy MinIO SUBNET settings retained for compatibility; external integration is disabled in Silo",
Optional: true, Optional: true,
}, },
config.HelpKV{ config.HelpKV{
Key: config.CallhomeSubSys, Key: config.CallhomeSubSys,
Type: "string", Type: "string",
Description: "enable callhome to MinIO SUBNET", Description: "legacy callhome settings retained for compatibility; diagnostic uploads are disabled in Silo",
Optional: true, Optional: true,
}, },
config.HelpKV{ config.HelpKV{
@@ -383,14 +383,9 @@ func validateSubSysConfig(ctx context.Context, s config.Config, subSys string, o
return err return err
} }
case config.CallhomeSubSys: case config.CallhomeSubSys:
cfg, err := callhome.LookupConfig(s[config.CallhomeSubSys][config.Default]) if _, err := callhome.LookupConfig(s[config.CallhomeSubSys][config.Default]); err != nil {
if err != nil {
return err return err
} }
// callhome cannot be enabled if license is not registered yet, throw an error.
if cfg.Enabled() && !globalSubnetConfig.Registered() {
return errors.New("Deployment is not registered with SUBNET. Please register the deployment via 'mc license register ALIAS'")
}
case config.DriveSubSys: case config.DriveSubSys:
if _, err := drive.LookupConfig(s[config.DriveSubSys][config.Default]); err != nil { if _, err := drive.LookupConfig(s[config.DriveSubSys][config.Default]); err != nil {
return err return err
@@ -676,11 +671,11 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
if err != nil { if err != nil {
configLogIf(ctx, fmt.Errorf("Unable to load callhome config: %w", err)) configLogIf(ctx, fmt.Errorf("Unable to load callhome config: %w", err))
} else { } else {
enable := callhomeCfg.Enable && !globalCallhomeConfig.Enabled() if callhomeCfg.Enable {
globalCallhomeConfig.Update(callhomeCfg) configLogIf(ctx, errors.New("callhome is configured but ignored: Silo does not upload diagnostics to MinIO SUBNET"))
if enable {
initCallhome(ctx, objAPI)
} }
callhomeCfg.Enable = false
globalCallhomeConfig.Update(callhomeCfg)
} }
case config.DriveSubSys: case config.DriveSubSys:
driveConfig, err := drive.LookupConfig(s[config.DriveSubSys][config.Default]) driveConfig, err := drive.LookupConfig(s[config.DriveSubSys][config.Default])
+42 -16
View File
@@ -20,13 +20,16 @@ package cmd
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"sync"
homedir "github.com/mitchellh/go-homedir" homedir "github.com/mitchellh/go-homedir"
) )
const ( const (
// Default minio configuration directory where below configuration files/directories are stored. // New installations use the Silo-branded directory. The legacy directory is
defaultMinioConfigDir = ".minio" // still selected when it is the only existing choice.
defaultSiloConfigDir = ".silo"
legacyMinioConfigDir = ".minio"
// Directory contains below files/directories for HTTPS configuration. // Directory contains below files/directories for HTTPS configuration.
certsDir = "certs" certsDir = "certs"
@@ -41,33 +44,56 @@ const (
privateKeyFile = "private.key" privateKeyFile = "private.key"
) )
type defaultConfigDirState uint8
const (
defaultConfigDirNew defaultConfigDirState = iota
defaultConfigDirLegacy
defaultConfigDirAmbiguous
)
// ConfigDir - points to a user set directory. // ConfigDir - points to a user set directory.
type ConfigDir struct { type ConfigDir struct {
path string path string
} }
func getDefaultConfigDir() string { func selectDefaultConfigDir(homeDir string) (string, defaultConfigDirState) {
siloDir := filepath.Join(homeDir, defaultSiloConfigDir)
minioDir := filepath.Join(homeDir, legacyMinioConfigDir)
_, siloErr := os.Stat(siloDir)
_, minioErr := os.Stat(minioDir)
siloExists := siloErr == nil || !os.IsNotExist(siloErr)
minioExists := minioErr == nil || !os.IsNotExist(minioErr)
switch {
case siloExists && minioExists:
return siloDir, defaultConfigDirAmbiguous
case siloExists:
return siloDir, defaultConfigDirNew
case minioExists:
return minioDir, defaultConfigDirLegacy
default:
return siloDir, defaultConfigDirNew
}
}
func getDefaultConfigDir() (string, defaultConfigDirState) {
homeDir, err := homedir.Dir() homeDir, err := homedir.Dir()
if err != nil { if err != nil {
return "" return "", defaultConfigDirNew
} }
return filepath.Join(homeDir, defaultMinioConfigDir) return selectDefaultConfigDir(homeDir)
}
func getDefaultCertsDir() string {
return filepath.Join(getDefaultConfigDir(), certsDir)
}
func getDefaultCertsCADir() string {
return filepath.Join(getDefaultCertsDir(), certsCADir)
} }
var ( var (
defaultConfigDirPath, defaultConfigDirSelection = getDefaultConfigDir()
defaultConfigDirWarningOnce sync.Once
// Default config, certs and CA directories. // Default config, certs and CA directories.
defaultConfigDir = &ConfigDir{path: getDefaultConfigDir()} defaultConfigDir = &ConfigDir{path: defaultConfigDirPath}
defaultCertsDir = &ConfigDir{path: getDefaultCertsDir()} defaultCertsDir = &ConfigDir{path: filepath.Join(defaultConfigDirPath, certsDir)}
defaultCertsCADir = &ConfigDir{path: getDefaultCertsCADir()} defaultCertsCADir = &ConfigDir{path: filepath.Join(defaultConfigDirPath, certsDir, certsCADir)}
// Points to current configuration directory -- deprecated, to be removed in future. // Points to current configuration directory -- deprecated, to be removed in future.
globalConfigDir = defaultConfigDir globalConfigDir = defaultConfigDir
+53
View File
@@ -0,0 +1,53 @@
// 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.
package cmd
import (
"os"
"path/filepath"
"testing"
)
func TestSelectDefaultConfigDir(t *testing.T) {
tests := []struct {
name string
createSilo bool
createMinio bool
wantDir string
wantState defaultConfigDirState
}{
{name: "new install", wantDir: defaultSiloConfigDir, wantState: defaultConfigDirNew},
{name: "silo exists", createSilo: true, wantDir: defaultSiloConfigDir, wantState: defaultConfigDirNew},
{name: "legacy only", createMinio: true, wantDir: legacyMinioConfigDir, wantState: defaultConfigDirLegacy},
{name: "both exist", createSilo: true, createMinio: true, wantDir: defaultSiloConfigDir, wantState: defaultConfigDirAmbiguous},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
home := t.TempDir()
for name, create := range map[string]bool{
defaultSiloConfigDir: tt.createSilo,
legacyMinioConfigDir: tt.createMinio,
} {
if create {
if err := os.Mkdir(filepath.Join(home, name), 0o700); err != nil {
t.Fatal(err)
}
}
}
gotDir, gotState := selectDefaultConfigDir(home)
if want := filepath.Join(home, tt.wantDir); gotDir != want {
t.Fatalf("directory = %q, want %q", gotDir, want)
}
if gotState != tt.wantState {
t.Fatalf("state = %d, want %d", gotState, tt.wantState)
}
})
}
}
+1 -1
View File
@@ -33,7 +33,7 @@ import (
) )
// This file implements and supports ellipses pattern for // This file implements and supports ellipses pattern for
// `minio server` command line arguments. // `silo server` command line arguments.
// Endpoint set represents parsed ellipses values, also provides // Endpoint set represents parsed ellipses values, also provides
// methods to get the sets of endpoints. // methods to get the sets of endpoints.
+2 -2
View File
@@ -207,7 +207,7 @@ func NewEndpoint(arg string) (ep Endpoint, e error) {
// On windows having a preceding SlashSeparator will cause problems, if the // On windows having a preceding SlashSeparator will cause problems, if the
// command line already has C:/<export-folder/ in it. Final resulting // command line already has C:/<export-folder/ in it. Final resulting
// path on windows might become C:/C:/ this will cause problems // path on windows might become C:/C:/ this will cause problems
// of starting minio server properly in distributed mode on windows. // of starting Silo properly in distributed mode on Windows.
// As a special case make sure to trim the separator. // As a special case make sure to trim the separator.
// NOTE: It is also perfectly fine for windows users to have a path // NOTE: It is also perfectly fine for windows users to have a path
@@ -226,7 +226,7 @@ func NewEndpoint(arg string) (ep Endpoint, e error) {
} else { } else {
// Only check if the arg is an ip address and ask for scheme since its absent. // Only check if the arg is an ip address and ask for scheme since its absent.
// localhost, example.com, any FQDN cannot be disambiguated from a regular file path such as // localhost, example.com, any FQDN cannot be disambiguated from a regular file path such as
// /mnt/export1. So we go ahead and start the minio server in FS modes in these cases. // /mnt/export1. So we go ahead and start Silo in FS mode in these cases.
if isHostIP(arg) { if isHostIP(arg) {
return ep, fmt.Errorf("invalid URL endpoint format: missing scheme http or https") return ep, fmt.Errorf("invalid URL endpoint format: missing scheme http or https")
} }
+1 -1
View File
@@ -307,7 +307,7 @@ func (z *erasureServerPools) GetRawData(ctx context.Context, volume, file string
r = io.NopCloser(bytes.NewBuffer([]byte{})) r = io.NopCloser(bytes.NewBuffer([]byte{}))
} }
// Keep disk path instead of ID, to ensure that the downloaded zip file can be // Keep disk path instead of ID, to ensure that the downloaded zip file can be
// easily automated with `minio server hostname{1...n}/disk{1...m}`. // easily automated with `silo server hostname{1...n}/disk{1...m}`.
err = fn(r, disk.Hostname(), disk.Endpoint().Path, pathJoin(volume, si.Name), si) err = fn(r, disk.Hostname(), disk.Endpoint().Path, pathJoin(volume, si.Name), si)
r.Close() r.Close()
if err != nil { if err != nil {
+1 -1
View File
@@ -1051,7 +1051,7 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
if !reflect.DeepEqual(s.format, refFormat) { if !reflect.DeepEqual(s.format, refFormat) {
// Format is corrupted and unrecognized by the running instance. // Format is corrupted and unrecognized by the running instance.
healingLogIf(ctx, fmt.Errorf("Unable to heal the newly replaced drives due to format.json inconsistencies, please engage MinIO support for further assistance: %w", healingLogIf(ctx, fmt.Errorf("Unable to heal the newly replaced drives due to format.json inconsistencies; please report this to Silo maintainers at https://github.com/pgsty/minio/issues: %w",
errCorruptedFormat)) errCorruptedFormat))
return res, errCorruptedFormat return res, errCorruptedFormat
} }
+2 -2
View File
@@ -34,7 +34,7 @@ var fmtGenFlags = []cli.Flag{
}, },
cli.StringFlag{ cli.StringFlag{
Name: "deployment-id", Name: "deployment-id",
Usage: "deployment-id of the MinIO cluster for which format.json is needed", Usage: "deployment-id of the Silo cluster for which format.json is needed",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "address", Name: "address",
@@ -69,7 +69,7 @@ FLAGS:
{{range .VisibleFlags}}{{.}} {{range .VisibleFlags}}{{.}}
{{end}}{{end}} {{end}}{{end}}
EXAMPLES: EXAMPLES:
1. Generate format.json.zip containing format.json files for all drives in a distributed MinIO server pool of 32 nodes with 32 drives each. 1. Generate format.json.zip containing format.json files for all drives in a distributed Silo server pool of 32 nodes with 32 drives each.
{{.Prompt}} {{.HelpName}} http://node{1...32}.example.com/mnt/export{1...32} {{.Prompt}} {{.HelpName}} http://node{1...32}.example.com/mnt/export{1...32}
`, `,
+2 -2
View File
@@ -139,9 +139,9 @@ func startFTPServer(args []string) {
logger.Fatal(fmt.Errorf("invalid TLS arguments provided. force-tls, but missing private key --ftp=\"tls-private-key=path/to/private.key\""), "unable to start FTP server") logger.Fatal(fmt.Errorf("invalid TLS arguments provided. force-tls, but missing private key --ftp=\"tls-private-key=path/to/private.key\""), "unable to start FTP server")
} }
name := "MinIO FTP Server" name := "Silo FTP Server"
if tls { if tls {
name = "MinIO FTP(Secure) Server" name = "Silo FTP(Secure) Server"
} }
ftpServer, err := ftp.NewServer(&ftp.Options{ ftpServer, err := ftp.NewServer(&ftp.Options{
+5 -9
View File
@@ -176,13 +176,13 @@ var (
// Global user opts context // Global user opts context
globalServerCtxt serverCtxt globalServerCtxt serverCtxt
// Indicates if the running minio server is distributed setup. // Indicates whether the running Silo server is a distributed setup.
globalIsDistErasure = false globalIsDistErasure = false
// Indicates if the running minio server is an erasure-code backend. // Indicates whether the running Silo server is an erasure-code backend.
globalIsErasure = false globalIsErasure = false
// Indicates if the running minio server is in single drive XL mode. // Indicates whether the running Silo server is in single-drive XL mode.
globalIsErasureSD = false globalIsErasureSD = false
// Indicates if server code should go through testing path. // Indicates if server code should go through testing path.
@@ -201,8 +201,8 @@ var (
// globalBrowserConfig Browser user configurable settings // globalBrowserConfig Browser user configurable settings
globalBrowserConfig browser.Config globalBrowserConfig browser.Config
// This flag is set to 'true' when MINIO_UPDATE env is set to 'off'. Default is false. // Silo permanently disables inherited in-place updates.
globalInplaceUpdateDisabled = false globalInplaceUpdateDisabled = true
// Captures site name and region // Captures site name and region
globalSite config.Site globalSite config.Site
@@ -433,10 +433,6 @@ var (
// MinIO client // MinIO client
globalMinioClient *minio.Client globalMinioClient *minio.Client
// Public key for subnet confidential information
subnetAdminPublicKey = []byte("-----BEGIN PUBLIC KEY-----\nMIIBCgKCAQEAyC+ol5v0FP+QcsR6d1KypR/063FInmNEFsFzbEwlHQyEQN3O7kNI\nwVDN1vqp1wDmJYmv4VZGRGzfFw1q+QV7K1TnysrEjrqpVxfxzDQCoUadAp8IxLLc\ns2fjyDNxnZjoC6fTID9C0khKnEa5fPZZc3Ihci9SiCGkPmyUyCGVSxWXIKqL2Lrj\nyDc0pGeEhWeEPqw6q8X2jvTC246tlzqpDeNsPbcv2KblXRcKniQNbBrizT37CKHQ\nM6hc9kugrZbFuo8U5/4RQvZPJnx/DVjLDyoKo2uzuVQs4s+iBrA5sSSLp8rPED/3\n6DgWw3e244Dxtrg972dIT1IOqgn7KUJzVQIDAQAB\n-----END PUBLIC KEY-----")
subnetAdminPublicKeyDev = []byte("-----BEGIN PUBLIC KEY-----\nMIIBCgKCAQEArhQYXQd6zI4uagtVfthAPOt6i4AYHnEWCoNeAovM4MNl42I9uQFh\n3VHkbWj9Gpx9ghf6PgRgK+8FcFvy+StmGcXpDCiFywXX24uNhcZjscX1C4Esk0BW\nidfI2eXYkOlymD4lcK70SVgJvC693Qa7Z3FE1KU8Nfv2bkxEE4bzOkojX9t6a3+J\nR8X6Z2U8EMlH1qxJPgiPogELhWP0qf2Lq7GwSAflo1Tj/ytxvD12WrnE0Rrj/8yP\nSnp7TbYm91KocKMExlmvx3l2XPLxeU8nf9U0U+KOmorejD3MDMEPF+tlk9LB3JWP\nZqYYe38rfALVTn4RVJriUcNOoEpEyC0WEwIDAQAB\n-----END PUBLIC KEY-----")
// dynamic sleeper to avoid thundering herd for trash folder expunge routine // dynamic sleeper to avoid thundering herd for trash folder expunge routine
deleteCleanupSleeper = newDynamicSleeper(5, 25*time.Millisecond, false) deleteCleanupSleeper = newDynamicSleeper(5, 25*time.Millisecond, false)
+1 -1
View File
@@ -465,7 +465,7 @@ func errorResponseHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodOptions { if r.Method == http.MethodOptions {
return return
} }
desc := "Do not upgrade one server at a time - please follow the recommended guidelines mentioned here https://github.com/minio/minio#upgrading-minio for your environment" desc := "Do not upgrade one server at a time; follow the Silo upgrade guide at https://silo.pgsty.com/operations/deployments/baremetal-upgrade-minio-deployment/"
switch { switch {
case strings.HasPrefix(r.URL.Path, peerRESTPrefix): case strings.HasPrefix(r.URL.Path, peerRESTPrefix):
writeErrorResponseString(r.Context(), w, APIError{ writeErrorResponseString(r.Context(), w, APIError{
+2 -2
View File
@@ -390,7 +390,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
if err := saveIAMFormat(retryCtx, sys.store); err != nil { if err := saveIAMFormat(retryCtx, sys.store); err != nil {
if configRetriableErrors(err) { if configRetriableErrors(err) {
retryInterval := time.Duration(r.Float64() * float64(time.Second)) retryInterval := time.Duration(r.Float64() * float64(time.Second))
logger.Info("Waiting for all MinIO IAM sub-system to be initialized.. possible cause (%v) (retrying in %s)", err, retryInterval) logger.Info("Waiting for the Silo IAM sub-system to be initialized.. possible cause (%v) (retrying in %s)", err, retryInterval)
time.Sleep(retryInterval) time.Sleep(retryInterval)
continue continue
} }
@@ -410,7 +410,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
if err := sys.Load(retryCtx, true); err != nil { if err := sys.Load(retryCtx, true); err != nil {
if configRetriableErrors(err) { if configRetriableErrors(err) {
retryInterval := time.Duration(r.Float64() * float64(time.Second)) retryInterval := time.Duration(r.Float64() * float64(time.Second))
logger.Info("Waiting for all MinIO IAM sub-system to be initialized.. possible cause (%v) (retrying in %s)", err, retryInterval) logger.Info("Waiting for the Silo IAM sub-system to be initialized.. possible cause (%v) (retrying in %s)", err, retryInterval)
time.Sleep(retryInterval) time.Sleep(retryInterval)
continue continue
} }
+78
View File
@@ -0,0 +1,78 @@
// 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.
package cmd
import (
"bytes"
crand "crypto/rand"
"crypto/rsa"
"io"
"testing"
"github.com/minio/madmin-go/v3/estream"
)
func TestInspectDataUsesOnlyRequesterKey(t *testing.T) {
privateKey, err := rsa.GenerateKey(crand.Reader, 2048)
if err != nil {
t.Fatal(err)
}
var encoded bytes.Buffer
writer := estream.NewWriter(&encoded)
clusterInfo := []byte("local cluster metadata")
if err = addInspectDataKey(writer, &privateKey.PublicKey, clusterInfo); err != nil {
t.Fatal(err)
}
inspect, err := writer.AddEncryptedStream("inspect.zip", nil)
if err != nil {
t.Fatal(err)
}
inspectData := []byte("local inspect archive")
if _, err = inspect.Write(inspectData); err != nil {
t.Fatal(err)
}
if err = inspect.Close(); err != nil {
t.Fatal(err)
}
if err = writer.Close(); err != nil {
t.Fatal(err)
}
reader, err := estream.NewReader(bytes.NewReader(encoded.Bytes()))
if err != nil {
t.Fatal(err)
}
reader.SetPrivateKey(privateKey)
want := []struct {
name string
data []byte
}{
{name: "cluster.info", data: clusterInfo},
{name: "inspect.zip", data: inspectData},
}
for _, expected := range want {
stream, err := reader.NextStream()
if err != nil {
t.Fatalf("read %s: %v", expected.name, err)
}
if stream.Name != expected.name {
t.Fatalf("stream name = %q, want %q", stream.Name, expected.name)
}
got, err := io.ReadAll(stream)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(got, expected.data) {
t.Fatalf("%s = %q, want %q", expected.name, got, expected.data)
}
}
if _, err = reader.NextStream(); err != io.EOF {
t.Fatalf("final stream error = %v, want EOF", err)
}
}
+9 -6
View File
@@ -150,16 +150,16 @@ func newApp(name string) *cli.App {
app := cli.NewApp() app := cli.NewApp()
app.Name = name app.Name = name
app.Author = "MinIO, Inc." app.Author = "Silo contributors"
app.Version = ReleaseTag app.Version = ReleaseTag
app.Usage = "High Performance Object Storage" app.Usage = "S3-compatible object storage"
app.Description = `Build high performance data infrastructure for machine learning, analytics and application data workloads with MinIO` app.Description = `Run independently maintained, S3-compatible object storage with Silo`
app.Flags = GlobalFlags app.Flags = GlobalFlags
app.HideHelpCommand = true // Hide `help, h` command, we already have `minio --help`. app.HideHelpCommand = true // Hide `help, h`; the top-level `silo --help` already covers it.
app.Commands = commands app.Commands = commands
app.CustomAppHelpTemplate = minioHelpTemplate app.CustomAppHelpTemplate = minioHelpTemplate
app.CommandNotFound = func(ctx *cli.Context, command string) { app.CommandNotFound = func(ctx *cli.Context, command string) {
console.Printf("%s is not a minio sub-command. See minio --help.\n", command) console.Printf("%s is not a %s sub-command. See %s --help.\n", command, ctx.App.Name, ctx.App.Name)
closestCommands := findClosestCommands(command) closestCommands := findClosestCommands(command)
if len(closestCommands) > 0 { if len(closestCommands) > 0 {
console.Println() console.Println()
@@ -178,6 +178,7 @@ func newApp(name string) *cli.App {
func startupBanner(banner io.Writer) { func startupBanner(banner io.Writer) {
CopyrightYear = strconv.Itoa(time.Now().Year()) CopyrightYear = strconv.Itoa(time.Now().Year())
fmt.Fprintln(banner, color.Blue("Copyright:")+color.Bold(" 2015-%s MinIO, Inc.", CopyrightYear)) fmt.Fprintln(banner, color.Blue("Copyright:")+color.Bold(" 2015-%s MinIO, Inc.", CopyrightYear))
fmt.Fprintln(banner, color.Blue("Modifications:")+color.Bold(" Copyright 2026 PGSTY contributors"))
fmt.Fprintln(banner, color.Blue("License:")+color.Bold(" "+MinioLicense)) fmt.Fprintln(banner, color.Blue("License:")+color.Bold(" "+MinioLicense))
fmt.Fprintln(banner, color.Blue("Version:")+color.Bold(" %s (%s %s/%s)", ReleaseTag, runtime.Version(), runtime.GOOS, runtime.GOARCH)) fmt.Fprintln(banner, color.Blue("Version:")+color.Bold(" %s (%s %s/%s)", ReleaseTag, runtime.Version(), runtime.GOOS, runtime.GOARCH))
} }
@@ -188,6 +189,8 @@ func versionBanner(c *cli.Context) io.Reader {
fmt.Fprintln(banner, color.Blue("Runtime:")+color.Bold(" %s %s/%s", runtime.Version(), runtime.GOOS, runtime.GOARCH)) fmt.Fprintln(banner, color.Blue("Runtime:")+color.Bold(" %s %s/%s", runtime.Version(), runtime.GOOS, runtime.GOARCH))
fmt.Fprintln(banner, color.Blue("License:")+color.Bold(" GNU AGPLv3 - https://www.gnu.org/licenses/agpl-3.0.html")) fmt.Fprintln(banner, color.Blue("License:")+color.Bold(" GNU AGPLv3 - https://www.gnu.org/licenses/agpl-3.0.html"))
fmt.Fprintln(banner, color.Blue("Copyright:")+color.Bold(" 2015-%s MinIO, Inc.", CopyrightYear)) fmt.Fprintln(banner, color.Blue("Copyright:")+color.Bold(" 2015-%s MinIO, Inc.", CopyrightYear))
fmt.Fprintln(banner, color.Blue("Modifications:")+color.Bold(" Copyright 2026 PGSTY contributors"))
fmt.Fprintln(banner, color.Blue("Source compatibility:")+color.Bold(" based on MinIO technology"))
return strings.NewReader(banner.String()) return strings.NewReader(banner.String())
} }
@@ -197,7 +200,7 @@ func printMinIOVersion(c *cli.Context) {
var debugNoExit = env.Get("_MINIO_DEBUG_NO_EXIT", "") != "" var debugNoExit = env.Get("_MINIO_DEBUG_NO_EXIT", "") != ""
// Main main for minio server. // Main is the Silo server entry point.
func Main(args []string) { func Main(args []string) {
// Set the minio app name. // Set the minio app name.
appName := filepath.Base(args[0]) appName := filepath.Base(args[0])
+8
View File
@@ -610,6 +610,10 @@ func (s *peerRESTServer) VerifyBinaryHandler(w http.ResponseWriter, r *http.Requ
s.writeErrorResponse(w, errors.New("Invalid request")) s.writeErrorResponse(w, errors.New("Invalid request"))
return return
} }
if globalInplaceUpdateDisabled {
s.writeErrorResponse(w, errInplaceUpdateDisabled)
return
}
if r.ContentLength < 0 { if r.ContentLength < 0 {
s.writeErrorResponse(w, errInvalidArgument) s.writeErrorResponse(w, errInvalidArgument)
@@ -659,6 +663,10 @@ func (s *peerRESTServer) CommitBinaryHandler(w http.ResponseWriter, r *http.Requ
s.writeErrorResponse(w, errors.New("Invalid request")) s.writeErrorResponse(w, errors.New("Invalid request"))
return return
} }
if globalInplaceUpdateDisabled {
s.writeErrorResponse(w, errInplaceUpdateDisabled)
return
}
if err := commitBinary(); err != nil { if err := commitBinary(); err != nil {
s.writeErrorResponse(w, err) s.writeErrorResponse(w, err)
+2 -2
View File
@@ -81,7 +81,7 @@ func bgFormatErasureCleanupTmp(diskPath string) {
// |__ e870a2c1-d09c-450c-a69c-6eaa54a89b3e // |__ e870a2c1-d09c-450c-a69c-6eaa54a89b3e
// //
// In this example, `33a58b40-aecc-4c9f-a22f-ff17bfa33b62` directory contains // In this example, `33a58b40-aecc-4c9f-a22f-ff17bfa33b62` directory contains
// temporary objects from one of the previous runs of minio server. // temporary objects from one of the previous runs of Silo.
tmpID := mustGetUUID() tmpID := mustGetUUID()
tmpOld := pathJoin(diskPath, minioMetaTmpBucket+"-old", tmpID) tmpOld := pathJoin(diskPath, minioMetaTmpBucket+"-old", tmpID)
if err := renameAll(pathJoin(diskPath, minioMetaTmpBucket), if err := renameAll(pathJoin(diskPath, minioMetaTmpBucket),
@@ -263,7 +263,7 @@ func waitForFormatErasure(firstDisk bool, endpoints Endpoints, poolCount, setCou
defer func() { defer func() {
if err == nil && format != nil { if err == nil && format != nil {
// Assign globalDeploymentID() on first run for the // Assign globalDeploymentID() on first run for the
// minio server managing the first disk // Silo server managing the first disk
globalDeploymentIDPtr.Store(&format.ID) globalDeploymentIDPtr.Store(&format.ID)
// Set the deployment ID here to avoid races. // Set the deployment ID here to avoid races.
+13 -23
View File
@@ -114,7 +114,7 @@ var ServerFlags = []cli.Flag{
}, },
cli.StringFlag{ cli.StringFlag{
Name: "interface", Name: "interface",
Usage: "bind to right VRF device for MinIO services", Usage: "bind to the VRF device used by Silo services",
Hidden: true, Hidden: true,
EnvVar: "MINIO_INTERFACE", EnvVar: "MINIO_INTERFACE",
}, },
@@ -220,20 +220,20 @@ FLAGS:
{{range .VisibleFlags}}{{.}} {{range .VisibleFlags}}{{.}}
{{end}}{{end}} {{end}}{{end}}
EXAMPLES: EXAMPLES:
1. Start MinIO server on "/home/shared" directory. 1. Start Silo server on "/home/shared" directory.
{{.Prompt}} {{.HelpName}} /home/shared {{.Prompt}} {{.HelpName}} /home/shared
2. Start single node server with 64 local drives "/mnt/data1" to "/mnt/data64". 2. Start single node server with 64 local drives "/mnt/data1" to "/mnt/data64".
{{.Prompt}} {{.HelpName}} /mnt/data{1...64} {{.Prompt}} {{.HelpName}} /mnt/data{1...64}
3. Start distributed MinIO server on an 32 node setup with 32 drives each, run following command on all the nodes 3. Start distributed Silo server on a 32-node setup with 32 drives each; run the following command on all nodes.
{{.Prompt}} {{.HelpName}} http://node{1...32}.example.com/mnt/export{1...32} {{.Prompt}} {{.HelpName}} http://node{1...32}.example.com/mnt/export{1...32}
4. Start distributed MinIO server in an expanded setup, run the following command on all the nodes 4. Start distributed Silo server in an expanded setup; run the following command on all nodes.
{{.Prompt}} {{.HelpName}} http://node{1...16}.example.com/mnt/export{1...32} \ {{.Prompt}} {{.HelpName}} http://node{1...16}.example.com/mnt/export{1...32} \
http://node{17...64}.example.com/mnt/export{1...64} http://node{17...64}.example.com/mnt/export{1...64}
5. Start distributed MinIO server, with FTP and SFTP servers on all interfaces via port 8021, 8022 respectively 5. Start distributed Silo server with FTP and SFTP on ports 8021 and 8022.
{{.Prompt}} {{.HelpName}} http://node{1...4}.example.com/mnt/export{1...4} \ {{.Prompt}} {{.HelpName}} http://node{1...4}.example.com/mnt/export{1...4} \
--ftp="address=:8021" --ftp="passive-port-range=30000-40000" \ --ftp="address=:8021" --ftp="passive-port-range=30000-40000" \
--sftp="address=:8022" --sftp="ssh-private-key=${HOME}/.ssh/id_rsa" --sftp="address=:8022" --sftp="ssh-private-key=${HOME}/.ssh/id_rsa"
@@ -597,7 +597,7 @@ func initServerConfig(ctx context.Context, newObject ObjectLayer) error {
// These messages only meant primarily for distributed setup, so only log during distributed setup. // These messages only meant primarily for distributed setup, so only log during distributed setup.
if globalIsDistErasure { if globalIsDistErasure {
logger.Info("Waiting for all MinIO sub-systems to be initialize...") logger.Info("Waiting for all Silo subsystems to initialize...")
} }
// Upon success migrating the config, initialize all sub-systems // Upon success migrating the config, initialize all sub-systems
@@ -607,13 +607,13 @@ func initServerConfig(ctx context.Context, newObject ObjectLayer) error {
// All successful return. // All successful return.
if globalIsDistErasure { if globalIsDistErasure {
// These messages only meant primarily for distributed setup, so only log during distributed setup. // These messages only meant primarily for distributed setup, so only log during distributed setup.
logger.Info("All MinIO sub-systems initialized successfully in %s", time.Since(t1)) logger.Info("All Silo subsystems initialized successfully in %s", time.Since(t1))
} }
return nil return nil
} }
if configRetriableErrors(err) { if configRetriableErrors(err) {
logger.Info("Waiting for all MinIO sub-systems to be initialized.. possible cause (%v)", err) logger.Info("Waiting for all Silo subsystems to initialize; possible cause: %v", err)
time.Sleep(time.Duration(r.Float64() * float64(5*time.Second))) time.Sleep(time.Duration(r.Float64() * float64(5*time.Second)))
continue continue
} }
@@ -742,7 +742,7 @@ func initializeLogRotate(ctx *cli.Context) (io.WriteCloser, error) {
return output, nil return output, nil
} }
// serverMain handler called for 'minio server' command. // serverMain handles the 'silo server' command.
func serverMain(ctx *cli.Context) { func serverMain(ctx *cli.Context) {
r := rand.New(rand.NewSource(time.Now().UnixNano())) r := rand.New(rand.NewSource(time.Now().UnixNano()))
@@ -851,16 +851,6 @@ func serverMain(ctx *cli.Context) {
getCert = globalTLSCerts.GetCertificate getCert = globalTLSCerts.GetCertificate
} }
// Check for updates in non-blocking manner.
go func() {
if !globalServerCtxt.Quiet && !globalInplaceUpdateDisabled {
// Check for new updates from dl.min.io.
bootstrapTrace("checkUpdate", func() {
checkUpdate(getMinioMode())
})
}
}()
// Set system resources to maximum. // Set system resources to maximum.
bootstrapTrace("setMaxResources", func() { bootstrapTrace("setMaxResources", func() {
_ = setMaxResources(globalServerCtxt) _ = setMaxResources(globalServerCtxt)
@@ -868,13 +858,13 @@ func serverMain(ctx *cli.Context) {
// Verify kernel release and version. // Verify kernel release and version.
if oldLinux() { if oldLinux() {
warnings = append(warnings, color.YellowBold("Detected Linux kernel version older than 4.0 release, there are some known potential performance problems with this kernel version. MinIO recommends a minimum of 4.x linux kernel version for best performance")) warnings = append(warnings, color.YellowBold("Detected a Linux kernel older than 4.0; Silo recommends kernel 4.x or newer to avoid known performance problems"))
} }
maxProcs := runtime.GOMAXPROCS(0) maxProcs := runtime.GOMAXPROCS(0)
cpuProcs := runtime.NumCPU() cpuProcs := runtime.NumCPU()
if maxProcs < cpuProcs { if maxProcs < cpuProcs {
warnings = append(warnings, color.YellowBold("Detected GOMAXPROCS(%d) < NumCPU(%d), please make sure to provide all PROCS to MinIO for optimal performance", warnings = append(warnings, color.YellowBold("Detected GOMAXPROCS(%d) < NumCPU(%d); provide all processors to Silo for optimal performance",
maxProcs, cpuProcs)) maxProcs, cpuProcs))
} }
@@ -1161,7 +1151,7 @@ func serverMain(ctx *cli.Context) {
Transport: globalRemoteTargetTransport, Transport: globalRemoteTargetTransport,
Region: region, Region: region,
}) })
logger.FatalIf(err, "Unable to initialize MinIO client") logger.FatalIf(err, "Unable to initialize the internal S3 client")
}) })
go bootstrapTrace("startResourceMetricsCollection", func() { go bootstrapTrace("startResourceMetricsCollection", func() {
@@ -1169,7 +1159,7 @@ func serverMain(ctx *cli.Context) {
}) })
// Add User-Agent to differentiate the requests. // Add User-Agent to differentiate the requests.
globalMinioClient.SetAppInfo("minio-perf-test", ReleaseTag) globalMinioClient.SetAppInfo("silo-perf-test", ReleaseTag)
if serverDebugLog { if serverDebugLog {
fmt.Println("== DEBUG Mode enabled ==") fmt.Println("== DEBUG Mode enabled ==")
+2 -2
View File
@@ -61,7 +61,7 @@ func printStartupMessage(apiEndpoints []string, err error) {
// Prints `mc` cli configuration message chooses // Prints `mc` cli configuration message chooses
// first endpoint as default. // first endpoint as default.
printCLIAccessMsg(strippedAPIEndpoints[0], "myminio") printCLIAccessMsg(strippedAPIEndpoints[0], "mysilo")
// Prints documentation message. // Prints documentation message.
printObjectAPIMsg() printObjectAPIMsg()
@@ -186,7 +186,7 @@ func printCLIAccessMsg(endPoint string, alias string) {
const mcQuickStartGuide = "https://silo.pgsty.com/reference/minio-mc/#quickstart" const mcQuickStartGuide = "https://silo.pgsty.com/reference/minio-mc/#quickstart"
// Configure 'mc', following block prints platform specific information for minio client. // Configure mc and print platform-specific connection information.
if color.IsTerminal() && (!globalServerCtxt.Anonymous && globalAPIConfig.permitRootAccess()) { if color.IsTerminal() && (!globalServerCtxt.Anonymous && globalAPIConfig.permitRootAccess()) {
logger.Startup(color.Blue("\nCLI: ") + mcQuickStartGuide) logger.Startup(color.Blue("\nCLI: ") + mcQuickStartGuide)
mcMessage := fmt.Sprintf("$ mc alias set '%s' '%s' '%s' '%s'", alias, mcMessage := fmt.Sprintf("$ mc alias set '%s' '%s' '%s' '%s'", alias,
+1 -1
View File
@@ -80,7 +80,7 @@ func TestPrintCLIAccessMsg(t *testing.T) {
} }
apiEndpoints := []string{"http://127.0.0.1:9000"} apiEndpoints := []string{"http://127.0.0.1:9000"}
printCLIAccessMsg(apiEndpoints[0], "myminio") printCLIAccessMsg(apiEndpoints[0], "mysilo")
} }
// Test print startup message. // Test print startup message.
+1 -1
View File
@@ -26,7 +26,7 @@ import (
var errMaxVersionsExceeded = StorageErr("maximum versions exceeded, please delete few versions to proceed") var errMaxVersionsExceeded = StorageErr("maximum versions exceeded, please delete few versions to proceed")
// errUnexpected - unexpected error, requires manual intervention. // errUnexpected - unexpected error, requires manual intervention.
var errUnexpected = StorageErr("unexpected error, please report this issue at https://github.com/minio/minio/issues") var errUnexpected = StorageErr("unexpected error, please report this issue at https://github.com/pgsty/minio/issues")
// errCorruptedFormat - corrupted format. // errCorruptedFormat - corrupted format.
var errCorruptedFormat = StorageErr("corrupted format") var errCorruptedFormat = StorageErr("corrupted format")
+4 -4
View File
@@ -1112,17 +1112,17 @@ func logFatalErrs(err error, endpoint Endpoint, exit bool) {
case errors.Is(err, errUnsupportedDisk): case errors.Is(err, errUnsupportedDisk):
var hint string var hint string
if endpoint.URL != nil { if endpoint.URL != nil {
hint = fmt.Sprintf("Drive '%s' does not support O_DIRECT flags, MinIO erasure coding requires filesystems with O_DIRECT support", endpoint.Path) hint = fmt.Sprintf("Drive '%s' does not support O_DIRECT flags, Silo erasure coding requires filesystems with O_DIRECT support", endpoint.Path)
} else { } else {
hint = "Drives do not support O_DIRECT flags, MinIO erasure coding requires filesystems with O_DIRECT support" hint = "Drives do not support O_DIRECT flags, Silo erasure coding requires filesystems with O_DIRECT support"
} }
logger.Fatal(config.ErrUnsupportedBackend(err).Hint("%s", hint), "Unable to initialize backend") logger.Fatal(config.ErrUnsupportedBackend(err).Hint("%s", hint), "Unable to initialize backend")
case errors.Is(err, errDiskNotDir): case errors.Is(err, errDiskNotDir):
var hint string var hint string
if endpoint.URL != nil { if endpoint.URL != nil {
hint = fmt.Sprintf("Drive '%s' is not a directory, MinIO erasure coding needs a directory", endpoint.Path) hint = fmt.Sprintf("Drive '%s' is not a directory, Silo erasure coding needs a directory", endpoint.Path)
} else { } else {
hint = "Drives are not directories, MinIO erasure coding needs directories" hint = "Drives are not directories, Silo erasure coding needs directories"
} }
logger.Fatal(config.ErrUnableToWriteInBackend(err).Hint("%s", hint), "Unable to initialize backend") logger.Fatal(config.ErrUnableToWriteInBackend(err).Hint("%s", hint), "Unable to initialize backend")
case errors.Is(err, errDiskAccessDenied): case errors.Is(err, errDiskAccessDenied):
+1 -1
View File
@@ -128,7 +128,7 @@ var stsErrCodes = stsErrorCodeMap{
}, },
ErrSTSInvalidClientGrantsToken: { ErrSTSInvalidClientGrantsToken: {
Code: "InvalidClientGrantsToken", Code: "InvalidClientGrantsToken",
Description: "The client grants token that was passed could not be validated by MinIO.", Description: "The client grants token that was passed could not be validated by Silo.",
HTTPStatusCode: http.StatusBadRequest, HTTPStatusCode: http.StatusBadRequest,
}, },
ErrSTSMalformedPolicyDocument: { ErrSTSMalformedPolicyDocument: {
+2 -2
View File
@@ -41,7 +41,7 @@ func prepareUpdateMessage(downloadURL string, older time.Duration) string {
newerThan := humanize.RelTime(t, t.Add(older), "before the latest release", "") newerThan := humanize.RelTime(t, t.Add(older), "before the latest release", "")
if globalServerCtxt.JSON { if globalServerCtxt.JSON {
return fmt.Sprintf("You are running an older version of MinIO released %s, update: %s", newerThan, downloadURL) return fmt.Sprintf("You are running an older version of Silo released %s, update: %s", newerThan, downloadURL)
} }
// Return the nicely colored and formatted update message. // Return the nicely colored and formatted update message.
@@ -50,7 +50,7 @@ func prepareUpdateMessage(downloadURL string, older time.Duration) string {
// colorizeUpdateMessage - inspired from Yeoman project npm package https://github.com/yeoman/update-notifier // colorizeUpdateMessage - inspired from Yeoman project npm package https://github.com/yeoman/update-notifier
func colorizeUpdateMessage(updateString string, newerThan string) string { func colorizeUpdateMessage(updateString string, newerThan string) string {
msgLine1Fmt := " You are running an older version of MinIO released %s " msgLine1Fmt := " You are running an older version of Silo released %s "
msgLine2Fmt := " Update: %s " msgLine2Fmt := " Update: %s "
// Calculate length *without* color coding: with ANSI terminal // Calculate length *without* color coding: with ANSI terminal
+1 -1
View File
@@ -67,7 +67,7 @@ func TestPrepareUpdateMessage(t *testing.T) {
{2 * 365 * 24 * time.Hour, "my_download_url", "2 years before the latest release"}, {2 * 365 * 24 * time.Hour, "my_download_url", "2 years before the latest release"},
} }
plainMsg := "You are running an older version of MinIO released" plainMsg := "You are running an older version of Silo released"
for i, testCase := range testCases { for i, testCase := range testCases {
output := prepareUpdateMessage(testCase.dlURL, testCase.older) output := prepareUpdateMessage(testCase.dlURL, testCase.older)
+19 -25
View File
@@ -51,6 +51,8 @@ const (
) )
var ( var (
errInplaceUpdateDisabled = errors.New("Silo in-place updates are disabled; use a package, image, or orchestrator upgrade")
// Newer official download info URLs appear earlier below. // Newer official download info URLs appear earlier below.
minioReleaseInfoURL = MinioReleaseURL + "minio.sha256sum" minioReleaseInfoURL = MinioReleaseURL + "minio.sha256sum"
@@ -223,13 +225,10 @@ func IsPCFTile() bool {
return env.Get("MINIO_PCF_TILE_VERSION", "") != "" return env.Get("MINIO_PCF_TILE_VERSION", "") != ""
} }
// DO NOT CHANGE USER AGENT STYLE. // Keep the inherited user agent field order stable while identifying Silo as
// The style should be // the maintained product. The style is:
// //
// MinIO (<OS>; <ARCH>[; <MODE>][; dcos][; kubernetes][; docker][; source]) MinIO/<VERSION> MinIO/<RELEASE-TAG> MinIO/<COMMIT-ID> [MinIO/universe-<PACKAGE-NAME>] [MinIO/helm-<HELM-VERSION>] // Silo (<OS>; <ARCH>[; <MODE>][; dcos][; kubernetes][; docker][; source]) <VERSION> <RELEASE-TAG> <COMMIT-ID> [universe-<PACKAGE-NAME>] [helm-<HELM-VERSION>]
//
// Any change here should be discussed by opening an issue at
// https://github.com/minio/minio/issues.
func getUserAgent(mode string) string { func getUserAgent(mode string) string {
userAgentParts := []string{} userAgentParts := []string{}
// Helper function to concisely append a pair of strings to a // Helper function to concisely append a pair of strings to a
@@ -438,22 +437,14 @@ func getUpdateTransport(timeout time.Duration) http.RoundTripper {
return updateTransport return updateTransport
} }
func getLatestReleaseTime(u *url.URL, timeout time.Duration, mode string) (sha256Sum []byte, releaseTime time.Time, err error) {
data, err := downloadReleaseURL(u, timeout, mode)
if err != nil {
return sha256Sum, releaseTime, err
}
sha256Sum, releaseTime, _, err = parseReleaseData(data)
return sha256Sum, releaseTime, err
}
const ( const (
// Kubernetes deployment doc link. // Kubernetes deployment doc link.
kubernetesDeploymentDoc = "https://silo.pgsty.com/operations/deployments/kubernetes/" kubernetesDeploymentDoc = "https://silo.pgsty.com/operations/deployments/kubernetes/"
// Mesos deployment doc link. // Mesos deployment doc link.
mesosDeploymentDoc = "https://silo.pgsty.com/operations/deployments/kubernetes/" mesosDeploymentDoc = "https://silo.pgsty.com/operations/deployments/kubernetes/"
siloDownloadPage = "https://silo.pgsty.com/download/"
) )
func getDownloadURL(releaseTag string) (downloadURL string) { func getDownloadURL(releaseTag string) (downloadURL string) {
@@ -472,15 +463,11 @@ func getDownloadURL(releaseTag string) (downloadURL string) {
// Check if we are docker environment, return docker update command // Check if we are docker environment, return docker update command
if IsDocker() { if IsDocker() {
// Construct release tag name. // Construct release tag name.
return fmt.Sprintf("podman pull quay.io/minio/minio:%s", releaseTag) return fmt.Sprintf("podman pull docker.io/pgsty/silo:%s", releaseTag)
} }
// For binary only installations, we return link to the latest binary. // Binary installations are upgraded from a verified release artifact.
if runtime.GOOS == "windows" { return siloDownloadPage
return MinioReleaseURL + "minio.exe"
}
return MinioReleaseURL + "minio"
} }
func getUpdateReaderFromURL(u *url.URL, transport http.RoundTripper, mode string) (io.ReadCloser, error) { func getUpdateReaderFromURL(u *url.URL, transport http.RoundTripper, mode string) (io.ReadCloser, error) {
@@ -551,11 +538,15 @@ func downloadBinary(u *url.URL, mode string) (binCompressed []byte, bin []byte,
} }
const ( const (
// Update this whenever the official minisign pubkey is rotated. // Silo has no in-place update trust root. The environment key remains
defaultMinisignPubkey = "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" // recognized by inherited code but cannot re-enable the disabled updater.
defaultMinisignPubkey = ""
) )
func verifyBinary(u *url.URL, sha256Sum []byte, releaseInfo, mode string, reader io.Reader) (err error) { func verifyBinary(u *url.URL, sha256Sum []byte, releaseInfo, mode string, reader io.Reader) (err error) {
if globalInplaceUpdateDisabled {
return errInplaceUpdateDisabled
}
if !updateInProgress.CompareAndSwap(0, 1) { if !updateInProgress.CompareAndSwap(0, 1) {
return errors.New("update already in progress") return errors.New("update already in progress")
} }
@@ -610,6 +601,9 @@ func verifyBinary(u *url.URL, sha256Sum []byte, releaseInfo, mode string, reader
} }
func commitBinary() (err error) { func commitBinary() (err error) {
if globalInplaceUpdateDisabled {
return errInplaceUpdateDisabled
}
if !updateInProgress.CompareAndSwap(0, 1) { if !updateInProgress.CompareAndSwap(0, 1) {
return errors.New("update already in progress") return errors.New("update already in progress")
} }
+24 -15
View File
@@ -20,6 +20,7 @@ package cmd
import ( import (
"bytes" "bytes"
"encoding/hex" "encoding/hex"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@@ -35,6 +36,9 @@ import (
) )
func TestDownloadBinaryReturnsOwnedBuffers(t *testing.T) { func TestDownloadBinaryReturnsOwnedBuffers(t *testing.T) {
previousDisabled := globalInplaceUpdateDisabled
globalInplaceUpdateDisabled = false
t.Cleanup(func() { globalInplaceUpdateDisabled = previousDisabled })
previousMaxProcs := runtime.GOMAXPROCS(1) previousMaxProcs := runtime.GOMAXPROCS(1)
t.Cleanup(func() { t.Cleanup(func() {
runtime.GOMAXPROCS(previousMaxProcs) runtime.GOMAXPROCS(previousMaxProcs)
@@ -96,6 +100,19 @@ func TestDownloadBinaryReturnsOwnedBuffers(t *testing.T) {
} }
} }
func TestInplaceUpdateCannotBeEnabled(t *testing.T) {
previousDisabled := globalInplaceUpdateDisabled
globalInplaceUpdateDisabled = true
t.Cleanup(func() { globalInplaceUpdateDisabled = previousDisabled })
if err := verifyBinary(nil, nil, "", "", nil); !errors.Is(err, errInplaceUpdateDisabled) {
t.Fatalf("verifyBinary error = %v, want %v", err, errInplaceUpdateDisabled)
}
if err := commitBinary(); !errors.Is(err, errInplaceUpdateDisabled) {
t.Fatalf("commitBinary error = %v, want %v", err, errInplaceUpdateDisabled)
}
}
func TestMinioVersionToReleaseTime(t *testing.T) { func TestMinioVersionToReleaseTime(t *testing.T) {
testCases := []struct { testCases := []struct {
version string version string
@@ -167,19 +184,11 @@ func TestDownloadURL(t *testing.T) {
minioVersion1 := releaseTimeToReleaseTag(UTCNow()) minioVersion1 := releaseTimeToReleaseTag(UTCNow())
durl := getDownloadURL(minioVersion1) durl := getDownloadURL(minioVersion1)
if IsDocker() { if IsDocker() {
if durl != "podman pull quay.io/minio/minio:"+minioVersion1 { if durl != "podman pull docker.io/pgsty/silo:"+minioVersion1 {
t.Errorf("Expected %s, got %s", "podman pull quay.io/minio/minio:"+minioVersion1, durl) t.Errorf("Expected %s, got %s", "podman pull docker.io/pgsty/silo:"+minioVersion1, durl)
}
} else {
if runtime.GOOS == "windows" {
if durl != MinioReleaseURL+"minio.exe" {
t.Errorf("Expected %s, got %s", MinioReleaseURL+"minio.exe", durl)
}
} else {
if durl != MinioReleaseURL+"minio" {
t.Errorf("Expected %s, got %s", MinioReleaseURL+"minio", durl)
}
} }
} else if durl != siloDownloadPage {
t.Errorf("Expected %s, got %s", siloDownloadPage, durl)
} }
t.Setenv("KUBERNETES_SERVICE_HOST", "10.11.148.5") t.Setenv("KUBERNETES_SERVICE_HOST", "10.11.148.5")
@@ -207,19 +216,19 @@ func TestUserAgent(t *testing.T) {
envName: "", envName: "",
envValue: "", envValue: "",
mode: globalMinioModeFS, mode: globalMinioModeFS,
expectedStr: fmt.Sprintf("MinIO (%s; %s; %s; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET", runtime.GOOS, runtime.GOARCH, globalMinioModeFS), expectedStr: fmt.Sprintf("Silo (%s; %s; %s; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET", runtime.GOOS, runtime.GOARCH, globalMinioModeFS),
}, },
{ {
envName: "MESOS_CONTAINER_NAME", envName: "MESOS_CONTAINER_NAME",
envValue: "mesos-11111", envValue: "mesos-11111",
mode: globalMinioModeErasure, mode: globalMinioModeErasure,
expectedStr: fmt.Sprintf("MinIO (%s; %s; %s; %s; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET universe-%s", runtime.GOOS, runtime.GOARCH, globalMinioModeErasure, "dcos", "mesos-1111"), expectedStr: fmt.Sprintf("Silo (%s; %s; %s; %s; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET universe-%s", runtime.GOOS, runtime.GOARCH, globalMinioModeErasure, "dcos", "mesos-1111"),
}, },
{ {
envName: "KUBERNETES_SERVICE_HOST", envName: "KUBERNETES_SERVICE_HOST",
envValue: "10.11.148.5", envValue: "10.11.148.5",
mode: globalMinioModeErasure, mode: globalMinioModeErasure,
expectedStr: fmt.Sprintf("MinIO (%s; %s; %s; %s; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET", runtime.GOOS, runtime.GOARCH, globalMinioModeErasure, "kubernetes"), expectedStr: fmt.Sprintf("Silo (%s; %s; %s; %s; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET", runtime.GOOS, runtime.GOARCH, globalMinioModeErasure, "kubernetes"),
}, },
} }
+2 -2
View File
@@ -156,11 +156,11 @@ func veeamSOSAPIGetObject(ctx context.Context, bucket, object string, rs *HTTPRa
case systemXMLObject: case systemXMLObject:
si := systemInfo{ si := systemInfo{
ProtocolVersion: `"1.0"`, ProtocolVersion: `"1.0"`,
ModelName: "\"MinIO " + ReleaseTag + "\"", ModelName: "\"Silo " + ReleaseTag + "\"",
} }
si.ProtocolCapabilities.CapacityInfo = true si.ProtocolCapabilities.CapacityInfo = true
// Default recommended block size with MinIO // Default recommended block size with Silo.
si.SystemRecommendations.KBBlockSize = 4096 si.SystemRecommendations.KBBlockSize = 4096
buf = encodeResponse(&si) buf = encodeResponse(&si)
+1 -1
View File
@@ -49,7 +49,7 @@ const probeObject = "probeobject"
// checkWarmBackend checks if tier config credentials have sufficient privileges // checkWarmBackend checks if tier config credentials have sufficient privileges
// to perform all operations defined in the WarmBackend interface. // to perform all operations defined in the WarmBackend interface.
func checkWarmBackend(ctx context.Context, w WarmBackend) error { func checkWarmBackend(ctx context.Context, w WarmBackend) error {
remoteVersionID, err := w.Put(ctx, probeObject, strings.NewReader("MinIO"), 5) remoteVersionID, err := w.Put(ctx, probeObject, strings.NewReader("Silo!"), 5)
if err != nil { if err != nil {
if _, ok := err.(BackendDown); ok { if _, ok := err.(BackendDown); ok {
return err return err
+1 -1
View File
@@ -29,7 +29,7 @@ var (
config.HelpKV{ config.HelpKV{
Key: Enable, Key: Enable,
Type: "on|off", Type: "on|off",
Description: "set to enable callhome" + defaultHelpPostfix(Enable), Description: "legacy callhome setting; retained for compatibility but unsupported by Silo" + defaultHelpPostfix(Enable),
Optional: true, Optional: true,
}, },
config.HelpKV{ config.HelpKV{
+13 -13
View File
@@ -21,8 +21,8 @@ package config
var ( var (
ErrInvalidXLValue = newErrFn( ErrInvalidXLValue = newErrFn(
"Invalid drive path", "Invalid drive path",
"Please provide a fresh drive for single drive MinIO setup", "Please provide a fresh drive for a single-drive Silo setup",
"MinIO only supports fresh drive paths", "Silo only supports fresh drive paths",
) )
ErrInvalidBrowserValue = newErrFn( ErrInvalidBrowserValue = newErrFn(
@@ -118,14 +118,14 @@ var (
ErrStorageClassValue = newErrFn( ErrStorageClassValue = newErrFn(
"Invalid storage class value", "Invalid storage class value",
"Please check the value", "Please check the value",
`MINIO_STORAGE_CLASS_STANDARD: Format "EC:<Default_Parity_Standard_Class>" (e.g. "EC:3"). This sets the number of parity drives for MinIO server in Standard mode. Objects are stored in Standard mode, if storage class is not defined in Put request `MINIO_STORAGE_CLASS_STANDARD: Format "EC:<Default_Parity_Standard_Class>" (e.g. "EC:3"). This sets the number of parity drives for Silo in Standard mode. Objects are stored in Standard mode if no storage class is defined in the Put request.
MINIO_STORAGE_CLASS_RRS: Format "EC:<Default_Parity_Reduced_Redundancy_Class>" (e.g. "EC:3"). This sets the number of parity drives for MinIO server in Reduced Redundancy mode. Objects are stored in Reduced Redundancy mode, if Put request specifies RRS storage class MINIO_STORAGE_CLASS_RRS: Format "EC:<Default_Parity_Reduced_Redundancy_Class>" (e.g. "EC:3"). This sets the number of parity drives for Silo in Reduced Redundancy mode. Objects are stored in Reduced Redundancy mode if the Put request specifies the RRS storage class.
Refer to the link https://github.com/minio/minio/tree/master/docs/erasure/storage-class for more information`, See https://silo.pgsty.com/operations/concepts/erasure-coding/ for more information.`,
) )
ErrUnexpectedBackendVersion = newErrFn( ErrUnexpectedBackendVersion = newErrFn(
"Backend version seems to be too recent", "Backend version seems to be too recent",
"Please update to the latest MinIO version", "Please update to the latest Silo version",
"", "",
) )
@@ -143,8 +143,8 @@ Refer to the link https://github.com/minio/minio/tree/master/docs/erasure/storag
"Please check the endpoint", "Please check the endpoint",
`Single-Node modes requires absolute path without hostnames: `Single-Node modes requires absolute path without hostnames:
Examples: Examples:
$ minio server /data/minio/ #Single Node Single Drive $ silo server /data/silo/ # Single Node Single Drive
$ minio server /data-{1...4}/minio # Single Node Multi Drive`, $ silo server /data-{1...4}/silo # Single Node Multi Drive`,
) )
ErrUnsupportedBackend = newErrFn( ErrUnsupportedBackend = newErrFn(
@@ -155,8 +155,8 @@ Examples:
ErrUnableToWriteInBackend = newErrFn( ErrUnableToWriteInBackend = newErrFn(
"Unable to write to the backend", "Unable to write to the backend",
"Please ensure MinIO binary has write permissions for the backend", "Please ensure the Silo binary has write permissions for the backend",
`Verify if MinIO binary is running as the same user who has write permissions for the backend`, `Verify that the Silo binary is running as the same user who has write permissions for the backend`,
) )
ErrPortAlreadyInUse = newErrFn( ErrPortAlreadyInUse = newErrFn(
@@ -167,8 +167,8 @@ Examples:
ErrPortAccess = newErrFn( ErrPortAccess = newErrFn(
"Unable to use specified port", "Unable to use specified port",
"Please ensure MinIO binary has 'cap_net_bind_service=+ep' permissions", "Please ensure the Silo binary has 'cap_net_bind_service=+ep' permissions",
`Use 'sudo setcap cap_net_bind_service=+ep /path/to/minio' to provide sufficient permissions`, `Use 'sudo setcap cap_net_bind_service=+ep /path/to/silo' to provide sufficient permissions`,
) )
ErrTLSReadError = newErrFn( ErrTLSReadError = newErrFn(
@@ -209,7 +209,7 @@ Examples:
ErrUnexpectedError = newErrFn( ErrUnexpectedError = newErrFn(
"Unexpected error", "Unexpected error",
"Please contact MinIO at https://slack.min.io", "Please report this Silo error at https://github.com/pgsty/minio/issues",
"", "",
) )
+1 -1
View File
@@ -69,7 +69,7 @@ var (
}, },
config.HelpKV{ config.HelpKV{
Key: Vendor, Key: Vendor,
Description: `Specify vendor type for vendor specific behavior to checking validity of temporary credentials and service accounts on MinIO` + defaultHelpPostfix(Vendor), Description: `Specify vendor type for vendor-specific behavior when checking temporary credentials and service accounts on Silo` + defaultHelpPostfix(Vendor),
Optional: true, Optional: true,
Type: "string", Type: "string",
}, },
+15 -31
View File
@@ -29,11 +29,6 @@ import (
xnet "github.com/minio/pkg/v3/net" xnet "github.com/minio/pkg/v3/net"
) )
const (
baseURL = "https://subnet.min.io"
baseURLDev = "http://localhost:9000"
)
// DefaultKVS - default KV config for subnet settings // DefaultKVS - default KV config for subnet settings
var DefaultKVS = config.KVS{ var DefaultKVS = config.KVS{
config.KV{ config.KV{
@@ -70,29 +65,21 @@ type Config struct {
var configLock sync.RWMutex var configLock sync.RWMutex
// Registered indicates if cluster is registered or not // Registered reports false because Silo does not use MinIO SUBNET. The
// inherited credentials remain parseable for configuration compatibility.
func (c *Config) Registered() bool { func (c *Config) Registered() bool {
configLock.RLock() return false
defer configLock.RUnlock()
return len(c.APIKey) > 0
} }
// ApplyEnv - applies the current subnet config to Console UI specific environment variables. // ApplyEnv - applies the current subnet config to Console UI specific environment variables.
func (c *Config) ApplyEnv() { func (c *Config) ApplyEnv() {
configLock.RLock() // Do not expose inherited commercial-service credentials or an endpoint to
defer configLock.RUnlock() // the embedded Console. These names are outputs, not the MINIO_* input
// compatibility surface.
if c.License != "" { os.Unsetenv("CONSOLE_SUBNET_LICENSE")
os.Setenv("CONSOLE_SUBNET_LICENSE", c.License) os.Unsetenv("CONSOLE_SUBNET_API_KEY")
} os.Unsetenv("CONSOLE_SUBNET_PROXY")
if c.APIKey != "" { os.Unsetenv("CONSOLE_SUBNET_URL")
os.Setenv("CONSOLE_SUBNET_API_KEY", c.APIKey)
}
if c.Proxy != "" {
os.Setenv("CONSOLE_SUBNET_PROXY", c.Proxy)
}
os.Setenv("CONSOLE_SUBNET_URL", c.BaseURL)
} }
// Update - in-place update with new license and registration information. // Update - in-place update with new license and registration information.
@@ -103,15 +90,12 @@ func (c *Config) Update(ncfg Config, isDevEnv bool) {
c.License = ncfg.License c.License = ncfg.License
c.APIKey = ncfg.APIKey c.APIKey = ncfg.APIKey
c.Proxy = ncfg.Proxy c.Proxy = ncfg.Proxy
c.transport = ncfg.transport c.transport = nil
c.BaseURL = baseURL c.BaseURL = ""
if isDevEnv { // Retain the hidden compatibility input but deliberately ignore its value.
c.BaseURL = os.Getenv("_MINIO_SUBNET_URL") _ = os.Getenv("_MINIO_SUBNET_URL")
if c.BaseURL == "" { _ = isDevEnv
c.BaseURL = baseURLDev
}
}
} }
// LookupConfig - lookup config and override with valid environment settings if any. // LookupConfig - lookup config and override with valid environment settings if any.
+6 -76
View File
@@ -18,97 +18,27 @@
package subnet package subnet
import ( import (
"bytes"
"encoding/json"
"errors" "errors"
"fmt"
"io"
"mime/multipart"
"net/http" "net/http"
"time"
xhttp "github.com/minio/minio/internal/http"
) )
const ( const (
respBodyLimit = 1 << 20 // 1 MiB
// LoggerWebhookName - subnet logger webhook target // LoggerWebhookName - subnet logger webhook target
LoggerWebhookName = "subnet" LoggerWebhookName = "subnet"
) )
var errSiloSubnetDisabled = errors.New("MinIO SUBNET integration is disabled in Silo")
// Upload given file content (payload) to specified URL // Upload given file content (payload) to specified URL
func (c Config) Upload(reqURL string, filename string, payload []byte) (string, error) { func (c Config) Upload(reqURL string, filename string, payload []byte) (string, error) {
if !c.Registered() { return "", errSiloSubnetDisabled
return "", errors.New("Deployment is not registered with SUBNET. Please register the deployment via 'mc license register ALIAS'")
} }
var body bytes.Buffer func (c Config) submitPost(_ *http.Request) (string, error) {
writer := multipart.NewWriter(&body) return "", errSiloSubnetDisabled
part, e := writer.CreateFormFile("file", filename)
if e != nil {
return "", e
}
if _, e = part.Write(payload); e != nil {
return "", e
}
writer.Close()
r, e := http.NewRequest(http.MethodPost, reqURL, &body)
if e != nil {
return "", e
}
r.Header.Add("Content-Type", writer.FormDataContentType())
return c.submitPost(r)
}
func (c Config) submitPost(r *http.Request) (string, error) {
configLock.RLock()
r.Header.Set(xhttp.SubnetAPIKey, c.APIKey)
configLock.RUnlock()
r.Header.Set(xhttp.MinioDeploymentID, xhttp.GlobalDeploymentID)
client := &http.Client{
Timeout: 10 * time.Second,
Transport: c.transport,
}
resp, err := client.Do(r)
if err != nil {
return "", err
}
defer xhttp.DrainBody(resp.Body)
respBytes, err := io.ReadAll(io.LimitReader(resp.Body, respBodyLimit))
if err != nil {
return "", err
}
respStr := string(respBytes)
if resp.StatusCode == http.StatusOK {
return respStr, nil
}
return respStr, fmt.Errorf("SUBNET request failed with code %d and error: %s", resp.StatusCode, respStr)
} }
// Post submit 'payload' to specified URL // Post submit 'payload' to specified URL
func (c Config) Post(reqURL string, payload any) (string, error) { func (c Config) Post(reqURL string, payload any) (string, error) {
if !c.Registered() { return "", errSiloSubnetDisabled
return "", errors.New("Deployment is not registered with SUBNET. Please register the deployment via 'mc license register ALIAS'")
}
body, err := json.Marshal(payload)
if err != nil {
return "", err
}
r, err := http.NewRequest(http.MethodPost, reqURL, bytes.NewReader(body))
if err != nil {
return "", err
}
r.Header.Set("Content-Type", "application/json")
return c.submitPost(r)
} }
+41
View File
@@ -0,0 +1,41 @@
// 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.
package subnet
import (
"errors"
"os"
"testing"
)
func TestSiloSubnetIsPermanentlyDisabled(t *testing.T) {
cfg := Config{License: "legacy-license", APIKey: "legacy-key", BaseURL: "https://example.invalid"}
if cfg.Registered() {
t.Fatal("legacy SUBNET credentials must not register a Silo deployment")
}
for _, name := range []string{"CONSOLE_SUBNET_LICENSE", "CONSOLE_SUBNET_API_KEY", "CONSOLE_SUBNET_PROXY", "CONSOLE_SUBNET_URL"} {
t.Setenv(name, "must-be-cleared")
}
cfg.ApplyEnv()
for _, name := range []string{"CONSOLE_SUBNET_LICENSE", "CONSOLE_SUBNET_API_KEY", "CONSOLE_SUBNET_PROXY", "CONSOLE_SUBNET_URL"} {
if _, ok := os.LookupEnv(name); ok {
t.Fatalf("%s remains set", name)
}
}
if _, err := cfg.Upload("https://example.invalid", "health.json", nil); !errors.Is(err, errSiloSubnetDisabled) {
t.Fatalf("Upload error = %v, want %v", err, errSiloSubnetDisabled)
}
if _, err := cfg.Post("https://example.invalid", struct{}{}); !errors.Is(err, errSiloSubnetDisabled) {
t.Fatalf("Post error = %v, want %v", err, errSiloSubnetDisabled)
}
if _, err := cfg.submitPost(nil); !errors.Is(err, errSiloSubnetDisabled) {
t.Fatalf("submitPost error = %v, want %v", err, errSiloSubnetDisabled)
}
}
+1 -1
View File
@@ -331,7 +331,7 @@ func (target *ElasticsearchTarget) checkAndInitClient(ctx context.Context) error
return errors.New("unable to determine support status of ES (should not happen)") return errors.New("unable to determine support status of ES (should not happen)")
case ESSDeprecated: case ESSDeprecated:
return errors.New("there is no currently deprecated version of ES in MinIO") return errors.New("there is no currently deprecated version of ES in Silo")
case ESSSupported: case ESSSupported:
target.client = clientV7 target.client = clientV7
+1 -1
View File
@@ -172,7 +172,7 @@ func (n NATSArgs) Validate() error {
// To obtain a nats connection from args. // To obtain a nats connection from args.
func (n NATSArgs) connectNats() (*nats.Conn, error) { func (n NATSArgs) connectNats() (*nats.Conn, error) {
connOpts := []nats.Option{nats.Name("Minio Notification"), nats.MaxReconnects(-1)} connOpts := []nats.Option{nats.Name("Silo Notification"), nats.MaxReconnects(-1)}
if n.Username != "" && n.Password != "" { if n.Username != "" && n.Password != "" {
connOpts = append(connOpts, nats.UserInfo(n.Username, n.Password)) connOpts = append(connOpts, nats.UserInfo(n.Username, n.Password))
} }
+1 -1
View File
@@ -352,7 +352,7 @@ func NewRedisTarget(id string, args RedisArgs, loggerOnce logger.LogOnce) (*Redi
} }
// Must be done after AUTH // Must be done after AUTH
if _, err = conn.Do("CLIENT", "SETNAME", "MinIO"); err != nil { if _, err = conn.Do("CLIENT", "SETNAME", "Silo"); err != nil {
conn.Close() conn.Close()
return nil, err return nil, err
} }
+13 -13
View File
@@ -358,11 +358,11 @@ func IsPresent() (bool, error) {
switch { switch {
case kmsPresent && kesPresent: case kmsPresent && kesPresent:
return false, errors.New("kms: configuration for MinIO KMS and MinIO KES is present") return false, errors.New("kms: both KMS and KES configuration are present")
case kmsPresent && staticKeyPresent: case kmsPresent && staticKeyPresent:
return false, errors.New("kms: configuration for MinIO KMS and static KMS key is present") return false, errors.New("kms: both KMS and static-key configuration are present")
case kesPresent && staticKeyPresent: case kesPresent && staticKeyPresent:
return false, errors.New("kms: configuration for MinIO KES and static KMS key is present") return false, errors.New("kms: both KES and static-key configuration are present")
} }
// Next, we check that all required configuration for the concrete // Next, we check that all required configuration for the concrete
@@ -375,16 +375,16 @@ func IsPresent() (bool, error) {
return false, nil // No KMS config present return false, nil // No KMS config present
case kmsPresent: case kmsPresent:
if !isPresent(EnvKMSEndpoint) { if !isPresent(EnvKMSEndpoint) {
return false, fmt.Errorf("kms: incomplete configuration for MinIO KMS: missing '%s'", EnvKMSEndpoint) return false, fmt.Errorf("kms: incomplete KMS configuration: missing '%s'", EnvKMSEndpoint)
} }
if !isPresent(EnvKMSEnclave) { if !isPresent(EnvKMSEnclave) {
return false, fmt.Errorf("kms: incomplete configuration for MinIO KMS: missing '%s'", EnvKMSEnclave) return false, fmt.Errorf("kms: incomplete KMS configuration: missing '%s'", EnvKMSEnclave)
} }
if !isPresent(EnvKMSDefaultKey) { if !isPresent(EnvKMSDefaultKey) {
return false, fmt.Errorf("kms: incomplete configuration for MinIO KMS: missing '%s'", EnvKMSDefaultKey) return false, fmt.Errorf("kms: incomplete KMS configuration: missing '%s'", EnvKMSDefaultKey)
} }
if !isPresent(EnvKMSAPIKey) { if !isPresent(EnvKMSAPIKey) {
return false, fmt.Errorf("kms: incomplete configuration for MinIO KMS: missing '%s'", EnvKMSAPIKey) return false, fmt.Errorf("kms: incomplete KMS configuration: missing '%s'", EnvKMSAPIKey)
} }
return true, nil return true, nil
case staticKeyPresent: case staticKeyPresent:
@@ -394,24 +394,24 @@ func IsPresent() (bool, error) {
return true, nil return true, nil
case kesPresent: case kesPresent:
if !isPresent(EnvKESEndpoint) { if !isPresent(EnvKESEndpoint) {
return false, fmt.Errorf("kms: incomplete configuration for MinIO KES: missing '%s'", EnvKESEndpoint) return false, fmt.Errorf("kms: incomplete KES configuration: missing '%s'", EnvKESEndpoint)
} }
if !isPresent(EnvKESDefaultKey) { if !isPresent(EnvKESDefaultKey) {
return false, fmt.Errorf("kms: incomplete configuration for MinIO KES: missing '%s'", EnvKESDefaultKey) return false, fmt.Errorf("kms: incomplete KES configuration: missing '%s'", EnvKESDefaultKey)
} }
if isPresent(EnvKESClientKey, EnvKESClientCert, EnvKESClientPassword) { if isPresent(EnvKESClientKey, EnvKESClientCert, EnvKESClientPassword) {
if isPresent(EnvKESAPIKey) { if isPresent(EnvKESAPIKey) {
return false, fmt.Errorf("kms: invalid configuration for MinIO KES: '%s' and client certificate is present", EnvKESAPIKey) return false, fmt.Errorf("kms: invalid KES configuration: '%s' and client certificate are both present", EnvKESAPIKey)
} }
if !isPresent(EnvKESClientCert) { if !isPresent(EnvKESClientCert) {
return false, fmt.Errorf("kms: incomplete configuration for MinIO KES: missing '%s'", EnvKESClientCert) return false, fmt.Errorf("kms: incomplete KES configuration: missing '%s'", EnvKESClientCert)
} }
if !isPresent(EnvKESClientKey) { if !isPresent(EnvKESClientKey) {
return false, fmt.Errorf("kms: incomplete configuration for MinIO KES: missing '%s'", EnvKESClientKey) return false, fmt.Errorf("kms: incomplete KES configuration: missing '%s'", EnvKESClientKey)
} }
} else if !isPresent(EnvKESAPIKey) { } else if !isPresent(EnvKESAPIKey) {
return false, errors.New("kms: incomplete configuration for MinIO KES: missing authentication method") return false, errors.New("kms: incomplete KES configuration: missing authentication method")
} }
return true, nil return true, nil
} }
+1 -1
View File
@@ -32,7 +32,7 @@ var (
// StubCreatedAt is a constant timestamp for testing // StubCreatedAt is a constant timestamp for testing
StubCreatedAt = time.Date(2024, time.January, 1, 15, 0, 0, 0, time.UTC) StubCreatedAt = time.Date(2024, time.January, 1, 15, 0, 0, 0, time.UTC)
// StubCreatedBy is a constant created identity for testing // StubCreatedBy is a constant created identity for testing
StubCreatedBy = "MinIO" StubCreatedBy = "Silo"
) )
// NewStub returns a stub of KMS for testing // NewStub returns a stub of KMS for testing
+1 -1
View File
@@ -31,7 +31,7 @@ import (
) )
func defaultFilenameFunc() string { func defaultFilenameFunc() string {
return fmt.Sprintf("minio-%s.log", fmt.Sprintf("%X", time.Now().UTC().UnixNano())) return fmt.Sprintf("silo-%s.log", fmt.Sprintf("%X", time.Now().UTC().UnixNano()))
} }
// Options define configuration options for Writer // Options define configuration options for Writer