mirror of
https://github.com/pgsty/minio.git
synced 2026-08-09 07:43:29 +03:00
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:
+33
-41
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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: {
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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])
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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 ==")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
@@ -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: {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -29,7 +29,7 @@ var (
|
||||
config.HelpKV{
|
||||
Key: Enable,
|
||||
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,
|
||||
},
|
||||
config.HelpKV{
|
||||
|
||||
+13
-13
@@ -21,8 +21,8 @@ package config
|
||||
var (
|
||||
ErrInvalidXLValue = newErrFn(
|
||||
"Invalid drive path",
|
||||
"Please provide a fresh drive for single drive MinIO setup",
|
||||
"MinIO only supports fresh drive paths",
|
||||
"Please provide a fresh drive for a single-drive Silo setup",
|
||||
"Silo only supports fresh drive paths",
|
||||
)
|
||||
|
||||
ErrInvalidBrowserValue = newErrFn(
|
||||
@@ -118,14 +118,14 @@ var (
|
||||
ErrStorageClassValue = newErrFn(
|
||||
"Invalid storage class 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_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
|
||||
Refer to the link https://github.com/minio/minio/tree/master/docs/erasure/storage-class for more information`,
|
||||
`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 Silo in Reduced Redundancy mode. Objects are stored in Reduced Redundancy mode if the Put request specifies the RRS storage class.
|
||||
See https://silo.pgsty.com/operations/concepts/erasure-coding/ for more information.`,
|
||||
)
|
||||
|
||||
ErrUnexpectedBackendVersion = newErrFn(
|
||||
"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",
|
||||
`Single-Node modes requires absolute path without hostnames:
|
||||
Examples:
|
||||
$ minio server /data/minio/ #Single Node Single Drive
|
||||
$ minio server /data-{1...4}/minio # Single Node Multi Drive`,
|
||||
$ silo server /data/silo/ # Single Node Single Drive
|
||||
$ silo server /data-{1...4}/silo # Single Node Multi Drive`,
|
||||
)
|
||||
|
||||
ErrUnsupportedBackend = newErrFn(
|
||||
@@ -155,8 +155,8 @@ Examples:
|
||||
|
||||
ErrUnableToWriteInBackend = newErrFn(
|
||||
"Unable to write to the backend",
|
||||
"Please ensure MinIO binary has write permissions for the backend",
|
||||
`Verify if MinIO binary is running as the same user who has write permissions for the backend`,
|
||||
"Please ensure the Silo binary 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(
|
||||
@@ -167,8 +167,8 @@ Examples:
|
||||
|
||||
ErrPortAccess = newErrFn(
|
||||
"Unable to use specified port",
|
||||
"Please ensure MinIO binary has 'cap_net_bind_service=+ep' permissions",
|
||||
`Use 'sudo setcap cap_net_bind_service=+ep /path/to/minio' to provide sufficient permissions`,
|
||||
"Please ensure the Silo binary has 'cap_net_bind_service=+ep' permissions",
|
||||
`Use 'sudo setcap cap_net_bind_service=+ep /path/to/silo' to provide sufficient permissions`,
|
||||
)
|
||||
|
||||
ErrTLSReadError = newErrFn(
|
||||
@@ -209,7 +209,7 @@ Examples:
|
||||
|
||||
ErrUnexpectedError = newErrFn(
|
||||
"Unexpected error",
|
||||
"Please contact MinIO at https://slack.min.io",
|
||||
"Please report this Silo error at https://github.com/pgsty/minio/issues",
|
||||
"",
|
||||
)
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ var (
|
||||
},
|
||||
config.HelpKV{
|
||||
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,
|
||||
Type: "string",
|
||||
},
|
||||
|
||||
@@ -29,11 +29,6 @@ import (
|
||||
xnet "github.com/minio/pkg/v3/net"
|
||||
)
|
||||
|
||||
const (
|
||||
baseURL = "https://subnet.min.io"
|
||||
baseURLDev = "http://localhost:9000"
|
||||
)
|
||||
|
||||
// DefaultKVS - default KV config for subnet settings
|
||||
var DefaultKVS = config.KVS{
|
||||
config.KV{
|
||||
@@ -70,29 +65,21 @@ type Config struct {
|
||||
|
||||
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 {
|
||||
configLock.RLock()
|
||||
defer configLock.RUnlock()
|
||||
|
||||
return len(c.APIKey) > 0
|
||||
return false
|
||||
}
|
||||
|
||||
// ApplyEnv - applies the current subnet config to Console UI specific environment variables.
|
||||
func (c *Config) ApplyEnv() {
|
||||
configLock.RLock()
|
||||
defer configLock.RUnlock()
|
||||
|
||||
if c.License != "" {
|
||||
os.Setenv("CONSOLE_SUBNET_LICENSE", c.License)
|
||||
}
|
||||
if c.APIKey != "" {
|
||||
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)
|
||||
// Do not expose inherited commercial-service credentials or an endpoint to
|
||||
// the embedded Console. These names are outputs, not the MINIO_* input
|
||||
// compatibility surface.
|
||||
os.Unsetenv("CONSOLE_SUBNET_LICENSE")
|
||||
os.Unsetenv("CONSOLE_SUBNET_API_KEY")
|
||||
os.Unsetenv("CONSOLE_SUBNET_PROXY")
|
||||
os.Unsetenv("CONSOLE_SUBNET_URL")
|
||||
}
|
||||
|
||||
// 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.APIKey = ncfg.APIKey
|
||||
c.Proxy = ncfg.Proxy
|
||||
c.transport = ncfg.transport
|
||||
c.BaseURL = baseURL
|
||||
c.transport = nil
|
||||
c.BaseURL = ""
|
||||
|
||||
if isDevEnv {
|
||||
c.BaseURL = os.Getenv("_MINIO_SUBNET_URL")
|
||||
if c.BaseURL == "" {
|
||||
c.BaseURL = baseURLDev
|
||||
}
|
||||
}
|
||||
// Retain the hidden compatibility input but deliberately ignore its value.
|
||||
_ = os.Getenv("_MINIO_SUBNET_URL")
|
||||
_ = isDevEnv
|
||||
}
|
||||
|
||||
// LookupConfig - lookup config and override with valid environment settings if any.
|
||||
|
||||
@@ -18,97 +18,27 @@
|
||||
package subnet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
)
|
||||
|
||||
const (
|
||||
respBodyLimit = 1 << 20 // 1 MiB
|
||||
|
||||
// LoggerWebhookName - subnet logger webhook target
|
||||
LoggerWebhookName = "subnet"
|
||||
)
|
||||
|
||||
var errSiloSubnetDisabled = errors.New("MinIO SUBNET integration is disabled in Silo")
|
||||
|
||||
// Upload given file content (payload) to specified URL
|
||||
func (c Config) Upload(reqURL string, filename string, payload []byte) (string, error) {
|
||||
if !c.Registered() {
|
||||
return "", errors.New("Deployment is not registered with SUBNET. Please register the deployment via 'mc license register ALIAS'")
|
||||
}
|
||||
|
||||
var body bytes.Buffer
|
||||
writer := multipart.NewWriter(&body)
|
||||
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)
|
||||
return "", errSiloSubnetDisabled
|
||||
}
|
||||
|
||||
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)
|
||||
func (c Config) submitPost(_ *http.Request) (string, error) {
|
||||
return "", errSiloSubnetDisabled
|
||||
}
|
||||
|
||||
// Post submit 'payload' to specified URL
|
||||
func (c Config) Post(reqURL string, payload any) (string, error) {
|
||||
if !c.Registered() {
|
||||
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)
|
||||
return "", errSiloSubnetDisabled
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)")
|
||||
|
||||
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:
|
||||
target.client = clientV7
|
||||
|
||||
@@ -172,7 +172,7 @@ func (n NATSArgs) Validate() error {
|
||||
|
||||
// To obtain a nats connection from args.
|
||||
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 != "" {
|
||||
connOpts = append(connOpts, nats.UserInfo(n.Username, n.Password))
|
||||
}
|
||||
|
||||
@@ -352,7 +352,7 @@ func NewRedisTarget(id string, args RedisArgs, loggerOnce logger.LogOnce) (*Redi
|
||||
}
|
||||
|
||||
// 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()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+13
-13
@@ -358,11 +358,11 @@ func IsPresent() (bool, error) {
|
||||
|
||||
switch {
|
||||
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:
|
||||
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:
|
||||
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
|
||||
@@ -375,16 +375,16 @@ func IsPresent() (bool, error) {
|
||||
return false, nil // No KMS config present
|
||||
case kmsPresent:
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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
|
||||
case staticKeyPresent:
|
||||
@@ -394,24 +394,24 @@ func IsPresent() (bool, error) {
|
||||
return true, nil
|
||||
case kesPresent:
|
||||
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) {
|
||||
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(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) {
|
||||
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) {
|
||||
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) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ var (
|
||||
// StubCreatedAt is a constant timestamp for testing
|
||||
StubCreatedAt = time.Date(2024, time.January, 1, 15, 0, 0, 0, time.UTC)
|
||||
// StubCreatedBy is a constant created identity for testing
|
||||
StubCreatedBy = "MinIO"
|
||||
StubCreatedBy = "Silo"
|
||||
)
|
||||
|
||||
// NewStub returns a stub of KMS for testing
|
||||
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user