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
// ----------
// 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) {
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}
// ----------
// 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) {
ctx := r.Context()
@@ -330,7 +330,7 @@ func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Req
}
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)
return
}
@@ -2708,10 +2708,10 @@ func fetchHealthInfo(healthCtx context.Context, objectAPI ObjectLayer, query *ur
}
// Server start command regex groups:
// 1 - minio server
// 2 - flags e.g. `--address :9000 --certs-dir /etc/minio/certs`
// 1 - silo server (or the legacy minio command)
// 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}`
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
cmdLineWithoutPools := re.ReplaceAllString(cmdLine, `$1$2`)
@@ -3282,28 +3282,7 @@ func (a adminAPIHandlers) InspectDataHandler(w http.ResponseWriter, r *http.Requ
stream := estream.NewWriter(w)
defer stream.Close()
clusterKey, err := bytesToPublicKey(getSubnetAdminPublicKey())
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 {
if err := addInspectDataKey(stream, publicKey, getClusterMetaInfo(ctx)); err != nil {
bugLogIf(ctx, stream.AddError(err.Error()))
return
}
@@ -3432,7 +3411,7 @@ func (a adminAPIHandlers) InspectDataHandler(w http.ResponseWriter, r *http.Requ
scheme = "http"
}
// save MinIO start script to inspect command
// Save a Silo start script to inspect command.
var scrb bytes.Buffer
fmt.Fprintf(&scrb, `#!/usr/bin/env bash
@@ -3443,30 +3422,43 @@ function main() {
done
# 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_CMD="CI=on _MINIO_AUTO_DRIVE_HEALING=off minio server ${MINIO_OPTS} &"
# Start Silo using the options
START_CMD="CI=on _MINIO_AUTO_DRIVE_HEALING=off silo server ${SILO_OPTS} &"
echo
echo "Starting MinIO instance: ${START_CMD}"
echo "Starting Silo: ${START_CMD}"
echo
eval "$START_CMD"
MINIO_SRVR_PID="$!"
echo "MinIO Server PID: ${MINIO_SRVR_PID}"
SILO_SRVR_PID="$!"
echo "Silo Server PID: ${SILO_SRVR_PID}"
echo
echo "Waiting for MinIO instance to get ready!"
echo "Waiting for Silo to get ready!"
sleep 10
}
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 {
if globalIsCICD {
return subnetAdminPublicKeyDev
// addInspectDataKey makes the requester the only recipient of encrypted
// diagnostic data. Silo has no built-in vendor or support-service recipient.
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 {
+1 -1
View File
@@ -64,7 +64,7 @@ func prepareAdminErasureTestBed(ctx context.Context) (*adminErasureTestBed, erro
return nil, xlErr
}
// Initialize minio server config.
// Initialize Silo server config.
if err := newTestConfig(globalMinioDefaultRegion, objLayer); err != nil {
cancel()
return nil, err
+1 -1
View File
@@ -1154,7 +1154,7 @@ var errorCodes = errorCodeMap{
},
ErrUnsupportedNotification: {
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,
},
ErrInvalidCopyPartRange: {
+9
View File
@@ -18,9 +18,18 @@
package cmd
import (
"net/http/httptest"
"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) {
// Ensure that it returns an alphanumeric result of length 16.
id := mustGetRequestID(UTCNow())
+3 -3
View File
@@ -391,7 +391,7 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
return err
}
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID)
c.SetAppInfo("silo-"+batchJobPrefix, r.APIVersion+" "+job.ID)
core := &minio.Core{Client: c}
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
}
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID)
c.SetAppInfo("silo-"+batchJobPrefix, r.APIVersion+" "+job.ID)
retry := false
for attempts := 1; attempts <= retryAttempts; attempts++ {
@@ -1477,7 +1477,7 @@ func (r *BatchJobReplicateV1) Validate(ctx context.Context, job BatchJobRequest,
if err != nil {
return err
}
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID)
c.SetAppInfo("silo-"+batchJobPrefix, r.APIVersion+" "+job.ID)
vcfg, err := c.GetBucketVersioning(ctx, remoteBkt)
if err != nil {
+1 -1
View File
@@ -53,7 +53,7 @@ type ServerSystemConfig struct {
// Diff - returns error on first difference found in two configs.
func (s1 *ServerSystemConfig) Diff(s2 *ServerSystemConfig) error {
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
+4 -4
View File
@@ -1415,7 +1415,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
Object: ObjectInfo{Name: objInfo.Name},
ReqParams: extractReqParams(r),
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),
})
continue
@@ -1434,7 +1434,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
Object: objInfo,
ReqParams: extractReqParams(r),
RespElements: extractRespElements(w),
UserAgent: r.UserAgent() + " " + "MinIO-Fan-Out",
UserAgent: r.UserAgent() + " " + "Silo-Fan-Out",
Host: handlers.GetSourceIP(r),
})
}
@@ -1462,7 +1462,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
Object: eventArgsList[i].Object,
ReqParams: extractReqParams(r),
RespElements: extractRespElements(w),
UserAgent: r.UserAgent() + " " + "MinIO-Fan-Out",
UserAgent: r.UserAgent() + " " + "Silo-Fan-Out",
Host: handlers.GetSourceIP(r),
})
@@ -1757,7 +1757,7 @@ func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.
if globalDNSConfig != 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)
return
}
+10 -8
View File
@@ -49,20 +49,22 @@ var (
// MinioOSARCH - OS and ARCH.
minioOSARCH = runtime.GOOS + "-" + runtime.GOARCH
// MinioReleaseBaseURL - release url without os and arch.
MinioReleaseBaseURL = "https://dl.min.io/server/minio/release/"
// MinioReleaseBaseURL is retained for source compatibility. Silo does not
// provide an in-place update endpoint.
MinioReleaseBaseURL = ""
// MinioReleaseURL - release URL.
MinioReleaseURL = MinioReleaseBaseURL + minioOSARCH + SlashSeparator
// MinioStoreName - MinIO store name.
MinioStoreName = "MinIO"
// MinioStoreName - Silo product name. The identifier is retained to avoid a
// source-only rename across compatibility-sensitive code.
MinioStoreName = "Silo"
// MinioUAName - MinIO user agent name.
MinioUAName = "MinIO"
// MinioUAName - Silo user agent name.
MinioUAName = "Silo"
// MinioBannerName - MinIO banner name for startup message.
MinioBannerName = "MinIO Object Storage Server"
// MinioBannerName - Silo banner name for startup message.
MinioBannerName = "Silo Object Storage Server"
// MinioLicense - MinIO server license.
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() {
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("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.Scanln()
os.Exit(1)
@@ -295,42 +295,6 @@ func initConsoleServer() (*consoleapi.Server, error) {
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) {
if dir == "" {
dir = getDefaultDir()
@@ -510,6 +474,16 @@ func handleCommonArgs(ctxt serverCtxt) {
var err error
globalConfigDir, err = newConfigDir(configDir, configSet, defaultConfigDir.Get)
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)
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.
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")
}
globalBrowserRedirectURL = u
@@ -745,7 +719,7 @@ func serverHandleEnvVars() {
}
// Look for if URL has invalid values and return error.
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")
}
u.Path = "" // remove any path component such as `/`
@@ -798,7 +772,7 @@ func serverHandleEnvVars() {
// Checking if the IP is a DNS entry.
addrs, err := globalDNSCache.LookupHost(GlobalContext, endpoint)
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 {
domainIPs.Add(addr)
@@ -817,10 +791,13 @@ func serverHandleEnvVars() {
updateDomainIPs(domainIPs)
}
// In place update is true by default if the MINIO_UPDATE is not set
// or is not set to 'off', if MINIO_UPDATE is set to 'off' then
// in-place update is off.
globalInplaceUpdateDisabled = strings.EqualFold(env.Get(config.EnvUpdate, config.EnableOn), config.EnableOff)
// MINIO_UPDATE remains accepted for configuration compatibility, but Silo is
// upgraded only through packages, images, or an orchestrator. It cannot
// re-enable the inherited in-place updater.
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,
// "MINIO_ROOT_USER" and "MINIO_ROOT_PASSWORD" are provided
@@ -829,14 +806,14 @@ func serverHandleEnvVars() {
// Check all error conditions first
//nolint:gocritic
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) {
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) {
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) {
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{
Key: config.SubnetSubSys,
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,
},
config.HelpKV{
Key: config.CallhomeSubSys,
Type: "string",
Description: "enable callhome to MinIO SUBNET",
Description: "legacy callhome settings retained for compatibility; diagnostic uploads are disabled in Silo",
Optional: true,
},
config.HelpKV{
@@ -383,14 +383,9 @@ func validateSubSysConfig(ctx context.Context, s config.Config, subSys string, o
return err
}
case config.CallhomeSubSys:
cfg, err := callhome.LookupConfig(s[config.CallhomeSubSys][config.Default])
if err != nil {
if _, err := callhome.LookupConfig(s[config.CallhomeSubSys][config.Default]); err != nil {
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:
if _, err := drive.LookupConfig(s[config.DriveSubSys][config.Default]); err != nil {
return err
@@ -676,11 +671,11 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
if err != nil {
configLogIf(ctx, fmt.Errorf("Unable to load callhome config: %w", err))
} else {
enable := callhomeCfg.Enable && !globalCallhomeConfig.Enabled()
globalCallhomeConfig.Update(callhomeCfg)
if enable {
initCallhome(ctx, objAPI)
if callhomeCfg.Enable {
configLogIf(ctx, errors.New("callhome is configured but ignored: Silo does not upload diagnostics to MinIO SUBNET"))
}
callhomeCfg.Enable = false
globalCallhomeConfig.Update(callhomeCfg)
}
case config.DriveSubSys:
driveConfig, err := drive.LookupConfig(s[config.DriveSubSys][config.Default])
+42 -16
View File
@@ -20,13 +20,16 @@ package cmd
import (
"os"
"path/filepath"
"sync"
homedir "github.com/mitchellh/go-homedir"
)
const (
// Default minio configuration directory where below configuration files/directories are stored.
defaultMinioConfigDir = ".minio"
// New installations use the Silo-branded directory. The legacy directory is
// still selected when it is the only existing choice.
defaultSiloConfigDir = ".silo"
legacyMinioConfigDir = ".minio"
// Directory contains below files/directories for HTTPS configuration.
certsDir = "certs"
@@ -41,33 +44,56 @@ const (
privateKeyFile = "private.key"
)
type defaultConfigDirState uint8
const (
defaultConfigDirNew defaultConfigDirState = iota
defaultConfigDirLegacy
defaultConfigDirAmbiguous
)
// ConfigDir - points to a user set directory.
type ConfigDir struct {
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()
if err != nil {
return ""
return "", defaultConfigDirNew
}
return filepath.Join(homeDir, defaultMinioConfigDir)
}
func getDefaultCertsDir() string {
return filepath.Join(getDefaultConfigDir(), certsDir)
}
func getDefaultCertsCADir() string {
return filepath.Join(getDefaultCertsDir(), certsCADir)
return selectDefaultConfigDir(homeDir)
}
var (
defaultConfigDirPath, defaultConfigDirSelection = getDefaultConfigDir()
defaultConfigDirWarningOnce sync.Once
// Default config, certs and CA directories.
defaultConfigDir = &ConfigDir{path: getDefaultConfigDir()}
defaultCertsDir = &ConfigDir{path: getDefaultCertsDir()}
defaultCertsCADir = &ConfigDir{path: getDefaultCertsCADir()}
defaultConfigDir = &ConfigDir{path: defaultConfigDirPath}
defaultCertsDir = &ConfigDir{path: filepath.Join(defaultConfigDirPath, certsDir)}
defaultCertsCADir = &ConfigDir{path: filepath.Join(defaultConfigDirPath, certsDir, certsCADir)}
// Points to current configuration directory -- deprecated, to be removed in future.
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
// `minio server` command line arguments.
// `silo server` command line arguments.
// Endpoint set represents parsed ellipses values, also provides
// 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
// command line already has C:/<export-folder/ in it. Final resulting
// 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.
// 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 {
// 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
// /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) {
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{}))
}
// 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)
r.Close()
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) {
// 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))
return res, errCorruptedFormat
}
+2 -2
View File
@@ -34,7 +34,7 @@ var fmtGenFlags = []cli.Flag{
},
cli.StringFlag{
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{
Name: "address",
@@ -69,7 +69,7 @@ FLAGS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
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}
`,
+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")
}
name := "MinIO FTP Server"
name := "Silo FTP Server"
if tls {
name = "MinIO FTP(Secure) Server"
name = "Silo FTP(Secure) Server"
}
ftpServer, err := ftp.NewServer(&ftp.Options{
+5 -9
View File
@@ -176,13 +176,13 @@ var (
// Global user opts context
globalServerCtxt serverCtxt
// Indicates if the running minio server is distributed setup.
// Indicates whether the running Silo server is a distributed setup.
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
// 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
// Indicates if server code should go through testing path.
@@ -201,8 +201,8 @@ var (
// globalBrowserConfig Browser user configurable settings
globalBrowserConfig browser.Config
// This flag is set to 'true' when MINIO_UPDATE env is set to 'off'. Default is false.
globalInplaceUpdateDisabled = false
// Silo permanently disables inherited in-place updates.
globalInplaceUpdateDisabled = true
// Captures site name and region
globalSite config.Site
@@ -433,10 +433,6 @@ var (
// 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
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 {
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 {
case strings.HasPrefix(r.URL.Path, peerRESTPrefix):
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 configRetriableErrors(err) {
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)
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 configRetriableErrors(err) {
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)
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.Name = name
app.Author = "MinIO, Inc."
app.Author = "Silo contributors"
app.Version = ReleaseTag
app.Usage = "High Performance Object Storage"
app.Description = `Build high performance data infrastructure for machine learning, analytics and application data workloads with MinIO`
app.Usage = "S3-compatible object storage"
app.Description = `Run independently maintained, S3-compatible object storage with Silo`
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.CustomAppHelpTemplate = minioHelpTemplate
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)
if len(closestCommands) > 0 {
console.Println()
@@ -178,6 +178,7 @@ func newApp(name string) *cli.App {
func startupBanner(banner io.Writer) {
CopyrightYear = strconv.Itoa(time.Now().Year())
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("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("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("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())
}
@@ -197,7 +200,7 @@ func printMinIOVersion(c *cli.Context) {
var debugNoExit = env.Get("_MINIO_DEBUG_NO_EXIT", "") != ""
// Main main for minio server.
// Main is the Silo server entry point.
func Main(args []string) {
// Set the minio app name.
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"))
return
}
if globalInplaceUpdateDisabled {
s.writeErrorResponse(w, errInplaceUpdateDisabled)
return
}
if r.ContentLength < 0 {
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"))
return
}
if globalInplaceUpdateDisabled {
s.writeErrorResponse(w, errInplaceUpdateDisabled)
return
}
if err := commitBinary(); err != nil {
s.writeErrorResponse(w, err)
+2 -2
View File
@@ -81,7 +81,7 @@ func bgFormatErasureCleanupTmp(diskPath string) {
// |__ e870a2c1-d09c-450c-a69c-6eaa54a89b3e
//
// 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()
tmpOld := pathJoin(diskPath, minioMetaTmpBucket+"-old", tmpID)
if err := renameAll(pathJoin(diskPath, minioMetaTmpBucket),
@@ -263,7 +263,7 @@ func waitForFormatErasure(firstDisk bool, endpoints Endpoints, poolCount, setCou
defer func() {
if err == nil && format != nil {
// Assign globalDeploymentID() on first run for the
// minio server managing the first disk
// Silo server managing the first disk
globalDeploymentIDPtr.Store(&format.ID)
// Set the deployment ID here to avoid races.
+13 -23
View File
@@ -114,7 +114,7 @@ var ServerFlags = []cli.Flag{
},
cli.StringFlag{
Name: "interface",
Usage: "bind to right VRF device for MinIO services",
Usage: "bind to the VRF device used by Silo services",
Hidden: true,
EnvVar: "MINIO_INTERFACE",
},
@@ -220,20 +220,20 @@ FLAGS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
EXAMPLES:
1. Start MinIO server on "/home/shared" directory.
1. Start Silo server on "/home/shared" directory.
{{.Prompt}} {{.HelpName}} /home/shared
2. Start single node server with 64 local drives "/mnt/data1" to "/mnt/data64".
{{.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}
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} \
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} \
--ftp="address=:8021" --ftp="passive-port-range=30000-40000" \
--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.
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
@@ -607,13 +607,13 @@ func initServerConfig(ctx context.Context, newObject ObjectLayer) error {
// All successful return.
if globalIsDistErasure {
// 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
}
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)))
continue
}
@@ -742,7 +742,7 @@ func initializeLogRotate(ctx *cli.Context) (io.WriteCloser, error) {
return output, nil
}
// serverMain handler called for 'minio server' command.
// serverMain handles the 'silo server' command.
func serverMain(ctx *cli.Context) {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
@@ -851,16 +851,6 @@ func serverMain(ctx *cli.Context) {
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.
bootstrapTrace("setMaxResources", func() {
_ = setMaxResources(globalServerCtxt)
@@ -868,13 +858,13 @@ func serverMain(ctx *cli.Context) {
// Verify kernel release and version.
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)
cpuProcs := runtime.NumCPU()
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))
}
@@ -1161,7 +1151,7 @@ func serverMain(ctx *cli.Context) {
Transport: globalRemoteTargetTransport,
Region: region,
})
logger.FatalIf(err, "Unable to initialize MinIO client")
logger.FatalIf(err, "Unable to initialize the internal S3 client")
})
go bootstrapTrace("startResourceMetricsCollection", func() {
@@ -1169,7 +1159,7 @@ func serverMain(ctx *cli.Context) {
})
// Add User-Agent to differentiate the requests.
globalMinioClient.SetAppInfo("minio-perf-test", ReleaseTag)
globalMinioClient.SetAppInfo("silo-perf-test", ReleaseTag)
if serverDebugLog {
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
// first endpoint as default.
printCLIAccessMsg(strippedAPIEndpoints[0], "myminio")
printCLIAccessMsg(strippedAPIEndpoints[0], "mysilo")
// Prints documentation message.
printObjectAPIMsg()
@@ -186,7 +186,7 @@ func printCLIAccessMsg(endPoint string, alias string) {
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()) {
logger.Startup(color.Blue("\nCLI: ") + mcQuickStartGuide)
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"}
printCLIAccessMsg(apiEndpoints[0], "myminio")
printCLIAccessMsg(apiEndpoints[0], "mysilo")
}
// 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")
// 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.
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):
var hint string
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 {
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")
case errors.Is(err, errDiskNotDir):
var hint string
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 {
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")
case errors.Is(err, errDiskAccessDenied):
+1 -1
View File
@@ -128,7 +128,7 @@ var stsErrCodes = stsErrorCodeMap{
},
ErrSTSInvalidClientGrantsToken: {
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,
},
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", "")
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.
@@ -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
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 "
// 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"},
}
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 {
output := prepareUpdateMessage(testCase.dlURL, testCase.older)
+19 -25
View File
@@ -51,6 +51,8 @@ const (
)
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.
minioReleaseInfoURL = MinioReleaseURL + "minio.sha256sum"
@@ -223,13 +225,10 @@ func IsPCFTile() bool {
return env.Get("MINIO_PCF_TILE_VERSION", "") != ""
}
// DO NOT CHANGE USER AGENT STYLE.
// The style should be
// Keep the inherited user agent field order stable while identifying Silo as
// 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>]
//
// Any change here should be discussed by opening an issue at
// https://github.com/minio/minio/issues.
// Silo (<OS>; <ARCH>[; <MODE>][; dcos][; kubernetes][; docker][; source]) <VERSION> <RELEASE-TAG> <COMMIT-ID> [universe-<PACKAGE-NAME>] [helm-<HELM-VERSION>]
func getUserAgent(mode string) string {
userAgentParts := []string{}
// Helper function to concisely append a pair of strings to a
@@ -438,22 +437,14 @@ func getUpdateTransport(timeout time.Duration) http.RoundTripper {
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 (
// Kubernetes deployment doc link.
kubernetesDeploymentDoc = "https://silo.pgsty.com/operations/deployments/kubernetes/"
// Mesos deployment doc link.
mesosDeploymentDoc = "https://silo.pgsty.com/operations/deployments/kubernetes/"
siloDownloadPage = "https://silo.pgsty.com/download/"
)
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
if IsDocker() {
// 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.
if runtime.GOOS == "windows" {
return MinioReleaseURL + "minio.exe"
}
return MinioReleaseURL + "minio"
// Binary installations are upgraded from a verified release artifact.
return siloDownloadPage
}
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 (
// Update this whenever the official minisign pubkey is rotated.
defaultMinisignPubkey = "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
// Silo has no in-place update trust root. The environment key remains
// 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) {
if globalInplaceUpdateDisabled {
return errInplaceUpdateDisabled
}
if !updateInProgress.CompareAndSwap(0, 1) {
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) {
if globalInplaceUpdateDisabled {
return errInplaceUpdateDisabled
}
if !updateInProgress.CompareAndSwap(0, 1) {
return errors.New("update already in progress")
}
+24 -15
View File
@@ -20,6 +20,7 @@ package cmd
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"net/http"
"net/http/httptest"
@@ -35,6 +36,9 @@ import (
)
func TestDownloadBinaryReturnsOwnedBuffers(t *testing.T) {
previousDisabled := globalInplaceUpdateDisabled
globalInplaceUpdateDisabled = false
t.Cleanup(func() { globalInplaceUpdateDisabled = previousDisabled })
previousMaxProcs := runtime.GOMAXPROCS(1)
t.Cleanup(func() {
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) {
testCases := []struct {
version string
@@ -167,19 +184,11 @@ func TestDownloadURL(t *testing.T) {
minioVersion1 := releaseTimeToReleaseTag(UTCNow())
durl := getDownloadURL(minioVersion1)
if IsDocker() {
if durl != "podman pull quay.io/minio/minio:"+minioVersion1 {
t.Errorf("Expected %s, got %s", "podman pull quay.io/minio/minio:"+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)
}
if durl != "podman pull docker.io/pgsty/silo:"+minioVersion1 {
t.Errorf("Expected %s, got %s", "podman pull docker.io/pgsty/silo:"+minioVersion1, durl)
}
} else if durl != siloDownloadPage {
t.Errorf("Expected %s, got %s", siloDownloadPage, durl)
}
t.Setenv("KUBERNETES_SERVICE_HOST", "10.11.148.5")
@@ -207,19 +216,19 @@ func TestUserAgent(t *testing.T) {
envName: "",
envValue: "",
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",
envValue: "mesos-11111",
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",
envValue: "10.11.148.5",
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:
si := systemInfo{
ProtocolVersion: `"1.0"`,
ModelName: "\"MinIO " + ReleaseTag + "\"",
ModelName: "\"Silo " + ReleaseTag + "\"",
}
si.ProtocolCapabilities.CapacityInfo = true
// Default recommended block size with MinIO
// Default recommended block size with Silo.
si.SystemRecommendations.KBBlockSize = 4096
buf = encodeResponse(&si)
+1 -1
View File
@@ -49,7 +49,7 @@ const probeObject = "probeobject"
// checkWarmBackend checks if tier config credentials have sufficient privileges
// to perform all operations defined in the WarmBackend interface.
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 _, ok := err.(BackendDown); ok {
return err