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