Compare commits

..

1 Commits

Author SHA1 Message Date
Feng Ruohang 96794aff81 docs: mark this branch as the archived MinIO-identity state
This branch is the last state of the project under the MinIO identity. It is
kept so the artifacts released in that form stay traceable to the source that
produced them, and so anyone landing here from an old link is not left reading
current-looking documentation for a line that has moved.

Both READMEs now open with an archive notice: development continues on main as
Silo, the repository is pgsty/silo, the last release cut from here is
RELEASE.2026-08-04T00-00-00Z, and its assets and container image keep the minio
names and are not moved or re-signed.

The notice also states what did not change, because that is the question an
operator arriving here will actually have: MINIO_* variables, minio_* metrics,
x-minio-* headers, /minio/* routes, the .minio.sys layout, IAM and ARN values,
and the github.com/minio/minio module path are all preserved on main, and the
packages install side by side so migration and rollback stay explicit.

Only the two README files change. The tree is otherwise the exact code of
RELEASE.2026-08-04T00-00-00Z, which the tag continues to mark.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 09:45:23 +08:00
396 changed files with 15036 additions and 22645 deletions
+5 -8
View File
@@ -8,12 +8,9 @@ assignees: ''
---
## NOTE
All GitHub issues are addressed on a best-effort basis at MinIO's sole discretion. There are no Service Level Agreements (SLA) or Objectives (SLO). Remember our [Code of Conduct](https://github.com/minio/minio/blob/master/code_of_conduct.md) when engaging with MinIO Engineers and the larger community.
Silo issues are handled by community maintainers on a best-effort basis. There
is no SLA, SLO, or emergency production-support channel. Follow the local
[Code of Conduct](../code_of_conduct.md) when participating. Report suspected
vulnerabilities through the private process in [SECURITY.md](../SECURITY.md),
not in a public issue.
For urgent issues (e.g. production down, etc.), subscribe to [SUBNET](https://min.io/pricing?jmp=github) for direct to engineering support.
<!--- Provide a general summary of the issue in the Title above -->
@@ -32,7 +29,7 @@ not in a public issue.
## Steps to Reproduce (for bugs)
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. Include code to reproduce, if relevant -->
<!--- and include relevant Silo logs with secrets and credentials removed -->
<!--- and make sure you have followed https://github.com/minio/minio/tree/release/docs/debugging to capture relevant logs -->
1.
2.
@@ -45,10 +42,10 @@ not in a public issue.
## Regression
<!-- Is this issue a regression? (Yes / No) -->
<!-- If Yes, optionally include the Silo version, commit id, or PR that caused this regression. -->
<!-- If Yes, optionally please include minio version or commit id or PR# that caused this regression, if you have these details. -->
## Your Environment
<!--- Include as many relevant details about the environment you experienced the bug in -->
* Version used (`silo --version`):
* Version used (`minio --version`):
* Server setup and configuration:
* Operating System and version (`uname -a`):
+3 -3
View File
@@ -24,7 +24,7 @@ assignees: ''
## Steps to Reproduce (for bugs)
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. Include code to reproduce, if relevant -->
<!--- and include the relevant Silo logs with secrets and credentials removed -->
<!--- and make sure you have followed https://github.com/minio/minio/tree/release/docs/debugging to capture relevant logs -->
1.
2.
@@ -37,10 +37,10 @@ assignees: ''
## Regression
<!-- Is this issue a regression? (Yes / No) -->
<!-- If Yes, optionally include the Silo version, commit id, or PR that caused the regression. -->
<!-- If Yes, optionally please include minio version or commit id or PR# that caused this regression, if you have these details. -->
## Your Environment
<!--- Include as many relevant details about the environment you experienced the bug in -->
* Version used (`silo --version`):
* Version used (`minio --version`):
* Server setup and configuration:
* Operating System and version (`uname -a`):
+6 -6
View File
@@ -1,8 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Silo Documentation
url: https://silo.pgsty.com/docs/
about: Installation, configuration, operations, and compatibility guidance
- name: Private Security Report
url: https://github.com/pgsty/silo/security/advisories/new
about: Privately report a suspected vulnerability in Silo
- name: MinIO Community Support
url: https://slack.min.io
about: Community support via Slack - for questions and discussions
- name: MinIO Enterprise Support (SUBNET)
url: https://min.io/pricing
about: Enterprise support with SLA - for production deployments and feature requests
+6 -21
View File
@@ -1,13 +1,8 @@
## Contribution Licensing (no CLA, inbound=outbound, DCO required)
This project does not use a CLA; contributions are accepted inbound=outbound.
By submitting this pull request I represent that I have the right to contribute
the changes, which are licensed under this repository's
[GNU Affero General Public License v3.0 or later](https://www.gnu.org/licenses/agpl-3.0.html)
and remain my copyright. Every commit must carry a DCO `Signed-off-by` trailer
(`git commit -s`) certifying the
[Developer Certificate of Origin](https://developercertificate.org/) — see
[CONTRIBUTING.md](https://github.com/pgsty/silo/blob/main/CONTRIBUTING.md).
## Community Contribution License
All community contributions in this pull request are licensed to the project maintainers
under the terms of the [Apache 2 license](https://www.apache.org/licenses/LICENSE-2.0).
By creating this pull request I represent that I have the right to license the
contributions to the project maintainers under the Apache 2 license.
## Description
@@ -18,12 +13,6 @@ and remain my copyright. Every commit must carry a DCO `Signed-off-by` trailer
## How to test this PR?
## Compatibility impact
<!-- Note effects on APIs, clients, MINIO_* configuration, metrics, headers,
routes, storage metadata, module/import paths, upgrades, or rollback. -->
## Types of changes
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
@@ -31,11 +20,7 @@ routes, storage metadata, module/import paths, upgrades, or rollback. -->
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
## Checklist:
- [ ] All commits are signed off (`git commit -s`) per the [DCO](https://developercertificate.org/)
- [ ] Fixes a regression (If yes, please add `commit-id` or `PR #` here)
- [ ] Unit tests added/updated
- [ ] `make verifiers` passes
- [ ] Relevant package tests and `make build` pass
- [ ] Compatibility and rollback impact documented
- [ ] Internal documentation updated
- [ ] Public documentation update opened in `pgsty/silo.pgsty.com`, if needed
- [ ] Create a documentation update request [here](https://github.com/minio/docs/issues/new?label=doc-change,title=Doc+Updated+Needed+For+PR+github.com%2fminio%2fminio%2fpull%2fNNNNN)
+7 -46
View File
@@ -4,9 +4,9 @@ env:
- CGO_ENABLED=0
builds:
- id: silo
- id: minio
main: .
binary: silo
binary: minio
goos:
- linux
- darwin
@@ -23,58 +23,19 @@ builds:
- "{{ .Env.LDFLAGS }}"
archives:
- id: silo
- id: minio
ids:
- silo
name_template: "silo_{{ .Env.PKG_VERSION }}_{{ .Os }}_{{ .Arch }}"
# Explicit so the license materials cannot silently drop out of the
# binary archives: GoReleaser's default file globs would miss NOTICE.
# CREDITS stays out deliberately -- at 1.8MB it would dominate the
# archive; it remains available in the repository and the OCI image.
files:
- README.md
- LICENSE
- NOTICE
- minio
name_template: "minio_{{ .Env.PKG_VERSION }}_{{ .Os }}_{{ .Arch }}"
checksum:
name_template: "silo_{{ .Env.PKG_VERSION }}_checksums.txt"
name_template: "minio_{{ .Env.PKG_VERSION }}_checksums.txt"
algorithm: sha256
# Generate one SPDX JSON document per platform archive. SBOMs are created
# before the checksum stage, so the signed checksum manifest covers both the
# archives and their corresponding software bills of materials.
sboms:
- id: silo-archives
artifacts: archive
# Avoid network-backed package enrichment: the release SBOM must be
# reproducible from the artifact alone and the PR gate must work offline.
args:
- "$artifact"
- "--output"
- "spdx-json=$document"
env:
- SYFT_FILE_METADATA_CATALOGER_ENABLED=true
- SYFT_CHECK_FOR_APP_UPDATE=false
# A keyless Sigstore bundle is the detached signature for the checksum
# manifest. Consumers can verify the whole archive/SBOM set without trusting a
# long-lived project key copied into the repository.
signs:
- id: silo-checksums
cmd: cosign
signature: "${artifact}.sigstore.json"
args:
- sign-blob
- "--bundle=${signature}"
- "${artifact}"
- --yes
artifacts: checksum
output: true
release:
github:
owner: pgsty
name: silo
name: minio
draft: true
prerelease: false
mode: append
+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="255 293 539 140" fill="none">
<path d="M255.222 385.861 304.199 383.375c1.061 6.446 3.22 11.358 6.478 14.734 5.303 5.465 12.879 8.197 22.728 8.197 7.348 0 13.011-1.397 16.988-4.19 3.977-2.794 5.966-6.032 5.966-9.716 0-3.5-1.894-6.631-5.682-9.394-3.788-2.763-12.576-5.372-26.364-7.828-22.576-4.113-38.674-9.577-48.295-16.392-9.697-6.815-14.546-15.502-14.546-26.062 0-6.938 2.481-13.491 7.443-19.662 4.962-6.17 12.424-11.02 22.386-14.55 9.962-3.53 23.617-5.295 40.966-5.295 21.288 0 37.519 3.208 48.693 9.624 11.174 6.416 17.822 16.622 19.943 30.621l-48.523 2.302c-1.288-6.078-3.996-10.498-8.125-13.261-4.129-2.763-9.83-4.144-17.103-4.144-5.984 0-10.492 1.028-13.522 3.085-3.03 2.056-4.545 4.558-4.545 7.505 0 2.149 1.25 4.083 3.75 5.802 2.424 1.78 8.182 3.438 17.273 4.973 22.5 3.93 38.617 7.905 48.352 11.926 9.735 4.022 16.818 9.01 21.25 14.965 4.432 5.956 6.648 12.617 6.648 19.984 0 8.657-2.954 16.638-8.863 23.944-5.909 7.306-14.167 12.847-24.773 16.623-10.606 3.776-23.977 5.664-40.113 5.664-28.333 0-47.954-4.42-58.863-13.261-10.909-8.841-17.083-20.076-18.522-33.706Z" fill="#287CAB"/>
<path d="M434.578 295.519h43.304v135.007h-43.304z" fill="#287CAB"/>
<path d="M517.881 295.519h43.209v101.762h67.436v33.245H517.881Z" fill="#287CAB"/>
<path d="M639.4 363.115c0-22.041 6.359-39.201 19.077-51.48 12.718-12.28 30.427-18.419 53.129-18.419 23.274 0 41.206 6.032 53.796 18.096 12.591 12.064 18.886 28.963 18.886 50.697 0 15.778-2.75 28.717-8.251 38.817-5.5 10.099-13.449 17.958-23.846 23.575-10.397 5.618-23.353 8.427-38.869 8.427-15.77 0-28.822-2.425-39.155-7.275-10.333-4.85-18.711-12.525-25.133-23.023-6.423-10.499-9.634-23.637-9.634-39.416Zm43.209.184c0 13.63 2.623 23.422 7.869 29.377 5.246 5.956 12.384 8.934 21.413 8.934 9.284 0 16.47-2.916 21.557-8.749 5.087-5.833 7.631-16.301 7.631-31.403 0-12.709-2.655-21.995-7.965-27.858-5.31-5.863-12.511-8.795-21.604-8.795-8.712 0-15.707 2.978-20.985 8.933-5.278 5.956-7.916 15.81-7.916 29.561Z" fill="#287CAB"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+4 -77
View File
@@ -1,4 +1,4 @@
name: silo
name: minio
arch: ${NFPM_ARCH}
platform: linux
version: ${PKG_VERSION}
@@ -7,99 +7,26 @@ release: ${NFPM_RELEASE}
section: utils
priority: optional
maintainer: "Ruohang Feng (@Vonng) <rh@vonng.com>"
description: S3-Interface Libre Object Storage, a community-maintained S3-compatible server.
description: S3-Interface Libre Object Storage, Community-maintained MinIO server fork.
vendor: PGSTY
homepage: https://silo.pgsty.com
license: AGPL-3.0-or-later
contents:
- src: ${NFPM_SOURCE}
dst: /usr/bin/silo
dst: /usr/local/bin/minio
expand: true
file_info:
mode: 0755
owner: root
group: root
- src: ${NFPM_UNIT}
dst: /usr/lib/systemd/system/silo.service
dst: /usr/lib/systemd/system/minio.service
expand: true
file_info:
mode: 0644
owner: root
group: root
- src: ${NFPM_DEFAULTS}
dst: /etc/default/silo
type: config|noreplace
expand: true
file_info:
mode: 0644
owner: root
group: root
- src: ${NFPM_SYSUSERS}
dst: /usr/lib/sysusers.d/silo.conf
expand: true
file_info:
mode: 0644
owner: root
group: root
# The license materials are declared once per packager: nfpm only honors
# type: license on rpm and silently drops such entries from deb and apk, so
# the rpm keeps its %license flag while deb and apk carry plain files at the
# same path.
- src: ${NFPM_LICENSE}
dst: /usr/share/doc/silo/LICENSE
type: license
packager: rpm
expand: true
file_info:
mode: 0644
owner: root
group: root
- src: ${NFPM_NOTICE}
dst: /usr/share/doc/silo/NOTICE
type: license
packager: rpm
expand: true
file_info:
mode: 0644
owner: root
group: root
- src: ${NFPM_LICENSE}
dst: /usr/share/doc/silo/LICENSE
packager: deb
expand: true
file_info:
mode: 0644
owner: root
group: root
- src: ${NFPM_NOTICE}
dst: /usr/share/doc/silo/NOTICE
packager: deb
expand: true
file_info:
mode: 0644
owner: root
group: root
- src: ${NFPM_LICENSE}
dst: /usr/share/doc/silo/LICENSE
packager: apk
expand: true
file_info:
mode: 0644
owner: root
group: root
- src: ${NFPM_NOTICE}
dst: /usr/share/doc/silo/NOTICE
packager: apk
expand: true
file_info:
mode: 0644
owner: root
group: root
scripts:
postinstall: buildscripts/package/postinstall.sh
preremove: buildscripts/package/preremove.sh
rpm:
group: Applications/File
-105
View File
@@ -1,105 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
width="1500" height="570" viewBox="0 0 1500 570"
preserveAspectRatio="xMidYMid meet"
role="img" aria-labelledby="silo-logo-title silo-logo-desc"
shape-rendering="geometricPrecision">
<title id="silo-logo-title">SILO logo</title>
<desc id="silo-logo-desc">The SILO horizontal lockup: the circular silo emblem on the left, the SILO wordmark on the right.</desc>
<defs>
<!-- Emblem gradient, in the emblem's local coordinates; the group transform maps it. -->
<linearGradient id="silo-logo-mark-color" x1="276" y1="720" x2="742" y2="286" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#064A83"/>
<stop offset="0.5" stop-color="#007FA8"/>
<stop offset="1" stop-color="#22C7C9"/>
</linearGradient>
<!-- Wordmark gradient, in the wordmark's local coordinates. -->
<linearGradient id="silo-logo-word-color" x1="44.0" y1="-94.4" x2="1950.0" y2="794.4" gradientUnits="userSpaceOnUse">
<stop class="silo-logo-wm-a" offset="0.06" stop-color="#1d588c"/>
<stop class="silo-logo-wm-b" offset="0.94" stop-color="#b4762e"/>
</linearGradient>
<style>
/* Light theme values of --pg-strong / --copper; dark theme swaps in its own pair. */
@media (prefers-color-scheme: dark) {
.silo-logo-wm-a { stop-color: #7fb8e8; }
.silo-logo-wm-b { stop-color: #e0a35c; }
}
</style>
</defs>
<!-- Circular emblem, shifted from its native viewBox (230 213 570 570) to the 0..570 square. -->
<g id="silo-logo-mark" transform="translate(-230 -213)" fill="url(#silo-logo-mark-color)">
<!-- Outer circular band, intentionally opened at the lower-right plinth. -->
<path d="
M 734 676
A 283.5 278.5 0 1 0 310 692
L 359 692
A 247 248.5 0 1 1 688 676
Z"/>
<!-- Flowing left wall; its upper tangent matches the inner ellipse. -->
<path d="
M 300.57 375
C 292 390 300 430 328 450
C 343 461 357 472 374 481
C 410 501 426 517 426 544
L 426 676
L 336 676
C 308 647 286 608 274 565
C 262 522 263 479 272 439
C 278 413 286 389 300.57 375
Z"/>
<!-- Right column with tangent-continuous upper shoulder. -->
<path d="
M 602 373
Q 602 368 607 370
C 619 374 634 381 634 389
L 634 647
Q 634 649 636 649
L 708 649
L 734 676
L 602 676
Z"/>
<!-- Lower circular cap. -->
<path d="
M 310 692
L 714 692
C 668 743 596 774 512 774
C 428 774 355 743 310 692
Z"/>
<!-- Main silo body, with a tangent-continuous right shoulder. -->
<path d="
M 389 389
C 389 374 447 351 512 351
C 540 351 565 354 580 359
Q 583 360 583 364
L 583 676
L 443 676
L 443 541
C 443 509 426 490 389 470
Z"/>
<!-- Peaked roof with softly tapered, burr-free tips. -->
<path d="
M 512 274
L 647 365
Q 649 370 647 376
C 609 350 563 337 512 337
C 460 337 414 350 377 376
Q 375 370 377 365
Z"/>
</g>
<!-- Wordmark, scaled 3/7 to a 300-unit cap height and centered on the emblem's axis. -->
<g id="silo-logo-word" transform="translate(645.429 135) scale(0.428571)"
fill="url(#silo-logo-word-color)" fill-rule="nonzero">
<path id="silo-logo-s" d="M0 592V492H134V551L167 584H374L408 550V434L375 401H110L2 293V108L110 0H426L534 108V209H400V149L367 116H169L136 149V252L169 285H434L542 393V590L432 700H108Z"/>
<path id="silo-logo-i" d="M637 0H773V700H637Z"/>
<path id="silo-logo-l" d="M888 0H1024V585H1374V700H888Z"/>
<path id="silo-logo-o" d="M1404 585V115L1519 0H1879L1994 115V585L1879 700H1519ZM1807 584 1858 533V167L1807 116H1591L1540 167V533L1591 584Z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

-43
View File
@@ -1,43 +0,0 @@
name: DCO
on:
pull_request:
branches:
- main
permissions:
contents: read
jobs:
check:
name: Verify DCO sign-off
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
# Every non-merge commit in the pull request must carry a Signed-off-by
# trailer matching the commit author's email, certifying the Developer
# Certificate of Origin 1.1 (https://developercertificate.org/).
# Only commits authored from a GitHub-issued bot address are exempt; a
# display name is attacker-controlled and must never grant the exemption.
- name: Check Signed-off-by trailers
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
fail=0
while read -r sha; do
author_name="$(git log -1 --format='%an' "${sha}")"
author_email="$(git log -1 --format='%ae' "${sha}")"
case "${author_email}" in
*"[bot]@users.noreply.github.com") continue ;;
esac
if ! git log -1 --format='%(trailers:key=Signed-off-by,valueonly)' "${sha}" |
grep -qiF "<${author_email}>"; then
echo "::error::commit ${sha} by ${author_name} <${author_email}> lacks a matching Signed-off-by trailer; sign with 'git commit -s', repair with 'git rebase --signoff'"
fail=1
fi
done < <(git rev-list --no-merges "${BASE_SHA}..${HEAD_SHA}")
exit "${fail}"
+28 -223
View File
@@ -4,15 +4,12 @@ on:
workflow_dispatch:
inputs:
tag:
description: "Published RELEASE.* tag to package as pgsty/silo"
description: "Published RELEASE.* tag to package as pgsty/minio"
required: true
type: string
permissions:
contents: read
id-token: write
attestations: write
artifact-metadata: write
concurrency:
group: docker-release
@@ -69,20 +66,11 @@ jobs:
fi
- name: Checkout release tag
uses: actions/checkout@v7
uses: actions/checkout@v4
with:
ref: ${{ inputs.tag }}
fetch-depth: 0
- name: Verify workflow identity matches release source
run: |
set -euo pipefail
CHECKED_OUT_REVISION="$(git rev-parse HEAD)"
if [ "${CHECKED_OUT_REVISION}" != "${GITHUB_SHA}" ]; then
echo "Checked out ${CHECKED_OUT_REVISION}, but workflow identity is ${GITHUB_SHA}. Dispatch this workflow from ${RELEASE_TAG}." >&2
exit 1
fi
- name: Prepare verified Docker contexts
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -91,9 +79,9 @@ jobs:
assets_dir="docker-release/assets"
mkdir -p "${assets_dir}"
amd64_archive="silo_${PKG_VERSION}_linux_amd64.tar.gz"
arm64_archive="silo_${PKG_VERSION}_linux_arm64.tar.gz"
checksums="silo_${PKG_VERSION}_checksums.txt"
amd64_archive="minio_${PKG_VERSION}_linux_amd64.tar.gz"
arm64_archive="minio_${PKG_VERSION}_linux_arm64.tar.gz"
checksums="minio_${PKG_VERSION}_checksums.txt"
gh release download "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" \
--dir "${assets_dir}" \
--pattern "${amd64_archive}" \
@@ -102,22 +90,10 @@ jobs:
# The binaries going into the images are the published ones, checked
# against the published checksums, not a rebuild that merely ought to
# match them. awk matches the manifest filename column exactly: a
# substring grep would also pull in the archive's .sbom.json line,
# whose file is deliberately not downloaded in this lane.
# match them.
cd "${assets_dir}"
awk -v name="${amd64_archive}" '$2 == name' "${checksums}" | sha256sum --check
awk -v name="${arm64_archive}" '$2 == name' "${checksums}" | sha256sum --check
# The checksum manifest and both archives must have provenance from
# this repository's release workflow at the exact checked-out tag.
for artifact in "${checksums}" "${amd64_archive}" "${arm64_archive}"; do
gh attestation verify "${artifact}" \
--repo "${GITHUB_REPOSITORY}" \
--signer-workflow "${GITHUB_REPOSITORY}/.github/workflows/release.yml" \
--source-digest "$(git -C "${GITHUB_WORKSPACE}" rev-parse HEAD)" \
--source-ref "refs/tags/${RELEASE_TAG}" >/dev/null
done
grep -F " ${amd64_archive}" "${checksums}" | sha256sum --check
grep -F " ${arm64_archive}" "${checksums}" | sha256sum --check
cd "${GITHUB_WORKSPACE}"
# Dockerfile.goreleaser expects the binary at the context root and
@@ -125,10 +101,10 @@ jobs:
# GoReleaser used to assemble via extra_files.
for arch in amd64 arm64; do
context="docker-release/${arch}"
archive="${assets_dir}/silo_${PKG_VERSION}_linux_${arch}.tar.gz"
archive="${assets_dir}/minio_${PKG_VERSION}_linux_${arch}.tar.gz"
mkdir -p "${context}/dockerscripts"
tar -xzf "${archive}" -C "${context}" silo
cp Dockerfile.goreleaser Dockerfile.distroless LICENSE NOTICE CREDITS "${context}/"
tar -xzf "${archive}" -C "${context}" minio
cp Dockerfile.goreleaser LICENSE CREDITS "${context}/"
cp dockerscripts/docker-entrypoint.sh dockerscripts/download-static-curl.sh \
"${context}/dockerscripts/"
done
@@ -136,230 +112,59 @@ jobs:
echo "RELEASE_REVISION=$(git rev-parse HEAD)" >> "${GITHUB_ENV}"
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
uses: docker/setup-qemu-action@v3
with:
platforms: arm64
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v4
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Install Syft
uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
with:
syft-version: v1.50.0
- name: Build and push amd64 image
id: build-amd64
uses: docker/build-push-action@v7
uses: docker/build-push-action@v6
with:
context: docker-release/amd64
file: docker-release/amd64/Dockerfile.goreleaser
platforms: linux/amd64
push: true
tags: |
pgsty/silo:${{ env.RELEASE_TAG }}-amd64
pgsty/silo:latest-amd64
pgsty/minio:${{ env.RELEASE_TAG }}-amd64
pgsty/minio:latest-amd64
labels: |
org.opencontainers.image.version=${{ env.RELEASE_TAG }}
org.opencontainers.image.created=${{ env.PUBLISHED_AT }}
org.opencontainers.image.revision=${{ env.RELEASE_REVISION }}
- name: Build and push arm64 image
id: build-arm64
uses: docker/build-push-action@v7
uses: docker/build-push-action@v6
with:
context: docker-release/arm64
file: docker-release/arm64/Dockerfile.goreleaser
platforms: linux/arm64
push: true
tags: |
pgsty/silo:${{ env.RELEASE_TAG }}-arm64
pgsty/silo:latest-arm64
pgsty/minio:${{ env.RELEASE_TAG }}-arm64
pgsty/minio:latest-arm64
labels: |
org.opencontainers.image.version=${{ env.RELEASE_TAG }}
org.opencontainers.image.created=${{ env.PUBLISHED_AT }}
org.opencontainers.image.revision=${{ env.RELEASE_REVISION }}
# The distroless variant is a pilot published alongside the classic
# image; it ships the silo binary alone and relies on the native
# `silo healthcheck` subcommand for container health.
# Design: https://silo.pgsty.com/compatibility/feature/healthcheck/
- name: Build and push amd64 distroless image
id: build-amd64-distroless
uses: docker/build-push-action@v7
with:
context: docker-release/amd64
file: docker-release/amd64/Dockerfile.distroless
platforms: linux/amd64
push: true
tags: |
pgsty/silo:${{ env.RELEASE_TAG }}-distroless-amd64
pgsty/silo:distroless-amd64
labels: |
org.opencontainers.image.version=${{ env.RELEASE_TAG }}
org.opencontainers.image.created=${{ env.PUBLISHED_AT }}
org.opencontainers.image.revision=${{ env.RELEASE_REVISION }}
- name: Build and push arm64 distroless image
id: build-arm64-distroless
uses: docker/build-push-action@v7
with:
context: docker-release/arm64
file: docker-release/arm64/Dockerfile.distroless
platforms: linux/arm64
push: true
tags: |
pgsty/silo:${{ env.RELEASE_TAG }}-distroless-arm64
pgsty/silo:distroless-arm64
labels: |
org.opencontainers.image.version=${{ env.RELEASE_TAG }}
org.opencontainers.image.created=${{ env.PUBLISHED_AT }}
org.opencontainers.image.revision=${{ env.RELEASE_REVISION }}
- name: Verify HEALTHCHECK survived the distroless push
run: |
set -euo pipefail
# HEALTHCHECK is a Docker extension absent from the OCI image
# spec, and a publish path can drop it silently. Check the pushed
# architecture image now, before the versioned and rolling
# multi-arch manifests are created, so a broken health config
# stops their promotion. (The architecture-suffixed tags above
# are already public by this point - full staging-then-promote
# would be a workflow-wide redesign shared with the classic
# image lanes.)
docker pull "pgsty/silo:${RELEASE_TAG}-distroless-amd64" >/dev/null
test "$(docker inspect -f '{{json .Config.Healthcheck.Test}}' "pgsty/silo:${RELEASE_TAG}-distroless-amd64")" \
= '["CMD","/usr/bin/silo","healthcheck","ready"]'
- name: Publish multi-architecture manifests
run: |
set -euo pipefail
mkdir -p docker-release/metadata
docker buildx imagetools create \
--tag "pgsty/silo:${RELEASE_TAG}" \
--metadata-file docker-release/metadata/release.json \
"pgsty/silo:${RELEASE_TAG}-amd64" \
"pgsty/silo:${RELEASE_TAG}-arm64"
--tag "pgsty/minio:${RELEASE_TAG}" \
"pgsty/minio:${RELEASE_TAG}-amd64" \
"pgsty/minio:${RELEASE_TAG}-arm64"
docker buildx imagetools create \
--tag "pgsty/silo:latest" \
--metadata-file docker-release/metadata/latest.json \
"pgsty/silo:latest-amd64" \
"pgsty/silo:latest-arm64"
docker buildx imagetools inspect "pgsty/silo:${RELEASE_TAG}"
docker buildx imagetools inspect "pgsty/silo:latest"
docker buildx imagetools create \
--tag "pgsty/silo:${RELEASE_TAG}-distroless" \
--metadata-file docker-release/metadata/release-distroless.json \
"pgsty/silo:${RELEASE_TAG}-distroless-amd64" \
"pgsty/silo:${RELEASE_TAG}-distroless-arm64"
docker buildx imagetools create \
--tag "pgsty/silo:distroless" \
--metadata-file docker-release/metadata/distroless.json \
"pgsty/silo:distroless-amd64" \
"pgsty/silo:distroless-arm64"
docker buildx imagetools inspect "pgsty/silo:${RELEASE_TAG}-distroless"
docker buildx imagetools inspect "pgsty/silo:distroless"
RELEASE_DIGEST="$(jq -r '."containerimage.descriptor".digest' docker-release/metadata/release.json)"
LATEST_DIGEST="$(jq -r '."containerimage.descriptor".digest' docker-release/metadata/latest.json)"
if ! [[ "${RELEASE_DIGEST}" =~ ^sha256:[0-9a-f]{64}$ ]]; then
echo "Invalid release manifest digest: ${RELEASE_DIGEST}" >&2
exit 1
fi
if [ "${RELEASE_DIGEST}" != "${LATEST_DIGEST}" ]; then
echo "Release and latest tags resolved to different manifests" >&2
exit 1
fi
echo "SILO_IMAGE_DIGEST=${RELEASE_DIGEST}" >> "${GITHUB_ENV}"
DISTROLESS_RELEASE_DIGEST="$(jq -r '."containerimage.descriptor".digest' docker-release/metadata/release-distroless.json)"
DISTROLESS_ROLLING_DIGEST="$(jq -r '."containerimage.descriptor".digest' docker-release/metadata/distroless.json)"
if ! [[ "${DISTROLESS_RELEASE_DIGEST}" =~ ^sha256:[0-9a-f]{64}$ ]]; then
echo "Invalid distroless manifest digest: ${DISTROLESS_RELEASE_DIGEST}" >&2
exit 1
fi
if [ "${DISTROLESS_RELEASE_DIGEST}" != "${DISTROLESS_ROLLING_DIGEST}" ]; then
echo "Distroless release and rolling tags resolved to different manifests" >&2
exit 1
fi
echo "SILO_DISTROLESS_DIGEST=${DISTROLESS_RELEASE_DIGEST}" >> "${GITHUB_ENV}"
- name: Generate architecture image SBOMs
env:
AMD64_DIGEST: ${{ steps.build-amd64.outputs.digest }}
ARM64_DIGEST: ${{ steps.build-arm64.outputs.digest }}
DISTROLESS_AMD64_DIGEST: ${{ steps.build-amd64-distroless.outputs.digest }}
DISTROLESS_ARM64_DIGEST: ${{ steps.build-arm64-distroless.outputs.digest }}
SYFT_CHECK_FOR_APP_UPDATE: "false"
run: |
set -euo pipefail
mkdir -p docker-release/sbom
# Each per-architecture digest names an OCI index (image plus the
# provenance attestation buildx attaches), and Syft's platform
# default on an index follows the amd64 runner - an arm64-only
# index would fail outright. Select the platform explicitly.
syft "registry:index.docker.io/pgsty/silo@${AMD64_DIGEST}" \
--platform linux/amd64 \
--output "spdx-json=docker-release/sbom/linux-amd64.spdx.json"
syft "registry:index.docker.io/pgsty/silo@${ARM64_DIGEST}" \
--platform linux/arm64 \
--output "spdx-json=docker-release/sbom/linux-arm64.spdx.json"
syft "registry:index.docker.io/pgsty/silo@${DISTROLESS_AMD64_DIGEST}" \
--platform linux/amd64 \
--output "spdx-json=docker-release/sbom/linux-amd64-distroless.spdx.json"
syft "registry:index.docker.io/pgsty/silo@${DISTROLESS_ARM64_DIGEST}" \
--platform linux/arm64 \
--output "spdx-json=docker-release/sbom/linux-arm64-distroless.spdx.json"
- name: Attest amd64 image SBOM
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-name: index.docker.io/pgsty/silo
subject-digest: ${{ steps.build-amd64.outputs.digest }}
sbom-path: docker-release/sbom/linux-amd64.spdx.json
push-to-registry: true
- name: Attest arm64 image SBOM
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-name: index.docker.io/pgsty/silo
subject-digest: ${{ steps.build-arm64.outputs.digest }}
sbom-path: docker-release/sbom/linux-arm64.spdx.json
push-to-registry: true
- name: Attest amd64 distroless image SBOM
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-name: index.docker.io/pgsty/silo
subject-digest: ${{ steps.build-amd64-distroless.outputs.digest }}
sbom-path: docker-release/sbom/linux-amd64-distroless.spdx.json
push-to-registry: true
- name: Attest arm64 distroless image SBOM
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-name: index.docker.io/pgsty/silo
subject-digest: ${{ steps.build-arm64-distroless.outputs.digest }}
sbom-path: docker-release/sbom/linux-arm64-distroless.spdx.json
push-to-registry: true
- name: Attest multi-architecture image provenance
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-name: index.docker.io/pgsty/silo
subject-digest: ${{ env.SILO_IMAGE_DIGEST }}
push-to-registry: true
- name: Attest multi-architecture distroless image provenance
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-name: index.docker.io/pgsty/silo
subject-digest: ${{ env.SILO_DISTROLESS_DIGEST }}
push-to-registry: true
--tag "pgsty/minio:latest" \
"pgsty/minio:latest-amd64" \
"pgsty/minio:latest-arm64"
docker buildx imagetools inspect "pgsty/minio:${RELEASE_TAG}"
docker buildx imagetools inspect "pgsty/minio:latest"
-234
View File
@@ -1,234 +0,0 @@
name: Finalize Release Packages
# Manual-only lane that runs AFTER the maintainer has GPG-signed the RPMs in a
# Draft release (buildscripts/sign-release-rpms.sh --upload) and BEFORE the
# release is published. GPG signing rewrites the RPM bytes, which strands the
# SBOMs, the packages checksum manifest, and the attestations that release.yml
# generated from the as-built packages. This lane regenerates those materials
# from the published (signed) bytes under the workflow identity, so the
# sigstore layer describes exactly what the release ships.
on:
workflow_dispatch:
inputs:
tag:
description: "Draft RELEASE.* tag whose signed RPMs need refreshed SBOMs and checksums"
required: true
type: string
permissions:
contents: write
id-token: write
attestations: write
artifact-metadata: write
concurrency:
group: finalize-release
cancel-in-progress: false
jobs:
finalize:
runs-on: ubuntu-latest
steps:
- name: Checkout release tag
uses: actions/checkout@v7
with:
ref: ${{ inputs.tag }}
fetch-depth: 0
- name: Verify workflow identity matches release source
run: |
set -euo pipefail
CHECKED_OUT_REVISION="$(git rev-parse HEAD)"
if [ "${CHECKED_OUT_REVISION}" != "${GITHUB_SHA}" ]; then
echo "Checked out ${CHECKED_OUT_REVISION}, but workflow identity is ${GITHUB_SHA}. Dispatch from the release tag." >&2
exit 1
fi
- name: Compute release variables
env:
# Environment passthrough keeps the dispatch input out of the script
# source, mirroring release.yml.
INPUT_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
TAG="${INPUT_TAG}"
if [[ ! "${TAG}" =~ ^RELEASE\.[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}-[0-9]{2}-[0-9]{2}Z$ ]]; then
echo "Invalid release tag format: ${TAG}" >&2
exit 1
fi
VERSION_HYPHEN="${TAG#RELEASE.}"
PKG_VERSION="$(echo "${VERSION_HYPHEN}" | sed -E 's/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2})-([0-9]{2})-([0-9]{2})Z$/\1\2\3\4\5\6.0.0/')"
{
echo "RELEASE_TAG=${TAG}"
echo "PKG_VERSION=${PKG_VERSION}"
} >> "${GITHUB_ENV}"
- name: Refuse to touch a published release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
if [ "$(gh release view "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" --json isDraft --jq .isDraft)" != "true" ]; then
echo "${RELEASE_TAG} is not a Draft release; finalize runs only before publishing." >&2
exit 1
fi
- name: Install Syft
uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
with:
syft-version: v1.50.0
- name: Install Cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: v3.1.2
- name: Download and verify the package set
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
# sign-release-rpms.sh owns the package identity; import its values
# the same way test-release.yml does so the lanes cannot drift.
eval "$(grep -E '^expected_(release|fingerprint)=' buildscripts/sign-release-rpms.sh)"
test -n "${expected_release}"
test -n "${expected_fingerprint}"
packages_dir="finalize/packages"
sidecar_dir="finalize/sidecars"
mkdir -p "${packages_dir}" "${sidecar_dir}"
manifest="silo_${PKG_VERSION}_packages_checksums.txt"
# Exactly the twelve manifest subjects land in packages_dir; the
# tarball SBOMs in the release root do not match these patterns.
gh release download "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" --dir "${packages_dir}" \
--pattern '*.rpm' --pattern '*.deb' --pattern '*.apk' \
--pattern '*.rpm.sbom.json' --pattern '*.deb.sbom.json' --pattern '*.apk.sbom.json'
gh release download "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" --dir "${sidecar_dir}" \
--pattern '*.rpm.sha256sum' --pattern "${manifest}"
cd "${packages_dir}"
subject_count="$(find . -maxdepth 1 -type f | wc -l | tr -d ' ')"
if [ "${subject_count}" -ne 12 ]; then
echo "Expected twelve package subjects, found ${subject_count}" >&2
find . -maxdepth 1 -type f >&2
exit 1
fi
# The signed RPMs must match the .sha256sum sidecars the signing
# script regenerated and uploaded alongside them.
for rpm_file in "silo-${PKG_VERSION}-${expected_release}.x86_64.rpm" \
"silo-${PKG_VERSION}-${expected_release}.aarch64.rpm"; do
test -s "${rpm_file}"
actual="$(sha256sum "${rpm_file}" | awk '{print $1}')"
recorded="$(awk '{print $1}' "../sidecars/${rpm_file}.sha256sum")"
if [ "${actual}" != "${recorded}" ]; then
echo "Digest mismatch for ${rpm_file}: sidecar ${recorded}, asset ${actual}" >&2
exit 1
fi
done
# Everything the maintainer did not re-sign must still match the
# manifest release.yml generated: this lane refreshes RPM materials,
# it does not accept drift anywhere else.
for package_file in *.deb *.apk *.deb.sbom.json *.apk.sbom.json; do
awk -v name="${package_file}" '$2 == name' "../sidecars/${manifest}" | sha256sum --check
done
- name: Verify RPM GPG signatures
run: |
set -euo pipefail
eval "$(grep -E '^expected_fingerprint=' buildscripts/sign-release-rpms.sh)"
sudo apt-get update
sudo apt-get install --yes rpm
sudo rpmkeys --import buildscripts/pgsty-rpm-signing-key.asc
key_id="$(printf '%s' "${expected_fingerprint}" | tail -c 8 | tr '[:upper:]' '[:lower:]')"
for rpm_file in finalize/packages/*.rpm; do
signature_output="$(sudo rpmkeys --checksig --verbose "${rpm_file}")"
printf '%s\n' "${signature_output}"
if ! printf '%s\n' "${signature_output}" | tr '[:upper:]' '[:lower:]' | grep -q "key id ${key_id}: ok"; then
echo "Signature verification failed for ${rpm_file}" >&2
exit 1
fi
done
- name: Regenerate RPM SBOMs and the packages checksum manifest
env:
SYFT_CHECK_FOR_APP_UPDATE: "false"
run: |
set -euo pipefail
cd finalize/packages
for rpm_file in *.rpm; do
rm -f "${rpm_file}.sbom.json"
syft "${rpm_file}" --output "spdx-json=${rpm_file}.sbom.json"
done
manifest="silo_${PKG_VERSION}_packages_checksums.txt"
mapfile -t subjects < <(find . -maxdepth 1 -type f \
\( -name '*.rpm' -o -name '*.deb' -o -name '*.apk' -o -name '*.sbom.json' \) | sort)
if [ "${#subjects[@]}" -ne 12 ]; then
echo "Expected six packages and six SBOMs, found ${#subjects[@]} subjects" >&2
exit 1
fi
sha256sum "${subjects[@]}" | sed 's# \./# #' > "${manifest}"
cosign sign-blob --bundle="${manifest}.sigstore.json" "${manifest}" --yes
- name: Attest the finalized package artifacts
id: attest-finalize
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-path: |
finalize/packages/*.rpm
finalize/packages/*.rpm.sbom.json
finalize/packages/*_checksums.txt
finalize/packages/*_checksums.txt.sigstore.json
finalize/sidecars/*.rpm.sha256sum
- name: Preserve finalize provenance bundle as a release asset
env:
BUNDLE_PATH: ${{ steps.attest-finalize.outputs.bundle-path }}
run: |
set -euo pipefail
test -s "${BUNDLE_PATH}"
cp "${BUNDLE_PATH}" "finalize/packages/silo_${PKG_VERSION}_packages_provenance.sigstore.json"
- name: Upload finalized assets to the Draft release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
eval "$(grep -E '^expected_release=' buildscripts/sign-release-rpms.sh)"
manifest="silo_${PKG_VERSION}_packages_checksums.txt"
cd finalize/packages
files=(
"silo-${PKG_VERSION}-${expected_release}.x86_64.rpm.sbom.json"
"silo-${PKG_VERSION}-${expected_release}.aarch64.rpm.sbom.json"
"${manifest}"
"${manifest}.sigstore.json"
"silo_${PKG_VERSION}_packages_provenance.sigstore.json"
)
gh release upload "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" --clobber "${files[@]}"
for asset in "${files[@]}"; do
local_digest="sha256:$(sha256sum "${asset}" | awk '{print $1}')"
remote_digest=""
for attempt in 1 2 3 4 5; do
remote_digest="$(gh release view "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" --json assets \
--jq ".assets[] | select(.name == \"${asset}\") | .digest")"
if [ "${local_digest}" = "${remote_digest}" ]; then
break
fi
if [ "${attempt}" -lt 5 ]; then
sleep 2
fi
done
if [ "${local_digest}" != "${remote_digest}" ]; then
echo "GitHub asset digest mismatch for ${asset}" >&2
echo "Local: ${local_digest}" >&2
echo "Remote: ${remote_digest}" >&2
exit 1
fi
echo "Verified GitHub asset: ${asset} ${remote_digest}"
done
+19 -26
View File
@@ -3,13 +3,13 @@ name: Go CI
on:
pull_request:
branches:
- main
- master
push:
branches:
- main
- master
workflow_dispatch:
# Cancel superseded runs for the same PR; never cancel main push runs.
# Cancel superseded runs for the same PR; never cancel master push runs.
# Keyed on PR number (not head_ref) so fork PRs sharing a branch name
# do not collide.
concurrency:
@@ -25,21 +25,20 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v4
- uses: actions/setup-go@v7
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Check gofmt
run: |
mapfile -t unformatted < <(gofmt -l main.go cmd internal \
buildscripts/rebrand-guard buildscripts/helm-migration-guard)
if [ "${#unformatted[@]}" -ne 0 ]; then
unformatted=$(gofmt -l main.go cmd internal)
if [ -n "${unformatted}" ]; then
echo "The following files are not gofmt-formatted:"
printf '%s\n' "${unformatted[@]}"
gofmt -d "${unformatted[@]}"
echo "${unformatted}"
gofmt -d ${unformatted}
exit 1
fi
@@ -51,20 +50,14 @@ jobs:
- name: Vet
run: go vet ./...
- name: Verify rebrand compatibility contracts
run: |
go run ./buildscripts/rebrand-guard
buildscripts/verify-rebrand.sh
dockerscripts/docker-entrypoint_test.sh
quality:
name: Lint, Generated Files
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v4
- uses: actions/setup-go@v7
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
@@ -80,9 +73,9 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v4
- uses: actions/setup-go@v7
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
@@ -95,9 +88,9 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v4
- uses: actions/setup-go@v7
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
@@ -110,9 +103,9 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v4
- uses: actions/setup-go@v7
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
@@ -134,9 +127,9 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 35
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v4
- uses: actions/setup-go@v7
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
+6 -97
View File
@@ -12,16 +12,13 @@ on:
permissions:
contents: write
id-token: write
attestations: write
artifact-metadata: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v4
with:
fetch-depth: 0
# Build the code at the tag being released, not whatever branch the
@@ -31,7 +28,7 @@ jobs:
ref: ${{ github.event.inputs.tag || github.ref }}
- name: Set up Go
uses: actions/setup-go@v7
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
@@ -39,28 +36,12 @@ jobs:
- name: Verify clean checkout
run: |
set -euo pipefail
# GitHub's OIDC certificate records GITHUB_SHA, not the ref passed to
# actions/checkout. A manual dispatch must therefore be launched from
# the release tag itself; otherwise the provenance identity would
# describe different source from the bytes being published.
CHECKED_OUT_REVISION="$(git rev-parse HEAD)"
if [ "${CHECKED_OUT_REVISION}" != "${GITHUB_SHA}" ]; then
echo "Checked out ${CHECKED_OUT_REVISION}, but workflow identity is ${GITHUB_SHA}. Dispatch from the release tag." >&2
exit 1
fi
if [ -n "$(git status --porcelain)" ]; then
echo "Refusing to release from a dirty working tree:" >&2
git status --porcelain >&2
exit 1
fi
- name: Verify rebrand compatibility contracts
run: |
set -euo pipefail
go run ./buildscripts/rebrand-guard
buildscripts/verify-rebrand.sh
dockerscripts/docker-entrypoint_test.sh
- name: Compute release variables
env:
# Passed through the environment, never interpolated into the script
@@ -93,21 +74,8 @@ jobs:
echo "Package version: ${PKG_VERSION}"
echo "LDFLAGS: ${LDFLAGS}"
# Both installer actions are pinned to immutable commits. The explicit
# tool versions keep the release format reproducible across workflow
# reruns while the installers verify the downloaded executables.
- name: Install Syft
uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
with:
syft-version: v1.50.0
- name: Install Cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: v3.1.2
- name: Build Draft release with GoReleaser
uses: goreleaser/goreleaser-action@v7
uses: goreleaser/goreleaser-action@v6
with:
version: "~> v2"
args: release --clean --skip=validate --config .github/goreleaser.yml
@@ -132,81 +100,22 @@ jobs:
set -euo pipefail
buildscripts/package-release.sh
- name: Generate package SBOMs and signed checksum manifest
env:
SYFT_CHECK_FOR_APP_UPDATE: "false"
run: |
set -euo pipefail
packages_dir="dist/packages"
mapfile -t packages < <(find "${packages_dir}" -maxdepth 1 -type f \
\( -name '*.rpm' -o -name '*.deb' -o -name '*.apk' \) | sort)
if [ "${#packages[@]}" -ne 6 ]; then
echo "Expected six Linux packages, found ${#packages[@]}" >&2
printf '%s\n' "${packages[@]}" >&2
exit 1
fi
for package in "${packages[@]}"; do
syft "${package}" --output "spdx-json=${package}.sbom.json"
done
manifest="${packages_dir}/silo_${PKG_VERSION}_packages_checksums.txt"
(
cd "${packages_dir}"
mapfile -t subjects < <(find . -maxdepth 1 -type f \
\( -name '*.rpm' -o -name '*.deb' -o -name '*.apk' -o -name '*.sbom.json' \) | sort)
if [ "${#subjects[@]}" -ne 12 ]; then
echo "Expected six packages and six SBOMs, found ${#subjects[@]} subjects" >&2
exit 1
fi
sha256sum "${subjects[@]}" | sed 's# \./# #' > "$(basename "${manifest}")"
)
cosign sign-blob --bundle="${manifest}.sigstore.json" "${manifest}" --yes
- name: Attest downloadable release artifacts
id: attest-release
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-path: |
dist/*.tar.gz
dist/*.zip
dist/*.sbom.json
dist/*_checksums.txt
dist/*.sigstore.json
dist/packages/*.rpm
dist/packages/*.deb
dist/packages/*.apk
dist/packages/*.sha256sum
dist/packages/*.sbom.json
dist/packages/*_checksums.txt
dist/packages/*.sigstore.json
- name: Preserve provenance bundle as a release asset
env:
BUNDLE_PATH: ${{ steps.attest-release.outputs.bundle-path }}
run: |
set -euo pipefail
test -s "${BUNDLE_PATH}"
cp "${BUNDLE_PATH}" "dist/silo_${PKG_VERSION}_provenance.sigstore.json"
- name: Upload nFPM packages to Draft release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
mapfile -t files < <(find dist/packages -maxdepth 1 -type f \
\( -name '*.rpm' -o -name '*.deb' -o -name '*.apk' -o -name '*.sha256sum' \
-o -name '*.sbom.json' -o -name '*_checksums.txt' -o -name '*.sigstore.json' \) | sort)
\( -name '*.rpm' -o -name '*.deb' -o -name '*.apk' -o -name '*.sha256sum' \) | sort)
if [ "${#files[@]}" -eq 0 ]; then
echo "No packages were generated."
exit 1
fi
gh release upload "${RELEASE_TAG}" "${files[@]}" \
"dist/silo_${PKG_VERSION}_provenance.sigstore.json"
gh release upload "${RELEASE_TAG}" "${files[@]}"
- name: Upload dist artifact
if: always()
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
+69 -293
View File
@@ -7,31 +7,13 @@ on:
- ".github/goreleaser.yml"
- ".github/nfpm.yml"
- "Dockerfile.goreleaser"
- "Dockerfile.distroless"
- "cmd/healthcheck-main.go"
- "cmd/main.go"
- "dockerscripts/download-static-curl.sh"
- "dockerscripts/docker-entrypoint.sh"
- "dockerscripts/docker-entrypoint_test.sh"
- "silo.service"
- "silo.env"
- "silo.sysusers"
- "minio.service"
- "buildscripts/package-release.sh"
- "buildscripts/package/postinstall.sh"
- "buildscripts/package/preremove.sh"
- "buildscripts/package/lifecycle_test.sh"
- "buildscripts/minio-upgrade.sh"
- "buildscripts/sign-release-rpms.sh"
- "buildscripts/verify-build-provenance.sh"
- "buildscripts/verify-rebrand.sh"
- "buildscripts/verify-helm-migration.sh"
- "buildscripts/helm-migration-guard/**"
- "helm/silo/**"
- "buildscripts/rebrand-guard/**"
- "buildscripts/gen-ldflags.go"
- ".github/workflows/release.yml"
- ".github/workflows/docker-release.yml"
- ".github/workflows/finalize-release.yml"
- ".github/workflows/test-release.yml"
- ".gitignore"
@@ -43,12 +25,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v7
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
@@ -69,46 +51,20 @@ jobs:
echo "LDFLAGS: ${LDFLAGS}"
- name: GoReleaser config check
uses: goreleaser/goreleaser-action@v7
uses: goreleaser/goreleaser-action@v6
with:
version: "~> v2"
args: check --config .github/goreleaser.yml
- name: Validate Helm chart and legacy upgrade identity
run: buildscripts/verify-helm-migration.sh
- name: Install Syft
uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
with:
syft-version: v1.50.0
- name: Build snapshot artifacts
uses: goreleaser/goreleaser-action@v7
uses: goreleaser/goreleaser-action@v6
with:
version: "~> v2"
# A pull-request snapshot has no trusted release identity. Exercise
# the SBOM/checksum pipeline here, and reserve keyless signing for
# the tag-triggered release workflow with GitHub OIDC.
args: release --snapshot --clean --skip=publish,docker,sign --config .github/goreleaser.yml
args: release --snapshot --clean --skip=publish,docker --config .github/goreleaser.yml
env:
LDFLAGS: ${{ env.LDFLAGS }}
PKG_VERSION: ${{ env.PKG_VERSION }}
- name: Verify archive SBOM and checksum coverage
run: |
set -euo pipefail
mapfile -t archives < <(find dist -maxdepth 1 -type f -name 'silo_*.tar.gz' | sort)
mapfile -t sboms < <(find dist -maxdepth 1 -type f -name 'silo_*.tar.gz.sbom.json' | sort)
test "${#archives[@]}" -eq 6
test "${#sboms[@]}" -eq 6
manifest="dist/silo_${PKG_VERSION}_checksums.txt"
test -s "${manifest}"
(
cd dist
sha256sum --check "$(basename "${manifest}")"
)
test "$(wc -l < "${manifest}" | tr -d ' ')" -eq 12
- name: Verify binary provenance stamps
run: |
set -euo pipefail
@@ -132,38 +88,15 @@ jobs:
set -euo pipefail
cd dist/packages
# The signing script asserts these same values, but it runs on the
# maintainer's machine after the release workflow has already built
# and uploaded. Take its expectations as the single source of truth
# so nfpm.yml and the signing script cannot drift apart without
# failing here first, while a fix is still cheap.
#
# This grep is deliberately limited to the eight identity variables,
# all of which are single-line. That is what makes the eval safe:
# should one ever become multi-line, the grep captures an
# unterminated quote and the eval aborts on a syntax error under
# set -e rather than quietly binding an empty value and comparing
# against nothing. expected_payload is multi-line by design and must
# stay out of this set for the same reason.
eval "$(grep -E '^expected_(release|vendor|packager|url|summary|description|license|group)=' \
../../buildscripts/sign-release-rpms.sh)"
for value in "${expected_release}" "${expected_vendor}" "${expected_packager}" \
"${expected_url}" "${expected_summary}" "${expected_description}" \
"${expected_license}" "${expected_group}"; do
test -n "${value}"
done
# These are the public download names; a drift here breaks every
# script that fetches packages by URL. RPM and DEB carry the PGSTY
# release segment; APK cannot (Alpine pkgrel admits only -r<integer>),
# so it stays bare. package-release.sh builds the same three shapes.
# script that fetches packages by URL.
expected=(
"silo-${PKG_VERSION}-${expected_release}.aarch64.rpm"
"silo-${PKG_VERSION}-${expected_release}.x86_64.rpm"
"silo_${PKG_VERSION}-${expected_release}_amd64.deb"
"silo_${PKG_VERSION}-${expected_release}_arm64.deb"
"silo_${PKG_VERSION}_aarch64.apk"
"silo_${PKG_VERSION}_x86_64.apk"
"minio-${PKG_VERSION}-1.aarch64.rpm"
"minio-${PKG_VERSION}-1.x86_64.rpm"
"minio_${PKG_VERSION}_aarch64.apk"
"minio_${PKG_VERSION}_amd64.deb"
"minio_${PKG_VERSION}_arm64.deb"
"minio_${PKG_VERSION}_x86_64.apk"
)
for package in "${expected[@]}"; do
@@ -174,14 +107,30 @@ jobs:
test "$(find . -maxdepth 1 -type f \( -name '*.rpm' -o -name '*.deb' -o -name '*.apk' \) | wc -l)" -eq 6
service_sha="$(sha256sum ../../silo.service | awk '{print $1}')"
defaults_sha="$(sha256sum ../../silo.env | awk '{print $1}')"
sysusers_sha="$(sha256sum ../../silo.sysusers | awk '{print $1}')"
license_sha="$(sha256sum ../../LICENSE | awk '{print $1}')"
notice_sha="$(sha256sum ../../NOTICE | awk '{print $1}')"
# The signing script asserts these same values, but it runs on the
# maintainer's machine after the release workflow has already built
# and uploaded. Take its expectations as the single source of truth
# so nfpm.yml and the signing script cannot drift apart without
# failing here first, while a fix is still cheap.
#
# This grep is deliberately limited to the seven identity variables,
# all of which are single-line. That is what makes the eval safe:
# should one ever become multi-line, the grep captures an
# unterminated quote and the eval aborts on a syntax error under
# set -e rather than quietly binding an empty value and comparing
# against nothing. expected_payload is multi-line by design and must
# stay out of this set for the same reason.
eval "$(grep -E '^expected_(vendor|packager|url|summary|description|license|group)=' \
../../buildscripts/sign-release-rpms.sh)"
for value in "${expected_vendor}" "${expected_packager}" "${expected_url}" \
"${expected_summary}" "${expected_description}" \
"${expected_license}" "${expected_group}"; do
test -n "${value}"
done
rpm_file="silo-${PKG_VERSION}-${expected_release}.x86_64.rpm"
test "$(rpm -qp --queryformat '%{RELEASE}' "${rpm_file}")" = "${expected_release}"
service_sha="$(sha256sum ../../minio.service | awk '{print $1}')"
rpm_file="minio-${PKG_VERSION}-1.x86_64.rpm"
test "$(rpm -qp --queryformat '%{VENDOR}' "${rpm_file}")" = "${expected_vendor}"
test "$(rpm -qp --queryformat '%{PACKAGER}' "${rpm_file}")" = "${expected_packager}"
test "$(rpm -qp --queryformat '%{URL}' "${rpm_file}")" = "${expected_url}"
@@ -189,68 +138,31 @@ jobs:
test "$(rpm -qp --queryformat '%{DESCRIPTION}' "${rpm_file}")" = "${expected_description}"
test "$(rpm -qp --queryformat '%{LICENSE}' "${rpm_file}")" = "${expected_license}"
test "$(rpm -qp --queryformat '%{GROUP}' "${rpm_file}")" = "${expected_group}"
rpm -qpl "${rpm_file}" | grep -Fx '/usr/bin/silo'
rpm -qpl "${rpm_file}" | grep -Fx '/usr/lib/systemd/system/silo.service'
rpm -qpl "${rpm_file}" | grep -Fx '/etc/default/silo'
rpm -qpl "${rpm_file}" | grep -Fx '/usr/lib/sysusers.d/silo.conf'
rpm -qpl "${rpm_file}" | grep -Fx '/usr/share/doc/silo/LICENSE'
rpm -qpl "${rpm_file}" | grep -Fx '/usr/share/doc/silo/NOTICE'
test "$(rpm -qpl "${rpm_file}" | wc -l)" -eq 6
# nfpm only honors type: license on rpm, which is why nfpm.yml
# declares the license materials once per packager. Pin the rpm
# %license flag so that split cannot silently regress.
rpm -qp --queryformat '[%{FILEFLAGS:fflags} %{FILENAMES}\n]' "${rpm_file}" \
| grep -Fx 'l /usr/share/doc/silo/LICENSE'
rpm -qp --queryformat '[%{FILEFLAGS:fflags} %{FILENAMES}\n]' "${rpm_file}" \
| grep -Fx 'l /usr/share/doc/silo/NOTICE'
if rpm -qp --conflicts "${rpm_file}" | grep -qi minio; then
echo "RPM must not declare a cross-name conflict with MinIO" >&2
exit 1
fi
if rpm -qp --obsoletes "${rpm_file}" | grep -qi minio; then
echo "RPM must not obsolete a MinIO package" >&2
exit 1
fi
if rpm -qp --provides "${rpm_file}" | grep -qi minio; then
echo "RPM must not provide a MinIO package alias" >&2
exit 1
fi
# Both payload entries: the unit file is as much a part of the
# package as the binary, and losing it would install a server with
# nothing to start it.
rpm -qpl "${rpm_file}" | grep -Fx '/usr/local/bin/minio'
rpm -qpl "${rpm_file}" | grep -Fx '/usr/lib/systemd/system/minio.service'
test "$(rpm -qpl "${rpm_file}" | wc -l)" -eq 2
deb_file="silo_${PKG_VERSION}-${expected_release}_amd64.deb"
deb_file="minio_${PKG_VERSION}_amd64.deb"
test "$(dpkg-deb --field "${deb_file}" Maintainer)" = "${expected_packager}"
test "$(dpkg-deb --field "${deb_file}" Version)" = "${PKG_VERSION}-${expected_release}"
test "$(dpkg-deb --field "${deb_file}" Version)" = "${PKG_VERSION}"
test "$(dpkg-deb --field "${deb_file}" License)" = "${expected_license}"
test "$(dpkg-deb --field "${deb_file}" Section)" = "utils"
test "$(dpkg-deb --field "${deb_file}" Homepage)" = "${expected_url}"
test "$(dpkg-deb --field "${deb_file}" Description)" = "${expected_description}"
dpkg-deb --contents "${deb_file}" | grep -E 'usr/bin/silo$'
dpkg-deb --contents "${deb_file}" | grep -E 'usr/lib/systemd/system/silo\.service$'
dpkg-deb --contents "${deb_file}" | grep -E 'etc/default/silo$'
dpkg-deb --contents "${deb_file}" | grep -E 'usr/lib/sysusers\.d/silo\.conf$'
dpkg-deb --contents "${deb_file}" | grep -E 'usr/share/doc/silo/LICENSE$'
dpkg-deb --contents "${deb_file}" | grep -E 'usr/share/doc/silo/NOTICE$'
test "$(dpkg-deb --contents "${deb_file}" | awk '$1 !~ /^d/ { count++ } END { print count + 0 }')" -eq 6
test -z "$(dpkg-deb --field "${deb_file}" Conflicts)"
test -z "$(dpkg-deb --field "${deb_file}" Replaces)"
test -z "$(dpkg-deb --field "${deb_file}" Provides)"
dpkg-deb --contents "${deb_file}" | grep -E 'usr/local/bin/minio$'
dpkg-deb --contents "${deb_file}" | grep -E 'usr/lib/systemd/system/minio\.service$'
apk_info="$(tar -xOzf "silo_${PKG_VERSION}_x86_64.apk" .PKGINFO)"
apk_info="$(tar -xOzf "minio_${PKG_VERSION}_x86_64.apk" .PKGINFO)"
grep -Fx "pkgver = ${PKG_VERSION}" <<< "${apk_info}"
grep -Fx "url = ${expected_url}" <<< "${apk_info}"
grep -Fx "maintainer = ${expected_packager}" <<< "${apk_info}"
grep -Fx "license = ${expected_license}" <<< "${apk_info}"
grep -Fx "pkgdesc = ${expected_description}" <<< "${apk_info}"
tar -tzf "silo_${PKG_VERSION}_x86_64.apk" | grep -Fx 'usr/bin/silo'
tar -tzf "silo_${PKG_VERSION}_x86_64.apk" | grep -Fx 'usr/lib/systemd/system/silo.service'
tar -tzf "silo_${PKG_VERSION}_x86_64.apk" | grep -Fx 'etc/default/silo'
tar -tzf "silo_${PKG_VERSION}_x86_64.apk" | grep -Fx 'usr/lib/sysusers.d/silo.conf'
tar -tzf "silo_${PKG_VERSION}_x86_64.apk" | grep -Fx 'usr/share/doc/silo/LICENSE'
tar -tzf "silo_${PKG_VERSION}_x86_64.apk" | grep -Fx 'usr/share/doc/silo/NOTICE'
test "$(tar -tzf "silo_${PKG_VERSION}_x86_64.apk" | awk '$0 !~ /^\./ && $0 !~ /\/$/ { count++ } END { print count + 0 }')" -eq 6
if grep -Ei '^provides = .*minio' <<< "${apk_info}"; then
echo "APK must not provide a MinIO package alias" >&2
exit 1
fi
tar -tzf "minio_${PKG_VERSION}_x86_64.apk" | grep -Fx 'usr/local/bin/minio'
tar -tzf "minio_${PKG_VERSION}_x86_64.apk" | grep -Fx 'usr/lib/systemd/system/minio.service'
for arch in amd64 arm64; do
if [ "${arch}" = amd64 ]; then
@@ -263,9 +175,9 @@ jobs:
apk_arch=aarch64
fi
test "$(rpm -qp --queryformat '%{ARCH}' "silo-${PKG_VERSION}-${expected_release}.${rpm_arch}.rpm")" = "${rpm_arch}"
test "$(dpkg-deb --field "silo_${PKG_VERSION}-${expected_release}_${deb_arch}.deb" Architecture)" = "${deb_arch}"
grep -Fx "arch = ${apk_arch}" <<< "$(tar -xOzf "silo_${PKG_VERSION}_${apk_arch}.apk" .PKGINFO)"
test "$(rpm -qp --queryformat '%{ARCH}' "minio-${PKG_VERSION}-1.${rpm_arch}.rpm")" = "${rpm_arch}"
test "$(dpkg-deb --field "minio_${PKG_VERSION}_${deb_arch}.deb" Architecture)" = "${deb_arch}"
grep -Fx "arch = ${apk_arch}" <<< "$(tar -xOzf "minio_${PKG_VERSION}_${apk_arch}.apk" .PKGINFO)"
# Accepted weakness: this takes the first match, unsorted, where
# find_binary in package-release.sh demands exactly one. It cannot
@@ -274,7 +186,7 @@ jobs:
# goamd64 gains a second level, or if find_binary's exactly-one
# contract is ever relaxed -- at that point this weak copy would be
# the only one left choosing silently.
source_binary="$(find .. -maxdepth 2 -type f -path "../silo_linux_${arch}*/silo" | head -n 1)"
source_binary="$(find .. -maxdepth 2 -type f -path "../minio_linux_${arch}*/minio" | head -n 1)"
source_sha="$(sha256sum "${source_binary}" | awk '{print $1}')"
# Do not pipe rpm2cpio here: Debian's build exits non-zero even when
@@ -282,31 +194,15 @@ jobs:
# rpm's own digests instead -- -K checks the payload against the
# header, and FILEDIGESTS is the sha256 rpm itself verifies on
# install.
rpm -K "silo-${PKG_VERSION}-${expected_release}.${rpm_arch}.rpm"
rpm -K "minio-${PKG_VERSION}-1.${rpm_arch}.rpm"
rpm_sha="$(rpm -qp --queryformat '[%{FILENAMES} %{FILEDIGESTS}\n]' \
"silo-${PKG_VERSION}-${expected_release}.${rpm_arch}.rpm" | awk '$1 == "/usr/bin/silo" { print $2 }')"
"minio-${PKG_VERSION}-1.${rpm_arch}.rpm" | awk '$1 == "/usr/local/bin/minio" { print $2 }')"
rpm_service_sha="$(rpm -qp --queryformat '[%{FILENAMES} %{FILEDIGESTS}\n]' \
"silo-${PKG_VERSION}-${expected_release}.${rpm_arch}.rpm" | awk '$1 == "/usr/lib/systemd/system/silo.service" { print $2 }')"
rpm_defaults_sha="$(rpm -qp --queryformat '[%{FILENAMES} %{FILEDIGESTS}\n]' \
"silo-${PKG_VERSION}-${expected_release}.${rpm_arch}.rpm" | awk '$1 == "/etc/default/silo" { print $2 }')"
rpm_sysusers_sha="$(rpm -qp --queryformat '[%{FILENAMES} %{FILEDIGESTS}\n]' \
"silo-${PKG_VERSION}-${expected_release}.${rpm_arch}.rpm" | awk '$1 == "/usr/lib/sysusers.d/silo.conf" { print $2 }')"
rpm_license_sha="$(rpm -qp --queryformat '[%{FILENAMES} %{FILEDIGESTS}\n]' \
"silo-${PKG_VERSION}-${expected_release}.${rpm_arch}.rpm" | awk '$1 == "/usr/share/doc/silo/LICENSE" { print $2 }')"
rpm_notice_sha="$(rpm -qp --queryformat '[%{FILENAMES} %{FILEDIGESTS}\n]' \
"silo-${PKG_VERSION}-${expected_release}.${rpm_arch}.rpm" | awk '$1 == "/usr/share/doc/silo/NOTICE" { print $2 }')"
deb_sha="$(ar p "silo_${PKG_VERSION}-${expected_release}_${deb_arch}.deb" data.tar.gz | tar -xzOf - ./usr/bin/silo | sha256sum | awk '{print $1}')"
deb_service_sha="$(ar p "silo_${PKG_VERSION}-${expected_release}_${deb_arch}.deb" data.tar.gz | tar -xzOf - ./usr/lib/systemd/system/silo.service | sha256sum | awk '{print $1}')"
deb_defaults_sha="$(ar p "silo_${PKG_VERSION}-${expected_release}_${deb_arch}.deb" data.tar.gz | tar -xzOf - ./etc/default/silo | sha256sum | awk '{print $1}')"
deb_sysusers_sha="$(ar p "silo_${PKG_VERSION}-${expected_release}_${deb_arch}.deb" data.tar.gz | tar -xzOf - ./usr/lib/sysusers.d/silo.conf | sha256sum | awk '{print $1}')"
deb_license_sha="$(ar p "silo_${PKG_VERSION}-${expected_release}_${deb_arch}.deb" data.tar.gz | tar -xzOf - ./usr/share/doc/silo/LICENSE | sha256sum | awk '{print $1}')"
deb_notice_sha="$(ar p "silo_${PKG_VERSION}-${expected_release}_${deb_arch}.deb" data.tar.gz | tar -xzOf - ./usr/share/doc/silo/NOTICE | sha256sum | awk '{print $1}')"
apk_sha="$(tar -xzOf "silo_${PKG_VERSION}_${apk_arch}.apk" usr/bin/silo | sha256sum | awk '{print $1}')"
apk_service_sha="$(tar -xzOf "silo_${PKG_VERSION}_${apk_arch}.apk" usr/lib/systemd/system/silo.service | sha256sum | awk '{print $1}')"
apk_defaults_sha="$(tar -xzOf "silo_${PKG_VERSION}_${apk_arch}.apk" etc/default/silo | sha256sum | awk '{print $1}')"
apk_sysusers_sha="$(tar -xzOf "silo_${PKG_VERSION}_${apk_arch}.apk" usr/lib/sysusers.d/silo.conf | sha256sum | awk '{print $1}')"
apk_license_sha="$(tar -xzOf "silo_${PKG_VERSION}_${apk_arch}.apk" usr/share/doc/silo/LICENSE | sha256sum | awk '{print $1}')"
apk_notice_sha="$(tar -xzOf "silo_${PKG_VERSION}_${apk_arch}.apk" usr/share/doc/silo/NOTICE | sha256sum | awk '{print $1}')"
"minio-${PKG_VERSION}-1.${rpm_arch}.rpm" | awk '$1 == "/usr/lib/systemd/system/minio.service" { print $2 }')"
deb_sha="$(ar p "minio_${PKG_VERSION}_${deb_arch}.deb" data.tar.gz | tar -xzOf - ./usr/local/bin/minio | sha256sum | awk '{print $1}')"
deb_service_sha="$(ar p "minio_${PKG_VERSION}_${deb_arch}.deb" data.tar.gz | tar -xzOf - ./usr/lib/systemd/system/minio.service | sha256sum | awk '{print $1}')"
apk_sha="$(tar -xzOf "minio_${PKG_VERSION}_${apk_arch}.apk" usr/local/bin/minio | sha256sum | awk '{print $1}')"
apk_service_sha="$(tar -xzOf "minio_${PKG_VERSION}_${apk_arch}.apk" usr/lib/systemd/system/minio.service | sha256sum | awk '{print $1}')"
test "${source_sha}" = "${rpm_sha}"
test "${source_sha}" = "${deb_sha}"
@@ -314,18 +210,6 @@ jobs:
test "${service_sha}" = "${rpm_service_sha}"
test "${service_sha}" = "${deb_service_sha}"
test "${service_sha}" = "${apk_service_sha}"
test "${defaults_sha}" = "${rpm_defaults_sha}"
test "${defaults_sha}" = "${deb_defaults_sha}"
test "${defaults_sha}" = "${apk_defaults_sha}"
test "${sysusers_sha}" = "${rpm_sysusers_sha}"
test "${sysusers_sha}" = "${deb_sysusers_sha}"
test "${sysusers_sha}" = "${apk_sysusers_sha}"
test "${license_sha}" = "${rpm_license_sha}"
test "${license_sha}" = "${deb_license_sha}"
test "${license_sha}" = "${apk_license_sha}"
test "${notice_sha}" = "${rpm_notice_sha}"
test "${notice_sha}" = "${deb_notice_sha}"
test "${notice_sha}" = "${apk_notice_sha}"
done
find . -maxdepth 1 -type f | sort
@@ -339,20 +223,19 @@ jobs:
# goreleaser already produced; the mcli-download build stage is skipped
# on purpose to keep this gate offline and deterministic.
ctx="$(mktemp -d)"
tar -xzf "dist/silo_${PKG_VERSION}_linux_amd64.tar.gz" -C "${ctx}" silo
tar -xzf "dist/minio_${PKG_VERSION}_linux_amd64.tar.gz" -C "${ctx}" minio
cp dockerscripts/docker-entrypoint.sh "${ctx}/docker-entrypoint.sh"
{
echo "FROM registry.access.redhat.com/ubi9/ubi-micro:latest"
echo "COPY silo /usr/bin/silo"
echo "COPY minio /usr/bin/minio"
echo "COPY docker-entrypoint.sh /usr/bin/docker-entrypoint.sh"
echo "RUN mkdir -p /data && chmod 0777 /data && chmod +x /usr/bin/silo /usr/bin/docker-entrypoint.sh"
echo 'ENV HOME=/tmp'
echo "RUN mkdir -p /data && chmod 0777 /data && chmod +x /usr/bin/minio /usr/bin/docker-entrypoint.sh"
echo 'ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]'
echo 'CMD ["silo"]'
echo 'CMD ["minio"]'
} > "${ctx}/Dockerfile"
docker build -t silo-runtime-test:snapshot "${ctx}"
docker build -t minio-runtime-test:snapshot "${ctx}"
# PID 1 must be silo, not the entry shell, on every privilege path, so
# PID 1 must be minio, not the entry shell, on every privilege path, so
# a SIGTERM from docker stop reaches the server and it exits gracefully
# instead of being killed at the stop timeout. Regression guard for the
# exec-into-chroot entrypoint fix.
@@ -361,7 +244,7 @@ jobs:
docker rm -f "${name}" >/dev/null 2>&1 || true
docker run -d --name "${name}" \
-e MINIO_CI_CD=1 -e MINIO_ROOT_USER=ciadmin -e MINIO_ROOT_PASSWORD=ciadmin-secret-123 \
"$@" silo-runtime-test:snapshot silo server /data --address :9000 >/dev/null
"$@" minio-runtime-test:snapshot minio server /data --address :9000 >/dev/null
up=""
for _ in $(seq 1 60); do
if docker logs "${name}" 2>&1 | grep -q "API:"; then up=1; break; fi
@@ -380,122 +263,15 @@ jobs:
[ "${code}" = "0" ] || { echo "non-zero exit (${name}): ${code}"; exit 1; }
[ "${elapsed}" -lt 10 ] || { echo "shutdown too slow (${name}): ${elapsed}s - signal not forwarded"; exit 1; }
}
assert_graceful silo-rt-default
assert_graceful silo-rt-dropuser -e MINIO_USERNAME=silo-user -e MINIO_GROUPNAME=silo-group
assert_graceful silo-rt-rootless --user 1001:1001
# The compatibility shim translates only the legacy first argv token;
# the image contains no /usr/bin/minio file.
docker run --rm silo-runtime-test:snapshot sh -c 'test ! -e /usr/bin/minio'
docker run --rm -d --name silo-rt-legacy \
-e MINIO_CI_CD=1 -e MINIO_ROOT_USER=ciadmin -e MINIO_ROOT_PASSWORD=ciadmin-secret-123 \
silo-runtime-test:snapshot minio server /data --address :9000 >/dev/null
sleep 2
test "$(docker exec silo-rt-legacy cat /proc/1/comm)" = silo
docker rm -f silo-rt-legacy >/dev/null
- name: Build distroless runtime image and verify native healthcheck
run: |
set -euo pipefail
# Unlike the classic image, Dockerfile.distroless has no release
# download stages, so the real shipped file can be built and gated
# here. It must keep working with nothing in it but the silo
# binary: no shell, no mc, no entrypoint script.
ctx="$(mktemp -d)"
tar -xzf "dist/silo_${PKG_VERSION}_linux_amd64.tar.gz" -C "${ctx}" silo
cp Dockerfile.distroless LICENSE NOTICE CREDITS "${ctx}/"
docker build -t silo-distroless-test:snapshot -f "${ctx}/Dockerfile.distroless" "${ctx}"
# HEALTHCHECK is a Docker extension absent from the OCI image
# spec; assert the exact probe command survived into the image
# config, not merely a substring of it.
test "$(docker inspect -f '{{json .Config.Healthcheck.Test}}' silo-distroless-test:snapshot)" \
= '["CMD","/usr/bin/silo","healthcheck","ready"]'
# /data ships in the image layer world-writable (issue #55):
# there is no entrypoint left to repair ownership at runtime.
# Export the rootfs once, then assert each required and each
# forbidden entry individually: tar's member-argument mode exits
# non-zero on any missing name, which under pipefail masks a
# found forbidden file, and -tv prints symlinks as 'name ->
# target' which defeats $-anchored greps.
probe="$(docker create silo-distroless-test:snapshot server /data)"
docker export "${probe}" -o "${ctx}/rootfs.tar"
docker rm "${probe}" >/dev/null
tar -tf "${ctx}/rootfs.tar" > "${ctx}/names.txt"
tar -tvf "${ctx}/rootfs.tar" > "${ctx}/verbose.txt"
grep -E '^drwxrwxrwx.* data/$' "${ctx}/verbose.txt" >/dev/null
for want in usr/bin/silo licenses/LICENSE licenses/NOTICE licenses/CREDITS; do
grep -Fxq "${want}" "${ctx}/names.txt" || { echo "missing ${want}"; exit 1; }
done
for forbid in bin/sh usr/bin/sh busybox/sh usr/bin/minio usr/bin/mc usr/bin/mcli; do
if grep -Fxq "${forbid}" "${ctx}/names.txt"; then
echo "distroless image unexpectedly contains ${forbid}"
exit 1
fi
done
# The baked-in healthcheck must drive Docker's health state on its
# own, the probe binary must be directly exec-able without any
# shell, and SIGTERM must still reach PID 1 (the server binary is
# the entrypoint) for a graceful stop.
assert_distroless() {
name="$1"; shift
docker rm -f "${name}" >/dev/null 2>&1 || true
docker run -d --name "${name}" \
-e MINIO_CI_CD=1 -e MINIO_ROOT_USER=ciadmin -e MINIO_ROOT_PASSWORD=ciadmin-secret-123 \
"$@" silo-distroless-test:snapshot server /data --address :9000 >/dev/null
status=""
for _ in $(seq 1 90); do
status="$(docker inspect -f '{{.State.Health.Status}}' "${name}" 2>/dev/null || echo '?')"
if [ "${status}" = "healthy" ]; then break; fi
if [ "$(docker inspect -f '{{.State.Running}}' "${name}")" != "true" ]; then break; fi
sleep 1
done
if [ "${status}" != "healthy" ]; then
echo "container never became healthy (${name}): status=${status}"
docker logs "${name}" 2>&1 | tail -5
exit 1
fi
docker exec "${name}" /usr/bin/silo healthcheck ready
docker exec "${name}" /usr/bin/silo healthcheck cluster
start="$(date +%s)"; docker stop -t 15 "${name}" >/dev/null; end="$(date +%s)"
code="$(docker inspect -f '{{.State.ExitCode}}' "${name}")"
elapsed=$((end - start))
graceful=0; docker logs "${name}" 2>&1 | grep -q "Exiting on signal" && graceful=1
echo "${name}: health=${status} stop=${elapsed}s exit=${code}"
docker rm -f "${name}" >/dev/null 2>&1 || true
[ "${graceful}" = "1" ] || { echo "no graceful-shutdown log (${name}) - signal not forwarded"; exit 1; }
[ "${code}" = "0" ] || { echo "non-zero exit (${name}): ${code}"; exit 1; }
[ "${elapsed}" -lt 10 ] || { echo "shutdown too slow (${name}): ${elapsed}s - signal not forwarded"; exit 1; }
}
assert_distroless silo-dl-default
assert_distroless silo-dl-rootless --user 1001:1001
assert_graceful minio-rt-default
assert_graceful minio-rt-dropuser -e MINIO_USERNAME=minio-user -e MINIO_GROUPNAME=minio-group
- name: Validate release scripts
run: |
set -euo pipefail
bash -n buildscripts/package-release.sh
bash -n buildscripts/minio-upgrade.sh
bash -n buildscripts/sign-release-rpms.sh
bash -n buildscripts/verify-build-provenance.sh
bash -n buildscripts/verify-rebrand.sh
bash -n buildscripts/verify-helm-migration.sh
sh -n buildscripts/package/postinstall.sh
sh -n buildscripts/package/preremove.sh
bash -n buildscripts/package/lifecycle_test.sh
buildscripts/package/lifecycle_test.sh
bash -n dockerscripts/docker-entrypoint_test.sh
bash -n dockerscripts/download-static-curl.sh
dockerscripts/docker-entrypoint_test.sh
go run ./buildscripts/rebrand-guard
buildscripts/verify-rebrand.sh
test -x buildscripts/package-release.sh
test -x buildscripts/sign-release-rpms.sh
test -x buildscripts/verify-build-provenance.sh
test -x buildscripts/verify-rebrand.sh
test -x buildscripts/verify-helm-migration.sh
test -x buildscripts/package/postinstall.sh
test -x buildscripts/package/preremove.sh
test -x buildscripts/package/lifecycle_test.sh
test -x dockerscripts/docker-entrypoint_test.sh
+4 -4
View File
@@ -3,10 +3,10 @@ name: VulnCheck
on:
pull_request:
branches:
- main
- master
push:
branches:
- main
- master
workflow_dispatch:
permissions:
@@ -19,10 +19,10 @@ jobs:
timeout-minutes: 20
steps:
- name: Check out code
uses: actions/checkout@v7
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v7
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
-3
View File
@@ -2,7 +2,6 @@
cover.out
*~
minio
silo
!*/
site/
**/*.test
@@ -67,5 +66,3 @@ CLAUDE.md
_bmad/
_bmad-output/
docs/security/
docs/rebranding.md
.release-sign/
+1
View File
@@ -0,0 +1 @@
charts.min.io
+3 -13
View File
@@ -1,17 +1,7 @@
# AGPLv3 Compliance
Silo is distributed under the [GNU Affero General Public License v3.0](LICENSE).
It incorporates source code from the MinIO project and preserves the original
copyright, license, and attribution notices in [`NOTICE`](NOTICE),
[`CREDITS`](CREDITS), and source-file headers.
We have designed MinIO as an Open Source software for the Open Source software community. This requires applications to consider whether their usage of MinIO is in compliance with the GNU AGPLv3 [license](https://github.com/minio/minio/blob/master/LICENSE).
You are responsible for determining how the AGPLv3 applies to your use,
modification, deployment, and distribution of Silo and its dependencies. The
Silo maintainers cannot provide legal advice or determine whether a particular
application or service satisfies the license. Consult qualified counsel when
the obligations are material to your deployment.
MinIO cannot make the determination as to whether your application's usage of MinIO is in compliance with the AGPLv3 license requirements. You should instead rely on your own legal counsel or licensing specialists to audit and ensure your application is in compliance with the licenses of MinIO and all other open-source projects with which your application integrates or interacts. We understand that AGPLv3 licensing is complex and nuanced. It is for that reason we strongly encourage using experts in licensing to make any such determinations around compliance instead of relying on apocryphal or anecdotal advice.
If you convey modified binaries or provide network access to a modified
version, review the complete AGPLv3 text and ensure that the corresponding
source and notices are made available as required. Dependency licenses and
separate notices continue to apply independently.
[MinIO Commercial Licensing](https://min.io/pricing) is the best option for applications that trigger AGPLv3 obligations (e.g. open sourcing your application). Applications using MinIO - or any other OSS-licensed code - without validating their usage do so at their own risk.
+30 -95
View File
@@ -1,61 +1,55 @@
# Contributing to Silo
# MinIO Contribution Guide [![Slack](https://slack.min.io/slack?type=svg)](https://slack.min.io) [![Docker Pulls](https://img.shields.io/docker/pulls/minio/minio.svg?maxAge=604800)](https://hub.docker.com/r/minio/minio/)
Silo welcomes focused contributions that improve security, reliability,
compatibility, packaging, tests, or maintainability. This repository preserves
MinIO-compatible interfaces and storage formats, so changes must identify and
test any compatibility impact.
``MinIO`` community welcomes your contribution. To make the process as seamless as possible, we recommend you read this contribution guide.
## Development Workflow
Fork the current Silo source repository, create a topic branch, and submit a
pull request. Discuss broad or compatibility-sensitive changes in an issue
before implementation.
Start by forking the MinIO GitHub repository, make changes in a branch and then send a pull request. We encourage pull requests to discuss code changes. Here are the steps in details:
### Set up a checkout
### Setup your MinIO GitHub Repository
Fork [MinIO upstream](https://github.com/minio/minio/fork) source repository to your own personal repository. Copy the URL of your MinIO fork (you will need it for the `git clone` command below).
```sh
git clone https://github.com/pgsty/silo
cd silo
go build -o silo .
./silo --version
git clone https://github.com/minio/minio
cd minio
go install -v
ls $(go env GOPATH)/bin/minio
```
### Keep the lineage remote separate
### Set up git remote as ``upstream``
```sh
git remote add lineage https://github.com/minio/minio
git fetch lineage
$ cd minio
$ git remote add upstream https://github.com/minio/minio
$ git fetch upstream
$ git merge upstream/master
...
```
Do not merge an upstream branch into a pull request unless the maintainers have
agreed on the scope. Silo intentionally carries a small downstream delta.
### Create your feature branch
Create a separate branch before making code changes:
Before making code changes, make sure you create a separate branch for these changes
```
git checkout -b my-new-feature
```
### Test Silo server changes
### Test MinIO server changes
Before opening a pull request:
After your code changes, make sure
- Add or update tests for changed behavior.
- Run `make verifiers`.
- Run the smallest relevant package tests, then `make test` when practical.
- Run `make build` and confirm the generated executable is `silo`.
- Explain any preserved `MINIO_*`, `minio_*`, `x-minio-*`, `/minio/*`,
`.minio.sys`, ARN, module/import-path, or serialized compatibility name.
- To add test cases for the new code. If you have questions about how to do it, please ask on our [Slack](https://slack.min.io) channel.
- To run `make verifiers`
- To squash your commits into a single commit. `git rebase -i`. It's okay to force update your pull request.
- To run `make test` and `make build` completes.
### Commit changes
After verification, commit your changes with a concise message and a DCO
sign-off (see [Licensing of Contributions](#licensing-of-contributions)):
After verification, commit your changes. This is a [great post](https://chris.beams.io/posts/git-commit/) on how to write useful commit messages
```
git commit -s -am 'Fix object replication retry handling'
git commit -am 'Add some feature'
```
### Push to the branch
@@ -68,71 +62,13 @@ git push origin my-new-feature
### Create a Pull Request
Pull requests should include motivation, reproduction steps where applicable,
test evidence, compatibility notes, and documentation impact. Public product
documentation is owned by the separate
[`pgsty/silo.pgsty.com`](https://github.com/pgsty/silo.pgsty.com) repository.
## Licensing of Contributions
Silo is licensed under the [GNU AGPL v3.0 or later](LICENSE). Its core is
Copyright (c) MinIO, Inc.; the combined work can never be relicensed, and this
fork does not try to.
* **No CLA.** We do not ask you to sign a Contributor License Agreement and we
do not take your copyright. Contributions are accepted inbound=outbound: you
keep the copyright to your changes and license them under the same
AGPL-3.0-or-later as the project itself. The maintainers receive no rights
beyond the project license.
* **DCO sign-off required.** Every commit must carry a
`Signed-off-by: Your Name <you@example.com>` trailer certifying the
[Developer Certificate of Origin 1.1](https://developercertificate.org/) —
your statement that you have the right to submit the code under the project
license. Sign each commit with:
```
git commit -s
```
Forgot some? Repair your branch with `git rebase --signoff` and force-push.
CI rejects pull requests containing unsigned commits; the sign-off email
must match the commit author email. (Lowercase `-s` is the plain-text DCO
sign-off; cryptographic `-S`/GPG signing is welcome but independent.)
* **Provenance.** Only submit code you are entitled to submit. This matters
more here than in most projects: Silo carries a downstream delta over an
upstream code base, and cherry-picks from the lineage remote or other forks
are routine. When relaying a patch written by someone else, preserve original
authorship (`git cherry-pick -x`, keep the author field and any existing
`Signed-off-by` trailers) and add your own sign-off as the person passing it
along. Never import code from a proprietary distribution.
* **File headers.** Files derived from upstream keep the original MinIO
copyright header unchanged. New files added by this fork use the dual
header, followed by the standard AGPL boilerplate:
```
// Copyright (c) 2015-2025 MinIO, Inc.
// Copyright (c) 2025-2026 PGSTY
```
* **Squash merges** must keep the `Signed-off-by:` trailers in the resulting
commit message.
* **Authorship and tooling.** The human contributor is the author of the commit
and the sole signatory of its DCO sign-off. Attribution trailers for
assistive tooling (for example `Co-Authored-By:` naming an AI assistant) are
informational only: they record which tools were used, and do not create
authorship, co-authorship, or any copyright claim. Whoever signs off remains
responsible for the content of the commit, whatever produced it.
Pull requests can be created via GitHub. Refer to [this document](https://help.github.com/articles/creating-a-pull-request/) for detailed steps on how to create a pull request. After a Pull Request gets peer reviewed and approved, it will be merged.
## FAQs
### How does Silo manage dependencies?
### How does ``MinIO`` manage dependencies?
Silo uses Go modules. Preserve the compatibility module and import paths in
`go.mod`; downstream forks are selected with explicit `replace` directives.
``MinIO`` uses `go mod` to manage its dependencies.
- Run `go get foo/bar` in the source folder to add the dependency to `go.mod` file.
@@ -141,7 +77,6 @@ To remove a dependency
- Edit your code and remove the import reference.
- Run `go mod tidy` in the source folder to remove dependency from `go.mod` file.
### What are the coding guidelines?
### What are the coding guidelines for MinIO?
Follow the existing Go style, run `gofmt` on changed Go files, and keep changes
compact. See the Go project's [code review comments](https://go.dev/wiki/CodeReviewComments).
``MinIO`` is fully conformant with Golang style. Refer: [Effective Go](https://github.com/golang/go/wiki/CodeReviewComments) article from Golang project. If you observe offending code, please feel free to send a pull request or ping us on [Slack](https://slack.min.io).
+5923 -3923
View File
File diff suppressed because it is too large Load Diff
+18
View File
@@ -0,0 +1,18 @@
FROM minio/minio:latest
ARG TARGETARCH
ARG RELEASE
RUN chmod -R 777 /usr/bin
COPY ./minio-${TARGETARCH}.${RELEASE} /usr/bin/minio
COPY ./minio-${TARGETARCH}.${RELEASE}.minisig /usr/bin/minio.minisig
COPY ./minio-${TARGETARCH}.${RELEASE}.sha256sum /usr/bin/minio.sha256sum
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
VOLUME ["/data"]
CMD ["minio"]
+3
View File
@@ -0,0 +1,3 @@
FROM minio/minio:edge
CMD ["minio", "server", "/data"]
-50
View File
@@ -1,50 +0,0 @@
# The distroless variant ships exactly one program: the silo binary.
# No shell, no mc, no curl, no entrypoint script; health checking is
# provided by the binary itself (`silo healthcheck`).
# Design note: https://silo.pgsty.com/compatibility/feature/healthcheck/
# A distroless final stage cannot RUN anything, so /data is prepared in a
# throwaway stage. It ships world-writable (see pgsty/silo#55): Docker
# seeds fresh volumes from the image-layer mountpoint, no entrypoint
# exists to repair ownership at runtime, and 0777 is what keeps every
# privilege mode working, --user included.
FROM busybox:1.37.0 AS prep
RUN mkdir -p /prep/data && chmod 0777 /prep/data
FROM gcr.io/distroless/static-debian12:latest
LABEL org.opencontainers.image.title="Silo" \
org.opencontainers.image.description="S3-Interface Libre Object Storage (distroless)" \
org.opencontainers.image.url="https://silo.pgsty.com" \
org.opencontainers.image.source="https://github.com/pgsty/silo" \
org.opencontainers.image.licenses="AGPL-3.0-or-later" \
maintainer="PGSTY <https://silo.pgsty.com>"
ENV MINIO_ACCESS_KEY_FILE=access_key \
MINIO_SECRET_KEY_FILE=secret_key \
MINIO_ROOT_USER_FILE=access_key \
MINIO_ROOT_PASSWORD_FILE=secret_key \
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
MINIO_CONFIG_ENV_FILE=config.env \
HOME=/tmp
COPY --chmod=0755 silo /usr/bin/silo
# COPY of a directory copies its contents, not the directory entry, so an
# empty /prep/data would arrive as a default root:0755 /data and non-root
# runs would fail storage init. Copying the parent makes data/ itself a
# copied entry, which --chmod then actually applies to.
COPY --from=prep --chmod=0777 /prep/ /
COPY LICENSE NOTICE CREDITS /licenses/
EXPOSE 9000
VOLUME ["/data"]
# Exec form is mandatory: there is no /bin/sh in this image. `ready`
# rather than `live` because Docker health feeds start-order gating
# (readiness semantics); the two are identical unless KMS/etcd are used.
# The outer timeout stays above the probe's own 5s deadline so the
# probe can report its diagnostic line instead of being SIGKILLed.
HEALTHCHECK --interval=30s --timeout=10s --start-period=2m --start-interval=2s --retries=3 \
CMD ["/usr/bin/silo", "healthcheck", "ready"]
ENTRYPOINT ["/usr/bin/silo"]
+10 -18
View File
@@ -6,9 +6,7 @@ ENV GOPATH=/go
ENV CGO_ENABLED=0
ARG MC_REPO=pgsty/mc
ARG MC_VERSION=RELEASE.2026-08-06T00-00-00Z
ARG MC_AMD64_SHA256=4b488bd30af54ad4214e5b654746677c79cd93dc6cad4be3aa2d09dbb48370ff
ARG MC_ARM64_SHA256=83f6fedb16ed9c1e8efa8aea6776203dff132bc474214543d5c0767ed2066c2f
ARG MC_VERSION=latest
RUN apk add -U --no-cache \
ca-certificates \
@@ -16,8 +14,8 @@ RUN apk add -U --no-cache \
curl \
jq && \
case "${TARGETARCH}" in \
amd64) MC_ARCH=amd64; MC_PINNED_SHA256="${MC_AMD64_SHA256}" ;; \
arm64) MC_ARCH=arm64; MC_PINNED_SHA256="${MC_ARM64_SHA256}" ;; \
amd64) MC_ARCH=amd64 ;; \
arm64) MC_ARCH=arm64 ;; \
*) echo "Unsupported TARGETARCH=${TARGETARCH}"; exit 1 ;; \
esac && \
if [ "${MC_VERSION}" = "latest" ]; then \
@@ -41,8 +39,7 @@ RUN apk add -U --no-cache \
EXPECTED=$(grep " ${ARCHIVE_NAME}$" /tmp/mcli_checksums.txt | awk '{print $1}') && \
ACTUAL=$(sha256sum /tmp/mcli.tar.gz | awk '{print $1}') && \
[ -n "${EXPECTED}" ] || { echo "Checksum entry not found for ${ARCHIVE_NAME}"; exit 1; } && \
[ "${EXPECTED}" = "${MC_PINNED_SHA256}" ] || { echo "Published checksum drift for ${ARCHIVE_NAME}"; exit 1; } && \
[ "${MC_PINNED_SHA256}" = "${ACTUAL}" ] || { echo "Checksum mismatch: expected ${MC_PINNED_SHA256}, got ${ACTUAL}"; exit 1; } && \
[ "${EXPECTED}" = "${ACTUAL}" ] || { echo "Checksum mismatch: expected ${EXPECTED}, got ${ACTUAL}"; exit 1; } && \
echo "Checksum OK: ${ACTUAL}" && \
mkdir -p /tmp/mcli-extract && \
tar -xzf /tmp/mcli.tar.gz -C /tmp/mcli-extract/ && \
@@ -69,36 +66,31 @@ RUN dnf -y install ca-certificates && \
FROM registry.access.redhat.com/ubi9/ubi-micro:latest
LABEL org.opencontainers.image.title="Silo" \
org.opencontainers.image.description="S3-Interface Libre Object Storage" \
org.opencontainers.image.url="https://silo.pgsty.com" \
org.opencontainers.image.source="https://github.com/pgsty/silo" \
org.opencontainers.image.licenses="AGPL-3.0-or-later" \
maintainer="PGSTY <https://silo.pgsty.com>"
LABEL maintainer="pgsty <https://github.com/pgsty/minio>" \
description="MinIO community fork, build by pgsty"
ENV MINIO_ACCESS_KEY_FILE=access_key \
MINIO_SECRET_KEY_FILE=secret_key \
MINIO_ROOT_USER_FILE=access_key \
MINIO_ROOT_PASSWORD_FILE=secret_key \
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" \
MINIO_CONFIG_ENV_FILE=config.env \
HOME=/tmp \
MC_CONFIG_DIR=/tmp/.mc
COPY --from=certs /tmp/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY silo /usr/bin/silo
COPY minio /usr/bin/minio
COPY --from=build /go/bin/mcli /usr/bin/mcli
COPY --from=build /go/bin/curl* /usr/bin/
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
COPY LICENSE /licenses/LICENSE
COPY NOTICE /licenses/NOTICE
COPY CREDITS /licenses/CREDITS
RUN chmod +x /usr/bin/silo /usr/bin/mcli /usr/bin/docker-entrypoint.sh && \
RUN chmod +x /usr/bin/minio /usr/bin/mcli /usr/bin/docker-entrypoint.sh && \
ln -sf mcli /usr/bin/mc
EXPOSE 9000
VOLUME ["/data"]
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
CMD ["silo"]
CMD ["minio"]
+71
View File
@@ -0,0 +1,71 @@
FROM golang:1.26.5-alpine as build
ARG TARGETARCH
ARG RELEASE
ENV GOPATH=/go
ENV CGO_ENABLED=0
# Install curl and minisign
RUN apk add -U --no-cache ca-certificates && \
apk add -U --no-cache curl && \
go install aead.dev/minisign/cmd/minisign@v0.2.1
# Download minio binary and signature files
RUN curl -s -q https://dl.min.io/server/minio/hotfixes/linux-${TARGETARCH}/archive/minio.${RELEASE} -o /go/bin/minio && \
curl -s -q https://dl.min.io/server/minio/hotfixes/linux-${TARGETARCH}/archive/minio.${RELEASE}.minisig -o /go/bin/minio.minisig && \
curl -s -q https://dl.min.io/server/minio/hotfixes/linux-${TARGETARCH}/archive/minio.${RELEASE}.sha256sum -o /go/bin/minio.sha256sum && \
chmod +x /go/bin/minio
# Download mc binary and signature files
RUN curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc -o /go/bin/mc && \
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.minisig -o /go/bin/mc.minisig && \
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.sha256sum -o /go/bin/mc.sha256sum && \
chmod +x /go/bin/mc
RUN if [ "$TARGETARCH" = "amd64" ]; then \
curl -L -s -q https://github.com/moparisthebest/static-curl/releases/latest/download/curl-${TARGETARCH} -o /go/bin/curl; \
chmod +x /go/bin/curl; \
fi
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav && \
minisign -Vqm /go/bin/mc -x /go/bin/mc.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
FROM registry.access.redhat.com/ubi9/ubi-micro:latest
ARG RELEASE
LABEL name="MinIO" \
vendor="MinIO Inc <dev@min.io>" \
maintainer="MinIO Inc <dev@min.io>" \
version="${RELEASE}" \
release="${RELEASE}" \
summary="MinIO is a High Performance Object Storage, API compatible with Amazon S3 cloud storage service." \
description="MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads."
ENV MINIO_ACCESS_KEY_FILE=access_key \
MINIO_SECRET_KEY_FILE=secret_key \
MINIO_ROOT_USER_FILE=access_key \
MINIO_ROOT_PASSWORD_FILE=secret_key \
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" \
MINIO_CONFIG_ENV_FILE=config.env \
MC_CONFIG_DIR=/tmp/.mc
RUN chmod -R 777 /usr/bin
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /go/bin/minio* /usr/bin/
COPY --from=build /go/bin/mc* /usr/bin/
COPY --from=build /go/bin/cur* /usr/bin/
COPY CREDITS /licenses/CREDITS
COPY LICENSE /licenses/LICENSE
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
EXPOSE 9000
VOLUME ["/data"]
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
CMD ["minio"]
+73
View File
@@ -0,0 +1,73 @@
FROM golang:1.26.5-alpine AS build
ARG TARGETARCH
ARG RELEASE
ENV GOPATH=/go
ENV CGO_ENABLED=0
WORKDIR /build
# Install curl and minisign
RUN apk add -U --no-cache ca-certificates && \
apk add -U --no-cache curl && \
apk add -U --no-cache bash && \
go install aead.dev/minisign/cmd/minisign@v0.2.1
# Download minio binary and signature files
RUN curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE} -o /go/bin/minio && \
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.minisig -o /go/bin/minio.minisig && \
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.sha256sum -o /go/bin/minio.sha256sum && \
chmod +x /go/bin/minio
# Download mc binary and signature files
RUN curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc -o /go/bin/mc && \
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.minisig -o /go/bin/mc.minisig && \
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.sha256sum -o /go/bin/mc.sha256sum && \
chmod +x /go/bin/mc
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav && \
minisign -Vqm /go/bin/mc -x /go/bin/mc.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
COPY dockerscripts/download-static-curl.sh /build/download-static-curl
RUN chmod +x /build/download-static-curl && \
/build/download-static-curl
FROM registry.access.redhat.com/ubi9/ubi-micro:latest
ARG RELEASE
LABEL name="MinIO" \
vendor="MinIO Inc <dev@min.io>" \
maintainer="MinIO Inc <dev@min.io>" \
version="${RELEASE}" \
release="${RELEASE}" \
summary="MinIO is a High Performance Object Storage, API compatible with Amazon S3 cloud storage service." \
description="MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads."
ENV MINIO_ACCESS_KEY_FILE=access_key \
MINIO_SECRET_KEY_FILE=secret_key \
MINIO_ROOT_USER_FILE=access_key \
MINIO_ROOT_PASSWORD_FILE=secret_key \
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" \
MINIO_CONFIG_ENV_FILE=config.env \
MC_CONFIG_DIR=/tmp/.mc
RUN chmod -R 777 /usr/bin
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /go/bin/minio* /usr/bin/
COPY --from=build /go/bin/mc* /usr/bin/
COPY --from=build /go/bin/curl* /usr/bin/
COPY CREDITS /licenses/CREDITS
COPY LICENSE /licenses/LICENSE
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
EXPOSE 9000
VOLUME ["/data"]
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
CMD ["minio"]
+71
View File
@@ -0,0 +1,71 @@
FROM golang:1.26.5-alpine AS build
ARG TARGETARCH
ARG RELEASE
ENV GOPATH=/go
ENV CGO_ENABLED=0
# Install curl and minisign
RUN apk add -U --no-cache ca-certificates && \
apk add -U --no-cache curl && \
go install aead.dev/minisign/cmd/minisign@v0.2.1
# Download minio binary and signature files
RUN curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE} -o /go/bin/minio && \
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.minisig -o /go/bin/minio.minisig && \
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.sha256sum -o /go/bin/minio.sha256sum && \
chmod +x /go/bin/minio
# Download mc binary and signature files
RUN curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc -o /go/bin/mc && \
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.minisig -o /go/bin/mc.minisig && \
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.sha256sum -o /go/bin/mc.sha256sum && \
chmod +x /go/bin/mc
RUN if [ "$TARGETARCH" = "amd64" ]; then \
curl -L -s -q https://github.com/moparisthebest/static-curl/releases/latest/download/curl-${TARGETARCH} -o /go/bin/curl; \
chmod +x /go/bin/curl; \
fi
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav && \
minisign -Vqm /go/bin/mc -x /go/bin/mc.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
FROM registry.access.redhat.com/ubi8/ubi-micro:latest
ARG RELEASE
LABEL name="MinIO" \
vendor="MinIO Inc <dev@min.io>" \
maintainer="MinIO Inc <dev@min.io>" \
version="${RELEASE}" \
release="${RELEASE}" \
summary="MinIO is a High Performance Object Storage, API compatible with Amazon S3 cloud storage service." \
description="MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads."
ENV MINIO_ACCESS_KEY_FILE=access_key \
MINIO_SECRET_KEY_FILE=secret_key \
MINIO_ROOT_USER_FILE=access_key \
MINIO_ROOT_PASSWORD_FILE=secret_key \
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" \
MINIO_CONFIG_ENV_FILE=config.env \
MC_CONFIG_DIR=/tmp/.mc
RUN chmod -R 777 /usr/bin
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /go/bin/minio* /usr/bin/
COPY --from=build /go/bin/mc* /usr/bin/
COPY --from=build /go/bin/cur* /usr/bin/
COPY CREDITS /licenses/CREDITS
COPY LICENSE /licenses/LICENSE
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
EXPOSE 9000
VOLUME ["/data"]
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
CMD ["minio"]
+5
View File
@@ -0,0 +1,5 @@
FROM scratch
COPY minio /minio
CMD ["/minio"]
+65 -62
View File
@@ -7,8 +7,8 @@ GOARCH ?= $(shell go env GOARCH)
GOLANGCI_VERSION ?= v2.11.3
VERSION ?= $(shell git describe --tags)
REPO ?= docker.io/pgsty
TAG ?= $(REPO)/silo:$(VERSION)
REPO ?= quay.io/minio
TAG ?= $(REPO)/minio:$(VERSION)
GOLANGCI_DIR = .bin/golangci/$(GOLANGCI_VERSION)
GOLANGCI = $(GOLANGCI_DIR)/golangci-lint
@@ -33,24 +33,15 @@ getdeps: ## fetch necessary dependencies
sh "$$script" -b $(GOLANGCI_DIR) $(GOLANGCI_VERSION); \
fi
crosscompile: ## cross compile Silo
crosscompile: ## cross compile minio
@(env bash $(PWD)/buildscripts/cross-compile.sh)
verifiers: lint check-gen rebrand-guard
rebrand-guard: ## verify Silo branding and protected compatibility identifiers
@go run ./buildscripts/rebrand-guard
@env bash $(PWD)/buildscripts/verify-rebrand.sh
@env bash $(PWD)/dockerscripts/docker-entrypoint_test.sh
credits: ## regenerate CREDITS from the licenses of Go modules linked into the binary
@env bash $(PWD)/buildscripts/gen-credits.sh
verifiers: lint check-gen
check-gen: ## check for updated autogenerated files
@go generate ./... >/dev/null
@go mod tidy -compat=1.26
@env bash $(PWD)/buildscripts/gen-credits.sh
@changed=$$(git diff --name-only -- '*_gen.go' '*_gen_test.go' '*_msgp_test.go' '*_string.go' go.mod go.sum CREDITS); \
@changed=$$(git diff --name-only -- '*_gen.go' '*_gen_test.go' '*_msgp_test.go' '*_string.go' go.mod go.sum); \
if [ -n "$$changed" ]; then \
echo "Non-committed generated changes detected:"; \
echo "$$changed"; \
@@ -73,12 +64,12 @@ lint-fix: getdeps ## runs golangci-lint suite of linters with automatic fixes
@$(GOLANGCI) run --build-tags kqueue --timeout=10m --config ./.golangci.yml --fix
check: test
test: verifiers build ## builds Silo, runs linters, tests
test: verifiers build ## builds minio, runs linters, tests
@echo "Running unit tests"
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -v -tags kqueue,dev ./...
test-root-disable: install-race
@echo "Running Silo root lockdown tests"
@echo "Running minio root lockdown tests"
@env bash $(PWD)/buildscripts/disable-root.sh
test-ilm: install-race
@@ -94,7 +85,7 @@ test-pbac: install-race
@env bash $(PWD)/docs/iam/policies/pbac-tests.sh
test-decom: install-race
@echo "Running Silo decom tests"
@echo "Running minio decom tests"
@env bash $(PWD)/docs/distributed/decom.sh
@env bash $(PWD)/docs/distributed/decom-encrypted.sh
@env bash $(PWD)/docs/distributed/decom-encrypted-sse-s3.sh
@@ -102,17 +93,17 @@ test-decom: install-race
@env bash $(PWD)/docs/distributed/decom-encrypted-kes.sh
test-versioning: install-race
@echo "Running Silo versioning tests"
@echo "Running minio versioning tests"
@env bash $(PWD)/docs/bucket/versioning/versioning-tests.sh
test-configfile: install-race
@env bash $(PWD)/docs/distributed/distributed-from-config-file.sh
test-upgrade:
@echo "Running MinIO-to-Silo upgrade tests"
test-upgrade: install-race
@echo "Running minio upgrade tests"
@(env bash $(PWD)/buildscripts/minio-upgrade.sh)
test-race: verifiers build ## builds Silo, runs linters, tests (race)
test-race: verifiers build ## builds minio, runs linters, tests (race)
@echo "Running unit tests under -race"
@(env bash $(PWD)/buildscripts/race.sh)
@@ -160,9 +151,9 @@ test-site-replication-oidc: install-race ## verify automatic site replication
@echo "Running tests for automatic site replication of IAM (with OIDC)"
@(env bash $(PWD)/docs/site-replication/run-multi-site-oidc.sh)
test-site-replication-silo: install-race ## verify automatic site replication
@echo "Running tests for automatic site replication of IAM (with Silo IDP)"
@(env bash $(PWD)/docs/site-replication/run-multi-site-silo-idp.sh)
test-site-replication-minio: install-race ## verify automatic site replication
@echo "Running tests for automatic site replication of IAM (with MinIO IDP)"
@(env bash $(PWD)/docs/site-replication/run-multi-site-minio-idp.sh)
@echo "Running tests for automatic site replication of SSE-C objects"
@(env bash $(PWD)/docs/site-replication/run-ssec-object-replication.sh)
@echo "Running tests for automatic site replication of SSE-C objects with SSE-KMS enabled for bucket"
@@ -178,11 +169,11 @@ test-timeout: install-race ## test multipart
@echo "Test server timeout"
@(env bash $(PWD)/buildscripts/test-timeout.sh)
verify: install-race ## verify Silo in various setups
verify: install-race ## verify minio various setups
@echo "Verifying build with race"
@(env bash $(PWD)/buildscripts/verify-build.sh)
verify-healing: install-race ## verify healing and replacing disks with the Silo binary
verify-healing: install-race ## verify healing and replacing disks with minio binary
@echo "Verify healing build with race"
@(env bash $(PWD)/buildscripts/verify-healing.sh)
@(env bash $(PWD)/buildscripts/verify-healing-empty-erasure-set.sh)
@@ -203,49 +194,59 @@ verify-healing-inconsistent-versions: install-race ## verify resolving inconsist
build-debugging:
@(env bash $(PWD)/docs/debugging/build.sh)
build: checks build-debugging ## builds Silo to $(PWD)
@echo "Building Silo binary to './silo'"
@CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/silo 1>/dev/null
build: checks build-debugging ## builds minio to $(PWD)
@echo "Building minio binary to './minio'"
@CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
docker: checks build-debugging ## builds the local Linux Silo container image
@echo "Building Silo container image '$(TAG)'"
@set -e; \
context=$$(mktemp -d); \
trap 'rm -rf "$$context"' EXIT; \
CGO_ENABLED=0 GOOS=linux GOARCH=$(GOARCH) go build -tags kqueue -trimpath \
--ldflags "$(LDFLAGS)" -o "$$context/silo"; \
mkdir -p "$$context/dockerscripts"; \
cp Dockerfile.goreleaser LICENSE NOTICE CREDITS "$$context/"; \
cp dockerscripts/docker-entrypoint.sh dockerscripts/download-static-curl.sh \
"$$context/dockerscripts/"; \
docker build -q --no-cache --platform linux/$(GOARCH) -t $(TAG) --build-arg TARGETARCH=$(GOARCH) \
-f "$$context/Dockerfile.goreleaser" "$$context"
hotfix-vars:
$(eval LDFLAGS := $(shell MINIO_RELEASE="RELEASE" MINIO_HOTFIX="hotfix.$(shell git rev-parse --short HEAD)" go run buildscripts/gen-ldflags.go $(shell git describe --tags --abbrev=0 | \
sed 's#RELEASE\.\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)T\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)Z#\1-\2-\3T\4:\5:\6Z#')))
$(eval VERSION := $(shell git describe --tags --abbrev=0).hotfix.$(shell git rev-parse --short HEAD))
docker-distroless: checks build-debugging ## builds the local Linux Silo distroless container image
@echo "Building Silo distroless container image '$(TAG)-distroless'"
@set -e; \
context=$$(mktemp -d); \
trap 'rm -rf "$$context"' EXIT; \
CGO_ENABLED=0 GOOS=linux GOARCH=$(GOARCH) go build -tags kqueue -trimpath \
--ldflags "$(LDFLAGS)" -o "$$context/silo"; \
cp Dockerfile.distroless LICENSE NOTICE CREDITS "$$context/"; \
docker build -q --no-cache --platform linux/$(GOARCH) -t $(TAG)-distroless \
-f "$$context/Dockerfile.distroless" "$$context"
hotfix: hotfix-vars clean install ## builds minio binary with hotfix tags
@wget -q -c https://github.com/minio/pkger/releases/download/v2.3.11/pkger_2.3.11_linux_amd64.deb
@wget -q -c https://raw.githubusercontent.com/minio/minio-service/v1.1.1/linux-systemd/distributed/minio.service
@sudo apt install ./pkger_2.3.11_linux_amd64.deb --yes
@mkdir -p minio-release/$(GOOS)-$(GOARCH)/archive
@cp -af ./minio minio-release/$(GOOS)-$(GOARCH)/minio
@cp -af ./minio minio-release/$(GOOS)-$(GOARCH)/minio.$(VERSION)
@minisign -qQSm minio-release/$(GOOS)-$(GOARCH)/minio.$(VERSION) -s "${CRED_DIR}/minisign.key" < "${CRED_DIR}/minisign-passphrase"
@sha256sum < minio-release/$(GOOS)-$(GOARCH)/minio.$(VERSION) | sed 's, -,minio.$(VERSION),g' > minio-release/$(GOOS)-$(GOARCH)/minio.$(VERSION).sha256sum
@cp -af minio-release/$(GOOS)-$(GOARCH)/minio.$(VERSION)* minio-release/$(GOOS)-$(GOARCH)/archive/
@pkger -r $(VERSION) --ignore
hotfix-push: hotfix
@scp -q -r minio-release/$(GOOS)-$(GOARCH)/* minio@dl-0.minio.io:~/releases/server/minio/hotfixes/linux-$(GOOS)/
@scp -q -r minio-release/$(GOOS)-$(GOARCH)/* minio@dl-0.minio.io:~/releases/server/minio/hotfixes/linux-$(GOOS)/archive
@scp -q -r minio-release/$(GOOS)-$(GOARCH)/* minio@dl-1.minio.io:~/releases/server/minio/hotfixes/linux-$(GOOS)/
@scp -q -r minio-release/$(GOOS)-$(GOARCH)/* minio@dl-1.minio.io:~/releases/server/minio/hotfixes/linux-$(GOOS)/archive
@echo "Published new hotfix binaries at https://dl.min.io/server/minio/hotfixes/linux-$(GOOS)/archive/minio.$(VERSION)"
docker-hotfix-push: docker-hotfix
@docker push -q $(TAG) && echo "Published new container $(TAG)"
docker-hotfix: hotfix-push checks ## builds minio docker container with hotfix tags
@echo "Building minio docker image '$(TAG)'"
@docker build -q --no-cache -t $(TAG) --build-arg RELEASE=$(VERSION) . -f Dockerfile.hotfix
docker: build ## builds minio docker container
@echo "Building minio docker image '$(TAG)'"
@docker build -q --no-cache -t $(TAG) . -f Dockerfile
test-resiliency: build
@echo "Running resiliency tests"
@(DOCKER_COMPOSE_FILE=$(PWD)/docs/resiliency/docker-compose.yaml env bash $(PWD)/docs/resiliency/resiliency-tests.sh)
install-race: checks build-debugging ## builds Silo to $(PWD)
@echo "Building Silo binary with -race to './silo'"
@GORACE=history_size=7 CGO_ENABLED=1 go build -tags kqueue,dev -race -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/silo 1>/dev/null
@echo "Installing Silo binary with -race to '$(GOPATH)/bin/silo'"
@mkdir -p $(GOPATH)/bin && cp -af $(PWD)/silo $(GOPATH)/bin/silo
install-race: checks build-debugging ## builds minio to $(PWD)
@echo "Building minio binary with -race to './minio'"
@GORACE=history_size=7 CGO_ENABLED=1 go build -tags kqueue,dev -race -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
@echo "Installing minio binary with -race to '$(GOPATH)/bin/minio'"
@mkdir -p $(GOPATH)/bin && cp -af $(PWD)/minio $(GOPATH)/bin/minio
install: build ## builds Silo and installs it to $GOPATH/bin.
@echo "Installing Silo binary to '$(GOPATH)/bin/silo'"
@mkdir -p $(GOPATH)/bin && cp -af $(PWD)/silo $(GOPATH)/bin/silo
@echo "Installation successful. To learn more, try \"silo --help\"."
install: build ## builds minio and installs it to $GOPATH/bin.
@echo "Installing minio binary to '$(GOPATH)/bin/minio'"
@mkdir -p $(GOPATH)/bin && cp -af $(PWD)/minio $(GOPATH)/bin/minio
@echo "Installation successful. To learn more, try \"minio --help\"."
clean: ## cleanup all generated assets
@echo "Cleaning up all the generated files"
@@ -253,8 +254,10 @@ clean: ## cleanup all generated assets
@find . -name '*~' | xargs rm -fv
@find . -name '.#*#' | xargs rm -fv
@find . -name '#*#' | xargs rm -fv
@rm -rvf silo
@rm -rvf minio
@rm -rvf build
@rm -rvf release
@rm -rvf .verify*
@rm -rvf minio-release
@rm -rvf minio.RELEASE*.hotfix.*
@rm -rvf pkger_*.deb
+1 -7
View File
@@ -1,4 +1,4 @@
MinIO Project, (C) 2015-2025 MinIO, Inc.
MinIO Project, (C) 2015-2023 MinIO, Inc.
This product includes software developed at MinIO, Inc.
(https://min.io/).
@@ -7,9 +7,3 @@ The MinIO project contains unmodified/modified subcomponents too with
separate copyright notices and license terms. Your use of the source
code for these subcomponents is subject to the terms and conditions
of GNU Affero General Public License 3.0.
Silo Project modifications, (C) 2025-2026 PGSTY.
Silo is an independent community-maintained project incorporating MinIO
source code. It is not affiliated with or endorsed by MinIO, Inc. Modified
source and Silo release artifacts are maintained by the Silo project.
+12 -12
View File
@@ -1,12 +1,12 @@
# Silo Pull Request Guidelines
# MinIO Pull Request Guidelines
These guidelines ensure high-quality commits in Silo's GitHub repositories, maintaining
a clear, valuable commit history for our open-source projects. They apply to all contributors,
These guidelines ensure high-quality commits in MinIOs GitHub repositories, maintaining
a clear, valuable commit history for our open-source projects. They apply to all contributors,
fostering efficient reviews and robust code.
## Why Pull Requests?
Pull Requests (PRs) drive quality in Silo's codebase by:
Pull Requests (PRs) drive quality in MinIOs codebase by:
- Enabling peer review without pair programming.
- Documenting changes for future reference.
- Ensuring commits tell a clear story of development.
@@ -15,12 +15,12 @@ Pull Requests (PRs) drive quality in Silo's codebase by:
## Crafting a Quality PR
A strong Silo PR:
A strong MinIO PR:
- Delivers a complete, valuable change (feature, bug fix, or improvement).
- Has a concise title (e.g., `[S3] Fix bucket policy parsing #1234`) and a summary with context, referencing issues (e.g., `#1234`).
- Contains well-written, logical commits explaining *why* changes were made (e.g., “Add S3 bucket tagging support so that users can organize resources efficiently”).
- Is small, focused, and easy to review—ideally one commit, unless multiple commits better narrate complex work.
- Adheres to Silo's coding standards (e.g., Go style, error handling, testing).
- Adheres to MinIOs coding standards (e.g., Go style, error handling, testing).
PRs must flow smoothly through review to reach production. Large PRs should be split into smaller, manageable ones.
@@ -48,14 +48,14 @@ PRs must flow smoothly through review to reach production. Large PRs should be s
## Reviewing PRs
Reviewers ensure Silo's commit history remains a clear, reliable record. Responsibilities include:
Reviewers ensure MinIOs commit history remains a clear, reliable record. Responsibilities include:
1. **Commit Quality**:
- Verify each commit explains *why* the change was made (e.g., “So that…”).
- Request rebasing if commits are unclear, redundant, or lack context (e.g., “Please squash typo fixes into the parent commit”).
2. **Code Quality**:
- Check adherence to Silo's Go standards (e.g., error handling, documentation).
- Check adherence to MinIOs Go standards (e.g., error handling, documentation).
- Ensure tests cover new code and pass CI.
- Flag bugs or critical issues for immediate fixes; suggest non-blocking improvements as follow-up issues.
@@ -65,7 +65,7 @@ Reviewers ensure Silo's commit history remains a clear, reliable record. Respons
- If unable to complete the review, tag another reviewer (e.g., `@username please take over`).
4. **Shared Responsibility**:
- All Silo contributors are reviewers. The first commenter on a PR owns the review unless they delegate.
- All MinIO contributors are reviewers. The first commenter on a PR owns the review unless they delegate.
- Multiple reviewers are encouraged for complex PRs.
5. **No Self-Edits**:
@@ -80,14 +80,14 @@ Reviewers ensure Silo's commit history remains a clear, reliable record. Respons
- **Small PRs**: Easier to review, faster to merge. Split large changes logically.
- **Clear Commits**: Use `git rebase -i` to refine history before submitting.
- **Engage Early**: Discuss complex changes in a GitHub issue before coding.
- **Engage Early**: Discuss complex changes in issues or Slack (https://slack.min.io) before coding.
- **Be Responsive**: Address reviewer feedback promptly to keep PRs moving.
- **Learn from Reviews**: Use feedback to improve future contributions.
## Resources
- [Silo Contribution Guide](CONTRIBUTING.md)
- [MinIO Coding Standards](https://github.com/minio/minio/blob/master/CONTRIBUTING.md)
- [Effective Commit Messages](https://mislav.net/2014/02/hidden-documentation/)
- [GitHub PR Tips](https://github.com/blog/1943-how-to-write-the-perfect-pull-request)
By following these guidelines, we ensure Silo's codebase remains high-quality, maintainable, and a joy to contribute to. Happy coding!
By following these guidelines, we ensure MinIOs codebase remains high-quality, maintainable, and a joy to contribute to. Happy coding!
+40 -141
View File
@@ -1,70 +1,59 @@
> [!WARNING]
> **This branch is archived and receives no further changes.**
>
> `minio` holds the final state of this project under the MinIO identity. Development continues on **[`main`](https://github.com/pgsty/silo/tree/main)**, where the project is named **Silo** and the binary, packages, systemd unit, container image, and Helm chart are all named `silo`. On 2026-08-06 the repository was renamed `pgsty/minio` → **[`pgsty/silo`](https://github.com/pgsty/silo)** and its default branch `master` → `main`.
>
> The last release cut from this branch is **[`RELEASE.2026-08-04T00-00-00Z`](https://github.com/pgsty/silo/releases/tag/RELEASE.2026-08-04T00-00-00Z)** (2026-08-04). Its 19 assets carry the `minio` artifact names, and the matching container image is `docker.io/pgsty/minio:RELEASE.2026-08-04T00-00-00Z`. Those artifacts stay published and unmodified — no tag is moved, re-signed, or removed. If you need the artifacts maintained under the original MinIO identity, this branch and the releases up to that tag are where they live. Later releases carry the `silo` names.
>
> **Nothing on the wire changed with the rename.** `MINIO_*` environment variables, `minio_*` Prometheus metrics, `x-minio-*` headers, `/minio/*` routes, the `.minio.sys` on-disk layout, IAM and ARN values, and the `github.com/minio/minio` Go module path are all preserved on `main`. A Silo server reads data written by this release, and the packages install side by side, so migrating or rolling back stays an explicit administrator action.
<h1 align="center">
<a href="https://silo.pgsty.com/">
<img src=".github/silo-logo.svg" alt="Silo" width="160">
</a>
<img src=".github/silo-word.svg" alt="SILO" height="80">
</h1>
<p align="center">
<strong>S3-compatible object storage — a MinIO fork maintained by PIGSTY</strong>
<strong>A conservatively maintained MinIO fork</strong><br>
Security maintenance, versioned release artifacts, and operational continuity for existing deployments.
</p>
<p align="center">
<a href="https://silo.pgsty.com/">Website</a> ·
<a href="https://silo.pgsty.com/docs/">Documentation</a> ·
<a href="https://silo.pgsty.com/download/">Download</a> ·
<a href="https://silo.pgsty.com/tags/silo/">Release Notes</a> ·
<a href="https://silo.pgsty.com/compatibility/server/">Compatibility</a> ·
<a href="https://silo.pgsty.com/blog/">Blog</a> ·
<a href="https://github.com/pgsty/minio/releases">Releases</a> ·
<a href="SECURITY.md">Security</a> ·
<a href="README_ZH.md">中文</a>
</p>
<p align="center">
<a href="https://silo.pgsty.com/"><img alt="Website" src="https://img.shields.io/badge/Website-silo.pgsty.com-1d588c"></a>
<a href="https://github.com/pgsty/silo/releases"><img alt="GitHub Release" src="https://img.shields.io/github/v/release/pgsty/silo?include_prereleases&label=release&logo=github"></a>
<a href="https://hub.docker.com/r/pgsty/silo"><img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/pgsty/minio?logo=docker"></a>
<a href="go.mod"><img alt="Go Version" src="https://img.shields.io/github/go-mod/go-version/pgsty/silo?logo=go"></a>
<a href="https://github.com/pgsty/minio/releases"><img alt="GitHub Release" src="https://img.shields.io/github/v/release/pgsty/minio?include_prereleases&label=release&logo=github"></a>
<a href="https://hub.docker.com/r/pgsty/minio"><img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/pgsty/minio?logo=docker"></a>
<a href="go.mod"><img alt="Go Version" src="https://img.shields.io/github/go-mod/go-version/pgsty/minio?logo=go"></a>
<a href="LICENSE"><img alt="License" src="https://img.shields.io/badge/license-AGPLv3-blue"></a>
</p>
> [!IMPORTANT]
> Silo is an independent, community-maintained fork of the open-source MinIO server, published by [Pigsty](https://pigsty.io) from [`pgsty/silo`](https://github.com/pgsty/silo). It is not affiliated with, endorsed by, or sponsored by MinIO, Inc. “MinIO” is used only to identify the upstream project and compatibility lineage.
> [!NOTE]
> This repository was renamed from `pgsty/minio` to `pgsty/silo`, and its default branch from `master` to `main`, on 2026-08-06. If you need the artifacts maintained under the original MinIO identity, they are on the archived [`minio`](https://github.com/pgsty/silo/tree/minio) branch and in the releases up to [`RELEASE.2026-08-04T00-00-00Z`](https://github.com/pgsty/silo/releases/tag/RELEASE.2026-08-04T00-00-00Z); those assets and the `docker.io/pgsty/minio` image stay published and unmodified. The rename changed product and artifact names only — `MINIO_*` variables, `minio_*` metrics, `x-minio-*` headers, `/minio/*` routes, and the on-disk format are unchanged.
> Silo is an independent, community-maintained fork of the open-source MinIO server, published by [Pigsty](https://pigsty.io) from [`pgsty/minio`](https://github.com/pgsty/minio). It is not affiliated with, endorsed by, or sponsored by MinIO, Inc. “MinIO” is used only to identify the upstream project and compatibility lineage.
## Overview
Silo maintains one downstream release line derived from the open-source MinIO server. It provides maintained builds and release artifacts for existing MinIO-compatible deployments after upstream community distribution ended. Pigsty uses Silo for object storage, including as an optional PostgreSQL backup repository.
Silo maintains one downstream release line based on MinIO [`RELEASE.2025-12-03T12-00-00Z`](https://github.com/minio/minio/releases/tag/RELEASE.2025-12-03T12-00-00Z). It provides maintained builds and release artifacts for existing MinIO-compatible deployments after upstream community distribution ended. Pigsty uses this fork for object storage as an optional PG backup repo.
The fork follows one rule: **the product and its delivery surfaces are renamed; the protocol and your data are not.** The `silo` executable, packages, service, and container image carry the new name, while the S3 API, `MINIO_*` variables, `minio_*` metrics, `x-minio-*` headers, reserved routes, and the on-disk format stay unchanged. The differences are recorded in the [compatibility notes](https://silo.pgsty.com/compatibility/server/).
The official project portal is [silo.pgsty.com](https://silo.pgsty.com/). It brings documentation, downloads, release and security notes, and project background together.
The official project portal is [silo.pgsty.com](https://silo.pgsty.com/). It brings documentation, downloads, release and security notes, and project background together. English is served at the site root; Chinese is available under [/zh/](https://silo.pgsty.com/zh/).
## Find the Right Resource
| Looking for | Canonical location |
| :-- | :-- |
| Project overview and navigation | [Silo Website](https://silo.pgsty.com/) |
| Installation methods and downloads | [Download & Install](https://silo.pgsty.com/download/) |
| Operations, administration, development, and reference | [Documentation](https://silo.pgsty.com/docs/) |
| Release notes for this server | [Silo release notes](https://silo.pgsty.com/tags/silo/) |
| Differences from upstream MinIO | [Silo vs. MinIO compatibility notes](https://silo.pgsty.com/compatibility/server/) |
| Project news and security advisories | [Blog](https://silo.pgsty.com/blog/) · [release](https://silo.pgsty.com/blog/release/) and [security](https://silo.pgsty.com/blog/security/) notes |
| Versioned binaries, checksums, and source archives | [GitHub Releases](https://github.com/pgsty/silo/releases) |
| Bug reports and feature discussions | [GitHub Issues](https://github.com/pgsty/silo/issues) |
| Vulnerability reporting | [`SECURITY.md`](SECURITY.md) and [`VULNERABILITY_REPORT.md`](VULNERABILITY_REPORT.md) |
| License, attribution, and trademark information | [`LICENSE`](LICENSE) · [`NOTICE`](NOTICE) · [`CREDITS`](CREDITS) · portal [license](https://silo.pgsty.com/about/license/), [attribution](https://silo.pgsty.com/about/attribution/), and [trademark](https://silo.pgsty.com/about/trademark/) pages |
## Related Projects
| Repository | Description |
| :-- | :-- |
| [`pgsty/silo`](https://github.com/pgsty/silo) | This repository — the Silo object storage server, an S3-compatible MinIO fork |
| [`pgsty/mc`](https://github.com/pgsty/mc) | The Silo command-line client, shipped as `mcli` with the `mc` command name |
| [`pgsty/silo-console`](https://github.com/pgsty/silo-console) | Admin web console, embedded in the server build |
| [`pgsty/silo-pkg`](https://github.com/pgsty/silo-pkg) | Shared Go packages maintained for the Silo forks |
| [`pgsty/pigsty`](https://github.com/pgsty/pigsty) | Pigsty — the PostgreSQL distribution that ships Silo as its object storage |
| Project overview and navigation | [Silo Website](https://silo.pgsty.com/) ([中文](https://silo.pgsty.com/zh/)) |
| Installation methods and downloads | [Download & Install](https://silo.pgsty.com/download/) ([中文](https://silo.pgsty.com/zh/download/)) |
| Operations, administration, development, and reference | [Documentation](https://silo.pgsty.com/docs/) ([中文](https://silo.pgsty.com/zh/docs/)) |
| Project news, release notes, and security notes | [Blog](https://silo.pgsty.com/blog/), including [releases](https://silo.pgsty.com/blog/release/) and [security](https://silo.pgsty.com/blog/security/) |
| Versioned binaries, checksums, and source archives | [GitHub Releases](https://github.com/pgsty/minio/releases) |
| Bug reports and feature discussions | [GitHub Issues](https://github.com/pgsty/minio/issues) |
| License, attribution, and trademark information | [License](https://silo.pgsty.com/about/license/), [Attribution](https://silo.pgsty.com/about/attribution/), and [Trademark](https://silo.pgsty.com/about/trademark/) |
## Maintenance Policy
@@ -86,21 +75,14 @@ Changes are kept narrow and tested where practical. Maintenance is best effort;
- commercial support, SLAs, 24×7 coverage, or SUBNET access;
- deployment design, access control, monitoring, backup, or recovery.
## Governance
The server, client, console, and shared packages are maintained under one release process: DCO-signed commits, reviewed pull requests, and versioned `RELEASE.YYYY-MM-DDTHH-MM-SSZ` tags whose artifacts ship with checksums, SPDX SBOMs, Sigstore-signed manifests, and GitHub build attestations. Each release is announced with a [release note](https://silo.pgsty.com/tags/silo/) on the portal; security handling follows [`SECURITY.md`](SECURITY.md) and the [advisory log](docs/security/advisories.md). Upstream copyright, license, and third-party notices are preserved in [`LICENSE`](LICENSE), [`NOTICE`](NOTICE), and [`CREDITS`](CREDITS).
## Compatibility
Silo aims to preserve:
- the `github.com/minio/minio` module path and `github.com/minio/*` import paths;
- MinIO-compatible S3 APIs, wire behavior, `MINIO_*` environment variables, metrics, protocol headers, reserved routes, and storage metadata;
- `RELEASE.YYYY-MM-DDTHH-MM-SSZ` tags and legacy `minio …` container argv translation.
- MinIO-compatible S3 APIs, configuration, environment variables, and CLI conventions;
- `RELEASE.YYYY-MM-DDTHH-MM-SSZ` tags, container entrypoints, and common deployment workflows.
Silo-owned delivery surfaces use the `silo` executable, package, service, Helm chart, and `pgsty/silo` container image. Native artifacts intentionally do not install a `minio` binary alias.
Compatibility is the default constraint. Silo preserves existing wire, client, configuration, and operational behavior whenever doing so remains safe. Compatibility is broken only when necessary to close a major security issue, and the release notes must identify the affected behavior and migration path. Treat each release as a downstream upgrade: pin versions, review the [release notes](https://silo.pgsty.com/tags/silo/), [compatibility notes](https://silo.pgsty.com/compatibility/server/), and [security advisories](docs/security/advisories.md), keep a rollback path, and test before production use.
Compatibility is the default constraint. Silo preserves existing wire, client, configuration, and operational behavior whenever doing so remains safe. Compatibility is broken only when necessary to close a major security issue, and the release notes must identify the affected behavior and migration path. Treat each release as a downstream upgrade: pin versions, review [release notes](https://silo.pgsty.com/blog/release/) and [security advisories](docs/security/advisories.md), keep a rollback path, and test before production use.
## Downloads and Release Artifacts
@@ -108,89 +90,13 @@ Use [Download & Install](https://silo.pgsty.com/download/) to choose an installa
| Artifact | Location |
| :-- | :-- |
| Source | [`github.com/pgsty/silo`](https://github.com/pgsty/silo) |
| Container image | [`pgsty/silo`](https://hub.docker.com/r/pgsty/silo), multi-arch for `linux/amd64` and `linux/arm64` |
| Server binaries and checksums | [GitHub Releases](https://github.com/pgsty/silo/releases) for Linux, macOS, and Windows on `amd64` and `arm64` |
| Source | [`github.com/pgsty/minio`](https://github.com/pgsty/minio) |
| Container image | [`pgsty/minio`](https://hub.docker.com/r/pgsty/minio), multi-arch for `linux/amd64` and `linux/arm64` |
| Server binaries and checksums | [GitHub Releases](https://github.com/pgsty/minio/releases) for Linux, macOS, and Windows on `amd64` and `arm64` |
| Linux packages | RPM, DEB, and APK artifacts, also distributed through the [Pigsty repository](https://pigsty.io/docs/repo/) |
| Client | [`pgsty/mc`](https://github.com/pgsty/mc), bundled in the container as `mcli` with an `mc` compatibility alias |
| Console | [`pgsty/silo-console`](https://github.com/pgsty/silo-console), embedded through the compatibility import path `github.com/minio/console` |
| Shared library | [`pgsty/silo-pkg`](https://github.com/pgsty/silo-pkg) v3.11.0, consumed through a `replace` directive while preserving the `github.com/minio/pkg/v3` import path |
Each new release publishes per-archive and per-package SPDX JSON SBOMs. The archive and package checksum manifests have detached keyless Sigstore bundles, while GitHub artifact attestations record signed provenance for every downloadable artifact and the multi-architecture container image.
After downloading an archive and its release files, verify integrity, the
published SBOM, the signed manifest, and build provenance independently:
```bash
# Integrity: choose the line for the artifact you downloaded.
grep -F ' silo_<version>_linux_amd64.tar.gz' \
silo_<version>_checksums.txt | sha256sum --check
# The archive SBOM is a separate checksummed release artifact.
grep -F ' silo_<version>_linux_amd64.tar.gz.sbom.json' \
silo_<version>_checksums.txt | sha256sum --check
# Signature over the archive/SBOM checksum manifest.
cosign verify-blob \
--bundle silo_<version>_checksums.txt.sigstore.json \
--certificate-identity-regexp \
'^https://github.com/pgsty/(minio|silo)/\.github/workflows/release\.yml@refs/(tags/RELEASE\..+|heads/(master|main))$' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
silo_<version>_checksums.txt
# Signed build provenance (online verification against this repository).
gh attestation verify silo_<version>_linux_amd64.tar.gz \
--repo pgsty/silo
```
For packages, use `silo_<version>_packages_checksums.txt` and its adjacent
Sigstore bundle with the same identity and issuer constraints. Inspect the
verified SPDX JSON SBOM with your preferred SPDX tooling. Verify the
multi-architecture container provenance by digest:
```bash
gh attestation verify \
oci://index.docker.io/pgsty/silo@sha256:<manifest-digest> \
--repo pgsty/silo
```
The platform SBOM attestations are attached to the `amd64` and `arm64` image
digests rather than the multi-architecture manifest. Verify each one explicitly:
```bash
gh attestation verify \
oci://index.docker.io/pgsty/silo@sha256:<platform-digest> \
--repo pgsty/silo \
--predicate-type https://spdx.dev/Document/v2.3
```
Verification by digest avoids trusting a mutable image tag.
### Native package migration
The `silo` RPM, DEB, and APK do not declare `Provides`, `Obsoletes`,
`Replaces`, or package-level `Conflicts` against `minio`. They can therefore be
installed beside an existing MinIO package without silently replacing it. The
two systemd units conflict at runtime, so switch them explicitly rather than
starting both.
Before switching, record the old unit's enabled/active state and `User`/`Group`,
and back up `/etc/default/minio`. Silo reads that legacy defaults file first and
then `/etc/default/silo`; administrator-set values in the latter take
precedence. If the existing data must continue to run under its original
UID/GID, create `/etc/systemd/system/silo.service.d/10-legacy-user.conf`:
```ini
[Service]
User=<legacy-user>
Group=<legacy-group>
```
Run `systemctl daemon-reload`, then disable and stop `minio.service` before
enabling and starting `silo.service`. Verify health, S3, Admin API, metrics, and
logs before masking or uninstalling the old service. Do not recursively change
data ownership as part of the package migration; keep the old package and unit
available during the rollback window.
| Console | Maintained [`georgmangold/console`](https://github.com/georgmangold/console) fork, embedded in the server build |
| Shared library | [`pgsty/silo-pkg`](https://github.com/pgsty/silo-pkg) v3.7.0, consumed through a `replace` directive while preserving `github.com/minio/pkg/v3` import paths ([release notes](https://silo.pgsty.com/blog/release/pkg-3.7.0/)) |
## Quick Start
@@ -208,7 +114,7 @@ docker run -d --name silo \
-e MINIO_ROOT_USER \
-e MINIO_ROOT_PASSWORD \
-v "$PWD/data:/data" \
docker.io/pgsty/silo:latest server /data --console-address ":9001"
pgsty/minio:latest server /data --console-address ":9001"
```
Open the console at <http://localhost:9001>; the S3 API listens on <http://localhost:9000>.
@@ -228,15 +134,15 @@ docker exec silo mcli ls local
Build the server from source:
```bash
go build -o silo .
./silo --version
go build -o minio .
./minio --version
```
For other installation paths—including native packages, binaries, Podman, Kubernetes, source, and Pigsty Ansible—use [Download & Install](https://silo.pgsty.com/download/). For production deployment and administration, start with the [Silo documentation](https://silo.pgsty.com/docs/). Pigsty users can also use the [Pigsty MinIO module](https://pigsty.io/docs/minio/).
## Security
Security fixes target the active development branch and are recorded in the [advisory log](docs/security/advisories.md) and the portal's [security notes](https://silo.pgsty.com/blog/security/). Report vulnerabilities privately as described in [`SECURITY.md`](SECURITY.md) and [`VULNERABILITY_REPORT.md`](VULNERABILITY_REPORT.md). Report issues that also affect upstream MinIO there as well.
Security fixes target the active `master` branch and are recorded in the [advisory log](docs/security/advisories.md) and the portal's [security notes](https://silo.pgsty.com/blog/security/). Report vulnerabilities privately as described in [`SECURITY.md`](SECURITY.md) and [`VULNERABILITY_REPORT.md`](VULNERABILITY_REPORT.md). Report issues that also affect upstream MinIO there as well.
## Contributing
@@ -244,8 +150,6 @@ Useful contributions include security and dependency updates, reproducible bug f
Issues and pull requests should include the affected version, reproduction steps, impact, expected behavior, tests, and compatibility notes. Discuss large changes in an issue first.
There is no CLA: contributions are accepted inbound=outbound under the project license (AGPL-3.0-or-later) and contributors keep their copyright. Every commit must be signed off (`git commit -s`) per the [Developer Certificate of Origin](https://developercertificate.org/); see [`CONTRIBUTING.md`](CONTRIBUTING.md).
## Background
This project was created in response to changes in the upstream community distribution and maintenance model. The maintainers analysis, alternatives considered, and early maintenance record are documented below:
@@ -253,14 +157,9 @@ This project was created in response to changes in the upstream community distri
| Essay | Subject |
| :-- | :-- |
| [MinIO Is Dead](https://silo.pgsty.com/blog/post/minio-is-dead/) | Changes to the upstream project and distribution model |
| [MinIO Is Dead, Who Takes Over?](https://silo.pgsty.com/blog/post/minio-alternative/) | Alternatives considered |
| [MinIO Is Dead, Long Live MinIO](https://silo.pgsty.com/blog/post/minio-resurrect/) | Establishing the fork and its release pipeline |
| [Two months into maintaining a MinIO fork](https://silo.pgsty.com/blog/post/minio-promise-kept/) | Initial security and maintenance work |
## License and Trademark
The server source is distributed under the [GNU Affero General Public License v3.0 or later](LICENSE). This fork derives from [`minio/minio`](https://github.com/minio/minio): [`NOTICE`](NOTICE) retains the upstream product notice, [`CREDITS`](CREDITS) records licenses and notices for included third-party components, and the Git history records downstream modifications.
MinIO is a trademark of MinIO, Inc. The name is used here only to identify the upstream project and compatibility lineage. Pigsty, Silo, and `pgsty/silo` are independent community efforts and are not affiliated with, endorsed by, or sponsored by MinIO, Inc.
The portal separately publishes the project [license summary](https://silo.pgsty.com/about/license/), [documentation attribution](https://silo.pgsty.com/about/attribution/), and [trademark notice](https://silo.pgsty.com/about/trademark/).
The server remains licensed under the [GNU Affero General Public License v3.0](LICENSE). See [`CREDITS`](CREDITS) for upstream authorship and attribution. MinIO is a trademark of MinIO, Inc. Silo and `pgsty/minio` are independent community efforts and are not affiliated with or endorsed by MinIO, Inc.
+46 -135
View File
@@ -1,70 +1,62 @@
> [!WARNING]
> **本分支已归档,不再接受任何修改。**
>
> `minio` 分支保留本项目以 MinIO 身份存在的最后状态。开发已迁移到 **[`main`](https://github.com/pgsty/silo/tree/main)** 分支,项目在那里以 **Silo** 的身份继续维护,二进制、软件包、systemd unit、容器镜像与 Helm chart 全部更名为 `silo`。2026-08-06,仓库由 `pgsty/minio` 更名为 **[`pgsty/silo`](https://github.com/pgsty/silo)**,默认分支由 `master` 更名为 `main`。
>
> 本分支上切出的最后一个版本是 **[`RELEASE.2026-08-04T00-00-00Z`](https://github.com/pgsty/silo/releases/tag/RELEASE.2026-08-04T00-00-00Z)**2026-08-04)。它的 19 个资产使用 `minio` 命名,对应容器镜像为 `docker.io/pgsty/minio:RELEASE.2026-08-04T00-00-00Z`。这些产物保持已发布状态且不做改动 —— 不移动、不重新签名、不删除任何 tag。如果你需要以原本 MinIO 形态维持的归档构件,就在本分支以及截止到该 tag 的历次发布中。此后的版本使用 `silo` 命名。
>
> **更名没有改变任何对外接口。** `MINIO_*` 环境变量、`minio_*` 指标、`x-minio-*` 头、`/minio/*` 路由、`.minio.sys` 磁盘布局、IAM 与 ARN 取值,以及 Go 模块路径 `github.com/minio/minio`,在 `main` 上全部原样保留。Silo 服务端可直接读取本版本写入的数据;新旧软件包并存安装,因此迁移与回滚始终是管理员显式触发的动作。
<h1 align="center">
<a href="https://silo.pgsty.com/zh/">
<img src=".github/silo-logo.svg" alt="Silo" width="112"><br>
</a>
<img src=".github/silo.svg" alt="" height="80">
<img src=".github/silo-word.svg" alt="SILO" height="80">
</h1>
<p align="center">
<strong>S3 兼容对象存储 —— 由 PIGSTY 维护的 MinIO 社区分支</strong>
<strong>审慎维护的 MinIO 社区分支</strong><br>
为现有部署提供安全维护、带版本的发行产物与持续运维支持。
</p>
<p align="center">
<a href="https://silo.pgsty.com/zh/">官网</a> ·
<a href="https://silo.pgsty.com/zh/docs/">文档</a> ·
<a href="https://silo.pgsty.com/zh/download/">下载</a> ·
<a href="https://silo.pgsty.com/zh/tags/silo/">版本说明</a> ·
<a href="https://silo.pgsty.com/zh/compatibility/server/">兼容性</a> ·
<a href="https://silo.pgsty.com/zh/blog/">博客</a> ·
<a href="https://github.com/pgsty/minio/releases">版本发布</a> ·
<a href="SECURITY.md">安全策略</a> ·
<a href="README.md">English</a>
</p>
<p align="center">
<a href="https://silo.pgsty.com/zh/"><img alt="官网" src="https://img.shields.io/badge/%E5%AE%98%E7%BD%91-silo.pgsty.com%2Fzh-1d588c"></a>
<a href="https://github.com/pgsty/silo/releases"><img alt="GitHub Release" src="https://img.shields.io/github/v/release/pgsty/silo?include_prereleases&label=release&logo=github"></a>
<a href="https://hub.docker.com/r/pgsty/silo"><img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/pgsty/minio?logo=docker"></a>
<a href="go.mod"><img alt="Go Version" src="https://img.shields.io/github/go-mod/go-version/pgsty/silo?logo=go"></a>
<a href="https://github.com/pgsty/minio/releases"><img alt="GitHub Release" src="https://img.shields.io/github/v/release/pgsty/minio?include_prereleases&label=release&logo=github"></a>
<a href="https://hub.docker.com/r/pgsty/minio"><img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/pgsty/minio?logo=docker"></a>
<a href="go.mod"><img alt="Go Version" src="https://img.shields.io/github/go-mod/go-version/pgsty/minio?logo=go"></a>
<a href="LICENSE"><img alt="License" src="https://img.shields.io/badge/license-AGPLv3-blue"></a>
</p>
> [!IMPORTANT]
> Silo 是由 [Pigsty](https://pigsty.cc) 独立维护、从 [`pgsty/silo`](https://github.com/pgsty/silo) 发布的开源 MinIO 社区分支。本项目与 MinIO, Inc. 不存在隶属、背书或赞助关系;文中使用 “MinIO” 仅用于说明上游项目及兼容谱系。
> [!NOTE]
> 2026-08-06,本仓库由 `pgsty/minio` 更名为 `pgsty/silo`,默认分支由 `master` 更名为 `main`。如果你需要以原本 MinIO 形态维持的归档构件,它们位于归档的 [`minio`](https://github.com/pgsty/silo/tree/minio) 分支,以及截止到 [`RELEASE.2026-08-04T00-00-00Z`](https://github.com/pgsty/silo/releases/tag/RELEASE.2026-08-04T00-00-00Z) 的历次发布中;这些资产与 `docker.io/pgsty/minio` 镜像保持已发布状态且不做改动。本次更名只改变产品与交付物名称 —— `MINIO_*` 变量、`minio_*` 指标、`x-minio-*` 头、`/minio/*` 路由与磁盘格式均保持不变。
> Silo 是由 [Pigsty](https://pigsty.cc) 独立维护、从 [`pgsty/minio`](https://github.com/pgsty/minio) 发布的开源 MinIO 社区分支。本项目与 MinIO, Inc. 不存在隶属、背书或赞助关系;文中使用 “MinIO” 仅用于说明上游项目及兼容谱系。
## 概述
Silo 维护一条源自开源 MinIO 服务端的下游版本线,为上游停止社区发行后仍在运行 MinIO 兼容部署的用户提供持续构建与发行产物。Pigsty 使用 Silo 提供对象存储,包括作为可选的 PostgreSQL 备份仓库
Silo 维护一条基于 MinIO [`RELEASE.2025-12-03T12-00-00Z`](https://github.com/minio/minio/releases/tag/RELEASE.2025-12-03T12-00-00Z) 的下游版本线,为上游停止社区发行后仍在运行 MinIO 兼容部署的用户提供持续构建与发行产物
本分支遵循一条原则:**改名的是产品与交付物,不是协议与你的数据。** `silo` 可执行文件、软件包、服务与容器镜像使用新名称,而 S3 API、`MINIO_*` 变量、`minio_*` 指标、`x-minio-*` 头、保留路由与磁盘格式保持不变。具体差异记录在[兼容性说明](https://silo.pgsty.com/zh/compatibility/server/)中
Pigsty 使用本分支提供对象存储,包括 PostgreSQL 备份存储
项目统一门户为 [silo.pgsty.com](https://silo.pgsty.com/zh/),集中提供文档、下载安装、版本与安全动态及项目背景。
项目统一门户为 [silo.pgsty.com](https://silo.pgsty.com/zh/),集中提供文档、下载安装、版本与安全动态及项目背景。中文内容位于 `/zh/`,英文内容位于站点根路径。
## 按需求选择入口
| 需求 | 权威入口 |
| :-- | :-- |
| 项目概览与全站导航 | [Silo 官网](https://silo.pgsty.com/zh/) |
| 安装方式与软件下载 | [下载与安装](https://silo.pgsty.com/zh/download/) |
| 运维、管理、开发与参考指南 | [文档](https://silo.pgsty.com/zh/docs/) |
| 本服务端的版本说明 | [Silo 版本说明](https://silo.pgsty.com/zh/tags/silo/) |
| 与上游 MinIO 的差异 | [Silo vs. MinIO 兼容性说明](https://silo.pgsty.com/zh/compatibility/server/) |
| 项目动态与安全通告 | [博客](https://silo.pgsty.com/zh/blog/) · [版本发布](https://silo.pgsty.com/zh/blog/release/)与[安全通告](https://silo.pgsty.com/zh/blog/security/) |
| 带版本的二进制、校验和与源码归档 | [GitHub Releases](https://github.com/pgsty/silo/releases) |
| 缺陷报告与功能讨论 | [GitHub Issues](https://github.com/pgsty/silo/issues) |
| 项目概览与全站导航 | [Silo 中文门户](https://silo.pgsty.com/zh/)[English](https://silo.pgsty.com/) |
| 安装方式与软件下载 | [下载与安装](https://silo.pgsty.com/zh/download/)[English](https://silo.pgsty.com/download/) |
| 运维、管理、开发与参考指南 | [中文文档](https://silo.pgsty.com/zh/docs/)[English](https://silo.pgsty.com/docs/) |
| 项目动态、版本说明与安全通告 | [博客](https://silo.pgsty.com/zh/blog/),包括[版本发布](https://silo.pgsty.com/zh/blog/release/)与[安全通告](https://silo.pgsty.com/zh/blog/security/) |
| 带版本的二进制、校验和与源码归档 | [GitHub Releases](https://github.com/pgsty/minio/releases) |
| 缺陷报告与功能讨论 | [GitHub Issues](https://github.com/pgsty/minio/issues) |
| 私密漏洞报告 | [`SECURITY.md`](SECURITY.md) 与 [`VULNERABILITY_REPORT.md`](VULNERABILITY_REPORT.md) |
| 许可证、署名与商标信息 | [`LICENSE`](LICENSE) · [`NOTICE`](NOTICE) · [`CREDITS`](CREDITS) · 门户的[许可证](https://silo.pgsty.com/zh/about/license/)、[署名归属](https://silo.pgsty.com/zh/about/attribution/)与[商标政策](https://silo.pgsty.com/zh/about/trademark/)页面 |
## 相关项目
| 仓库 | 说明 |
| :-- | :-- |
| [`pgsty/silo`](https://github.com/pgsty/silo) | 本仓库 —— Silo 对象存储服务器,S3 兼容的 MinIO fork |
| [`pgsty/mc`](https://github.com/pgsty/mc) | Silo 命令行客户端,以 `mcli` 发行、保留 `mc` 命令名 |
| [`pgsty/silo-console`](https://github.com/pgsty/silo-console) | Web 管理控制台,内嵌于服务端构建 |
| [`pgsty/silo-pkg`](https://github.com/pgsty/silo-pkg) | 为 Silo 系 fork 维护的共享 Go 包 |
| [`pgsty/pigsty`](https://github.com/pgsty/pigsty) | Pigsty —— 内置 Silo 作为对象存储的 PostgreSQL 发行版 |
| 许可证、署名与商标信息 | [许可证](https://silo.pgsty.com/zh/about/license/)、[署名归属](https://silo.pgsty.com/zh/about/attribution/)与[商标政策](https://silo.pgsty.com/zh/about/trademark/) |
## 维护政策
@@ -86,21 +78,15 @@ Silo 维护一条源自开源 MinIO 服务端的下游版本线,为上游停
- 商业支持、SLA、7×24 服务或 SUBNET 服务;
- 部署设计、访问控制、监控、备份与恢复。
## 治理
服务端、客户端、控制台与共享包在同一套发布流程下共同维护:提交必须签署 DCO、Pull Request 经过评审、以 `RELEASE.YYYY-MM-DDTHH-MM-SSZ` 标签发布,产物附带校验和、SPDX SBOM、Sigstore 签名清单与 GitHub 构建证明。每个版本都会在门户发布[版本说明](https://silo.pgsty.com/zh/tags/silo/);安全问题按照 [`SECURITY.md`](SECURITY.md) 与仓库[安全公告](docs/security/advisories.md)处理。上游版权、许可证与第三方声明完整保留于 [`LICENSE`](LICENSE)、[`NOTICE`](NOTICE) 与 [`CREDITS`](CREDITS)。
## 兼容策略
Silo 尽量保留:
- `github.com/minio/minio` module path`github.com/minio/*` 导入路径
- MinIO 兼容的 S3 API、线协议、`MINIO_*` 环境变量、指标、协议头、保留路由与存储元数据
- `RELEASE.YYYY-MM-DDTHH-MM-SSZ` 标签,以及容器入口对旧式 `minio …` 参数的转换
- `minio` 可执行文件与 `github.com/minio/minio` module path
- MinIO 兼容的 S3 API、配置、环境变量与命令行约定
- `RELEASE.YYYY-MM-DDTHH-MM-SSZ` 标签容器入口与常见部署方式
Silo 自有交付面统一使用 `silo` 可执行文件、软件包、服务、Helm Chart 与 `pgsty/silo` 容器镜像;原生交付物不会安装 `minio` 二进制别名
兼容性是默认约束。只要不会留下安全问题,Silo 就保留既有的协议、客户端、配置与运维行为;只有在修复重大安全问题确有必要时才会打破兼容,并在版本说明中明确受影响行为与迁移方式。每个版本仍应视为下游升级:锁定版本,阅读[版本说明](https://silo.pgsty.com/zh/tags/silo/)、[兼容性说明](https://silo.pgsty.com/zh/compatibility/server/)与[安全公告](docs/security/advisories.md),保留回滚路径,并在生产使用前完成测试。
兼容性是默认约束。只要不会留下安全问题,Silo 就保留既有的协议、客户端、配置与运维行为;只有在修复重大安全问题确有必要时才会打破兼容,并在版本说明中明确受影响行为与迁移方式。每个版本仍应视为下游升级:锁定版本,阅读[版本说明](https://silo.pgsty.com/zh/blog/release/)与[安全公告](docs/security/advisories.md),保留回滚路径,并在生产使用前完成测试
## 下载与发行产物
@@ -108,84 +94,13 @@ Silo 自有交付面统一使用 `silo` 可执行文件、软件包、服务、H
| 产物 | 位置 |
| :-- | :-- |
| 源码 | [`github.com/pgsty/silo`](https://github.com/pgsty/silo) |
| 容器镜像 | [`pgsty/silo`](https://hub.docker.com/r/pgsty/silo),支持 `linux/amd64``linux/arm64` 多架构清单 |
| 服务端二进制与校验和 | [GitHub Releases](https://github.com/pgsty/silo/releases),覆盖 Linux、macOS、Windows 的 `amd64``arm64` |
| 源码 | [`github.com/pgsty/minio`](https://github.com/pgsty/minio) |
| 容器镜像 | [`pgsty/minio`](https://hub.docker.com/r/pgsty/minio),支持 `linux/amd64``linux/arm64` 多架构清单 |
| 服务端二进制与校验和 | [GitHub Releases](https://github.com/pgsty/minio/releases),覆盖 Linux、macOS、Windows 的 `amd64``arm64` |
| Linux 软件包 | RPM、DEB、APK,并通过 [Pigsty 软件仓库](https://pigsty.cc/docs/repo/) 分发 |
| 客户端 | [`pgsty/mc`](https://github.com/pgsty/mc),容器内以 `mcli` 提供,并保留 `mc` 兼容别名 |
| 管理控制台 | [`pgsty/silo-console`](https://github.com/pgsty/silo-console),通过兼容导入路径 `github.com/minio/console` 嵌入服务端构建 |
| 共享库 | [`pgsty/silo-pkg`](https://github.com/pgsty/silo-pkg) v3.11.0,通过 `replace` 指令使用,同时保留 `github.com/minio/pkg/v3` 导入路径 |
每个新版本都会为各平台归档和软件包发布 SPDX JSON SBOM。归档与软件包的校验和清单分别带有无长期密钥的 Sigstore 签名包;GitHub 制品证明则为全部可下载产物及多架构容器镜像记录已签名的构建来源。
下载归档及配套文件后,请分别验证完整性、已发布 SBOM、签名清单与构建来源:
```bash
# 完整性:选择与你下载产物相符的一行。
grep -F ' silo_<version>_linux_amd64.tar.gz' \
silo_<version>_checksums.txt | sha256sum --check
# 归档 SBOM 是另一个独立校验的 Release 产物。
grep -F ' silo_<version>_linux_amd64.tar.gz.sbom.json' \
silo_<version>_checksums.txt | sha256sum --check
# 验证归档/SBOM 校验和清单的签名。
cosign verify-blob \
--bundle silo_<version>_checksums.txt.sigstore.json \
--certificate-identity-regexp \
'^https://github.com/pgsty/(minio|silo)/\.github/workflows/release\.yml@refs/(tags/RELEASE\..+|heads/(master|main))$' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
silo_<version>_checksums.txt
# 在线验证本仓库签发的构建来源。
gh attestation verify silo_<version>_linux_amd64.tar.gz \
--repo pgsty/silo
```
软件包使用 `silo_<version>_packages_checksums.txt` 及其相邻的 Sigstore
签名包,并使用相同的 identity 与 issuer 约束;请用 SPDX 工具检查已验证的
SPDX JSON SBOM。按 digest 验证多架构容器清单的构建来源:
```bash
gh attestation verify \
oci://index.docker.io/pgsty/silo@sha256:<manifest-digest> \
--repo pgsty/silo
```
分架构 SBOM 证明附在 `amd64``arm64` 平台镜像的 digest 上,而非多架构
清单上,需要分别显式验证:
```bash
gh attestation verify \
oci://index.docker.io/pgsty/silo@sha256:<platform-digest> \
--repo pgsty/silo \
--predicate-type https://spdx.dev/Document/v2.3
```
按 digest 验证可避免信任可变镜像标签。
### 原生软件包迁移
`silo` RPM、DEB 与 APK 不针对 `minio` 声明 `Provides``Obsoletes`
`Replaces` 或包级 `Conflicts`,因此可以与已有 MinIO 软件包并存安装,
不会被普通升级静默替换。两个 systemd unit 在运行时互斥,应由管理员显式
切换,不能同时启动。
切换前请记录旧 unit 的 enabled/active 状态与 `User`/`Group`,并备份
`/etc/default/minio`。Silo 先读取该旧配置,再读取 `/etc/default/silo`;后者
中由管理员设置的同名变量优先。如现有数据必须继续使用原 UID/GID,请创建
`/etc/systemd/system/silo.service.d/10-legacy-user.conf`
```ini
[Service]
User=<legacy-user>
Group=<legacy-group>
```
执行 `systemctl daemon-reload`,先停用并停止 `minio.service`,再启用并启动
`silo.service`。确认健康检查、S3、Admin API、指标与日志后,才 mask 或卸载
旧服务。软件包迁移期间不要递归修改数据属主;在回滚观察窗口内保留旧包与
旧 unit。
| 管理控制台 | 社区维护的 [`georgmangold/console`](https://github.com/georgmangold/console)嵌入服务端构建 |
| 共享库 | [`pgsty/silo-pkg`](https://github.com/pgsty/silo-pkg) v3.7.0,通过 `replace` 指令使用,同时保留 `github.com/minio/pkg/v3` 导入路径[版本说明](https://silo.pgsty.com/zh/blog/release/pkg-3.7.0/) |
## 快速开始
@@ -203,7 +118,7 @@ docker run -d --name silo \
-e MINIO_ROOT_USER \
-e MINIO_ROOT_PASSWORD \
-v "$PWD/data:/data" \
docker.io/pgsty/silo:latest server /data --console-address ":9001"
pgsty/minio:latest server /data --console-address ":9001"
```
管理控制台位于 <http://localhost:9001>S3 API 位于 <http://localhost:9000>。
@@ -223,24 +138,22 @@ docker exec silo mcli ls local
从源码构建服务端:
```bash
go build -o silo .
./silo --version
go build -o minio .
./minio --version
```
其他安装方式(包括原生软件包、二进制、Podman、Kubernetes、源码构建与 Pigsty Ansible)请前往[下载与安装](https://silo.pgsty.com/zh/download/);生产部署与管理请从 [Silo 文档](https://silo.pgsty.com/zh/docs/)开始。Pigsty 用户也可以直接使用 [Pigsty MinIO 模块](https://pigsty.cc/docs/minio/)。
其他安装方式(包括原生软件包、二进制、Podman、Kubernetes、源码构建与 Pigsty Ansible)请前往[下载与安装](https://silo.pgsty.com/zh/download/);生产部署与管理请从 [Silo 中文文档](https://silo.pgsty.com/zh/docs/)开始。Pigsty 用户也可以直接使用 [Pigsty MinIO 模块](https://pigsty.cc/docs/minio/)。
## 安全
安全修复面向当前活跃开发分支,并记录在仓库[安全公告](docs/security/advisories.md)与门户[安全通告](https://silo.pgsty.com/zh/blog/security/)中。请按照 [`SECURITY.md`](SECURITY.md) 与 [`VULNERABILITY_REPORT.md`](VULNERABILITY_REPORT.md) 私密报告漏洞;同时影响上游 MinIO 的问题也应向上游报告。
安全修复面向活跃的 `master` 分支,并记录在仓库[安全公告](docs/security/advisories.md)与门户[安全通告](https://silo.pgsty.com/zh/blog/security/)中。请按照 [`SECURITY.md`](SECURITY.md) 与 [`VULNERABILITY_REPORT.md`](VULNERABILITY_REPORT.md) 私密报告漏洞;同时影响上游 MinIO 的问题也应向上游报告。
## 贡献
## 参与贡献
欢迎安全与依赖项更新、可复现缺陷修复、测试、发布自动化、打包与文档改进。
Issue 与 Pull Request 应说明受影响版本、复现步骤、影响、预期行为、测试与兼容性说明。大型改动请先提交 Issue 讨论。
本项目不要求签署 CLA:贡献按项目许可证(AGPL-3.0-or-laterinbound=outbound)接收,贡献者保留自己的版权。每个提交都必须按照 [DCO](https://developercertificate.org/) 签署(`git commit -s`),详见 [`CONTRIBUTING.md`](CONTRIBUTING.md)。
## 背景
本项目源于上游社区发行与维护模式的变化。维护者对相关变化的分析、替代方案评估与早期维护记录见以下文章:
@@ -254,8 +167,6 @@ Issue 与 Pull Request 应说明受影响版本、复现步骤、影响、预期
## 许可证与商标
服务端源码依据 [GNU Affero 通用公共许可证第 3 版或任何后续版本](LICENSE)分发。本分支衍生自 [`minio/minio`](https://github.com/minio/minio)[`NOTICE`](NOTICE) 保留上游产品声明,[`CREDITS`](CREDITS) 记录所含第三方组件的许可证与声明,Git 历史记录下游修改
服务端继续采用 [GNU Affero General Public License v3.0](LICENSE) 发布。上游作者与署名信息见 [`CREDITS`](CREDITS)。
MinIO 是 MinIO, Inc. 的商标;本文仅为标识上游项目与兼容谱系而使用该名称。Pigsty、Silo`pgsty/silo` 均为独立社区项目,与 MinIO, Inc. 不存在隶属背书或赞助关系。
门户另行发布项目的[许可证摘要](https://silo.pgsty.com/zh/about/license/)、[文档署名归属](https://silo.pgsty.com/zh/about/attribution/)与[商标声明](https://silo.pgsty.com/zh/about/trademark/)。
MinIO 是 MinIO, Inc. 的商标。Silo、Pigsty 与 `pgsty/minio` 均为独立社区项目,与 MinIO, Inc. 不存在隶属背书关系。
+4 -10
View File
@@ -1,24 +1,18 @@
# Security Policy
Silo is an independent, community-maintained object-storage server derived from
the open-source MinIO server. Upstream MinIO security contacts do not handle
Silo-specific fixes or release notes.
This repository is the `pgsty/minio` community fork of `minio/minio`. Upstream MinIO security contacts do not handle fork-specific fixes or release notes for this repository.
## Supported Versions
Security fixes are tracked on the active development branch and summarized in
[docs/security/advisories.md](docs/security/advisories.md). Only the current
Silo release line is supported unless an advisory says otherwise.
Security fixes are tracked on the active `master` branch and summarized in [docs/security/advisories.md](docs/security/advisories.md).
## Reporting a Vulnerability
For vulnerabilities in this fork:
1. Follow the fork-specific expectations in [VULNERABILITY_REPORT.md](VULNERABILITY_REPORT.md).
2. Prefer this repository's [private GitHub security advisory](https://github.com/pgsty/silo/security/advisories/new) workflow.
3. If private reporting is unavailable, contact the maintainers through the
repository without publishing exploit details until a private channel is
established.
2. Prefer the `pgsty/minio` repository's GitHub security reporting workflow when it is available.
3. If private reporting is not available, contact the maintainers through the `pgsty/minio` repository before publishing detailed exploit information.
4. If you confirm the issue also affects upstream `minio/minio`, report it upstream separately.
## Disclosure Process
+3 -3
View File
@@ -1,6 +1,6 @@
# Vulnerability Management Policy
This document describes how the Silo maintainers investigate,
This document describes how the `pgsty/minio` maintainers investigate,
assess, and remediate reported vulnerabilities affecting this fork, any
directly shipped component, or a direct / indirect dependency used by this
repository.
@@ -8,7 +8,7 @@ repository.
## Scope
This policy covers vulnerability reports opened by repository maintainers or
external third parties against Silo itself, its release artifacts, or
external third parties against `pgsty/minio` itself, its release artifacts, or
dependencies that materially affect this fork.
It defines the information needed for triage and the expected remediation
@@ -24,7 +24,7 @@ A useful vulnerability report should contain the following information:
a well-established vulnerability identifier, such as a CVE or GHSA ID, can
be used instead.
Based on the report, the Silo maintainers investigate:
Based on the report, the `pgsty/minio` maintainers investigate:
- Whether the reported vulnerability exists.
- The conditions that are required such that the vulnerability can be exploited.
+1
View File
@@ -0,0 +1 @@
theme: jekyll-theme-minimal
+23 -22
View File
@@ -3,7 +3,7 @@
set -x
export MINIO_CI_CD=1
killall -9 silo
killall -9 minio
rm -rf ${HOME}/tmp/dist
@@ -19,27 +19,28 @@ done
echo $args
for ((i = 0; i < $((nr_servers)); i++)); do
(silo server --address ":$((9100 + i))" $args 2>&1 >/tmp/log$i.txt) &
(minio server --address ":$((9100 + i))" $args 2>&1 >/tmp/log$i.txt) &
done
sleep 10s
if [ ! -f ./mc ]; then
"$(git rev-parse --show-toplevel)/buildscripts/install-mcli.sh" ./mc
wget --quiet -O ./mc https://dl.minio.io/client/mc/release/linux-amd64/./mc &&
chmod +x mc
fi
set +e
export MC_HOST_siloadm=http://minioadmin:minioadmin@localhost:9100/
./mc ready siloadm
export MC_HOST_minioadm=http://minioadmin:minioadmin@localhost:9100/
./mc ready minioadm
./mc ls siloadm/
./mc ls minioadm/
./mc admin config set siloadm/ api root_access=off
./mc admin config set minioadm/ api root_access=off
sleep 3s # let things settle a little
./mc ls siloadm/
./mc ls minioadm/
if [ $? -eq 0 ]; then
echo "listing succeeded, 'minioadmin' was not disabled"
exit 1
@@ -47,38 +48,38 @@ fi
set -e
killall -9 silo
killall -9 minio
export MINIO_API_ROOT_ACCESS=on
for ((i = 0; i < $((nr_servers)); i++)); do
(silo server --address ":$((9100 + i))" $args 2>&1 >/tmp/log$i.txt) &
(minio server --address ":$((9100 + i))" $args 2>&1 >/tmp/log$i.txt) &
done
set +e
./mc ready siloadm/
./mc ready minioadm/
./mc ls siloadm/
./mc ls minioadm/
if [ $? -ne 0 ]; then
echo "listing failed, 'minioadmin' should be enabled"
exit 1
fi
killall -9 silo
killall -9 minio
rm -rf /tmp/multisitea/
rm -rf /tmp/multisiteb/
echo "Setup site-replication and then disable root credentials"
silo server --address 127.0.0.1:9001 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
minio server --address 127.0.0.1:9001 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
"http://127.0.0.1:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_1.log 2>&1 &
silo server --address 127.0.0.1:9002 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
minio server --address 127.0.0.1:9002 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
"http://127.0.0.1:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_2.log 2>&1 &
silo server --address 127.0.0.1:9003 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
minio server --address 127.0.0.1:9003 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_1.log 2>&1 &
silo server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
minio server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_2.log 2>&1 &
export MC_HOST_sitea=http://minioadmin:minioadmin@127.0.0.1:9001
@@ -95,19 +96,19 @@ export MC_HOST_siteb=http://minioadmin:minioadmin@127.0.0.1:9004
./mc admin user info siteb foobar
killall -9 silo
killall -9 minio
echo "turning off root access, however site replication must continue"
export MINIO_API_ROOT_ACCESS=off
silo server --address 127.0.0.1:9001 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
minio server --address 127.0.0.1:9001 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
"http://127.0.0.1:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_1.log 2>&1 &
silo server --address 127.0.0.1:9002 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
minio server --address 127.0.0.1:9002 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
"http://127.0.0.1:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_2.log 2>&1 &
silo server --address 127.0.0.1:9003 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
minio server --address 127.0.0.1:9003 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_1.log 2>&1 &
silo server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
minio server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_2.log 2>&1 &
export MC_HOST_sitea=http://foobar:foo12345@127.0.0.1:9001
-125
View File
@@ -1,125 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Regenerates CREDITS from the license text of every Go module linked into the
# silo binary. The module set is what `go list -deps` reports for the main
# package, so test-only and tool dependencies stay out: CREDITS documents what
# a shipped binary actually contains. check-gen runs this and fails on a diff,
# which keeps CREDITS from drifting when go.mod changes.
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_dir="$(cd "${script_dir}/.." && pwd)"
cd "${repo_dir}"
out_file="${1:-${repo_dir}/CREDITS}"
tmp_file="${out_file}.tmp"
trap 'rm -f "${tmp_file}"' EXIT
rule_dash='----------------------------------------------------------------'
rule_equal='================================================================'
# These modules repackage Go standard library code and publish no license
# file; their source files carry the Go Authors' BSD-style header pointing at
# the Go project license, so that text is reproduced for them.
stdlib_derived='github.com/minio/colorjson github.com/minio/csvparser github.com/minio/filepath'
is_stdlib_derived() {
case " ${stdlib_derived} " in
*" $1 "*) return 0 ;;
*) return 1 ;;
esac
}
# Command substitution strips trailing newlines; printf adds exactly one back,
# so every entry ends the same way regardless of how the license file ends.
emit_text() {
printf '%s\n' "$(cat "$1")"
}
# The module cache must hold every dependency before .Dir can resolve.
go mod download
# The Go project license text is taken from the pinned golang.org/x/sys module
# rather than GOROOT: Homebrew's Go does not ship GOROOT/LICENSE, and the
# module copy is version-locked by go.mod, so the output cannot vary with the
# machine's toolchain packaging.
go_license="$(go list -m -f '{{.Dir}}' golang.org/x/sys)/LICENSE"
if [ ! -f "${go_license}" ]; then
echo "Missing Go license text: ${go_license}" >&2
exit 1
fi
{
printf '%s\n' \
'Silo bundles third-party software under the licenses reproduced below.' \
'Generated by buildscripts/gen-credits.sh (make credits) from the Go' \
'modules linked into the silo binary. Do not edit by hand.' \
'' \
"${rule_equal}" \
''
printf '%s\n%s\n%s\n' 'Go (the standard library)' 'https://golang.org/' "${rule_dash}"
emit_text "${go_license}"
printf '\n%s\n\n' "${rule_equal}"
# The dependency closure is GOOS/GOARCH-specific: platform-only modules
# (darwin's go-m1cpu, windows' wmi, ...) enter and leave it with the host.
# Pin the primary shipped target and the release build tags so regenerating
# CREDITS produces identical output on every machine, including CI.
GOOS=linux GOARCH=amd64 go list -deps -tags kqueue \
-f '{{if and (not .Standard) .Module}}{{.Module.Path}}{{end}}' . \
| LC_ALL=C sort -u \
| grep -vx 'github.com/minio/minio' \
| xargs go list -m -f '{{.Path}}|{{with .Replace}}{{.Path}}{{end}}|{{.Dir}}' \
| while IFS='|' read -r path replacement dir; do
if [ -z "${dir}" ] || [ ! -d "${dir}" ]; then
echo "Module cache directory missing for ${path}" >&2
exit 1
fi
name="${path}"
url="https://${path}"
# A same-path replace only pins a version; the annotation is for
# dependencies actually served from a different repository.
if [ -n "${replacement}" ] && [ "${replacement}" != "${path}" ]; then
name="${path} (replaced by ${replacement})"
url="https://${replacement}"
fi
printf '%s\n%s\n%s\n' "${name}" "${url}" "${rule_dash}"
if is_stdlib_derived "${path}"; then
printf '%s\n%s\n\n' \
'This module repackages Go standard library code and publishes no' \
'license file; the Go project license below applies per its file headers.'
emit_text "${go_license}"
else
license_file=''
for candidate in LICENSE LICENSE.txt LICENSE.md COPYING COPYING.txt LICENCE UNLICENSE; do
if [ -f "${dir}/${candidate}" ]; then
license_file="${dir}/${candidate}"
break
fi
done
if [ -z "${license_file}" ]; then
echo "No license file found for ${path} in ${dir}" >&2
exit 1
fi
emit_text "${license_file}"
fi
# Apache License 2.0 section 4(d) requires redistributing the NOTICE
# file contents alongside the licensed work.
for notice in NOTICE NOTICE.txt; do
if [ -f "${dir}/${notice}" ]; then
printf '\n%s\n\n' 'Bundled NOTICE file:'
emit_text "${dir}/${notice}"
break
fi
done
printf '\n%s\n\n' "${rule_equal}"
done
} > "${tmp_file}"
mv "${tmp_file}" "${out_file}"
+30 -19
View File
@@ -5,34 +5,45 @@ set -o pipefail
set -x
WORK_DIR="$PWD/.verify-$RANDOM"
SILO_CONFIG_DIR="$WORK_DIR/.silo"
SILO=("$PWD/silo" --config-dir "$SILO_CONFIG_DIR" server)
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
if [ ! -x "$PWD/silo" ]; then
echo "Silo executable binary not found in current directory"
if [ ! -x "$PWD/minio" ]; then
echo "minio executable binary not found in current directory"
exit 1
fi
if [ ! -x "$PWD/silo" ]; then
echo "Silo executable binary not found in current directory"
if [ ! -x "$PWD/minio" ]; then
echo "minio executable binary not found in current directory"
exit 1
fi
function start_silo_4drive() {
function start_minio_4drive() {
start_port=$1
export MINIO_ROOT_USER=silo
export MINIO_ROOT_PASSWORD=silo123
export MC_HOST_silo="http://silo:silo123@127.0.0.1:${start_port}/"
export MINIO_ROOT_USER=minio
export MINIO_ROOT_PASSWORD=minio123
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
export MINIO_CI_CD=1
mkdir ${WORK_DIR}
C_PWD=${PWD}
if [ ! -x "$PWD/mc" ]; then
"$(git rev-parse --show-toplevel)/buildscripts/install-mcli.sh" "$PWD/mc"
MC_BUILD_DIR="mc-$RANDOM"
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
echo "failed to download https://github.com/minio/mc"
purge "${MC_BUILD_DIR}"
exit 1
fi
(cd "${MC_BUILD_DIR}" && go build -o "$C_PWD/mc")
# remove mc source.
purge "${MC_BUILD_DIR}"
fi
"${SILO[@]}" --address ":$start_port" "${WORK_DIR}/disk{1...4}" >"${WORK_DIR}/server1.log" 2>&1 &
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/disk{1...4}" >"${WORK_DIR}/server1.log" 2>&1 &
pid=$!
disown $pid
sleep 5
@@ -45,30 +56,30 @@ function start_silo_4drive() {
exit 1
fi
"${PWD}/mc" mb --with-versioning silo/bucket
"${PWD}/mc" mb --with-versioning minio/bucket
for i in $(seq 1 4); do
"${PWD}/mc" cp /etc/hosts silo/bucket/testobj
"${PWD}/mc" cp /etc/hosts minio/bucket/testobj
sudo chown -R root. "${WORK_DIR}/disk${i}"
"${PWD}/mc" cp /etc/hosts silo/bucket/testobj
"${PWD}/mc" cp /etc/hosts minio/bucket/testobj
sudo chown -R ${USER}. "${WORK_DIR}/disk${i}"
done
for vid in $("${PWD}/mc" ls --json --versions silo/bucket/testobj | jq -r .versionId); do
"${PWD}/mc" cat --vid "${vid}" silo/bucket/testobj | md5sum
for vid in $("${PWD}/mc" ls --json --versions minio/bucket/testobj | jq -r .versionId); do
"${PWD}/mc" cat --vid "${vid}" minio/bucket/testobj | md5sum
done
pkill silo
pkill minio
sleep 3
}
function main() {
start_port=$(shuf -i 10000-65000 -n 1)
start_silo_4drive ${start_port}
start_minio_4drive ${start_port}
}
function purge() {
-331
View File
@@ -1,331 +0,0 @@
// Copyright 2026 PGSTY contributors.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// helm-migration-guard compares a rendered legacy MinIO chart with the Silo
// upgrade candidate. Product labels, images, and commands may change; resource
// identity, selectors, PVCs, storage mounts, ports, secrets, and service-account
// references must not.
package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
"sort"
"strings"
"go.yaml.in/yaml/v3"
)
type resource struct {
key string
doc map[string]any
}
func main() {
if len(os.Args) != 3 {
fatal(errors.New("usage: helm-migration-guard OLD_RENDER NEW_RENDER"))
}
oldResources, err := readResources(os.Args[1])
if err != nil {
fatal(err)
}
newResources, err := readResources(os.Args[2])
if err != nil {
fatal(err)
}
if err := compare(oldResources, newResources); err != nil {
fatal(err)
}
fmt.Printf("Silo Helm migration identity is stable across %d rendered resources\n", len(oldResources))
}
func readResources(path string) (map[string]resource, error) {
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("open %s: %w", path, err)
}
defer file.Close()
resources := make(map[string]resource)
decoder := yaml.NewDecoder(file)
for document := 1; ; document++ {
var doc map[string]any
err = decoder.Decode(&doc)
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return nil, fmt.Errorf("decode %s document %d: %w", path, document, err)
}
if len(doc) == 0 || text(doc["kind"]) == "" {
continue
}
metadata := object(doc["metadata"])
key := strings.Join([]string{text(doc["kind"]), text(metadata["namespace"]), text(metadata["name"])}, "/")
if _, exists := resources[key]; exists {
return nil, fmt.Errorf("%s contains duplicate resource %s", path, key)
}
resources[key] = resource{key: key, doc: doc}
}
return resources, nil
}
func compare(oldResources, newResources map[string]resource) error {
for key := range oldResources {
if _, ok := newResources[key]; !ok {
return fmt.Errorf("legacy resource would be removed or renamed: %s", key)
}
}
for key := range newResources {
if _, ok := oldResources[key]; !ok {
return fmt.Errorf("upgrade candidate unexpectedly adds a resource: %s", key)
}
}
keys := make([]string, 0, len(oldResources))
for key := range oldResources {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
oldDoc := oldResources[key].doc
newDoc := newResources[key].doc
kind := text(oldDoc["kind"])
switch kind {
case "Service":
if err := same(key, "Service selector", at(oldDoc, "spec", "selector"), at(newDoc, "spec", "selector")); err != nil {
return err
}
if err := same(key, "Service ports", at(oldDoc, "spec", "ports"), at(newDoc, "spec", "ports")); err != nil {
return err
}
case "Deployment", "StatefulSet":
if err := same(key, "workload selector", at(oldDoc, "spec", "selector"), at(newDoc, "spec", "selector")); err != nil {
return err
}
}
if kind == "StatefulSet" {
if err := same(key, "StatefulSet serviceName", at(oldDoc, "spec", "serviceName"), at(newDoc, "spec", "serviceName")); err != nil {
return err
}
if err := same(key, "volume claim templates", claimTemplates(oldDoc), claimTemplates(newDoc)); err != nil {
return err
}
}
if kind == "PersistentVolumeClaim" {
if err := same(key, "PVC specification", at(oldDoc, "spec"), at(newDoc, "spec")); err != nil {
return err
}
}
if kind == "Secret" {
if err := same(key, "Secret keys", secretKeys(oldDoc), secretKeys(newDoc)); err != nil {
return err
}
}
if kind == "Deployment" || kind == "StatefulSet" || kind == "Job" {
if err := comparePod(key, kind, oldDoc, newDoc); err != nil {
return err
}
}
}
return nil
}
func comparePod(key, kind string, oldDoc, newDoc map[string]any) error {
oldPod := object(at(oldDoc, "spec", "template", "spec"))
newPod := object(at(newDoc, "spec", "template", "spec"))
if err := same(key, "service account", oldPod["serviceAccountName"], newPod["serviceAccountName"]); err != nil {
return err
}
if err := same(key, "referenced volume sources", volumeSources(oldPod), volumeSources(newPod)); err != nil {
return err
}
oldContainers := containers(oldPod)
newContainers := containers(newPod)
if err := same(key, "container identities", sortedKeys(oldContainers), sortedKeys(newContainers)); err != nil {
return err
}
for _, name := range sortedKeys(oldContainers) {
oldContainer := oldContainers[name]
newContainer := newContainers[name]
if err := same(key, name+" ports", oldContainer["ports"], newContainer["ports"]); err != nil {
return err
}
if err := same(key, name+" environment", oldContainer["env"], newContainer["env"]); err != nil {
return err
}
if err := same(key, name+" envFrom", oldContainer["envFrom"], newContainer["envFrom"]); err != nil {
return err
}
if err := same(key, name+" storage mounts", normalizedMounts(oldContainer, oldPod), normalizedMounts(newContainer, newPod)); err != nil {
return err
}
image := text(newContainer["image"])
if strings.HasPrefix(image, "pgsty/minio:") || strings.HasPrefix(image, "docker.io/pgsty/minio:") {
return fmt.Errorf("%s container %s still uses frozen image %s", key, name, image)
}
command := commandText(newContainer)
if strings.Contains(command, "/usr/bin/minio") {
return fmt.Errorf("%s container %s still invokes /usr/bin/minio", key, name)
}
if (kind == "Deployment" || kind == "StatefulSet") && strings.Contains(image, "pgsty/silo:") {
if !strings.Contains(command, "silo") || !strings.Contains(command, "server") {
return fmt.Errorf("%s container %s does not invoke the Silo server: %q", key, name, command)
}
}
}
return nil
}
func claimTemplates(doc map[string]any) []string {
var result []string
for _, raw := range list(at(doc, "spec", "volumeClaimTemplates")) {
claim := object(raw)
metadata := object(claim["metadata"])
result = append(result, text(metadata["name"])+"="+canonical(claim["spec"]))
}
sort.Strings(result)
return result
}
func secretKeys(doc map[string]any) []string {
var result []string
for _, section := range []string{"data", "stringData"} {
for key := range object(doc[section]) {
result = append(result, section+":"+key)
}
}
sort.Strings(result)
return result
}
func volumeSources(pod map[string]any) []string {
var result []string
for _, raw := range list(pod["volumes"]) {
volume := cloneObject(object(raw))
delete(volume, "name")
result = append(result, canonical(volume))
}
sort.Strings(result)
return result
}
func volumeSourceByName(pod map[string]any) map[string]string {
result := make(map[string]string)
for _, raw := range list(pod["volumes"]) {
volume := cloneObject(object(raw))
name := text(volume["name"])
delete(volume, "name")
result[name] = canonical(volume)
}
return result
}
func normalizedMounts(container, pod map[string]any) []string {
sources := volumeSourceByName(pod)
var result []string
for _, raw := range list(container["volumeMounts"]) {
mount := cloneObject(object(raw))
name := text(mount["name"])
delete(mount, "name")
mount["source"] = sources[name]
result = append(result, canonical(mount))
}
sort.Strings(result)
return result
}
func containers(pod map[string]any) map[string]map[string]any {
result := make(map[string]map[string]any)
for _, section := range []string{"initContainers", "containers"} {
for _, raw := range list(pod[section]) {
container := object(raw)
result[section+":"+text(container["name"])] = container
}
}
return result
}
func commandText(container map[string]any) string {
var parts []string
for _, field := range []string{"command", "args"} {
for _, value := range list(container[field]) {
parts = append(parts, text(value))
}
}
return strings.Join(parts, " ")
}
func at(root map[string]any, path ...string) any {
var current any = root
for _, part := range path {
current = object(current)[part]
}
return current
}
func object(value any) map[string]any {
if value == nil {
return map[string]any{}
}
result, _ := value.(map[string]any)
return result
}
func cloneObject(value map[string]any) map[string]any {
result := make(map[string]any, len(value))
for key, item := range value {
result[key] = item
}
return result
}
func list(value any) []any {
result, _ := value.([]any)
return result
}
func text(value any) string {
result, _ := value.(string)
return result
}
func sortedKeys[T any](values map[string]T) []string {
result := make([]string, 0, len(values))
for key := range values {
result = append(result, key)
}
sort.Strings(result)
return result
}
func same(resourceKey, field string, oldValue, newValue any) error {
oldCanonical := canonical(oldValue)
newCanonical := canonical(newValue)
if oldCanonical != newCanonical {
return fmt.Errorf("%s changes %s\nold: %s\nnew: %s", resourceKey, field, oldCanonical, newCanonical)
}
return nil
}
func canonical(value any) string {
data, err := json.Marshal(value)
if err != nil {
return fmt.Sprintf("<unmarshalable %T: %v>", value, err)
}
return string(data)
}
func fatal(err error) {
fmt.Fprintf(os.Stderr, "Silo Helm migration check failed: %v\n", err)
os.Exit(1)
}
-91
View File
@@ -1,91 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
if [ "$#" -ne 1 ]; then
echo "usage: $0 TARGET" >&2
exit 2
fi
target=$1
target_dir=$(dirname "${target}")
if [ ! -d "${target_dir}" ]; then
echo "target directory does not exist: ${target_dir}" >&2
exit 1
fi
sha256_file() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$1" | awk '{print $1}'
else
shasum -a 256 "$1" | awk '{print $1}'
fi
}
if [ -n "${MCLI_BIN:-}" ]; then
if [ ! -f "${MCLI_BIN}" ]; then
echo "MCLI_BIN is not a regular file: ${MCLI_BIN}" >&2
exit 1
fi
if ! printf '%s\n' "${MCLI_SHA256:-}" | grep -Eq '^[0-9a-fA-F]{64}$'; then
echo "MCLI_SHA256 must contain the expected SHA-256 for MCLI_BIN" >&2
exit 1
fi
actual=$(sha256_file "${MCLI_BIN}")
if [ "${actual}" != "${MCLI_SHA256,,}" ]; then
echo "MCLI_BIN checksum mismatch: expected ${MCLI_SHA256,,}, got ${actual}" >&2
exit 1
fi
install -m 0755 "${MCLI_BIN}" "${target}"
exit 0
fi
release=${MCLI_RELEASE:-RELEASE.2026-08-06T00-00-00Z}
version_hyphen=${release#RELEASE.}
package_version=$(printf '%s\n' "${version_hyphen}" | sed -E 's/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2})-([0-9]{2})-([0-9]{2})Z$/\1\2\3\4\5\6.0.0/')
if [ "${package_version}" = "${version_hyphen}" ]; then
echo "invalid MCLI_RELEASE: ${release}" >&2
exit 1
fi
case $(uname -s) in
Linux) os=linux ;;
Darwin) os=darwin ;;
*) echo "unsupported mcli host OS: $(uname -s)" >&2; exit 1 ;;
esac
case $(uname -m) in
x86_64 | amd64) arch=amd64 ;;
aarch64 | arm64) arch=arm64 ;;
*) echo "unsupported mcli host architecture: $(uname -m)" >&2; exit 1 ;;
esac
archive="mcli_${package_version}_${os}_${arch}.tar.gz"
checksums="mcli_${package_version}_checksums.txt"
base_url="https://github.com/pgsty/mc/releases/download/${release}"
tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/silo-mcli.XXXXXX")
trap 'rm -rf "${tmp_dir}"' EXIT
curl --fail --location --retry 3 --silent --show-error \
"${base_url}/${checksums}" --output "${tmp_dir}/${checksums}"
curl --fail --location --retry 3 --silent --show-error \
"${base_url}/${archive}" --output "${tmp_dir}/${archive}"
expected=$(awk -v asset="${archive}" '
{
name=$2
sub(/^\*/, "", name)
if (name == asset && $1 ~ /^[0-9a-fA-F]{64}$/) print tolower($1)
}
' "${tmp_dir}/${checksums}")
if ! printf '%s\n' "${expected}" | grep -Eq '^[0-9a-f]{64}$'; then
echo "checksum manifest does not contain exactly one valid entry for ${archive}" >&2
exit 1
fi
actual=$(sha256_file "${tmp_dir}/${archive}")
if [ "${actual}" != "${expected}" ]; then
echo "downloaded ${archive} checksum mismatch" >&2
exit 1
fi
tar -xzf "${tmp_dir}/${archive}" -C "${tmp_dir}" mcli
install -m 0755 "${tmp_dir}/mcli" "${target}"
-48
View File
@@ -1,48 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
if [ "$#" -ne 3 ]; then
echo "usage: $0 SOURCE SHA256 TARGET" >&2
exit 2
fi
source_ref=$1
expected=${2,,}
target=$3
if ! printf '%s\n' "${expected}" | grep -Eq '^[0-9a-f]{64}$'; then
echo "expected checksum must be a lowercase SHA-256 digest" >&2
exit 1
fi
if [ ! -d "$(dirname "${target}")" ]; then
echo "target directory does not exist: $(dirname "${target}")" >&2
exit 1
fi
tmp_file=$(mktemp "${TMPDIR:-/tmp}/silo-fixture.XXXXXX")
trap 'rm -f "${tmp_file}"' EXIT
case ${source_ref} in
https://*)
curl --fail --location --retry 3 --silent --show-error \
"${source_ref}" --output "${tmp_file}"
;;
*)
if [ ! -f "${source_ref}" ]; then
echo "fixture is not a regular file: ${source_ref}" >&2
exit 1
fi
cp "${source_ref}" "${tmp_file}"
;;
esac
if command -v sha256sum >/dev/null 2>&1; then
actual=$(sha256sum "${tmp_file}" | awk '{print $1}')
else
actual=$(shasum -a 256 "${tmp_file}" | awk '{print $1}')
fi
if [ "${actual}" != "${expected}" ]; then
echo "fixture checksum mismatch: expected ${expected}, got ${actual}" >&2
exit 1
fi
install -m 0755 "${tmp_file}" "${target}"
@@ -1,7 +1,7 @@
#!/bin/bash
# This script tests IAM migration from an old MinIO compatibility fixture into
# the current Silo server.
# This script is used to test the migration of IAM content from old minio
# instance to new minio instance.
#
# To run it locally, start the LDAP server in github.com/minio/minio-iam-testing
# repo (e.g. make podman-run), and then run this script.
@@ -15,7 +15,6 @@
OLD_VERSION=RELEASE.2024-03-26T22-10-45Z
OLD_BINARY_LINK=https://dl.min.io/server/minio/release/linux-amd64/archive/minio.${OLD_VERSION}
OLD_BINARY_SHA256=2050199d89e3057571620a1d453118fed5bd2de9d4f3b266b11365fdf984d676
__init__() {
if which curl &>/dev/null; then
@@ -28,16 +27,17 @@ __init__() {
export GOPATH=/tmp/gopath
export PATH="${PATH}":"${GOPATH}"/bin
if [ ! -x "${GOPATH}/bin/mc" ]; then
echo "Installing verified compatible client fixture"
mkdir -p "${GOPATH}/bin"
"$(git rev-parse --show-toplevel)/buildscripts/install-mcli.sh" "${GOPATH}/bin/mc"
if which mc &>/dev/null; then
echo "mc is already installed"
else
echo "Installing mc:"
go install github.com/minio/mc@latest
fi
if [ ! -x ./minio.${OLD_VERSION} ]; then
echo "Installing verified upstream compatibility fixture minio.${OLD_VERSION}"
"$(git rev-parse --show-toplevel)/buildscripts/install-verified-fixture.sh" \
"${OLD_BINARY_LINK}" "${OLD_BINARY_SHA256}" "minio.${OLD_VERSION}"
echo "Downloading minio.${OLD_VERSION} binary"
curl -o minio.${OLD_VERSION} ${OLD_BINARY_LINK}
chmod +x minio.${OLD_VERSION}
fi
if [ -z "$_MINIO_LDAP_TEST_SERVER" ]; then
@@ -49,7 +49,7 @@ __init__() {
}
create_iam_content_in_old_minio() {
echo "Creating IAM content in the old MinIO compatibility fixture."
echo "Creating IAM content in old minio instance."
MINIO_CI_CD=1 ./minio.${OLD_VERSION} server /tmp/data/{1...4} &
sleep 5
@@ -80,9 +80,9 @@ create_iam_content_in_old_minio() {
}
import_iam_content_in_new_minio() {
echo "Importing IAM content into the current Silo instance."
# Assume the current Silo binary exists.
MINIO_CI_CD=1 ./silo server /tmp/data/{1...4} &
echo "Importing IAM content in new minio instance."
# Assume current minio binary exists.
MINIO_CI_CD=1 ./minio server /tmp/data/{1...4} &
sleep 5
set -x
+95 -106
View File
@@ -1,124 +1,113 @@
#!/usr/bin/env bash
#!/bin/bash
set -euo pipefail
# Exercise both directions of the on-disk compatibility contract using an
# immutable pre-rebrand image and a container built from the current checkout.
# Every Docker resource is uniquely named and removed explicitly; this test
# never prunes unrelated images, containers, networks, or volumes.
repo_dir="$(git rev-parse --show-toplevel)"
old_image="${OLD_IMAGE:-docker.io/pgsty/minio@sha256:b6bfe7239bfc83fb90d31612d9704d86039dd714f7904b3f1ad68f211e602372}"
new_image="${NEW_IMAGE:-silo-upgrade-test:dev}"
suffix="$(date +%s)-$$"
network="silo-upgrade-net-${suffix}"
volume="silo-upgrade-data-${suffix}"
old_container="silo-upgrade-old-${suffix}"
new_container="silo-upgrade-new-${suffix}"
rollback_container="silo-upgrade-rollback-${suffix}"
root_user=silo-upgrade-admin
root_password=silo-upgrade-secret-123
trap 'cleanup $LINENO' ERR
# shellcheck disable=SC2120
cleanup() {
status=$?
trap - EXIT
if [ "${status}" -ne 0 ]; then
for name in "${old_container}" "${new_container}" "${rollback_container}"; do
docker logs "${name}" 2>/dev/null | tail -n 80 >&2 || true
done
fi
if [ "${KEEP_UPGRADE_TEST_RESOURCES:-0}" = 1 ]; then
printf 'Retained Docker resources for inspection: %s %s\n' "${network}" "${volume}" >&2
exit "${status}"
fi
docker rm -f "${old_container}" "${new_container}" "${rollback_container}" >/dev/null 2>&1 || true
docker network rm "${network}" >/dev/null 2>&1 || true
docker volume rm "${volume}" >/dev/null 2>&1 || true
exit "${status}"
}
trap cleanup EXIT
trap 'exit 130' INT TERM
MINIO_VERSION=dev /tmp/gopath/bin/docker-compose \
-f "buildscripts/upgrade-tests/compose.yml" \
down || true
wait_ready() {
name="$1"
ready=""
for _ in $(seq 1 90); do
if docker logs "${name}" 2>&1 | grep -q 'API:'; then
ready=1
break
fi
if [ "$(docker inspect -f '{{.State.Running}}' "${name}")" != true ]; then
break
fi
sleep 1
MINIO_VERSION=dev /tmp/gopath/bin/docker-compose \
-f "buildscripts/upgrade-tests/compose.yml" \
rm || true
for volume in $(docker volume ls -q | grep upgrade); do
docker volume rm ${volume} || true
done
if [ -z "${ready}" ]; then
echo "Server did not become ready: ${name}" >&2
return 1
docker volume prune -f
docker system prune -f || true
docker volume prune -f || true
docker volume rm $(docker volume ls -q -f dangling=true) || true
}
verify_checksum_after_heal() {
local sum1
sum1=$(curl -s "$2" | sha256sum)
mc admin heal --json -r "$1" >/dev/null # test after healing
local sum1_heal
sum1_heal=$(curl -s "$2" | sha256sum)
if [ "${sum1_heal}" != "${sum1}" ]; then
echo "mismatch expected ${sum1_heal}, got ${sum1}"
exit 1
fi
}
start_server() {
name="$1"
image="$2"
shift 2
docker run -d --name "${name}" --network "${network}" \
--mount "source=${volume},target=/data" \
-e MINIO_CI_CD=1 -e MINIO_ROOT_USER="${root_user}" -e MINIO_ROOT_PASSWORD="${root_password}" \
"${image}" "$@" >/dev/null
wait_ready "${name}"
docker exec "${name}" mcli alias set local http://127.0.0.1:9000 "${root_user}" "${root_password}" >/dev/null
verify_checksum_mc() {
local expected
expected=$(mc cat "$1" | sha256sum)
local got
got=$(mc cat "$2" | sha256sum)
if [ "${expected}" != "${got}" ]; then
echo "mismatch - expected ${expected}, got ${got}"
exit 1
fi
echo "matches - ${expected}, got ${got}"
}
stop_server() {
name="$1"
docker stop -t 20 "${name}" >/dev/null
test "$(docker inspect -f '{{.State.ExitCode}}' "${name}")" = 0
docker logs "${name}" 2>&1 | grep -q 'Exiting on signal'
docker rm "${name}" >/dev/null
add_alias() {
for i in $(seq 1 4); do
echo "... attempting to add alias $i"
until (mc alias set minio http://127.0.0.1:9000 minioadmin minioadmin); do
echo "...waiting... for 5secs" && sleep 5
done
done
echo "Sleeping for nginx"
sleep 20
}
command -v docker >/dev/null
docker info >/dev/null
if ! docker image inspect "${old_image}" >/dev/null 2>&1; then
docker pull "${old_image}"
fi
__init__() {
sudo apt install curl -y
export GOPATH=/tmp/gopath
export PATH=${PATH}:${GOPATH}/bin
if [ "${SILO_UPGRADE_SKIP_BUILD:-0}" != 1 ]; then
make -C "${repo_dir}" docker TAG="${new_image}"
fi
docker image inspect "${new_image}" >/dev/null
go install github.com/minio/mc@latest
docker network create "${network}" >/dev/null
docker volume create "${volume}" >/dev/null
## this is needed because github actions don't have
## docker-compose on all runners
COMPOSE_VERSION=v2.35.1
mkdir -p /tmp/gopath/bin/
wget -O /tmp/gopath/bin/docker-compose https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-linux-x86_64
chmod +x /tmp/gopath/bin/docker-compose
start_server "${old_container}" "${old_image}" minio server /data --address :9000
docker exec "${old_container}" mcli mb local/compat >/dev/null
docker exec "${old_container}" mcli version enable local/compat >/dev/null
printf 'old-version-1\n' | docker exec -i "${old_container}" mcli pipe local/compat/versioned.txt >/dev/null
printf 'old-version-2\n' | docker exec -i "${old_container}" mcli pipe local/compat/versioned.txt >/dev/null
test "$(docker exec "${old_container}" mcli ls --versions local/compat/versioned.txt | grep -c 'versioned.txt')" -ge 2
docker exec "${old_container}" mcli mb --with-lock local/locked >/dev/null
printf 'locked-by-old\n' | docker exec -i "${old_container}" mcli pipe local/locked/object.txt >/dev/null
dd if=/dev/zero bs=1048576 count=70 2>/dev/null | docker exec -i "${old_container}" mcli pipe local/compat/multipart.bin >/dev/null
test "$(docker exec "${old_container}" mcli stat --json local/compat/multipart.bin | jq -r '.size')" = 73400320
docker exec "${old_container}" mcli admin user add local migration-user migration-secret-123 >/dev/null
docker exec "${old_container}" mcli admin policy attach local readwrite --user migration-user >/dev/null
stop_server "${old_container}"
cleanup
start_server "${new_container}" "${new_image}" silo server /data --address :9000
test "$(docker exec "${new_container}" mcli cat local/compat/versioned.txt)" = old-version-2
test "$(docker exec "${new_container}" mcli cat local/locked/object.txt)" = locked-by-old
test "$(docker exec "${new_container}" mcli stat --json local/compat/multipart.bin | jq -r '.size')" = 73400320
docker exec "${new_container}" mcli admin user info local migration-user >/dev/null
docker exec "${new_container}" mcli alias set migrated http://127.0.0.1:9000 migration-user migration-secret-123 >/dev/null
printf 'written-by-silo\n' | docker exec -i "${new_container}" mcli pipe migrated/compat/silo.txt >/dev/null
stop_server "${new_container}"
TAG=minio/minio:dev make docker
start_server "${rollback_container}" "${old_image}" minio server /data --address :9000
test "$(docker exec "${rollback_container}" mcli cat local/compat/versioned.txt)" = old-version-2
test "$(docker exec "${rollback_container}" mcli cat local/compat/silo.txt)" = written-by-silo
test "$(docker exec "${rollback_container}" mcli stat --json local/compat/multipart.bin | jq -r '.size')" = 73400320
stop_server "${rollback_container}"
MINIO_VERSION=RELEASE.2019-12-19T22-52-26Z docker-compose \
-f "buildscripts/upgrade-tests/compose.yml" \
up -d --build
echo "MinIO-to-Silo data upgrade and rollback checks passed"
add_alias
mc mb minio/minio-test/
mc cp ./minio minio/minio-test/to-read/
mc cp /etc/hosts minio/minio-test/to-read/hosts
mc anonymous set download minio/minio-test
verify_checksum_mc ./minio minio/minio-test/to-read/minio
curl -s http://127.0.0.1:9000/minio-test/to-read/hosts | sha256sum
MINIO_VERSION=dev /tmp/gopath/bin/docker-compose -f "buildscripts/upgrade-tests/compose.yml" stop
}
main() {
MINIO_VERSION=dev /tmp/gopath/bin/docker-compose -f "buildscripts/upgrade-tests/compose.yml" up -d --build
add_alias
verify_checksum_after_heal minio/minio-test http://127.0.0.1:9000/minio-test/to-read/hosts
verify_checksum_mc ./minio minio/minio-test/to-read/minio
verify_checksum_mc /etc/hosts minio/minio-test/to-read/hosts
cleanup
}
(__init__ "$@" && main "$@")
+30 -19
View File
@@ -5,16 +5,16 @@ if [ -n "$TEST_DEBUG" ]; then
fi
WORK_DIR="$PWD/.verify-$RANDOM"
SILO_CONFIG_DIR="$WORK_DIR/.silo"
SILO=("$PWD/silo" --config-dir "$SILO_CONFIG_DIR" server)
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
if [ ! -x "$PWD/silo" ]; then
echo "Silo executable binary not found in current directory"
if [ ! -x "$PWD/minio" ]; then
echo "minio executable binary not found in current directory"
exit 1
fi
if [ ! -x "$PWD/silo" ]; then
echo "Silo executable binary not found in current directory"
if [ ! -x "$PWD/minio" ]; then
echo "minio executable binary not found in current directory"
exit 1
fi
@@ -30,9 +30,9 @@ catch() {
echo "error on line $1"
fi
echo "Cleaning up instances of Silo"
pkill silo || true
pkill -9 silo || true
echo "Cleaning up instances of MinIO"
pkill minio || true
pkill -9 minio || true
purge "$WORK_DIR"
if [ $# -ne 0 ]; then
exit $#
@@ -41,21 +41,32 @@ catch() {
catch
function start_silo_10drive() {
function start_minio_10drive() {
start_port=$1
export MINIO_ROOT_USER=silo
export MINIO_ROOT_PASSWORD=silo123
export MC_HOST_silo="http://silo:silo123@127.0.0.1:${start_port}/"
export MINIO_ROOT_USER=minio
export MINIO_ROOT_PASSWORD=minio123
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
export MINIO_CI_CD=1
mkdir ${WORK_DIR}
C_PWD=${PWD}
if [ ! -x "$PWD/mc" ]; then
"$(git rev-parse --show-toplevel)/buildscripts/install-mcli.sh" "$PWD/mc"
MC_BUILD_DIR="mc-$RANDOM"
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
echo "failed to download https://github.com/minio/mc"
purge "${MC_BUILD_DIR}"
exit 1
fi
(cd "${MC_BUILD_DIR}" && go build -o "$C_PWD/mc")
# remove mc source.
purge "${MC_BUILD_DIR}"
fi
"${SILO[@]}" --address ":$start_port" "${WORK_DIR}/disk{1...10}" >"${WORK_DIR}/server1.log" 2>&1 &
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/disk{1...10}" >"${WORK_DIR}/server1.log" 2>&1 &
pid=$!
disown $pid
sleep 5
@@ -68,10 +79,10 @@ function start_silo_10drive() {
exit 1
fi
"${PWD}/mc" mb --with-versioning silo/bucket
"${PWD}/mc" mb --with-versioning minio/bucket
export AWS_ACCESS_KEY_ID=silo
export AWS_SECRET_ACCESS_KEY=silo123
export AWS_ACCESS_KEY_ID=minio
export AWS_SECRET_ACCESS_KEY=minio123
aws --endpoint-url http://localhost:"$start_port" s3api create-multipart-upload --bucket bucket --key obj-1 >upload-id.json
uploadId=$(jq -r '.UploadId' upload-id.json)
@@ -109,7 +120,7 @@ EOF
function main() {
start_port=$(shuf -i 10000-65000 -n 1)
start_silo_10drive ${start_port}
start_minio_10drive ${start_port}
}
main "$@"
+13 -50
View File
@@ -20,10 +20,6 @@ if ! [[ "${PKG_VERSION}" =~ ^[0-9]{14}\.0\.0$ ]]; then
exit 1
fi
# The PGSTY release segment, PGDG-style. sign-release-rpms.sh declares the
# same value as expected_release, and test-release.yml asserts the two agree.
PKG_RELEASE="1PGSTY"
if ! command -v nfpm >/dev/null 2>&1; then
echo "nfpm is required" >&2
exit 1
@@ -38,37 +34,11 @@ fi
# against the config file, so the unit path is passed in absolute. Otherwise
# this only works when invoked from the repository root and fails elsewhere on
# a message that names the file rather than the cause.
unit_file="${repo_dir}/silo.service"
defaults_file="${repo_dir}/silo.env"
sysusers_file="${repo_dir}/silo.sysusers"
license_file="${repo_dir}/LICENSE"
notice_file="${repo_dir}/NOTICE"
postinstall_file="${repo_dir}/buildscripts/package/postinstall.sh"
preremove_file="${repo_dir}/buildscripts/package/preremove.sh"
unit_file="${repo_dir}/minio.service"
if [ ! -f "${unit_file}" ]; then
echo "Missing systemd unit: ${unit_file}" >&2
exit 1
fi
if [ ! -f "${defaults_file}" ]; then
echo "Missing defaults file: ${defaults_file}" >&2
exit 1
fi
if [ ! -f "${sysusers_file}" ]; then
echo "Missing sysusers file: ${sysusers_file}" >&2
exit 1
fi
for distributed_doc in "${license_file}" "${notice_file}"; do
if [ ! -s "${distributed_doc}" ]; then
echo "Missing license material: ${distributed_doc}" >&2
exit 1
fi
done
for lifecycle_script in "${postinstall_file}" "${preremove_file}"; do
if [ ! -x "${lifecycle_script}" ]; then
echo "Missing executable package lifecycle script: ${lifecycle_script}" >&2
exit 1
fi
done
packages_dir="${dist_dir}/packages"
mkdir -p "${packages_dir}"
@@ -98,7 +68,7 @@ find_binary() {
# variants (an added goamd64 level, a stale dist entry) would silently ship a
# package whose contents do not match its name.
matches="$(find "${dist_dir}" -maxdepth 2 -type f \
-path "${dist_dir}/silo_linux_${goarch}*/silo" | sort)"
-path "${dist_dir}/minio_linux_${goarch}*/minio" | sort)"
count="$(printf '%s' "${matches}" | grep -c . || true)"
if [ "${count}" -eq 0 ]; then
@@ -126,25 +96,18 @@ build_arch() {
source="$(find_binary "${goarch}")"
# These names are the public download names and must not drift; RPM and DEB
# carry the PGSTY release number (nfpm renders it as the RPM Release tag and
# as the Debian revision after a dash). APK stays bare: Alpine pkgrel only
# admits -r<integer>, so a lettered release cannot ride along there.
rpm_file="${packages_dir}/silo-${PKG_VERSION}-${PKG_RELEASE}.${rpm_arch}.rpm"
deb_file="${packages_dir}/silo_${PKG_VERSION}-${PKG_RELEASE}_${deb_arch}.deb"
apk_file="${packages_dir}/silo_${PKG_VERSION}_${apk_arch}.apk"
# These names are the public download names and must not drift; RPM carries a
# release number, DEB and APK do not, matching what pkger produced.
rpm_file="${packages_dir}/minio-${PKG_VERSION}-1.${rpm_arch}.rpm"
deb_file="${packages_dir}/minio_${PKG_VERSION}_${deb_arch}.deb"
apk_file="${packages_dir}/minio_${PKG_VERSION}_${apk_arch}.apk"
(
cd "${repo_dir}"
export NFPM_UNIT="${unit_file}" NFPM_DEFAULTS="${defaults_file}" NFPM_SYSUSERS="${sysusers_file}" \
NFPM_LICENSE="${license_file}" NFPM_NOTICE="${notice_file}"
PKG_VERSION="${PKG_VERSION}" NFPM_RELEASE="${PKG_RELEASE}" NFPM_ARCH="${goarch}" NFPM_SOURCE="${source}" \
nfpm package --config "${nfpm_config}" --packager rpm --target "${rpm_file}"
PKG_VERSION="${PKG_VERSION}" NFPM_RELEASE="${PKG_RELEASE}" NFPM_ARCH="${goarch}" NFPM_SOURCE="${source}" \
nfpm package --config "${nfpm_config}" --packager deb --target "${deb_file}"
PKG_VERSION="${PKG_VERSION}" NFPM_RELEASE='' NFPM_ARCH="${goarch}" NFPM_SOURCE="${source}" \
nfpm package --config "${nfpm_config}" --packager apk --target "${apk_file}"
)
PKG_VERSION="${PKG_VERSION}" NFPM_RELEASE=1 NFPM_ARCH="${goarch}" NFPM_SOURCE="${source}" NFPM_UNIT="${unit_file}" \
nfpm package --config "${nfpm_config}" --packager rpm --target "${rpm_file}"
PKG_VERSION="${PKG_VERSION}" NFPM_RELEASE='' NFPM_ARCH="${goarch}" NFPM_SOURCE="${source}" NFPM_UNIT="${unit_file}" \
nfpm package --config "${nfpm_config}" --packager deb --target "${deb_file}"
PKG_VERSION="${PKG_VERSION}" NFPM_RELEASE='' NFPM_ARCH="${goarch}" NFPM_SOURCE="${source}" NFPM_UNIT="${unit_file}" \
nfpm package --config "${nfpm_config}" --packager apk --target "${apk_file}"
sha256_file "${rpm_file}"
sha256_file "${deb_file}"
-172
View File
@@ -1,172 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_dir="$(cd "${script_dir}/../.." && pwd)"
postinstall="${script_dir}/postinstall.sh"
preremove="${script_dir}/preremove.sh"
test_dir="$(mktemp -d)"
fakebin="${test_dir}/bin"
log_file="${test_dir}/calls.log"
useradd_shell=/usr/sbin/nologin
[ -x "${useradd_shell}" ] || useradd_shell=/sbin/nologin
busybox_shell=/sbin/nologin
[ -x "${busybox_shell}" ] || busybox_shell=/bin/false
cleanup() {
rm -rf "${test_dir}"
}
trap cleanup EXIT
mkdir -p "${fakebin}"
touch "${log_file}"
# One dispatcher represents every external command used by the lifecycle
# scripts. The tested scripts run with no host utilities in PATH, so a green
# result cannot create a real account or touch the host service manager.
cat > "${fakebin}/fake-command" <<'EOF'
#!/bin/sh
set -eu
command_name=${0##*/}
case "${command_name}" in
id)
[ "${PACKAGE_TEST_USER_EXISTS:-0}" = 1 ]
;;
getent)
[ "${PACKAGE_TEST_GROUP_EXISTS:-0}" = 1 ]
;;
systemd-sysusers|useradd|addgroup|adduser|systemctl)
{
printf '%s' "${command_name}"
for argument in "$@"; do
printf ' %s' "${argument}"
done
printf '\n'
} >> "${PACKAGE_TEST_LOG}"
;;
*)
echo "unexpected fake command: ${command_name}" >&2
exit 1
;;
esac
EOF
chmod +x "${fakebin}/fake-command"
link_command() {
ln -sf fake-command "${fakebin}/$1"
}
unlink_optional_commands() {
rm -f \
"${fakebin}/systemd-sysusers" \
"${fakebin}/useradd" \
"${fakebin}/adduser" \
"${fakebin}/addgroup"
}
reset_log() {
: > "${log_file}"
}
run_postinstall() {
PACKAGE_TEST_LOG="${log_file}" \
PACKAGE_TEST_USER_EXISTS="${1}" \
PACKAGE_TEST_GROUP_EXISTS="${2}" \
PATH="${fakebin}" \
/bin/sh "${postinstall}"
}
run_preremove() {
PACKAGE_TEST_LOG="${log_file}" PATH="${fakebin}" \
/bin/sh "${preremove}" "$@"
}
assert_log_line() {
grep -Fx -- "$1" "${log_file}" >/dev/null
}
reject_log_text() {
if grep -F -- "$1" "${log_file}" >/dev/null; then
echo "unexpected lifecycle call containing '$1':" >&2
cat "${log_file}" >&2
exit 1
fi
}
link_command id
link_command getent
link_command systemctl
# Clean install through systemd-sysusers. Side-by-side safety is represented
# by the fact that the only service-manager operation is daemon-reload: no old
# service is stopped, disabled, enabled, masked, or restarted.
unlink_optional_commands
link_command systemd-sysusers
reset_log
run_postinstall 0 0
assert_log_line "systemd-sysusers /usr/lib/sysusers.d/silo.conf"
assert_log_line "systemctl daemon-reload"
test "$(wc -l < "${log_file}" | tr -d ' ')" -eq 2
# An existing service account is preserved without modification.
reset_log
run_postinstall 1 0
test "$(cat "${log_file}")" = "systemctl daemon-reload"
# useradd creates a private group only when one does not already exist. An
# administrator may pre-create group silo with the legacy GID; that group must
# be reused rather than causing installation to fail.
unlink_optional_commands
link_command useradd
reset_log
run_postinstall 0 0
assert_log_line "useradd --system --user-group --no-create-home --shell ${useradd_shell} --comment Silo object storage service silo"
reject_log_text "--gid silo"
reset_log
run_postinstall 0 1
assert_log_line "useradd --system --gid silo --no-create-home --shell ${useradd_shell} --comment Silo object storage service silo"
reject_log_text "--user-group"
# BusyBox follows the same existing-group contract.
unlink_optional_commands
link_command adduser
link_command addgroup
reset_log
run_postinstall 0 0
assert_log_line "addgroup -S silo"
assert_log_line "adduser -S -D -H -G silo -s ${busybox_shell} silo"
reset_log
run_postinstall 0 1
reject_log_text "addgroup"
assert_log_line "adduser -S -D -H -G silo -s ${busybox_shell} silo"
# Debian remove, RPM erase, and Alpine deinstall stop the Silo unit. Upgrade
# arguments must leave the running service alone.
for removal_argument in remove 0 20260214120000.0.0-r0; do
reset_log
run_preremove "${removal_argument}"
test "$(cat "${log_file}")" = "systemctl disable --now silo.service"
done
for upgrade_argument in upgrade 1; do
reset_log
run_preremove "${upgrade_argument}"
test ! -s "${log_file}"
done
# The package deliberately leaves legacy ownership changes to an explicit
# systemd drop-in. Lifecycle scripts must never rewrite ownership or touch the
# old unit, and the base unit must expose overridable User/Group directives.
grep -Fx 'User=silo' "${repo_dir}/silo.service" >/dev/null
grep -Fx 'Group=silo' "${repo_dir}/silo.service" >/dev/null
if grep -Ein '\b(chown|chgrp|usermod|groupmod)\b|minio\.service' \
"${postinstall}" "${preremove}"; then
echo "package lifecycle scripts must not mutate data ownership or the legacy service" >&2
exit 1
fi
echo "Silo package lifecycle checks passed"
-44
View File
@@ -1,44 +0,0 @@
#!/bin/sh
set -eu
sysusers_file=/usr/lib/sysusers.d/silo.conf
group_exists() {
if command -v getent >/dev/null 2>&1; then
getent group silo >/dev/null 2>&1
return
fi
[ -r /etc/group ] || return 1
while IFS=: read -r group_name _; do
[ "${group_name}" = silo ] && return 0
done < /etc/group
return 1
}
if ! id -u silo >/dev/null 2>&1; then
if command -v systemd-sysusers >/dev/null 2>&1; then
systemd-sysusers "${sysusers_file}"
elif command -v useradd >/dev/null 2>&1; then
nologin_shell=/usr/sbin/nologin
[ -x "${nologin_shell}" ] || nologin_shell=/sbin/nologin
if group_exists; then
useradd --system --gid silo --no-create-home --shell "${nologin_shell}" --comment "Silo object storage service" silo
else
useradd --system --user-group --no-create-home --shell "${nologin_shell}" --comment "Silo object storage service" silo
fi
elif command -v adduser >/dev/null 2>&1 && command -v addgroup >/dev/null 2>&1; then
nologin_shell=/sbin/nologin
[ -x "${nologin_shell}" ] || nologin_shell=/bin/false
group_exists || addgroup -S silo
adduser -S -D -H -G silo -s "${nologin_shell}" silo
else
echo "Unable to create the silo system account: systemd-sysusers, useradd, or BusyBox adduser is required" >&2
exit 1
fi
fi
if command -v systemctl >/dev/null 2>&1; then
systemctl daemon-reload >/dev/null 2>&1 || true
fi
-14
View File
@@ -1,14 +0,0 @@
#!/bin/sh
set -eu
# Debian passes "remove" for an actual removal, RPM passes 0 to %preun, and
# Alpine runs pre-deinstall only for removal and passes the old dotted version.
# Upgrade paths deliberately leave the running service untouched.
case "${1:-}" in
remove|0|*.*)
if command -v systemctl >/dev/null 2>&1; then
systemctl disable --now silo.service >/dev/null 2>&1 || true
fi
;;
esac
-52
View File
@@ -1,52 +0,0 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBGaV5PwBEACbErI+7yOrsXTT3mR83O6Fw9WyHJqozhyNPF3dA1gAtWpfWqd4
S9x6vBjVwUbIRn21jYgov0hDiaLABNQhRzifvVr0r1IjBW8lhA8zJGaO42Uz0aBW
YIkajOklsXgYMX+gSmy5WXzM31sDQVMnzptHh9dwW067hMM5pJKDslu2pLMwSb9K
QgIFcYsaR0taBkcDg4dNu1gncriD/GcdXIS0/V4R82DIYeIqj2S0lt0jDTACbUz3
C6esrTw2XerCeHKHb9c/V+KMhqvLJOOpy/aJWLrTGBoaH7xw6v0qg32OYiBxlUj9
VEzoQbDfbRkR+jlxiuYP3scUs/ziKrSh+0mshVbeuLRSNfuHLa7C4xTEnATcgD1J
MZeMaJXIcDt+DN+1aHVQjY5YNvr5wA3ykxW51uReZf7/odgqVW3+1rhW5pd8NQKQ
qoVUHOtIrC9KaiGfrczEtJTNUxcNZV9eBgcKHYDXB2hmR2pIf7WvydgXTs/qIsXg
SIzfKjisi795Dd5GrvdLYXVnu9YzylWlkJ5rjod1wnSxkI/CcCJaoPLnXZA9KV7A
cpMWWaUEXP/XBIwIU+vxDd1taBIaPIOv1KIdzvG7QqAQtf5Lphi5HfaGvBud/CVt
mvWhRPJMr1J0ER2xAgU2iZR7dN0vSF6zDqc0W09RAoC0nDS3tupDX2BrOwARAQAB
tCRSdW9oYW5nIEZlbmcgKFBpZ3N0eSkgPHJoQHZvbm5nLmNvbT6JAlEEEwEIADsW
IQSVkqe8emguczM3bgnnk12Nub2LIAUCZpXk/AIbAwULCQgHAgIiAgYVCgkICwIE
FgIDAQIeBwIXgAAKCRDnk12Nub2LIOMuEACBLVc09O4icFwc45R3KMvOMu14Egpn
UkpmBKhErjup0TIunzI0zZH6HG8LGuf6XEdH4ItCJeLg5349UE00BUHNmxk2coo2
u4Wtu28LPqmxb6sqpuRAaefedU6vqfs7YN6WWp52pVF1KdOHkIOcgAQ9z3ZHdosM
I/Y/UxO2t4pjdCAfJHOmGPrbgLcHSMpoLLxjuf3YIwS5NSfjNDd0Y8sKFUcMGLCF
5P0lv5feLLdZvh2Una34UmHKhZlXC5E3vlY9bf/LgsRzXRFQosD0RsCXbz3Tk+zF
+j/eP3WhUvJshqIDuY6eJYCzMjiA8sM5gety+htVJuD0mewp+qAhjxE0d4bIr4qO
BKQzBt9tT2ackCPdgW42VPS+IZymm1oMET0hgZfKiVpwsKO6qxeWn4RW2jJ0zkUJ
MsrrxOPFdZQAtuFcLwa5PUAHHs6XQT2vzxDpeE9lInQ14lshofU5ZKIeb9sbvb/w
P+xnDqvZ1pcotEIBvDK0S0jHbHHqtioIUdDFvdCBlBlYP1TQRNPlJ7TJDBBvhj8i
fmjQsYSV1u36aHOJVGYNHv+SyJpVd3nHCZn97ADM9qHnDm7xljyHXPzIx4FMmBGJ
UTiLH5yxa1xhWr42Iv3TykaQJVbpydmBuegFR8WbWitAvVqI3HvRG+FalLsjJruc
8YDAf7gHdj/937kCDQRmleT8ARAAmJxscC76NZzqFBiaeq2+aJxOt1HGPqKb4pbz
jLKRX9sFkeXuzhfZaNDljnr2yrnQ75rit9Aah/loEhbSHanNUDCNmvOeSEISr9yA
yfOnqlcVOtcwWQK57n6MvlCSM8Js3jdoSmCFHVtdFFwxejE5ok0dk1VFYDIg6DRk
ZBMuxGO7ZJW7TzCxhK4AL+NNYA2wX6b+IVMn6CA9kwNwCNrrnGHR1sblSxZp7lPo
+GsqzYY0LXGR2eEicgKd4lk38gaO8Q4d1mlpX95vgdhGKxR+CM26y9QU0qrO1hXP
Fw6lX9HfIUkVNrqAa1mzgneYXivnLvcj8gc7bFAdweX4MyBHsmiPm32WqjUJFAmw
kcKYaiyfDJ+1wusa/b+7RCnshWc8B9udYbXfvcpOGgphpUuvomKT8at3ToJfEWmR
BzToYYTsgAAX8diY/X53BHCE/+MhLccglEUYNZyBRkTwDLrS9QgNkhrADaTwxsv1
8PwnVKve/ZxwOU0QGf4ZOhA2YQOE5hkRDR5uY2OHsOS5vHsd9Y6kNNnO8EBy99d1
QiBJOW3AP0nr4Cj1/NhdigAujsYRKiCAuPT7dgqART58VU4bZ3PgonMlziLe7+ht
YYxV+wyP6LVqicDd0MLLvG7r/JOiWuABOUxsFFaRecehoPJjeAEQxnWJjedokXKL
HVOFaEkAEQEAAYkCNgQYAQgAIBYhBJWSp7x6aC5zMzduCeeTXY25vYsgBQJmleT8
AhsMAAoJEOeTXY25vYsgG8sP/3UdsWuiwTsf/x4BTW82K+Uk9YwZDnUNH+4dUMED
bKT1C6CbuSZ7Mnbi2rVsmGzOMs9MehIx6Ko8/iCR2OCeWi8Q+wM+iffAfWuT1GK6
7f/VIfoYBUWEa+kvDcPgEbd5Tu7ZdUO/jROVBSlXRSjzK9LpIj7GozBTJ8Vqy5x7
oqbWPPEYtGDVHime8o6f5/wfhNgL3mFnoq6srK7KhwACwfTXlNqAlGiXGa30Yj+b
Cj6IvmxoII49E67/ovMEmzDCb3RXiaL6OATy25P+HQJvWvAam7Qq5Xn+bZg65Mup
vXq3zoX0a7EKXc5vsJVNtTlXO1ATdYszKP5uNzkHrNAN52VRYaowq1vPy/MVMbSI
rL/hTFKr7ZNhmC7jmS3OuJyCYQsfEerubtBUuc/W6JDc2oTI3xOG1S2Zj8f4PxLl
H7vMG4E+p6eOrUGw6VQXjFsH9GtwhkPh/ZGMKENb2+JztJ02674Cok4s5c/lZFKz
mmRUcNjX2bm2K0GfGG5/hAog/CHCeUZvwIh4hZLkdeJ1QsIYpN8xbvY7QP6yh4VB
XrL18+2sontZ45MsGResrRibB35x7IrCrxZsVtRJZthHqshiORPatgy+AiWcAtEv
UWEnnC1xBSasNebw4fSE8AJg9JMCRw+3GAetlotOeW9q7PN6yrXD9rGuV/QquQNd
/c7w
=4rRi
-----END PGP PUBLIC KEY BLOCK-----
File diff suppressed because it is too large Load Diff
-480
View File
@@ -1,480 +0,0 @@
// Copyright 2026 PGSTY contributors.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// rebrand-guard records the compatibility identifiers that a product rebrand
// must not accidentally rename. It intentionally excludes product branding and
// delivery names, which are validated by buildscripts/verify-rebrand.sh.
package main
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"flag"
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
)
const manifestVersion = 3
var (
minioImportRE = regexp.MustCompile(`github\.com/minio/[A-Za-z0-9_./-]+`)
envRE = regexp.MustCompile(`\b_?MINIO_[A-Z0-9_]+\b`)
metricRE = regexp.MustCompile(`\bminio_[A-Za-z0-9_]+\b`)
headerRE = regexp.MustCompile(`(?i)\bx-minio-[a-z0-9_-]+\b`)
routeRE = regexp.MustCompile(`^/[A-Za-z0-9._~!$&'()*+,;=:@%/?{}=-]*`)
storageRE = regexp.MustCompile(`\.minio\.sys(?:/[A-Za-z0-9._${}-]+)*`)
policyRE = regexp.MustCompile(`(?:arn:minio|minio:s3)[A-Za-z0-9_:/.*${}-]*`)
brandRE = regexp.MustCompile(`(?i)(^|[^a-z0-9_])minio([^a-z0-9_]|$)`)
)
type manifest struct {
Version int `json:"version"`
ModulePath string `json:"module_path"`
MinioImports []string `json:"minio_imports"`
Environment []string `json:"environment"`
Metrics []string `json:"metrics"`
Headers []string `json:"headers"`
Routes []string `json:"routes"`
RouteRoots []string `json:"route_roots"`
GridRoutes []string `json:"grid_routes"`
StorageMarkers []string `json:"storage_markers"`
PolicyValues []string `json:"policy_values"`
ExportedSymbols []string `json:"exported_symbols"`
BrandAllowlist []string `json:"brand_allowlist"`
}
func main() {
write := flag.Bool("write", false, "replace the checked-in compatibility baseline")
flag.Parse()
repo, err := gitOutput("rev-parse", "--show-toplevel")
if err != nil {
fatal(err)
}
repo = strings.TrimSpace(repo)
baselinePath := filepath.Join(repo, "buildscripts", "rebrand-guard", "compat-baseline.json")
current, err := collect(repo)
if err != nil {
fatal(err)
}
if *write {
if err := writeManifest(baselinePath, current); err != nil {
fatal(err)
}
fmt.Printf("wrote %s\n", baselinePath)
printSummary(current)
return
}
want, err := readManifest(baselinePath)
if err != nil {
fatal(err)
}
if err := compare(want, current); err != nil {
fatal(err)
}
printSummary(current)
fmt.Println("Silo rebrand compatibility baseline is unchanged")
}
func collect(repo string) (manifest, error) {
files, err := trackedFiles(repo)
if err != nil {
return manifest{}, err
}
sets := map[string]map[string]struct{}{
"imports": {},
"env": {},
"metrics": {},
"headers": {},
"routes": {},
"roots": {},
"grid": {},
"storage": {},
"policy": {},
"exported": {},
"brand": {},
}
modulePath := ""
fset := token.NewFileSet()
for _, rel := range files {
if rel == "SILO_REBRANDING_MIGRATION.md" ||
strings.HasPrefix(rel, "buildscripts/rebrand-guard/") ||
strings.HasPrefix(rel, "buildscripts/helm-migration-guard/") {
continue
}
path := filepath.Join(repo, filepath.FromSlash(rel))
data, err := os.ReadFile(path)
if errors.Is(err, os.ErrNotExist) {
continue
}
if err != nil {
return manifest{}, fmt.Errorf("read %s: %w", rel, err)
}
if bytes.IndexByte(data, 0) >= 0 {
continue
}
text := string(data)
addMatches(sets["env"], envRE, text, false)
addMatches(sets["headers"], headerRE, text, true)
addMatches(sets["storage"], storageRE, text, false)
addMatches(sets["policy"], policyRE, text, false)
if strings.HasSuffix(rel, ".go") && (strings.HasPrefix(rel, "cmd/") || strings.HasPrefix(rel, "internal/")) {
addMatches(sets["metrics"], metricRE, text, false)
}
if rel == "go.mod" {
addMatches(sets["imports"], minioImportRE, text, false)
for _, line := range strings.Split(text, "\n") {
fields := strings.Fields(line)
if len(fields) == 2 && fields[0] == "module" {
modulePath = fields[1]
break
}
}
}
if strings.HasSuffix(rel, ".go") {
file, err := parser.ParseFile(fset, path, data, parser.SkipObjectResolution)
if err != nil {
return manifest{}, fmt.Errorf("parse %s: %w", rel, err)
}
for _, spec := range file.Imports {
value, err := strconv.Unquote(spec.Path.Value)
if err == nil && strings.HasPrefix(value, "github.com/minio/") {
sets["imports"][value] = struct{}{}
}
}
collectStringMatches(sets["routes"], routeRE, file)
collectNamedStringValues(sets["roots"], rel, file, "minioReservedBucket")
if rel == "internal/grid/manager.go" {
collectStringMatches(sets["grid"], routeRE, file)
}
if !strings.HasSuffix(rel, "_test.go") {
collectExported(sets["exported"], filepath.ToSlash(filepath.Dir(rel)), file)
if strings.HasPrefix(rel, "cmd/") || strings.HasPrefix(rel, "internal/") {
collectBrandStrings(sets["brand"], rel, file)
}
}
}
}
// This was a shell-local PID variable in the generated inspect script,
// never a supported environment setting.
delete(sets["env"], "MINIO_SRVR_PID")
if modulePath == "" {
return manifest{}, errors.New("go.mod module path was not found")
}
return manifest{
Version: manifestVersion,
ModulePath: modulePath,
MinioImports: sorted(sets["imports"]),
Environment: sorted(sets["env"]),
Metrics: sorted(sets["metrics"]),
Headers: sorted(sets["headers"]),
Routes: sorted(sets["routes"]),
RouteRoots: sorted(sets["roots"]),
GridRoutes: sorted(sets["grid"]),
StorageMarkers: sorted(sets["storage"]),
PolicyValues: sorted(sets["policy"]),
ExportedSymbols: sorted(sets["exported"]),
BrandAllowlist: sorted(sets["brand"]),
}, nil
}
func collectBrandStrings(dst map[string]struct{}, rel string, file *ast.File) {
ast.Inspect(file, func(node ast.Node) bool {
literal, ok := node.(*ast.BasicLit)
if !ok || literal.Kind != token.STRING {
return true
}
value, err := strconv.Unquote(literal.Value)
if err != nil || !brandRE.MatchString(value) || strings.HasPrefix(value, "github.com/minio/") {
return true
}
dst[filepath.ToSlash(rel)+"="+strconv.Quote(value)] = struct{}{}
return true
})
}
func collectNamedStringValues(dst map[string]struct{}, rel string, file *ast.File, names ...string) {
wanted := make(map[string]struct{}, len(names))
for _, name := range names {
wanted[name] = struct{}{}
}
for _, decl := range file.Decls {
gen, ok := decl.(*ast.GenDecl)
if !ok {
continue
}
for _, rawSpec := range gen.Specs {
spec, ok := rawSpec.(*ast.ValueSpec)
if !ok {
continue
}
for i, name := range spec.Names {
if _, ok := wanted[name.Name]; !ok || i >= len(spec.Values) {
continue
}
literal, ok := spec.Values[i].(*ast.BasicLit)
if !ok || literal.Kind != token.STRING {
continue
}
value, err := strconv.Unquote(literal.Value)
if err == nil {
dst[filepath.ToSlash(rel)+":"+name.Name+"="+value] = struct{}{}
}
}
}
}
}
func collectStringMatches(dst map[string]struct{}, re *regexp.Regexp, file *ast.File) {
ast.Inspect(file, func(node ast.Node) bool {
literal, ok := node.(*ast.BasicLit)
if !ok || literal.Kind != token.STRING {
return true
}
value, err := strconv.Unquote(literal.Value)
if err == nil {
addMatches(dst, re, value, false)
}
return true
})
}
func trackedFiles(repo string) ([]string, error) {
cmd := exec.Command("git", "-C", repo, "ls-files", "--cached", "--others", "--exclude-standard", "-z")
out, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("git ls-files: %w", err)
}
parts := bytes.Split(out, []byte{0})
files := make([]string, 0, len(parts))
for _, part := range parts {
if len(part) > 0 {
files = append(files, string(part))
}
}
return files, nil
}
func addMatches(dst map[string]struct{}, re *regexp.Regexp, text string, lower bool) {
for _, match := range re.FindAllString(text, -1) {
if lower {
match = strings.ToLower(match)
}
dst[match] = struct{}{}
}
}
func collectExported(dst map[string]struct{}, dir string, file *ast.File) {
prefix := dir + ":" + file.Name.Name + ":"
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.FuncDecl:
if !ast.IsExported(decl.Name.Name) {
continue
}
if decl.Recv == nil {
dst[prefix+"func:"+decl.Name.Name] = struct{}{}
continue
}
receiver := receiverName(decl.Recv.List[0].Type)
dst[prefix+"method:"+receiver+"."+decl.Name.Name] = struct{}{}
case *ast.GenDecl:
for _, spec := range decl.Specs {
switch spec := spec.(type) {
case *ast.TypeSpec:
if !ast.IsExported(spec.Name.Name) {
continue
}
dst[prefix+"type:"+spec.Name.Name] = struct{}{}
collectExportedFields(dst, prefix, spec.Name.Name, spec.Type)
case *ast.ValueSpec:
kind := strings.ToLower(decl.Tok.String())
for _, name := range spec.Names {
if ast.IsExported(name.Name) {
dst[prefix+kind+":"+name.Name] = struct{}{}
}
}
}
}
}
}
}
func collectExportedFields(dst map[string]struct{}, prefix, typeName string, expr ast.Expr) {
var fields *ast.FieldList
switch typed := expr.(type) {
case *ast.StructType:
fields = typed.Fields
case *ast.InterfaceType:
fields = typed.Methods
default:
return
}
for _, field := range fields.List {
for _, name := range field.Names {
if ast.IsExported(name.Name) {
dst[prefix+"field:"+typeName+"."+name.Name] = struct{}{}
}
}
}
}
func receiverName(expr ast.Expr) string {
switch expr := expr.(type) {
case *ast.Ident:
return expr.Name
case *ast.StarExpr:
return receiverName(expr.X)
case *ast.IndexExpr:
return receiverName(expr.X)
case *ast.IndexListExpr:
return receiverName(expr.X)
case *ast.SelectorExpr:
return receiverName(expr.X) + "." + expr.Sel.Name
default:
return fmt.Sprintf("%T", expr)
}
}
func sorted(set map[string]struct{}) []string {
values := make([]string, 0, len(set))
for value := range set {
values = append(values, value)
}
sort.Strings(values)
return values
}
func writeManifest(path string, value manifest) error {
data, err := json.MarshalIndent(value, "", " ")
if err != nil {
return err
}
data = append(data, '\n')
return os.WriteFile(path, data, 0o644)
}
func readManifest(path string) (manifest, error) {
data, err := os.ReadFile(path)
if err != nil {
return manifest{}, fmt.Errorf("read compatibility baseline (run go run ./buildscripts/rebrand-guard --write once): %w", err)
}
var value manifest
if err := json.Unmarshal(data, &value); err != nil {
return manifest{}, err
}
if value.Version != manifestVersion {
return manifest{}, fmt.Errorf("unsupported compatibility baseline version %d", value.Version)
}
return value, nil
}
func compare(want, got manifest) error {
var failures []string
if want.ModulePath != got.ModulePath {
failures = append(failures, fmt.Sprintf("module_path: want %q, got %q", want.ModulePath, got.ModulePath))
}
checks := []struct {
name string
want, got []string
}{
{"minio_imports", want.MinioImports, got.MinioImports},
{"environment", want.Environment, got.Environment},
{"metrics", want.Metrics, got.Metrics},
{"headers", want.Headers, got.Headers},
{"routes", want.Routes, got.Routes},
{"route_roots", want.RouteRoots, got.RouteRoots},
{"grid_routes", want.GridRoutes, got.GridRoutes},
{"storage_markers", want.StorageMarkers, got.StorageMarkers},
{"policy_values", want.PolicyValues, got.PolicyValues},
{"exported_symbols", want.ExportedSymbols, got.ExportedSymbols},
{"brand_allowlist", want.BrandAllowlist, got.BrandAllowlist},
}
for _, check := range checks {
if missing, added := setDiff(check.want, check.got); len(missing) > 0 || len(added) > 0 {
var b strings.Builder
fmt.Fprintf(&b, "%s compatibility set changed", check.name)
for _, value := range missing {
fmt.Fprintf(&b, "\n - %s", value)
}
for _, value := range added {
fmt.Fprintf(&b, "\n + %s", value)
}
failures = append(failures, b.String())
}
}
if len(failures) > 0 {
return errors.New(strings.Join(failures, "\n"))
}
return nil
}
func setDiff(want, got []string) (missing, added []string) {
wantSet := make(map[string]struct{}, len(want))
gotSet := make(map[string]struct{}, len(got))
for _, value := range want {
wantSet[value] = struct{}{}
}
for _, value := range got {
gotSet[value] = struct{}{}
}
for _, value := range want {
if _, ok := gotSet[value]; !ok {
missing = append(missing, value)
}
}
for _, value := range got {
if _, ok := wantSet[value]; !ok {
added = append(added, value)
}
}
return missing, added
}
func printSummary(value manifest) {
fmt.Printf("compatibility manifest: imports=%d env=%d metrics=%d headers=%d routes=%d roots=%d grid=%d storage=%d policy=%d exported=%d brand=%d sha256=%s\n",
len(value.MinioImports), len(value.Environment), len(value.Metrics), len(value.Headers),
len(value.Routes), len(value.RouteRoots), len(value.GridRoutes), len(value.StorageMarkers), len(value.PolicyValues),
len(value.ExportedSymbols), len(value.BrandAllowlist), manifestDigest(value))
}
func manifestDigest(value manifest) string {
data, _ := json.Marshal(value)
sum := sha256.Sum256(data)
return hex.EncodeToString(sum[:])
}
func gitOutput(args ...string) (string, error) {
out, err := exec.Command("git", args...).CombinedOutput()
if err != nil {
return "", fmt.Errorf("git %s: %s: %w", strings.Join(args, " "), strings.TrimSpace(string(out)), err)
}
return string(out), nil
}
func fatal(err error) {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
+23 -14
View File
@@ -6,29 +6,38 @@ set -x
set -e
WORK_DIR="$PWD/.verify-$RANDOM"
SILO_CONFIG_DIR="$WORK_DIR/.silo"
SILO=("$PWD/silo" --config-dir "$SILO_CONFIG_DIR" server)
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
if [ ! -x "$PWD/silo" ]; then
echo "Silo executable binary not found in current directory"
if [ ! -x "$PWD/minio" ]; then
echo "minio executable binary not found in current directory"
exit 1
fi
function start_silo_5drive() {
function start_minio_5drive() {
start_port=$1
export MINIO_ROOT_USER=silo
export MINIO_ROOT_PASSWORD=silo123
export MC_HOST_silo="http://silo:silo123@127.0.0.1:${start_port}/"
export MINIO_ROOT_USER=minio
export MINIO_ROOT_PASSWORD=minio123
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
export MINIO_CI_CD=1
mkdir -p "${WORK_DIR}"
"$(git rev-parse --show-toplevel)/buildscripts/install-mcli.sh" "${WORK_DIR}/mc"
MC_BUILD_DIR="mc-$RANDOM"
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
echo "failed to download https://github.com/minio/mc"
purge "${MC_BUILD_DIR}"
exit 1
fi
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
# remove mc source.
purge "${MC_BUILD_DIR}"
"${WORK_DIR}/mc" cp --quiet -r "buildscripts/cicd-corpus/" "${WORK_DIR}/cicd-corpus/"
"${SILO[@]}" --address ":$start_port" "${WORK_DIR}/cicd-corpus/disk{1...5}" >"${WORK_DIR}/server1.log" 2>&1 &
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/cicd-corpus/disk{1...5}" >"${WORK_DIR}/server1.log" 2>&1 &
pid=$!
disown $pid
sleep 5
@@ -41,16 +50,16 @@ function start_silo_5drive() {
exit 1
fi
"${WORK_DIR}/mc" stat silo/bucket/testobj
"${WORK_DIR}/mc" stat minio/bucket/testobj
pkill silo
pkill minio
sleep 3
}
function main() {
start_port=$(shuf -i 10000-65000 -n 1)
start_silo_5drive ${start_port}
start_minio_5drive ${start_port}
}
function purge() {
+36 -30
View File
@@ -5,42 +5,48 @@ set -o pipefail
set -x
WORK_DIR="$PWD/.verify-$RANDOM"
SILO_CONFIG_DIR="$WORK_DIR/.silo"
MINIO_OLD=("$PWD/minio.RELEASE.2020-10-28T08-16-50Z" --config-dir "$SILO_CONFIG_DIR" server)
SILO=("$PWD/silo" --config-dir "$SILO_CONFIG_DIR" server)
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
MINIO_OLD=("$PWD/minio.RELEASE.2020-10-28T08-16-50Z" --config-dir "$MINIO_CONFIG_DIR" server)
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
if [ ! -x "$PWD/silo" ]; then
echo "Silo executable binary not found in current directory"
if [ ! -x "$PWD/minio" ]; then
echo "minio executable binary not found in current directory"
exit 1
fi
function download_old_release() {
if [ ! -x minio.RELEASE.2020-10-28T08-16-50Z ]; then
: "${SILO_LEGACY_FIXTURE_2020:?set SILO_LEGACY_FIXTURE_2020 to the audited legacy binary}"
: "${SILO_LEGACY_SHA256_2020:?set SILO_LEGACY_SHA256_2020 to its audited SHA-256}"
"$(git rev-parse --show-toplevel)/buildscripts/install-verified-fixture.sh" \
"${SILO_LEGACY_FIXTURE_2020}" "${SILO_LEGACY_SHA256_2020}" \
"minio.RELEASE.2020-10-28T08-16-50Z"
if [ ! -f minio.RELEASE.2020-10-28T08-16-50Z ]; then
curl --silent -O https://dl.minio.io/server/minio/release/linux-amd64/archive/minio.RELEASE.2020-10-28T08-16-50Z
chmod a+x minio.RELEASE.2020-10-28T08-16-50Z
fi
}
function verify_rewrite() {
start_port=$1
export MINIO_ACCESS_KEY=silo
export MINIO_SECRET_KEY=silo123
export MC_HOST_silo="http://silo:silo123@127.0.0.1:${start_port}/"
export MINIO_ACCESS_KEY=minio
export MINIO_SECRET_KEY=minio123
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
export MINIO_CI_CD=1
mkdir -p "${WORK_DIR}"
"$(git rev-parse --show-toplevel)/buildscripts/install-mcli.sh" "${WORK_DIR}/mc"
MC_BUILD_DIR="mc-$RANDOM"
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
echo "failed to download https://github.com/minio/mc"
purge "${MC_BUILD_DIR}"
exit 1
fi
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
# remove mc source.
purge "${MC_BUILD_DIR}"
"${MINIO_OLD[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
pid=$!
disown $pid
"${WORK_DIR}/mc" ready silo/
"${WORK_DIR}/mc" ready minio/
if ! ps -p ${pid} 1>&2 >/dev/null; then
echo "server1 log:"
@@ -50,30 +56,30 @@ function verify_rewrite() {
exit 1
fi
"${WORK_DIR}/mc" mb silo/healing-rewrite-bucket --quiet --with-lock
"${WORK_DIR}/mc" mb minio/healing-rewrite-bucket --quiet --with-lock
"${WORK_DIR}/mc" cp \
buildscripts/verify-build.sh \
silo/healing-rewrite-bucket/ \
minio/healing-rewrite-bucket/ \
--disable-multipart --quiet
"${WORK_DIR}/mc" cp \
buildscripts/verify-build.sh \
silo/healing-rewrite-bucket/ \
minio/healing-rewrite-bucket/ \
--disable-multipart --quiet
"${WORK_DIR}/mc" cp \
buildscripts/verify-build.sh \
silo/healing-rewrite-bucket/ \
minio/healing-rewrite-bucket/ \
--disable-multipart --quiet
kill ${pid}
sleep 3
"${SILO[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
pid=$!
disown $pid
"${WORK_DIR}/mc" ready silo/
"${WORK_DIR}/mc" ready minio/
if ! ps -p ${pid} 1>&2 >/dev/null; then
echo "server1 log:"
@@ -86,8 +92,8 @@ function verify_rewrite() {
if ! ./s3-check-md5 \
-debug \
-versions \
-access-key silo \
-secret-key silo123 \
-access-key minio \
-secret-key minio123 \
-endpoint "http://127.0.0.1:${start_port}/" 2>&1 | grep INTACT; then
echo "server1 log:"
cat "${WORK_DIR}/server1.log"
@@ -95,7 +101,7 @@ function verify_rewrite() {
mkdir -p inspects
(
cd inspects
"${WORK_DIR}/mc" admin inspect silo/healing-rewrite-bucket/verify-build.sh/**
"${WORK_DIR}/mc" admin inspect minio/healing-rewrite-bucket/verify-build.sh/**
)
"${WORK_DIR}/mc" mb play/inspects
@@ -105,14 +111,14 @@ function verify_rewrite() {
exit 1
fi
go run ./buildscripts/heal-manual.go "127.0.0.1:${start_port}" "silo" "silo123"
go run ./buildscripts/heal-manual.go "127.0.0.1:${start_port}" "minio" "minio123"
sleep 1
if ! ./s3-check-md5 \
-debug \
-versions \
-access-key silo \
-secret-key silo123 \
-access-key minio \
-secret-key minio123 \
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
echo "server1 log:"
cat "${WORK_DIR}/server1.log"
@@ -120,7 +126,7 @@ function verify_rewrite() {
mkdir -p inspects
(
cd inspects
"${WORK_DIR}/mc" admin inspect silo/healing-rewrite-bucket/verify-build.sh/**
"${WORK_DIR}/mc" admin inspect minio/healing-rewrite-bucket/verify-build.sh/**
)
"${WORK_DIR}/mc" mb play/inspects
+10 -15
View File
@@ -7,21 +7,16 @@ set -euo pipefail
# other way round would only surface here, on the maintainer's machine, after
# the build has already run and uploaded.
expected_fingerprint="9592A7BC7A682E7333376E09E7935D8DB9BD8B20"
expected_release="1PGSTY"
expected_vendor="PGSTY"
expected_packager="Ruohang Feng (@Vonng) <rh@vonng.com>"
expected_url="https://silo.pgsty.com"
expected_summary="S3-Interface Libre Object Storage, a community-maintained S3-compatible server."
expected_description="S3-Interface Libre Object Storage, a community-maintained S3-compatible server."
expected_summary="S3-Interface Libre Object Storage, Community-maintained MinIO server fork."
expected_description="S3-Interface Libre Object Storage, Community-maintained MinIO server fork."
expected_license="AGPL-3.0-or-later"
expected_group="Applications/File"
expected_payload="/etc/default/silo
/usr/bin/silo
/usr/lib/systemd/system/silo.service
/usr/lib/sysusers.d/silo.conf
/usr/share/doc/silo/LICENSE
/usr/share/doc/silo/NOTICE"
repository="${GH_REPO:-pgsty/silo}"
expected_payload="/usr/lib/systemd/system/minio.service
/usr/local/bin/minio"
repository="${GH_REPO:-pgsty/minio}"
container="${DNFUPDATE_CONTAINER:-dnfupdate}"
upload=false
release_tag=""
@@ -127,8 +122,8 @@ mkdir -p "${unsigned_dir}" "${signed_dir}"
chmod 700 "${work_dir}" "${unsigned_dir}" "${signed_dir}"
rpm_files=(
"silo-${package_version}-${expected_release}.x86_64.rpm"
"silo-${package_version}-${expected_release}.aarch64.rpm"
"minio-${package_version}-1.x86_64.rpm"
"minio-${package_version}-1.aarch64.rpm"
)
download_patterns=()
@@ -164,7 +159,7 @@ for rpm_file in "${rpm_files[@]}"; do
done
safe_tag="$(printf '%s' "${release_tag}" | tr -c 'A-Za-z0-9._-' '_')"
container_dir="/tmp/silo-sign-${safe_tag}-$$"
container_dir="/tmp/minio-sign-${safe_tag}-$$"
docker exec "${container}" mkdir -p "${container_dir}"
cleanup_container() {
@@ -208,9 +203,9 @@ for rpm_file in "${rpm_files[@]}"; do
docker cp "${unsigned_dir}/${rpm_file}" "${container}:${container_dir}/${rpm_file}" >/dev/null
container_rpm="${container_dir}/${rpm_file}"
assert_rpm_tag "${container_rpm}" NAME silo
assert_rpm_tag "${container_rpm}" NAME minio
assert_rpm_tag "${container_rpm}" VERSION "${package_version}"
assert_rpm_tag "${container_rpm}" RELEASE "${expected_release}"
assert_rpm_tag "${container_rpm}" RELEASE 1
assert_rpm_tag "${container_rpm}" ARCH "${expected_arch}"
assert_rpm_tag "${container_rpm}" VENDOR "${expected_vendor}"
assert_rpm_tag "${container_rpm}" PACKAGER "${expected_packager}"
+32 -21
View File
@@ -5,16 +5,16 @@ if [ -n "$TEST_DEBUG" ]; then
fi
WORK_DIR="$PWD/.verify-$RANDOM"
SILO_CONFIG_DIR="$WORK_DIR/.silo"
SILO=("$PWD/silo" --config-dir "$SILO_CONFIG_DIR" server)
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
if [ ! -x "$PWD/silo" ]; then
echo "Silo executable binary not found in current directory"
if [ ! -x "$PWD/minio" ]; then
echo "minio executable binary not found in current directory"
exit 1
fi
if [ ! -x "$PWD/silo" ]; then
echo "Silo executable binary not found in current directory"
if [ ! -x "$PWD/minio" ]; then
echo "minio executable binary not found in current directory"
exit 1
fi
@@ -30,9 +30,9 @@ catch() {
echo "error on line $1"
fi
echo "Cleaning up instances of Silo"
pkill silo || true
pkill -9 silo || true
echo "Cleaning up instances of MinIO"
pkill minio || true
pkill -9 minio || true
purge "$WORK_DIR"
if [ $# -ne 0 ]; then
exit $#
@@ -70,20 +70,31 @@ function send_put_object_request() {
return 0
}
function test_silo_with_timeout() {
function test_minio_with_timeout() {
start_port=$1
export MINIO_ROOT_USER=silo
export MINIO_ROOT_PASSWORD=silo123
export MC_HOST_silo="http://silo:silo123@127.0.0.1:${start_port}/"
export MINIO_ROOT_USER=minio
export MINIO_ROOT_PASSWORD=minio123
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
export MINIO_CI_CD=1
mkdir ${WORK_DIR}
C_PWD=${PWD}
if [ ! -x "$PWD/mc" ]; then
"$(git rev-parse --show-toplevel)/buildscripts/install-mcli.sh" "$PWD/mc"
MC_BUILD_DIR="mc-$RANDOM"
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
echo "failed to download https://github.com/minio/mc"
purge "${MC_BUILD_DIR}"
exit 1
fi
(cd "${MC_BUILD_DIR}" && go build -o "$C_PWD/mc")
# remove mc source.
purge "${MC_BUILD_DIR}"
fi
"${SILO[@]}" --address ":$start_port" --read-header-timeout ${srv_hdr_timeout}s --idle-timeout ${srv_idle_timeout}s "${WORK_DIR}/disk/" >"${WORK_DIR}/server1.log" 2>&1 &
"${MINIO[@]}" --address ":$start_port" --read-header-timeout ${srv_hdr_timeout}s --idle-timeout ${srv_idle_timeout}s "${WORK_DIR}/disk/" >"${WORK_DIR}/server1.log" 2>&1 &
pid=$!
disown $pid
sleep 1
@@ -98,20 +109,20 @@ function test_silo_with_timeout() {
set -e
"${PWD}/mc" mb silo/testbucket
"${PWD}/mc" anonymous set public silo/testbucket
"${PWD}/mc" mb minio/testbucket
"${PWD}/mc" anonymous set public minio/testbucket
# slow header writing
send_put_object_request 20 0 && exit -1
"${PWD}/mc" stat silo/testbucket/testobject && exit -1
"${PWD}/mc" stat minio/testbucket/testobject && exit -1
# quick header write and slow bodywrite
send_put_object_request 0 40 && exit -1
"${PWD}/mc" stat silo/testbucket/testobject && exit -1
"${PWD}/mc" stat minio/testbucket/testobject && exit -1
# quick header and body write
send_put_object_request 1 1 || exit -1
"${PWD}/mc" stat silo/testbucket/testobject || exit -1
"${PWD}/mc" stat minio/testbucket/testobject || exit -1
}
function main() {
@@ -120,7 +131,7 @@ function main() {
export srv_idle_timeout=5
export -f gen_put_request
test_silo_with_timeout ${start_port}
test_minio_with_timeout ${start_port}
}
main "$@"
+74
View File
@@ -0,0 +1,74 @@
# Settings and configurations that are common for all containers
x-minio-common: &minio-common
image: minio/minio:${MINIO_VERSION}
command: server http://minio{1...4}/data{1...3}
env_file:
- ./minio.env
expose:
- "9000"
- "9001"
# starts 4 docker containers running minio server instances.
# using nginx reverse proxy, load balancing, you can access
# it through port 9000.
services:
minio1:
<<: *minio-common
hostname: minio1
volumes:
- data1-1:/data1
- data1-2:/data2
- data1-3:/data3
minio2:
<<: *minio-common
hostname: minio2
volumes:
- data2-1:/data1
- data2-2:/data2
- data2-3:/data3
minio3:
<<: *minio-common
hostname: minio3
volumes:
- data3-1:/data1
- data3-2:/data2
- data3-3:/data3
minio4:
<<: *minio-common
hostname: minio4
volumes:
- data4-1:/data1
- data4-2:/data2
- data4-3:/data3
nginx:
image: nginx:1.19.2-alpine
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
ports:
- "9000:9000"
- "9001:9001"
depends_on:
- minio1
- minio2
- minio3
- minio4
## By default this config uses default local driver,
## For custom volumes replace with volume driver configuration.
volumes:
data1-1:
data1-2:
data1-3:
data2-1:
data2-2:
data2-3:
data3-1:
data3-2:
data3-3:
data4-1:
data4-2:
data4-3:
+3
View File
@@ -0,0 +1,3 @@
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin
MINIO_BROWSER=off
+68
View File
@@ -0,0 +1,68 @@
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
# include /etc/nginx/conf.d/*.conf;
upstream minio {
server minio1:9000;
server minio2:9000;
server minio3:9000;
server minio4:9000;
}
# main minio
server {
listen 9000;
listen [::]:9000;
server_name localhost;
# To allow special characters in headers
ignore_invalid_headers off;
# Allow any size file to be uploaded.
# Set to a value such as 1000m; to restrict file size to a specific value
client_max_body_size 0;
# To disable buffering
proxy_buffering off;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 300;
# Default is HTTP/1, keepalive is only enabled in HTTP/1.1
proxy_http_version 1.1;
proxy_set_header Connection "";
chunked_transfer_encoding off;
proxy_pass http://minio;
}
}
}
+1 -1
View File
@@ -41,7 +41,7 @@ while IFS= read -r binary; do
grep -F 'vcs.' <<< "${info}" >&2 || true
exit 1
fi
done < <(find "${dist_dir}" -maxdepth 2 -type f \( -name 'silo' -o -name 'silo.exe' \) | sort)
done < <(find "${dist_dir}" -maxdepth 2 -type f \( -name 'minio' -o -name 'minio.exe' \) | sort)
if [ "${count}" -ne "${expected_count}" ]; then
echo "Expected ${expected_count} release binaries, found ${count}" >&2
+66 -56
View File
@@ -5,8 +5,8 @@ set -e
set -E
set -o pipefail
if [ ! -x "$PWD/silo" ]; then
echo "Silo executable binary not found in current directory"
if [ ! -x "$PWD/minio" ]; then
echo "minio executable binary not found in current directory"
exit 1
fi
@@ -15,196 +15,196 @@ WORK_DIR="$PWD/.verify-$RANDOM"
export MINT_MODE=core
export MINT_DATA_DIR="$WORK_DIR/data"
export SERVER_ENDPOINT="127.0.0.1:9000"
export MC_HOST_verify="http://silo:silo123@${SERVER_ENDPOINT}/"
export MC_HOST_verify_ipv6="http://silo:silo123@[::1]:9000/"
export ACCESS_KEY="silo"
export SECRET_KEY="silo123"
export MC_HOST_verify="http://minio:minio123@${SERVER_ENDPOINT}/"
export MC_HOST_verify_ipv6="http://minio:minio123@[::1]:9000/"
export ACCESS_KEY="minio"
export SECRET_KEY="minio123"
export ENABLE_HTTPS=0
export GO111MODULE=on
export GOGC=25
export ENABLE_ADMIN=1
export MINIO_CI_CD=1
SILO_CONFIG_DIR="$WORK_DIR/.silo"
SILO=("$PWD/silo" --config-dir "$SILO_CONFIG_DIR")
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR")
FILE_1_MB="$MINT_DATA_DIR/datafile-1-MB"
FILE_65_MB="$MINT_DATA_DIR/datafile-65-MB"
FUNCTIONAL_TESTS="$WORK_DIR/functional-tests.sh"
function start_silo_fs() {
function start_minio_fs() {
export MINIO_ROOT_USER=$ACCESS_KEY
export MINIO_ROOT_PASSWORD=$SECRET_KEY
"${SILO[@]}" server "${WORK_DIR}/fs-disk" >"$WORK_DIR/fs-silo.log" 2>&1 &
"${MINIO[@]}" server "${WORK_DIR}/fs-disk" >"$WORK_DIR/fs-minio.log" 2>&1 &
"${WORK_DIR}/mc" ready verify
}
function start_silo_erasure() {
"${SILO[@]}" server "${WORK_DIR}/erasure-disk1" "${WORK_DIR}/erasure-disk2" "${WORK_DIR}/erasure-disk3" "${WORK_DIR}/erasure-disk4" >"$WORK_DIR/erasure-silo.log" 2>&1 &
function start_minio_erasure() {
"${MINIO[@]}" server "${WORK_DIR}/erasure-disk1" "${WORK_DIR}/erasure-disk2" "${WORK_DIR}/erasure-disk3" "${WORK_DIR}/erasure-disk4" >"$WORK_DIR/erasure-minio.log" 2>&1 &
"${WORK_DIR}/mc" ready verify
}
function start_silo_erasure_sets() {
function start_minio_erasure_sets() {
export MINIO_ENDPOINTS="${WORK_DIR}/erasure-disk-sets{1...32}"
"${SILO[@]}" server >"$WORK_DIR/erasure-silo-sets.log" 2>&1 &
"${MINIO[@]}" server >"$WORK_DIR/erasure-minio-sets.log" 2>&1 &
"${WORK_DIR}/mc" ready verify
}
function start_silo_pool_erasure_sets() {
function start_minio_pool_erasure_sets() {
export MINIO_ROOT_USER=$ACCESS_KEY
export MINIO_ROOT_PASSWORD=$SECRET_KEY
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/pool-disk-sets{1...4} http://127.0.0.1:9001${WORK_DIR}/pool-disk-sets{5...8}"
"${SILO[@]}" server --address ":9000" >"$WORK_DIR/pool-silo-9000.log" 2>&1 &
"${SILO[@]}" server --address ":9001" >"$WORK_DIR/pool-silo-9001.log" 2>&1 &
"${MINIO[@]}" server --address ":9000" >"$WORK_DIR/pool-minio-9000.log" 2>&1 &
"${MINIO[@]}" server --address ":9001" >"$WORK_DIR/pool-minio-9001.log" 2>&1 &
"${WORK_DIR}/mc" ready verify
}
function start_silo_pool_erasure_sets_ipv6() {
function start_minio_pool_erasure_sets_ipv6() {
export MINIO_ROOT_USER=$ACCESS_KEY
export MINIO_ROOT_PASSWORD=$SECRET_KEY
export MINIO_ENDPOINTS="http://[::1]:9000${WORK_DIR}/pool-disk-sets-ipv6{1...4} http://[::1]:9001${WORK_DIR}/pool-disk-sets-ipv6{5...8}"
"${SILO[@]}" server --address="[::1]:9000" >"$WORK_DIR/pool-silo-ipv6-9000.log" 2>&1 &
"${SILO[@]}" server --address="[::1]:9001" >"$WORK_DIR/pool-silo-ipv6-9001.log" 2>&1 &
"${MINIO[@]}" server --address="[::1]:9000" >"$WORK_DIR/pool-minio-ipv6-9000.log" 2>&1 &
"${MINIO[@]}" server --address="[::1]:9001" >"$WORK_DIR/pool-minio-ipv6-9001.log" 2>&1 &
"${WORK_DIR}/mc" ready verify_ipv6
}
function start_silo_dist_erasure() {
function start_minio_dist_erasure() {
export MINIO_ROOT_USER=$ACCESS_KEY
export MINIO_ROOT_PASSWORD=$SECRET_KEY
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/dist-disk1 http://127.0.0.1:9001${WORK_DIR}/dist-disk2 http://127.0.0.1:9002${WORK_DIR}/dist-disk3 http://127.0.0.1:9003${WORK_DIR}/dist-disk4"
for i in $(seq 0 3); do
"${SILO[@]}" server --address ":900${i}" >"$WORK_DIR/dist-silo-900${i}.log" 2>&1 &
"${MINIO[@]}" server --address ":900${i}" >"$WORK_DIR/dist-minio-900${i}.log" 2>&1 &
done
"${WORK_DIR}/mc" ready verify
}
function run_test_fs() {
start_silo_fs
start_minio_fs
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
rv=$?
pkill silo
pkill minio
sleep 3
if [ "$rv" -ne 0 ]; then
cat "$WORK_DIR/fs-silo.log"
cat "$WORK_DIR/fs-minio.log"
fi
rm -f "$WORK_DIR/fs-silo.log"
rm -f "$WORK_DIR/fs-minio.log"
return "$rv"
}
function run_test_erasure_sets() {
start_silo_erasure_sets
start_minio_erasure_sets
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
rv=$?
pkill silo
pkill minio
sleep 3
if [ "$rv" -ne 0 ]; then
cat "$WORK_DIR/erasure-silo-sets.log"
cat "$WORK_DIR/erasure-minio-sets.log"
fi
rm -f "$WORK_DIR/erasure-silo-sets.log"
rm -f "$WORK_DIR/erasure-minio-sets.log"
return "$rv"
}
function run_test_pool_erasure_sets() {
start_silo_pool_erasure_sets
start_minio_pool_erasure_sets
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
rv=$?
pkill silo
pkill minio
sleep 3
if [ "$rv" -ne 0 ]; then
for i in $(seq 0 1); do
echo "server$i log:"
cat "$WORK_DIR/pool-silo-900$i.log"
cat "$WORK_DIR/pool-minio-900$i.log"
done
fi
for i in $(seq 0 1); do
rm -f "$WORK_DIR/pool-silo-900$i.log"
rm -f "$WORK_DIR/pool-minio-900$i.log"
done
return "$rv"
}
function run_test_pool_erasure_sets_ipv6() {
start_silo_pool_erasure_sets_ipv6
start_minio_pool_erasure_sets_ipv6
export SERVER_ENDPOINT="[::1]:9000"
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
rv=$?
pkill silo
pkill minio
sleep 3
if [ "$rv" -ne 0 ]; then
for i in $(seq 0 1); do
echo "server$i log:"
cat "$WORK_DIR/pool-silo-ipv6-900$i.log"
cat "$WORK_DIR/pool-minio-ipv6-900$i.log"
done
fi
for i in $(seq 0 1); do
rm -f "$WORK_DIR/pool-silo-ipv6-900$i.log"
rm -f "$WORK_DIR/pool-minio-ipv6-900$i.log"
done
return "$rv"
}
function run_test_erasure() {
start_silo_erasure
start_minio_erasure
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
rv=$?
pkill silo
pkill minio
sleep 3
if [ "$rv" -ne 0 ]; then
cat "$WORK_DIR/erasure-silo.log"
cat "$WORK_DIR/erasure-minio.log"
fi
rm -f "$WORK_DIR/erasure-silo.log"
rm -f "$WORK_DIR/erasure-minio.log"
return "$rv"
}
function run_test_dist_erasure() {
start_silo_dist_erasure
start_minio_dist_erasure
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
rv=$?
pkill silo
pkill minio
sleep 3
if [ "$rv" -ne 0 ]; then
echo "server1 log:"
cat "$WORK_DIR/dist-silo-9000.log"
cat "$WORK_DIR/dist-minio-9000.log"
echo "server2 log:"
cat "$WORK_DIR/dist-silo-9001.log"
cat "$WORK_DIR/dist-minio-9001.log"
echo "server3 log:"
cat "$WORK_DIR/dist-silo-9002.log"
cat "$WORK_DIR/dist-minio-9002.log"
echo "server4 log:"
cat "$WORK_DIR/dist-silo-9003.log"
cat "$WORK_DIR/dist-minio-9003.log"
fi
rm -f "$WORK_DIR/dist-silo-9000.log" "$WORK_DIR/dist-silo-9001.log" "$WORK_DIR/dist-silo-9002.log" "$WORK_DIR/dist-silo-9003.log"
rm -f "$WORK_DIR/dist-minio-9000.log" "$WORK_DIR/dist-minio-9001.log" "$WORK_DIR/dist-minio-9002.log" "$WORK_DIR/dist-minio-9003.log"
return "$rv"
}
@@ -216,21 +216,31 @@ function purge() {
function __init__() {
echo "Initializing environment"
mkdir -p "$WORK_DIR"
mkdir -p "$SILO_CONFIG_DIR"
mkdir -p "$MINIO_CONFIG_DIR"
mkdir -p "$MINT_DATA_DIR"
"$(git rev-parse --show-toplevel)/buildscripts/install-mcli.sh" "${WORK_DIR}/mc"
MC_BUILD_DIR="mc-$RANDOM"
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
echo "failed to download https://github.com/minio/mc"
purge "${MC_BUILD_DIR}"
exit 1
fi
(cd "${MC_BUILD_DIR}" && go build -o "${WORK_DIR}/mc")
# remove mc source.
purge "${MC_BUILD_DIR}"
shred -n 1 -s 1M - 1>"$FILE_1_MB" 2>/dev/null
shred -n 1 -s 65M - 1>"$FILE_65_MB" 2>/dev/null
## version is purposefully set to '3' for minio to migrate configuration file
echo '{"version": "3", "credential": {"accessKey": "silo", "secretKey": "silo123"}, "region": "us-east-1"}' >"$SILO_CONFIG_DIR/config.json"
echo '{"version": "3", "credential": {"accessKey": "minio", "secretKey": "minio123"}, "region": "us-east-1"}' >"$MINIO_CONFIG_DIR/config.json"
"$(git rev-parse --show-toplevel)/buildscripts/install-verified-fixture.sh" \
https://raw.githubusercontent.com/pgsty/mc/4c4dcc4b55baf238cd0c81030d77945b3828f157/functional-tests.sh \
9b98c8152b294d567b9bc732869226dd4f65d0f4d84092dc066a900a66a9e22c \
"$FUNCTIONAL_TESTS"
if ! wget -q -O "$FUNCTIONAL_TESTS" https://raw.githubusercontent.com/minio/mc/master/functional-tests.sh; then
echo "failed to download https://raw.githubusercontent.com/minio/mc/master/functional-tests.sh"
exit 1
fi
sed -i 's|-sS|-sSg|g' "$FUNCTIONAL_TESTS"
chmod a+x "$FUNCTIONAL_TESTS"
@@ -4,18 +4,18 @@
set -E
set -o pipefail
if [ ! -x "$PWD/silo" ]; then
echo "Silo executable binary not found in current directory"
if [ ! -x "$PWD/minio" ]; then
echo "minio executable binary not found in current directory"
exit 1
fi
WORK_DIR="$PWD/.verify-$RANDOM"
SILO_CONFIG_DIR="$WORK_DIR/.silo"
SILO=("$PWD/silo" --config-dir "$SILO_CONFIG_DIR" server)
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
function start_silo_3_node() {
export MINIO_ROOT_USER=silo
export MINIO_ROOT_PASSWORD=silo123
function start_minio_3_node() {
export MINIO_ROOT_USER=minio
export MINIO_ROOT_PASSWORD=minio123
export MINIO_ERASURE_SET_DRIVE_COUNT=6
export MINIO_CI_CD=1
@@ -25,26 +25,26 @@ function start_silo_3_node() {
args="$args http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/1/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/2/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/3/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/4/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/5/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/6/"
done
"${SILO[@]}" --address ":$((start_port + 1))" $args >"${WORK_DIR}/dist-silo-server1.log" 2>&1 &
"${MINIO[@]}" --address ":$((start_port + 1))" $args >"${WORK_DIR}/dist-minio-server1.log" 2>&1 &
pid1=$!
disown ${pid1}
"${SILO[@]}" --address ":$((start_port + 2))" $args >"${WORK_DIR}/dist-silo-server2.log" 2>&1 &
"${MINIO[@]}" --address ":$((start_port + 2))" $args >"${WORK_DIR}/dist-minio-server2.log" 2>&1 &
pid2=$!
disown $pid2
"${SILO[@]}" --address ":$((start_port + 3))" $args >"${WORK_DIR}/dist-silo-server3.log" 2>&1 &
"${MINIO[@]}" --address ":$((start_port + 3))" $args >"${WORK_DIR}/dist-minio-server3.log" 2>&1 &
pid3=$!
disown $pid3
export MC_HOST_mysilo="http://silo:silo123@127.0.0.1:$((start_port + 1))"
export MC_HOST_myminio="http://minio:minio123@127.0.0.1:$((start_port + 1))"
timeout 15m /tmp/mc ready mysilo || fail
timeout 15m /tmp/mc ready myminio || fail
# Wait for all drives to be online and formatted
while [ $(/tmp/mc admin info --json mysilo | jq '.info.servers[].drives[].state | select(. != "ok")' | wc -l) -gt 0 ]; do sleep 1; done
while [ $(/tmp/mc admin info --json myminio | jq '.info.servers[].drives[].state | select(. != "ok")' | wc -l) -gt 0 ]; do sleep 1; done
# Wait for all drives to be healed
while [ $(/tmp/mc admin info --json mysilo | jq '.info.servers[].drives[].healing | select(. != null) | select(. == true)' | wc -l) -gt 0 ]; do sleep 1; done
while [ $(/tmp/mc admin info --json myminio | jq '.info.servers[].drives[].healing | select(. != null) | select(. == true)' | wc -l) -gt 0 ]; do sleep 1; done
# Wait for Status: in MinIO output
while true; do
@@ -66,26 +66,26 @@ function start_silo_3_node() {
done
if ! ps -p $pid1 1>&2 >/dev/null; then
echo "silo-server-1 is not running." && fail
echo "minio-server-1 is not running." && fail
fi
if ! ps -p $pid2 1>&2 >/dev/null; then
echo "silo-server-2 is not running." && fail
echo "minio-server-2 is not running." && fail
fi
if ! ps -p $pid3 1>&2 >/dev/null; then
echo "silo-server-3 is not running." && fail
echo "minio-server-3 is not running." && fail
fi
if ! pkill silo; then
if ! pkill minio; then
fail
fi
sleep 1
if pgrep silo; then
if pgrep minio; then
# forcibly killing, to proceed further properly.
if ! pkill -9 silo; then
echo "no Silo process running anymore, proceed."
if ! pkill -9 minio; then
echo "no minio process running anymore, proceed."
fi
fi
}
@@ -93,7 +93,7 @@ function start_silo_3_node() {
function fail() {
for i in $(seq 1 3); do
echo "server$i log:"
cat "${WORK_DIR}/dist-silo-server$i.log"
cat "${WORK_DIR}/dist-minio-server$i.log"
done
echo "FAILED"
purge "$WORK_DIR"
@@ -101,7 +101,7 @@ function fail() {
}
function check_online() {
if ! grep -q 'API:' ${WORK_DIR}/dist-silo-*.log; then
if ! grep -q 'API:' ${WORK_DIR}/dist-minio-*.log; then
echo "1"
fi
}
@@ -113,18 +113,19 @@ function purge() {
function __init__() {
echo "Initializing environment"
mkdir -p "$WORK_DIR"
mkdir -p "$SILO_CONFIG_DIR"
mkdir -p "$MINIO_CONFIG_DIR"
## version is purposefully set to '3' for minio to migrate configuration file
echo '{"version": "3", "credential": {"accessKey": "silo", "secretKey": "silo123"}, "region": "us-east-1"}' >"$SILO_CONFIG_DIR/config.json"
echo '{"version": "3", "credential": {"accessKey": "minio", "secretKey": "minio123"}, "region": "us-east-1"}' >"$MINIO_CONFIG_DIR/config.json"
if [ ! -f /tmp/mc ]; then
"$(git rev-parse --show-toplevel)/buildscripts/install-mcli.sh" /tmp/mc
wget --quiet -O /tmp/mc https://dl.minio.io/client/mc/release/linux-amd64/mc &&
chmod +x /tmp/mc
fi
}
function perform_test() {
start_silo_3_node $2
start_minio_3_node $2
echo "Testing Distributed Erasure setup healing of drives"
echo "Remove the contents of the disks belonging to '${1}' erasure set"
@@ -132,7 +133,7 @@ function perform_test() {
rm -rf ${WORK_DIR}/${1}/*/
set -x
start_silo_3_node $2
start_minio_3_node $2
}
function main() {
+10 -10
View File
@@ -4,20 +4,20 @@ set -E
set -o pipefail
set -x
if [ ! -x "$PWD/silo" ]; then
echo "Silo executable binary not found in current directory"
if [ ! -x "$PWD/minio" ]; then
echo "minio executable binary not found in current directory"
exit 1
fi
WORK_DIR="$(mktemp -d)"
SILO_CONFIG_DIR="$WORK_DIR/.silo"
SILO=("$PWD/silo" --config-dir "$SILO_CONFIG_DIR" server)
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
function start_silo() {
function start_minio() {
start_port=$1
export MINIO_ROOT_USER=silo
export MINIO_ROOT_PASSWORD=silo123
export MINIO_ROOT_USER=minio
export MINIO_ROOT_PASSWORD=minio123
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
unset MINIO_CI_CD
unset CI
@@ -28,7 +28,7 @@ function start_silo() {
done
for i in $(seq 1 4); do
"${SILO[@]}" --address ":$((start_port + i))" ${args[@]} 2>&1 >"${WORK_DIR}/server$i.log" &
"${MINIO[@]}" --address ":$((start_port + i))" ${args[@]} 2>&1 >"${WORK_DIR}/server$i.log" &
done
# Wait until all nodes return 403
@@ -60,7 +60,7 @@ function prepare_block_devices() {
# Start a distributed MinIO setup, unmount one disk and check if it is formatted
function main() {
start_port=$(shuf -i 10000-65000 -n 1)
start_silo ${start_port}
start_minio ${start_port}
# Unmount the disk, after the unmount the device id
# /tmp/xxx/mnt/disk4 will be the same as '/' and it
@@ -82,7 +82,7 @@ function main() {
}
function cleanup() {
pkill silo
pkill minio
sudo umount ${WORK_DIR}/mnt/disk{1..3}/
sudo rm /dev/minio-loopdisk*
rm -rf "$WORK_DIR"
+31 -30
View File
@@ -4,23 +4,23 @@
set -E
set -o pipefail
if [ ! -x "$PWD/silo" ]; then
echo "Silo executable binary not found in current directory"
if [ ! -x "$PWD/minio" ]; then
echo "minio executable binary not found in current directory"
exit 1
fi
WORK_DIR="$PWD/.verify-$RANDOM"
SILO_CONFIG_DIR="$WORK_DIR/.silo"
SILO=("$PWD/silo" --config-dir "$SILO_CONFIG_DIR" server)
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
GOPATH=/tmp/gopath
function start_silo_3_node() {
function start_minio_3_node() {
for i in $(seq 1 3); do
rm "${WORK_DIR}/dist-silo-server$i.log"
rm "${WORK_DIR}/dist-minio-server$i.log"
done
export MINIO_ROOT_USER=silo
export MINIO_ROOT_PASSWORD=silo123
export MINIO_ROOT_USER=minio
export MINIO_ROOT_PASSWORD=minio123
export MINIO_ERASURE_SET_DRIVE_COUNT=6
export MINIO_CI_CD=1
@@ -34,51 +34,51 @@ function start_silo_3_node() {
args="$args http://127.0.0.1:$((start_port + 1))${WORK_DIR}/1/${d}/ http://127.0.0.1:$((start_port + 2))${WORK_DIR}/2/${d}/ http://127.0.0.1:$((start_port + 3))${WORK_DIR}/3/${d}/ "
done
"${SILO[@]}" --address ":$((start_port + 1))" $args >"${WORK_DIR}/dist-silo-server1.log" 2>&1 &
"${MINIO[@]}" --address ":$((start_port + 1))" $args >"${WORK_DIR}/dist-minio-server1.log" 2>&1 &
pid1=$!
disown ${pid1}
"${SILO[@]}" --address ":$((start_port + 2))" $args >"${WORK_DIR}/dist-silo-server2.log" 2>&1 &
"${MINIO[@]}" --address ":$((start_port + 2))" $args >"${WORK_DIR}/dist-minio-server2.log" 2>&1 &
pid2=$!
disown $pid2
"${SILO[@]}" --address ":$((start_port + 3))" $args >"${WORK_DIR}/dist-silo-server3.log" 2>&1 &
"${MINIO[@]}" --address ":$((start_port + 3))" $args >"${WORK_DIR}/dist-minio-server3.log" 2>&1 &
pid3=$!
disown $pid3
export MC_HOST_mysilo="http://silo:silo123@127.0.0.1:$((start_port + 1))"
timeout 15m /tmp/mc ready mysilo || fail
export MC_HOST_myminio="http://minio:minio123@127.0.0.1:$((start_port + 1))"
timeout 15m /tmp/mc ready myminio || fail
[ ${first_time} -eq 0 ] && upload_objects
[ ${first_time} -ne 0 ] && sleep 120
if ! ps -p $pid1 1>&2 >/dev/null; then
echo "silo server 1 is not running" && fail
echo "minio server 1 is not running" && fail
fi
if ! ps -p $pid2 1>&2 >/dev/null; then
echo "silo server 2 is not running" && fail
echo "minio server 2 is not running" && fail
fi
if ! ps -p $pid3 1>&2 >/dev/null; then
echo "silo server 3 is not running" && fail
echo "minio server 3 is not running" && fail
fi
if ! pkill silo; then
if ! pkill minio; then
fail
fi
sleep 1
if pgrep silo; then
if pgrep minio; then
# forcibly killing, to proceed further properly.
if ! pkill -9 silo; then
echo "no Silo process running anymore, proceed."
if ! pkill -9 minio; then
echo "no minio process running anymore, proceed."
fi
fi
}
function check_heal() {
if ! grep -q 'API:' ${WORK_DIR}/dist-silo-*.log; then
if ! grep -q 'API:' ${WORK_DIR}/dist-minio-*.log; then
return 1
fi
@@ -103,9 +103,9 @@ function purge() {
function fail() {
for i in $(seq 1 3); do
echo "server$i log:"
cat "${WORK_DIR}/dist-silo-server$i.log"
cat "${WORK_DIR}/dist-minio-server$i.log"
done
pkill -9 silo
pkill -9 minio
echo "FAILED"
purge "$WORK_DIR"
exit 1
@@ -114,27 +114,28 @@ function fail() {
function __init__() {
echo "Initializing environment"
mkdir -p "$WORK_DIR"
mkdir -p "$SILO_CONFIG_DIR"
mkdir -p "$MINIO_CONFIG_DIR"
## version is purposefully set to '3' for minio to migrate configuration file
echo '{"version": "3", "credential": {"accessKey": "silo", "secretKey": "silo123"}, "region": "us-east-1"}' >"$SILO_CONFIG_DIR/config.json"
echo '{"version": "3", "credential": {"accessKey": "minio", "secretKey": "minio123"}, "region": "us-east-1"}' >"$MINIO_CONFIG_DIR/config.json"
if [ ! -f /tmp/mc ]; then
"$(git rev-parse --show-toplevel)/buildscripts/install-mcli.sh" /tmp/mc
wget --quiet -O /tmp/mc https://dl.minio.io/client/mc/release/linux-amd64/mc &&
chmod +x /tmp/mc
fi
}
function upload_objects() {
/tmp/mc mb mysilo/testbucket/
/tmp/mc mb myminio/testbucket/
for ((i = 0; i < 20; i++)); do
echo "my content" | /tmp/mc pipe mysilo/testbucket/file-$i
echo "my content" | /tmp/mc pipe myminio/testbucket/file-$i
done
}
function perform_test() {
start_port=$2
start_silo_3_node $start_port
start_minio_3_node $start_port
echo "Testing Distributed Erasure setup healing of drives"
echo "Remove the contents of the disks belonging to '${1}' node"
@@ -142,7 +143,7 @@ function perform_test() {
rm -rf ${WORK_DIR}/${1}/*/
set -x
start_silo_3_node $start_port
start_minio_3_node $start_port
check_heal ${1}
rv=$?
-122
View File
@@ -1,122 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_dir="$(cd "${script_dir}/.." && pwd)"
baseline_commit="${HELM_LEGACY_COMMIT:-d88f46cce}"
helm_image="${HELM_IMAGE:-alpine/helm:3.18.6@sha256:c6d8088ddb279625a2e1ca3b08b22c18c946d1f65c8b810f28f1597435a1134c}"
work_dir="$(mktemp -d "${TMPDIR:-/tmp}/silo-helm.XXXXXX")"
cleanup() {
rm -rf "${work_dir}"
}
trap cleanup EXIT
cd "${repo_dir}"
git cat-file -e "${baseline_commit}^{commit}"
git archive "${baseline_commit}" helm/minio | tar -x -C "${work_dir}"
if command -v helm >/dev/null 2>&1; then
new_chart="${repo_dir}/helm/silo"
old_chart="${work_dir}/helm/minio"
output_dir="${work_dir}"
helm_run() {
helm "$@"
}
else
command -v docker >/dev/null 2>&1 || {
echo "helm or docker is required" >&2
exit 1
}
new_chart=/repo/helm/silo
old_chart=/check/helm/minio
output_dir=/check
helm_run() {
docker run --rm \
-v "${repo_dir}:/repo:ro" \
-v "${work_dir}:/check" \
"${helm_image}" "$@"
}
fi
helm_run lint "${new_chart}"
helm_run template silo "${new_chart}" \
--namespace silo \
--set rootUser=silo-admin \
--set rootPassword=test-password-123456 >/dev/null
helm_run template silo "${new_chart}" \
--namespace silo \
--set mode=standalone \
--set replicas=1 \
--set persistence.enabled=false \
--set rootUser=silo-admin \
--set rootPassword=test-password-123456 >/dev/null
# Exercise optional templates that the default render leaves dormant.
helm_run template silo-all "${new_chart}" \
--namespace silo \
--set rootUser=silo-admin \
--set rootPassword=test-password-123456 \
--set tls.enabled=true \
--set tls.certSecret=silo-tls \
--set trustedCertsSecret=silo-trusted-ca \
--set ingress.enabled=true \
--set consoleIngress.enabled=true \
--set networkPolicy.enabled=true \
--set podDisruptionBudget.enabled=true \
--set metrics.serviceMonitor.enabled=true \
--set metrics.serviceMonitor.includeNode=true \
--set 'buckets[0].name=chart-test' \
--set 'buckets[0].policy=none' \
--set 'buckets[0].purge=false' >/dev/null
# Existing values commonly address the historical myminio target. Render the
# custom-command path explicitly so both the new and compatibility aliases are
# protected by the release gate rather than only by a source-text assertion.
custom_render="${work_dir}/custom-command.yaml"
helm_run template silo-custom "${new_chart}" \
--namespace silo \
--set rootUser=silo-admin \
--set rootPassword=test-password-123456 \
--set-string 'customCommands[0].command=admin info myminio' \
--show-only templates/configmap.yaml >"${custom_render}"
for expected in \
'alias set mysilo' \
'alias set myminio' \
'runCommand admin info myminio'; do
grep -F -- "${expected}" "${custom_render}" >/dev/null || {
echo "rendered custom command is missing: ${expected}" >&2
exit 1
}
done
old_render="${work_dir}/legacy.yaml"
new_render="${work_dir}/candidate.yaml"
helm_run template my-release "${old_chart}" \
--namespace my-namespace \
--set rootUser=legacy-admin \
--set rootPassword=legacy-password-123456 >"${old_render}"
helm_run template my-release "${new_chart}" \
--namespace my-namespace \
-f "${old_chart}/values.yaml" \
--set rootUser=legacy-admin \
--set rootPassword=legacy-password-123456 \
--set nameOverride=minio \
--set fullnameOverride=my-release-minio \
--set serviceAccount.name=minio-sa \
--set image.repository=pgsty/silo \
--set mcImage.repository=pgsty/silo \
--set-string image.tag=RELEASE.2026-08-04T00-00-00Z \
--set-string mcImage.tag=RELEASE.2026-08-04T00-00-00Z >"${new_render}"
go run ./buildscripts/helm-migration-guard "${old_render}" "${new_render}"
helm_run package "${new_chart}" --destination "${output_dir}" >/dev/null
test -s "${work_dir}/silo-7.0.1.tgz"
if find "${work_dir}" -maxdepth 1 -type f -name 'minio-*.tgz' | grep -q .; then
echo "Helm packaging emitted a legacy MinIO chart name" >&2
exit 1
fi
echo "Silo Helm lint, render, legacy-upgrade, and package checks passed"
-218
View File
@@ -1,218 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_dir="$(cd "${script_dir}/.." && pwd)"
cd "${repo_dir}"
fail() {
echo "Silo rebrand verification failed: $*" >&2
exit 1
}
require_file() {
[ -f "$1" ] || fail "missing required file: $1"
}
require_text() {
local file="$1"
local text="$2"
grep -Fq -- "${text}" "${file}" || fail "${file} does not contain: ${text}"
}
reject_text() {
local file="$1"
local text="$2"
if grep -Fq -- "${text}" "${file}"; then
fail "${file} still contains forbidden delivery text: ${text}"
fi
}
for file in \
.github/goreleaser.yml \
.github/nfpm.yml \
buildscripts/package/lifecycle_test.sh \
buildscripts/verify-helm-migration.sh \
Dockerfile.goreleaser \
Dockerfile.distroless \
dockerscripts/download-static-curl.sh \
dockerscripts/docker-entrypoint.sh \
helm/silo/Chart.yaml \
helm/silo/values.yaml \
silo.service \
silo.env \
silo.sysusers; do
require_file "${file}"
done
for retired_path in \
CNAME _config.yml index.yaml helm-reindex.sh helm/minio helm-releases \
Dockerfile Dockerfile.cicd Dockerfile.hotfix Dockerfile.release \
Dockerfile.release.old_cpu Dockerfile.scratch docker-buildx.sh \
minio.service cmd/callhome.go buildscripts/upgrade-tests .github/logo.svg \
docs/federation/lookup/bucket-lookup.png \
docs/screenshots/Minio_Cloud_Native_Arch.jpg \
docs/screenshots/Minio_Cloud_Native_Arch.png \
docs/screenshots/Minio_Cloud_Native_Arch.svg \
docs/screenshots/Architecture-diagram_distributed_8.jpg \
docs/screenshots/Architecture-diagram_distributed_8.png \
docs/screenshots/Architecture-diagram_distributed_8.svg \
docs/screenshots/Architecture-diagram_distributed_16.jpg \
docs/screenshots/Architecture-diagram_distributed_16.png \
docs/screenshots/Architecture-diagram_distributed_16.svg \
docs/screenshots/Architecture-diagram_distributed_nm.png \
docs/screenshots/Example-1.jpg docs/screenshots/Example-1.png \
docs/screenshots/Example-2.jpg docs/screenshots/Example-2.png \
docs/screenshots/Example-3.jpg docs/screenshots/Example-3.png \
docs/screenshots/pic1.png docs/screenshots/pic2.png \
docs/metrics/prometheus/grafana/grafana-minio.png \
docs/metrics/prometheus/grafana/bucket/grafana-bucket.png \
docs/metrics/prometheus/grafana/node/grafana-node.png \
docs/metrics/prometheus/grafana/replication/grafana-replication-cluster.png \
docs/metrics/prometheus/grafana/replication/grafana-replication-node.png; do
[ ! -e "${retired_path}" ] || fail "retired upstream delivery path remains: ${retired_path}"
done
require_text .github/goreleaser.yml "binary: silo"
require_text .github/goreleaser.yml 'name_template: "silo_{{ .Env.PKG_VERSION }}_{{ .Os }}_{{ .Arch }}"'
require_text .github/goreleaser.yml "sboms:"
require_text .github/goreleaser.yml "artifacts: archive"
require_text .github/goreleaser.yml "cmd: cosign"
# shellcheck disable=SC2016 # Match the literal GoReleaser template variable.
require_text .github/goreleaser.yml 'signature: "${artifact}.sigstore.json"'
require_text .github/nfpm.yml "name: silo"
require_text .github/nfpm.yml "dst: /usr/bin/silo"
require_text .github/nfpm.yml "dst: /etc/default/silo"
require_text .github/nfpm.yml "dst: /usr/lib/sysusers.d/silo.conf"
require_text buildscripts/package/lifecycle_test.sh "Silo package lifecycle checks passed"
require_text buildscripts/verify-helm-migration.sh "Silo Helm lint, render, legacy-upgrade, and package checks passed"
require_text silo.service "Conflicts=minio.service"
require_text silo.service "EnvironmentFile=-/etc/default/minio"
require_text silo.service "EnvironmentFile=-/etc/default/silo"
# shellcheck disable=SC2016 # Match the literal service environment variables.
require_text silo.service 'ExecStart=/usr/bin/silo server $MINIO_OPTS $MINIO_VOLUMES'
require_text README.md "/etc/systemd/system/silo.service.d/10-legacy-user.conf"
require_text README_ZH.md "/etc/systemd/system/silo.service.d/10-legacy-user.conf"
require_text Dockerfile.goreleaser "COPY silo /usr/bin/silo"
require_text Dockerfile.goreleaser 'CMD ["silo"]'
require_text Dockerfile.goreleaser "MC_AMD64_SHA256="
require_text Dockerfile.goreleaser "Published checksum drift"
require_text Dockerfile.distroless 'COPY --chmod=0755 silo /usr/bin/silo'
require_text Dockerfile.distroless 'ENTRYPOINT ["/usr/bin/silo"]'
require_text Dockerfile.distroless '"/usr/bin/silo", "healthcheck", "ready"'
require_text dockerscripts/download-static-curl.sh "sha256sum -c"
require_text helm/silo/Chart.yaml "name: silo"
require_text helm/silo/values.yaml "repository: pgsty/silo"
require_text helm/silo/templates/deployment.yaml "/usr/bin/docker-entrypoint.sh silo server"
require_text helm/silo/templates/statefulset.yaml "/usr/bin/docker-entrypoint.sh silo server"
require_text docs/orchestration/docker-compose/docker-compose.yaml 'http://silo{1...4}/data{1...2}'
require_text docs/resiliency/docker-compose.yaml 'http://silo{1...4}/data{1...8}'
require_text docs/distributed/DECOMMISSION.md 'systemctl restart silo'
# shellcheck disable=SC2016 # Match the literal shell variable.
require_text docs/resiliency/resiliency-tests.sh 'docker exec resiliency-silo$NODE-1'
require_text .github/workflows/release.yml "Attest downloadable release artifacts"
require_text .github/workflows/release.yml "packages_checksums.txt"
require_text .github/workflows/docker-release.yml "Attest multi-architecture image provenance"
require_text .github/workflows/docker-release.yml "index.docker.io/pgsty/silo"
# Copyright notices credit both parties with fixed terms: upstream MinIO
# development ends at its own last year, and the fork's own term starts when
# the fork did. Deriving the upstream end year from the clock would extend
# MinIO's copyright term every January.
require_text cmd/build-constants.go 'upstreamCopyrightEndYear = "2025"'
require_text cmd/build-constants.go 'forkCopyrightStartYear = "2025"'
require_text cmd/main.go 'upstreamCopyrightEndYear'
reject_text cmd/main.go 'CopyrightYear = strconv.Itoa(time.Now().Year())'
require_text NOTICE 'MinIO Project, (C) 2015-2025 MinIO, Inc.'
require_text NOTICE 'Silo Project modifications, (C) 2025-2026 PGSTY.'
# Contribution policy: no CLA, inbound=outbound, DCO sign-off enforced in CI.
require_file .github/workflows/dco.yml
require_text .github/workflows/dco.yml "Signed-off-by"
require_text CONTRIBUTING.md "developercertificate.org"
require_text CONTRIBUTING.md "No CLA"
for file in .github/nfpm.yml Dockerfile.goreleaser silo.service; do
reject_text "${file}" "/usr/bin/minio"
reject_text "${file}" "/usr/local/bin/minio"
done
reject_text Dockerfile.goreleaser "MINIO_UPDATE_MINISIGN_PUBKEY"
reject_text buildscripts/minio-upgrade.sh "docker system prune"
reject_text buildscripts/minio-upgrade.sh "docker volume prune"
reject_text docs/orchestration/docker-compose/docker-compose.yaml 'http://minio{1...4}'
reject_text docs/resiliency/docker-compose.yaml 'http://minio{1...4}'
reject_text docs/distributed/DECOMMISSION.md 'systemctl restart minio'
reject_text docs/resiliency/resiliency-tests.sh 'resiliency-minio'
reject_text docs/resiliency/resiliency-tests.sh 'docker system prune'
reject_text docs/resiliency/resiliency-tests.sh 'docker image prune'
reject_text docs/resiliency/resiliency-tests.sh 'docker ps -q'
if grep -Ev '^[[:space:]]*(#|$)' silo.env | grep -q '='; then
fail "silo.env must not contain active assignments that shadow /etc/default/minio"
fi
if rg -n 'pgsty/minio:' .github/workflows Dockerfile.goreleaser helm/silo; then
fail "an active delivery surface still publishes the frozen pgsty/minio image"
fi
# The repository and its default branch are pgsty/silo and main. The invariant
# is that the old name is never a live target, not that it is never spoken: the
# READMEs have to name it to explain the rename and to point at the archived
# artifacts, which is the opposite of stranding a reader on it.
#
# So two rules. First, no live URL may resolve to the old repository anywhere,
# READMEs included.
stale_repo_url="$(rg -n -e 'github\.com/pgsty/minio' -e 'hub\.docker\.com/r/pgsty/minio' \
--glob '!.git/**' --glob '!dist/**' \
--glob '!SILO_REBRANDING_MIGRATION.md' \
--glob '!buildscripts/rebrand-guard/compat-baseline.json' . |
sed 's#^\./##' | grep -v '^buildscripts/verify-rebrand\.sh:' || true)"
if [ -n "${stale_repo_url}" ]; then
printf '%s\n' "${stale_repo_url}" >&2
fail "a link still resolves to the pre-rename pgsty/minio repository"
fi
# Second, the bare name may only appear where it is deliberate: the pinned
# pre-rebrand image digest in the upgrade test, the two guards that refuse a
# legacy image, and the two READMEs that document the rename and the archived
# minio branch.
repo_guard_allowlist='^(buildscripts/minio-upgrade\.sh|buildscripts/verify-rebrand\.sh|buildscripts/helm-migration-guard/main\.go|README\.md|README_ZH\.md):'
stale_repo="$(rg -n 'pgsty/minio' --glob '!.git/**' --glob '!dist/**' \
--glob '!SILO_REBRANDING_MIGRATION.md' \
--glob '!buildscripts/rebrand-guard/compat-baseline.json' . |
sed 's#^\./##' | grep -Ev "${repo_guard_allowlist}" || true)"
if [ -n "${stale_repo}" ]; then
printf '%s\n' "${stale_repo}" >&2
fail "a source reference still names the pre-rename pgsty/minio repository"
fi
stale_branch="$(rg -n 'pgsty/silo/(blob/|tree/|raw/)?master' \
--glob '!.git/**' --glob '!dist/**' . || true)"
if [ -n "${stale_branch}" ]; then
printf '%s\n' "${stale_branch}" >&2
fail "a link still targets the retired master branch; raw and Actions URLs do not follow a branch rename"
fi
for workflow in .github/workflows/go.yml .github/workflows/vulncheck.yml; do
if rg -q '^\s+- master$' "${workflow}"; then
fail "${workflow} still filters on master and would go silently dormant on main"
fi
require_text "${workflow}" " - main"
done
network_hits="$(rg -n --glob '*.go' --glob '!**/*_test.go' \
'https?://[^"`[:space:]]*(dl\.min\.io|subnet\.min\.io|api\.min\.io|slack\.min\.io|play\.min\.io)' \
cmd internal || true)"
network_hits="$(printf '%s\n' "${network_hits}" | grep -Ev '^[^:]+:[0-9]+:[[:space:]]*//' || true)"
if [ -n "${network_hits}" ]; then
printf '%s\n' "${network_hits}" >&2
fail "runtime code still contains an upstream MinIO service endpoint"
fi
require_text cmd/build-constants.go 'MinioReleaseBaseURL = ""'
require_text cmd/globals.go "globalInplaceUpdateDisabled = true"
reject_text cmd/globals.go "subnetAdminPublicKey"
reject_text cmd/admin-handlers.go "getSubnetAdminPublicKey"
echo "Silo delivery and runtime rebrand checks passed"
+41 -33
View File
@@ -84,7 +84,7 @@ const (
// ServerUpdateV2Handler - POST /minio/admin/v3/update?updateURL={updateURL}&type=2
// ----------
// Retained for Admin API compatibility. Silo always returns MethodNotAllowed.
// updates all minio servers and restarts them gracefully.
func (a adminAPIHandlers) ServerUpdateV2Handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
@@ -320,7 +320,7 @@ func (a adminAPIHandlers) ServerUpdateV2Handler(w http.ResponseWriter, r *http.R
// ServerUpdateHandler - POST /minio/admin/v3/update?updateURL={updateURL}
// ----------
// Retained for Admin API compatibility. Silo always returns MethodNotAllowed.
// updates all minio servers and restarts them gracefully.
func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
@@ -330,7 +330,7 @@ func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Req
}
if globalInplaceUpdateDisabled || currentReleaseTime.IsZero() {
// MINIO_UPDATE is retained, but Silo permanently disables in-place updates.
// if MINIO_UPDATE=off - inplace update is disabled, mostly in containers.
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return
}
@@ -2708,10 +2708,10 @@ func fetchHealthInfo(healthCtx context.Context, objectAPI ObjectLayer, query *ur
}
// Server start command regex groups:
// 1 - silo server (or the legacy minio command)
// 2 - flags e.g. `--address :9000 --certs-dir /etc/silo/certs`
// 1 - minio server
// 2 - flags e.g. `--address :9000 --certs-dir /etc/minio/certs`
// 3 - pool args e.g. `https://node{01...16}.domain/data/disk{001...204} https://node{17...32}.domain/data/disk{001...204}`
re := regexp.MustCompile(`^(.*silo\s+server\s+|.*minio\s+server\s+)(--[^\s]+\s+[^\s]+\s+)*(.*)`)
re := regexp.MustCompile(`^(.*minio\s+server\s+)(--[^\s]+\s+[^\s]+\s+)*(.*)`)
// stays unchanged in the anonymized version
cmdLineWithoutPools := re.ReplaceAllString(cmdLine, `$1$2`)
@@ -3282,7 +3282,28 @@ func (a adminAPIHandlers) InspectDataHandler(w http.ResponseWriter, r *http.Requ
stream := estream.NewWriter(w)
defer stream.Close()
if err := addInspectDataKey(stream, publicKey, getClusterMetaInfo(ctx)); err != nil {
clusterKey, err := bytesToPublicKey(getSubnetAdminPublicKey())
if err != nil {
bugLogIf(ctx, stream.AddError(err.Error()))
return
}
err = stream.AddKeyEncrypted(clusterKey)
if err != nil {
bugLogIf(ctx, stream.AddError(err.Error()))
return
}
if b := getClusterMetaInfo(ctx); len(b) > 0 {
w, err := stream.AddEncryptedStream("cluster.info", nil)
if err != nil {
bugLogIf(ctx, err)
return
}
w.Write(b)
w.Close()
}
// Add new key for inspect data.
if err := stream.AddKeyEncrypted(publicKey); err != nil {
bugLogIf(ctx, stream.AddError(err.Error()))
return
}
@@ -3411,7 +3432,7 @@ func (a adminAPIHandlers) InspectDataHandler(w http.ResponseWriter, r *http.Requ
scheme = "http"
}
// Save a Silo start script to inspect command.
// save MinIO start script to inspect command
var scrb bytes.Buffer
fmt.Fprintf(&scrb, `#!/usr/bin/env bash
@@ -3422,43 +3443,30 @@ function main() {
done
# Read content of inspect-input.txt
SILO_OPTS=$(grep "Server command line args" <./inspect-input.txt | sed "s/Server command line args: //g" | sed -r "s#%s:\/\/#\.\/#g")
MINIO_OPTS=$(grep "Server command line args" <./inspect-input.txt | sed "s/Server command line args: //g" | sed -r "s#%s:\/\/#\.\/#g")
# Start Silo using the options
START_CMD="CI=on _MINIO_AUTO_DRIVE_HEALING=off silo server ${SILO_OPTS} &"
# Start MinIO instance using the options
START_CMD="CI=on _MINIO_AUTO_DRIVE_HEALING=off minio server ${MINIO_OPTS} &"
echo
echo "Starting Silo: ${START_CMD}"
echo "Starting MinIO instance: ${START_CMD}"
echo
eval "$START_CMD"
SILO_SRVR_PID="$!"
echo "Silo Server PID: ${SILO_SRVR_PID}"
MINIO_SRVR_PID="$!"
echo "MinIO Server PID: ${MINIO_SRVR_PID}"
echo
echo "Waiting for Silo to get ready!"
echo "Waiting for MinIO instance to get ready!"
sleep 10
}
main "$@"`, scheme)
adminLogIf(ctx, embedFileInZip(inspectZipW, "start-silo.sh", scrb.Bytes(), 0o755))
adminLogIf(ctx, embedFileInZip(inspectZipW, "start-minio.sh", scrb.Bytes(), 0o755))
}
// addInspectDataKey makes the requester the only recipient of encrypted
// diagnostic data. Silo has no built-in vendor or support-service recipient.
func addInspectDataKey(stream *estream.Writer, publicKey *rsa.PublicKey, clusterInfo []byte) error {
if err := stream.AddKeyEncrypted(publicKey); err != nil {
return err
func getSubnetAdminPublicKey() []byte {
if globalIsCICD {
return subnetAdminPublicKeyDev
}
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()
return subnetAdminPublicKey
}
func createHostAnonymizerForFSMode() map[string]string {
+1 -1
View File
@@ -64,7 +64,7 @@ func prepareAdminErasureTestBed(ctx context.Context) (*adminErasureTestBed, erro
return nil, xlErr
}
// Initialize Silo server config.
// Initialize minio server config.
if err := newTestConfig(globalMinioDefaultRegion, objLayer); err != nil {
cancel()
return nil, err
+1 -1
View File
@@ -1154,7 +1154,7 @@ var errorCodes = errorCodeMap{
},
ErrUnsupportedNotification: {
Code: "UnsupportedNotification",
Description: "Silo does not support Topic or Cloud Function based notifications.",
Description: "MinIO server does not support Topic or Cloud Function based notifications.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrInvalidCopyPartRange: {
-9
View File
@@ -18,18 +18,9 @@
package cmd
import (
"net/http/httptest"
"testing"
)
func TestCommonHeadersUseSiloProductName(t *testing.T) {
recorder := httptest.NewRecorder()
setCommonHeaders(recorder)
if got := recorder.Header().Get("Server"); got != "Silo" {
t.Fatalf("Server header = %q, want Silo", got)
}
}
func TestNewRequestID(t *testing.T) {
// Ensure that it returns an alphanumeric result of length 16.
id := mustGetRequestID(UTCNow())
+3 -3
View File
@@ -391,7 +391,7 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
return err
}
c.SetAppInfo("silo-"+batchJobPrefix, r.APIVersion+" "+job.ID)
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID)
core := &minio.Core{Client: c}
workerSize, err := strconv.Atoi(env.Get("_MINIO_BATCH_REPLICATION_WORKERS", strconv.Itoa(runtime.GOMAXPROCS(0)/2)))
@@ -1155,7 +1155,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
return err
}
c.SetAppInfo("silo-"+batchJobPrefix, r.APIVersion+" "+job.ID)
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID)
retry := false
for attempts := 1; attempts <= retryAttempts; attempts++ {
@@ -1477,7 +1477,7 @@ func (r *BatchJobReplicateV1) Validate(ctx context.Context, job BatchJobRequest,
if err != nil {
return err
}
c.SetAppInfo("silo-"+batchJobPrefix, r.APIVersion+" "+job.ID)
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID)
vcfg, err := c.GetBucketVersioning(ctx, remoteBkt)
if err != nil {
+1 -1
View File
@@ -53,7 +53,7 @@ type ServerSystemConfig struct {
// Diff - returns error on first difference found in two configs.
func (s1 *ServerSystemConfig) Diff(s2 *ServerSystemConfig) error {
if s1.Checksum != s2.Checksum {
return fmt.Errorf("Expected Silo binary checksum: %s, seen: %s", s1.Checksum, s2.Checksum)
return fmt.Errorf("Expected MinIO binary checksum: %s, seen: %s", s1.Checksum, s2.Checksum)
}
ns1 := s1.NEndpoints
+4 -4
View File
@@ -1415,7 +1415,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
Object: ObjectInfo{Name: objInfo.Name},
ReqParams: extractReqParams(r),
RespElements: extractRespElements(w),
UserAgent: fmt.Sprintf("%s Silo-Fan-Out (failed: %v)", r.UserAgent(), errs[i]),
UserAgent: fmt.Sprintf("%s MinIO-Fan-Out (failed: %v)", r.UserAgent(), errs[i]),
Host: handlers.GetSourceIP(r),
})
continue
@@ -1434,7 +1434,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
Object: objInfo,
ReqParams: extractReqParams(r),
RespElements: extractRespElements(w),
UserAgent: r.UserAgent() + " " + "Silo-Fan-Out",
UserAgent: r.UserAgent() + " " + "MinIO-Fan-Out",
Host: handlers.GetSourceIP(r),
})
}
@@ -1462,7 +1462,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
Object: eventArgsList[i].Object,
ReqParams: extractReqParams(r),
RespElements: extractRespElements(w),
UserAgent: r.UserAgent() + " " + "Silo-Fan-Out",
UserAgent: r.UserAgent() + " " + "MinIO-Fan-Out",
Host: handlers.GetSourceIP(r),
})
@@ -1757,7 +1757,7 @@ func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.
if globalDNSConfig != nil {
if err := globalDNSConfig.Delete(bucket); err != nil {
dnsLogIf(ctx, fmt.Errorf("Unable to delete bucket DNS entry %w; please delete it manually because the bucket no longer exists on Silo", err))
dnsLogIf(ctx, fmt.Errorf("Unable to delete bucket DNS entry %w, please delete it manually, bucket on MinIO no longer exists", err))
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
+9 -34
View File
@@ -17,30 +17,7 @@
package cmd
import (
"runtime"
"strconv"
"time"
)
// upstreamCopyrightEndYear is the last year of upstream MinIO, Inc. development
// this fork derives from. It is fixed: the fork's own maintenance does not
// extend the upstream copyright term.
const upstreamCopyrightEndYear = "2025"
// forkCopyrightStartYear is the year the Silo fork began.
const forkCopyrightStartYear = "2025"
// copyrightEndYear returns the end year shown in copyright notices for the
// fork's own modifications. Release builds inject CopyrightYear through
// ldflags; source builds keep the "0000" placeholder, for which the current
// year is used instead.
func copyrightEndYear() string {
if CopyrightYear == "" || CopyrightYear == "0000" {
return strconv.Itoa(time.Now().Year())
}
return CopyrightYear
}
import "runtime"
// DO NOT EDIT THIS FILE DIRECTLY. These are build-time constants
// set through buildscripts/gen-ldflags.go.
@@ -72,22 +49,20 @@ var (
// MinioOSARCH - OS and ARCH.
minioOSARCH = runtime.GOOS + "-" + runtime.GOARCH
// MinioReleaseBaseURL is retained for source compatibility. Silo does not
// provide an in-place update endpoint.
MinioReleaseBaseURL = ""
// MinioReleaseBaseURL - release url without os and arch.
MinioReleaseBaseURL = "https://dl.min.io/server/minio/release/"
// MinioReleaseURL - release URL.
MinioReleaseURL = MinioReleaseBaseURL + minioOSARCH + SlashSeparator
// MinioStoreName - Silo product name. The identifier is retained to avoid a
// source-only rename across compatibility-sensitive code.
MinioStoreName = "Silo"
// MinioStoreName - MinIO store name.
MinioStoreName = "MinIO"
// MinioUAName - Silo user agent name.
MinioUAName = "Silo"
// MinioUAName - MinIO user agent name.
MinioUAName = "MinIO"
// MinioBannerName - Silo banner name for startup message.
MinioBannerName = "Silo Object Storage Server"
// MinioBannerName - MinIO banner name for startup message.
MinioBannerName = "MinIO Object Storage Server"
// MinioLicense - MinIO server license.
MinioLicense = "GNU AGPLv3 - https://www.gnu.org/licenses/agpl-3.0.html"
+199
View File
@@ -0,0 +1,199 @@
// 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()
}
+48 -25
View File
@@ -83,7 +83,7 @@ func init() {
if mousetrap.StartedByExplorer() {
fmt.Printf("Don't double-click %s\n", os.Args[0])
fmt.Println("You need to open cmd.exe/PowerShell and run it from the command line")
fmt.Println("See Silo deployment documentation: https://silo.pgsty.com/operations/deployments/")
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("Press the Enter Key to Exit")
fmt.Scanln()
os.Exit(1)
@@ -295,6 +295,42 @@ func initConsoleServer() (*consoleapi.Server, error) {
return server, nil
}
// Check for updates and print a notification message
func checkUpdate(mode string) {
updateURL := minioReleaseInfoURL
if runtime.GOOS == globalWindowsOSName {
updateURL = minioReleaseWindowsInfoURL
}
u, err := url.Parse(updateURL)
if err != nil {
return
}
if currentReleaseTime.IsZero() {
return
}
_, lrTime, err := getLatestReleaseTime(u, 2*time.Second, mode)
if err != nil {
return
}
var older time.Duration
var downloadURL string
if lrTime.After(currentReleaseTime) {
older = lrTime.Sub(currentReleaseTime)
downloadURL = getDownloadURL(releaseTimeToReleaseTag(lrTime))
}
updateMsg := prepareUpdateMessage(downloadURL, older)
if updateMsg == "" {
return
}
logger.Info(prepareUpdateMessage("Run `mc admin update ALIAS`", lrTime.Sub(currentReleaseTime)))
}
func newConfigDir(dir string, dirSet bool, getDefaultDir func() string) (*ConfigDir, error) {
if dir == "" {
dir = getDefaultDir()
@@ -474,16 +510,6 @@ func handleCommonArgs(ctxt serverCtxt) {
var err error
globalConfigDir, err = newConfigDir(configDir, configSet, defaultConfigDir.Get)
logger.FatalIf(err, "Unable to initialize the (deprecated) config directory")
if !configSet {
defaultConfigDirWarningOnce.Do(func() {
switch defaultConfigDirSelection {
case defaultConfigDirLegacy:
logger.Info("Using legacy MinIO configuration directory %s because %s does not exist; no files were moved", defaultConfigDir.Get(), filepath.Join(filepath.Dir(defaultConfigDir.Get()), defaultSiloConfigDir))
case defaultConfigDirAmbiguous:
logger.Warning("Both Silo and legacy MinIO configuration directories exist; using %s. Set --config-dir explicitly before changing either directory", defaultConfigDir.Get())
}
})
}
globalCertsDir, err = newConfigDir(certsDir, certsSet, defaultCertsDir.Get)
logger.FatalIf(err, "Unable to initialize the certs directory")
@@ -704,7 +730,7 @@ func serverHandleEnvVars() {
}
// Look for if URL has invalid values and return error.
if !isValidURLEndpoint((*url.URL)(u)) {
err := fmt.Errorf("URL contains unexpected resources, expected URL to be one of http(s)://console.example.com or a /minio subpath on an API endpoint such as http(s)://silo.example.com/minio: %v", 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)
logger.Fatal(err, "Invalid MINIO_BROWSER_REDIRECT_URL value is environment variable")
}
globalBrowserRedirectURL = u
@@ -719,7 +745,7 @@ func serverHandleEnvVars() {
}
// Look for if URL has invalid values and return error.
if !isValidURLEndpoint((*url.URL)(u)) {
err := fmt.Errorf("URL contains unexpected resources, expected a URL such as http(s)://silo.example.com: %v", u)
err := fmt.Errorf("URL contains unexpected resources, expected URL to be of http(s)://minio.example.com format: %v", u)
logger.Fatal(err, "Invalid MINIO_SERVER_URL value is environment variable")
}
u.Path = "" // remove any path component such as `/`
@@ -772,7 +798,7 @@ func serverHandleEnvVars() {
// Checking if the IP is a DNS entry.
addrs, err := globalDNSCache.LookupHost(GlobalContext, endpoint)
if err != nil {
logger.FatalIf(err, "Unable to initialize Silo server with [%s] invalid entry found in MINIO_PUBLIC_IPS", endpoint)
logger.FatalIf(err, "Unable to initialize MinIO server with [%s] invalid entry found in MINIO_PUBLIC_IPS", endpoint)
}
for _, addr := range addrs {
domainIPs.Add(addr)
@@ -791,13 +817,10 @@ func serverHandleEnvVars() {
updateDomainIPs(domainIPs)
}
// MINIO_UPDATE remains accepted for configuration compatibility, but Silo is
// upgraded only through packages, images, or an orchestrator. It cannot
// re-enable the inherited in-place updater.
if updateSetting := env.Get(config.EnvUpdate, config.EnableOff); !strings.EqualFold(updateSetting, config.EnableOff) {
logger.Warning("%s=%s is ignored: Silo in-place updates are permanently disabled", config.EnvUpdate, updateSetting)
}
globalInplaceUpdateDisabled = true
// In place update is true by default if the MINIO_UPDATE is not set
// or is not set to 'off', if MINIO_UPDATE is set to 'off' then
// in-place update is off.
globalInplaceUpdateDisabled = strings.EqualFold(env.Get(config.EnvUpdate, config.EnableOn), config.EnableOff)
// Check if the supported credential env vars,
// "MINIO_ROOT_USER" and "MINIO_ROOT_PASSWORD" are provided
@@ -806,14 +829,14 @@ func serverHandleEnvVars() {
// Check all error conditions first
//nolint:gocritic
if !env.IsSet(config.EnvRootUser) && env.IsSet(config.EnvRootPassword) {
logger.Fatal(config.ErrMissingEnvCredentialRootUser(nil), "Unable to start Silo")
logger.Fatal(config.ErrMissingEnvCredentialRootUser(nil), "Unable to start MinIO")
} else if env.IsSet(config.EnvRootUser) && !env.IsSet(config.EnvRootPassword) {
logger.Fatal(config.ErrMissingEnvCredentialRootPassword(nil), "Unable to start Silo")
logger.Fatal(config.ErrMissingEnvCredentialRootPassword(nil), "Unable to start MinIO")
} else if !env.IsSet(config.EnvRootUser) && !env.IsSet(config.EnvRootPassword) {
if !env.IsSet(config.EnvAccessKey) && env.IsSet(config.EnvSecretKey) {
logger.Fatal(config.ErrMissingEnvCredentialAccessKey(nil), "Unable to start Silo")
logger.Fatal(config.ErrMissingEnvCredentialAccessKey(nil), "Unable to start MinIO")
} else if env.IsSet(config.EnvAccessKey) && !env.IsSet(config.EnvSecretKey) {
logger.Fatal(config.ErrMissingEnvCredentialSecretKey(nil), "Unable to start Silo")
logger.Fatal(config.ErrMissingEnvCredentialSecretKey(nil), "Unable to start MinIO")
}
}
+12 -7
View File
@@ -92,13 +92,13 @@ func initHelp() {
config.HelpKV{
Key: config.SubnetSubSys,
Type: "string",
Description: "legacy MinIO SUBNET settings retained for compatibility; external integration is disabled in Silo",
Description: "register Enterprise license for the cluster",
Optional: true,
},
config.HelpKV{
Key: config.CallhomeSubSys,
Type: "string",
Description: "legacy callhome settings retained for compatibility; diagnostic uploads are disabled in Silo",
Description: "enable callhome to MinIO SUBNET",
Optional: true,
},
config.HelpKV{
@@ -383,9 +383,14 @@ func validateSubSysConfig(ctx context.Context, s config.Config, subSys string, o
return err
}
case config.CallhomeSubSys:
if _, err := callhome.LookupConfig(s[config.CallhomeSubSys][config.Default]); err != nil {
cfg, err := callhome.LookupConfig(s[config.CallhomeSubSys][config.Default])
if err != nil {
return err
}
// callhome cannot be enabled if license is not registered yet, throw an error.
if cfg.Enabled() && !globalSubnetConfig.Registered() {
return errors.New("Deployment is not registered with SUBNET. Please register the deployment via 'mc license register ALIAS'")
}
case config.DriveSubSys:
if _, err := drive.LookupConfig(s[config.DriveSubSys][config.Default]); err != nil {
return err
@@ -671,11 +676,11 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
if err != nil {
configLogIf(ctx, fmt.Errorf("Unable to load callhome config: %w", err))
} else {
if callhomeCfg.Enable {
configLogIf(ctx, errors.New("callhome is configured but ignored: Silo does not upload diagnostics to MinIO SUBNET"))
}
callhomeCfg.Enable = false
enable := callhomeCfg.Enable && !globalCallhomeConfig.Enabled()
globalCallhomeConfig.Update(callhomeCfg)
if enable {
initCallhome(ctx, objAPI)
}
}
case config.DriveSubSys:
driveConfig, err := drive.LookupConfig(s[config.DriveSubSys][config.Default])
+16 -42
View File
@@ -20,16 +20,13 @@ package cmd
import (
"os"
"path/filepath"
"sync"
homedir "github.com/mitchellh/go-homedir"
)
const (
// New installations use the Silo-branded directory. The legacy directory is
// still selected when it is the only existing choice.
defaultSiloConfigDir = ".silo"
legacyMinioConfigDir = ".minio"
// Default minio configuration directory where below configuration files/directories are stored.
defaultMinioConfigDir = ".minio"
// Directory contains below files/directories for HTTPS configuration.
certsDir = "certs"
@@ -44,56 +41,33 @@ const (
privateKeyFile = "private.key"
)
type defaultConfigDirState uint8
const (
defaultConfigDirNew defaultConfigDirState = iota
defaultConfigDirLegacy
defaultConfigDirAmbiguous
)
// ConfigDir - points to a user set directory.
type ConfigDir struct {
path string
}
func 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) {
func getDefaultConfigDir() string {
homeDir, err := homedir.Dir()
if err != nil {
return "", defaultConfigDirNew
return ""
}
return selectDefaultConfigDir(homeDir)
return filepath.Join(homeDir, defaultMinioConfigDir)
}
func getDefaultCertsDir() string {
return filepath.Join(getDefaultConfigDir(), certsDir)
}
func getDefaultCertsCADir() string {
return filepath.Join(getDefaultCertsDir(), certsCADir)
}
var (
defaultConfigDirPath, defaultConfigDirSelection = getDefaultConfigDir()
defaultConfigDirWarningOnce sync.Once
// Default config, certs and CA directories.
defaultConfigDir = &ConfigDir{path: defaultConfigDirPath}
defaultCertsDir = &ConfigDir{path: filepath.Join(defaultConfigDirPath, certsDir)}
defaultCertsCADir = &ConfigDir{path: filepath.Join(defaultConfigDirPath, certsDir, certsCADir)}
defaultConfigDir = &ConfigDir{path: getDefaultConfigDir()}
defaultCertsDir = &ConfigDir{path: getDefaultCertsDir()}
defaultCertsCADir = &ConfigDir{path: getDefaultCertsCADir()}
// Points to current configuration directory -- deprecated, to be removed in future.
globalConfigDir = defaultConfigDir
-53
View File
@@ -1,53 +0,0 @@
// Copyright 2026 PGSTY contributors.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
package cmd
import (
"os"
"path/filepath"
"testing"
)
func TestSelectDefaultConfigDir(t *testing.T) {
tests := []struct {
name string
createSilo bool
createMinio bool
wantDir string
wantState defaultConfigDirState
}{
{name: "new install", wantDir: defaultSiloConfigDir, wantState: defaultConfigDirNew},
{name: "silo exists", createSilo: true, wantDir: defaultSiloConfigDir, wantState: defaultConfigDirNew},
{name: "legacy only", createMinio: true, wantDir: legacyMinioConfigDir, wantState: defaultConfigDirLegacy},
{name: "both exist", createSilo: true, createMinio: true, wantDir: defaultSiloConfigDir, wantState: defaultConfigDirAmbiguous},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
home := t.TempDir()
for name, create := range map[string]bool{
defaultSiloConfigDir: tt.createSilo,
legacyMinioConfigDir: tt.createMinio,
} {
if create {
if err := os.Mkdir(filepath.Join(home, name), 0o700); err != nil {
t.Fatal(err)
}
}
}
gotDir, gotState := selectDefaultConfigDir(home)
if want := filepath.Join(home, tt.wantDir); gotDir != want {
t.Fatalf("directory = %q, want %q", gotDir, want)
}
if gotState != tt.wantState {
t.Fatalf("state = %d, want %d", gotState, tt.wantState)
}
})
}
}
+1 -1
View File
@@ -33,7 +33,7 @@ import (
)
// This file implements and supports ellipses pattern for
// `silo server` command line arguments.
// `minio server` command line arguments.
// Endpoint set represents parsed ellipses values, also provides
// methods to get the sets of endpoints.
+2 -2
View File
@@ -207,7 +207,7 @@ func NewEndpoint(arg string) (ep Endpoint, e error) {
// On windows having a preceding SlashSeparator will cause problems, if the
// command line already has C:/<export-folder/ in it. Final resulting
// path on windows might become C:/C:/ this will cause problems
// of starting Silo properly in distributed mode on Windows.
// of starting minio server properly in distributed mode on windows.
// As a special case make sure to trim the separator.
// NOTE: It is also perfectly fine for windows users to have a path
@@ -226,7 +226,7 @@ func NewEndpoint(arg string) (ep Endpoint, e error) {
} else {
// Only check if the arg is an ip address and ask for scheme since its absent.
// localhost, example.com, any FQDN cannot be disambiguated from a regular file path such as
// /mnt/export1. So we go ahead and start Silo in FS mode in these cases.
// /mnt/export1. So we go ahead and start the minio server in FS modes in these cases.
if isHostIP(arg) {
return ep, fmt.Errorf("invalid URL endpoint format: missing scheme http or https")
}
+1 -1
View File
@@ -148,7 +148,7 @@ func apiErrorCode(t *testing.T, rec *httptest.ResponseRecorder) string {
return e.Code
}
// TestAPICompleteMultipartFullObjectChecksum covers pgsty/silo#31.
// TestAPICompleteMultipartFullObjectChecksum covers pgsty/minio#31.
//
// A multipart upload created with a full object checksum type must be
// completable by sending only PartNumber and ETag per part, plus the object
+1 -1
View File
@@ -307,7 +307,7 @@ func (z *erasureServerPools) GetRawData(ctx context.Context, volume, file string
r = io.NopCloser(bytes.NewBuffer([]byte{}))
}
// Keep disk path instead of ID, to ensure that the downloaded zip file can be
// easily automated with `silo server hostname{1...n}/disk{1...m}`.
// easily automated with `minio server hostname{1...n}/disk{1...m}`.
err = fn(r, disk.Hostname(), disk.Endpoint().Path, pathJoin(volume, si.Name), si)
r.Close()
if err != nil {
+1 -1
View File
@@ -1051,7 +1051,7 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
if !reflect.DeepEqual(s.format, refFormat) {
// Format is corrupted and unrecognized by the running instance.
healingLogIf(ctx, fmt.Errorf("Unable to heal the newly replaced drives due to format.json inconsistencies; please report this to Silo maintainers at https://github.com/pgsty/silo/issues: %w",
healingLogIf(ctx, fmt.Errorf("Unable to heal the newly replaced drives due to format.json inconsistencies, please engage MinIO support for further assistance: %w",
errCorruptedFormat))
return res, errCorruptedFormat
}
+2 -2
View File
@@ -34,7 +34,7 @@ var fmtGenFlags = []cli.Flag{
},
cli.StringFlag{
Name: "deployment-id",
Usage: "deployment-id of the Silo cluster for which format.json is needed",
Usage: "deployment-id of the MinIO cluster for which format.json is needed",
},
cli.StringFlag{
Name: "address",
@@ -69,7 +69,7 @@ FLAGS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
EXAMPLES:
1. Generate format.json.zip containing format.json files for all drives in a distributed Silo server pool of 32 nodes with 32 drives each.
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.
{{.Prompt}} {{.HelpName}} http://node{1...32}.example.com/mnt/export{1...32}
`,
+2 -2
View File
@@ -139,9 +139,9 @@ func startFTPServer(args []string) {
logger.Fatal(fmt.Errorf("invalid TLS arguments provided. force-tls, but missing private key --ftp=\"tls-private-key=path/to/private.key\""), "unable to start FTP server")
}
name := "Silo FTP Server"
name := "MinIO FTP Server"
if tls {
name = "Silo FTP(Secure) Server"
name = "MinIO FTP(Secure) Server"
}
ftpServer, err := ftp.NewServer(&ftp.Options{
+9 -5
View File
@@ -176,13 +176,13 @@ var (
// Global user opts context
globalServerCtxt serverCtxt
// Indicates whether the running Silo server is a distributed setup.
// Indicates if the running minio server is distributed setup.
globalIsDistErasure = false
// Indicates whether the running Silo server is an erasure-code backend.
// Indicates if the running minio server is an erasure-code backend.
globalIsErasure = false
// Indicates whether the running Silo server is in single-drive XL mode.
// Indicates if the running minio server is in single drive XL mode.
globalIsErasureSD = false
// Indicates if server code should go through testing path.
@@ -201,8 +201,8 @@ var (
// globalBrowserConfig Browser user configurable settings
globalBrowserConfig browser.Config
// Silo permanently disables inherited in-place updates.
globalInplaceUpdateDisabled = true
// This flag is set to 'true' when MINIO_UPDATE env is set to 'off'. Default is false.
globalInplaceUpdateDisabled = false
// Captures site name and region
globalSite config.Site
@@ -433,6 +433,10 @@ var (
// MinIO client
globalMinioClient *minio.Client
// Public key for subnet confidential information
subnetAdminPublicKey = []byte("-----BEGIN PUBLIC KEY-----\nMIIBCgKCAQEAyC+ol5v0FP+QcsR6d1KypR/063FInmNEFsFzbEwlHQyEQN3O7kNI\nwVDN1vqp1wDmJYmv4VZGRGzfFw1q+QV7K1TnysrEjrqpVxfxzDQCoUadAp8IxLLc\ns2fjyDNxnZjoC6fTID9C0khKnEa5fPZZc3Ihci9SiCGkPmyUyCGVSxWXIKqL2Lrj\nyDc0pGeEhWeEPqw6q8X2jvTC246tlzqpDeNsPbcv2KblXRcKniQNbBrizT37CKHQ\nM6hc9kugrZbFuo8U5/4RQvZPJnx/DVjLDyoKo2uzuVQs4s+iBrA5sSSLp8rPED/3\n6DgWw3e244Dxtrg972dIT1IOqgn7KUJzVQIDAQAB\n-----END PUBLIC KEY-----")
subnetAdminPublicKeyDev = []byte("-----BEGIN PUBLIC KEY-----\nMIIBCgKCAQEArhQYXQd6zI4uagtVfthAPOt6i4AYHnEWCoNeAovM4MNl42I9uQFh\n3VHkbWj9Gpx9ghf6PgRgK+8FcFvy+StmGcXpDCiFywXX24uNhcZjscX1C4Esk0BW\nidfI2eXYkOlymD4lcK70SVgJvC693Qa7Z3FE1KU8Nfv2bkxEE4bzOkojX9t6a3+J\nR8X6Z2U8EMlH1qxJPgiPogELhWP0qf2Lq7GwSAflo1Tj/ytxvD12WrnE0Rrj/8yP\nSnp7TbYm91KocKMExlmvx3l2XPLxeU8nf9U0U+KOmorejD3MDMEPF+tlk9LB3JWP\nZqYYe38rfALVTn4RVJriUcNOoEpEyC0WEwIDAQAB\n-----END PUBLIC KEY-----")
// dynamic sleeper to avoid thundering herd for trash folder expunge routine
deleteCleanupSleeper = newDynamicSleeper(5, 25*time.Millisecond, false)
+1 -1
View File
@@ -465,7 +465,7 @@ func errorResponseHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodOptions {
return
}
desc := "Do not upgrade one server at a time; follow the Silo upgrade guide at https://silo.pgsty.com/operations/deployments/baremetal-upgrade-minio-deployment/"
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"
switch {
case strings.HasPrefix(r.URL.Path, peerRESTPrefix):
writeErrorResponseString(r.Context(), w, APIError{
-304
View File
@@ -1,304 +0,0 @@
// Copyright (c) 2015-2026 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 (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"github.com/minio/cli"
xhttp "github.com/minio/minio/internal/http"
)
// Default probe deadlines. Cluster checks are evaluated server-side under the
// (default 10s) cluster_deadline, so their client deadline must be longer or
// an unhealthy cluster answer would never be received.
const (
healthcheckLocalTimeout = 5 * time.Second
healthcheckClusterTimeout = 15 * time.Second
)
// healthcheckChecks maps the CLI check vocabulary 1:1 onto the server's
// /minio/health/<path> endpoints. The path literals are shared with the
// health router; the semantics live server-side only.
var healthcheckChecks = map[string]string{
"live": healthCheckLivenessPath,
"ready": healthCheckReadinessPath,
"cluster": healthCheckClusterPath,
"cluster-read": healthCheckClusterReadPath,
}
var healthcheckFlags = []cli.Flag{
cli.StringFlag{
Name: "address",
Value: ":" + GlobalMinioDefaultPort,
Usage: "probe the server bound to a specific ADDRESS:PORT, an empty ADDRESS is probed as 127.0.0.1",
EnvVar: "MINIO_ADDRESS",
},
cli.StringFlag{
Name: "url",
Usage: "probe this base URL (http[s]://HOST:PORT) instead of deriving one from --address and the certs directory",
EnvVar: "MINIO_HEALTHCHECK_URL",
},
cli.BoolFlag{
Name: "maintenance",
Usage: "with the cluster check only: ask whether taking this node down would lose quorum (HTTP 412 means it would)",
},
cli.DurationFlag{
Name: "timeout",
Usage: "overall probe deadline (default: 5s for live/ready, 15s for cluster checks)",
},
}
var healthcheckCmd = cli.Command{
Name: "healthcheck",
Usage: "Probe the health of a Silo server and report it as the exit code",
Flags: append(healthcheckFlags, GlobalFlags...),
Action: healthcheckMain,
CustomHelpTemplate: `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{.HelpName}} {{if .VisibleFlags}}[FLAGS] {{end}}[CHECK]
CHECK:
live the process is serving requests (default); touches no external system
ready live, plus KMS and etcd reachability when they are configured
cluster cluster-wide write quorum across every erasure set
cluster-read cluster-wide read quorum across every erasure set
{{if .VisibleFlags}}
FLAGS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
EXIT CODE:
0 - healthy (with --maintenance: safe to take the node down)
1 - anything else
EXAMPLES:
1. Probe local liveness, e.g. as a container HEALTHCHECK:
{{.Prompt}} {{.HelpName}}
2. Probe readiness of a server on a non-default port:
{{.Prompt}} {{.HelpName}} --address :9010 ready
3. Ask whether this node can be taken down without losing HA:
{{.Prompt}} {{.HelpName}} --maintenance cluster
`,
}
// healthcheckResult is the outcome of a single probe. It doubles as the
// --json output schema, so field changes are compatibility-relevant.
type healthcheckResult struct {
Check string `json:"check"`
Healthy bool `json:"healthy"`
StatusCode int `json:"status,omitempty"`
DurationMS int64 `json:"durationMs,omitempty"`
ServerStatus string `json:"serverStatus,omitempty"`
WriteQuorum string `json:"writeQuorum,omitempty"`
ReadQuorum string `json:"readQuorum,omitempty"`
HealingDrives string `json:"healingDrives,omitempty"`
Err string `json:"error,omitempty"`
}
// line renders the single human-readable output line. Container runtimes
// store only the first 4096 bytes of probe output, so it stays short.
func (r healthcheckResult) line() string {
if r.Err != "" {
return fmt.Sprintf("%s: unreachable (%s)", r.Check, r.Err)
}
if r.Healthy {
return fmt.Sprintf("%s: ok (%d, %dms)", r.Check, r.StatusCode, r.DurationMS)
}
label := "unhealthy"
if r.StatusCode == http.StatusPreconditionFailed {
label = "not safe for maintenance"
}
var b strings.Builder
fmt.Fprintf(&b, "%s: %s (%d)", r.Check, label, r.StatusCode)
for _, kv := range []struct{ k, v string }{
{"server-status", r.ServerStatus},
{"write-quorum", r.WriteQuorum},
{"read-quorum", r.ReadQuorum},
{"healing-drives", r.HealingDrives},
} {
if kv.v != "" {
fmt.Fprintf(&b, " %s=%s", kv.k, kv.v)
}
}
return b.String()
}
// healthcheckTarget derives the base URL to probe. An explicit rawURL wins;
// otherwise the address' host:port is used, with the scheme decided by the
// same certificate presence check the server performs at startup. URLs are
// serialized via url.URL so IPv6 zone identifiers survive as %25-escapes.
func healthcheckTarget(rawURL, address, certsDir string) (string, error) {
if rawURL != "" {
u, err := url.Parse(rawURL)
if err != nil {
return "", fmt.Errorf("invalid --url %q: %w", rawURL, err)
}
if (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" {
return "", fmt.Errorf("invalid --url %q: expected http[s]://HOST:PORT", rawURL)
}
return (&url.URL{Scheme: u.Scheme, Host: u.Host}).String(), nil
}
host, port, err := net.SplitHostPort(address)
if err != nil {
return "", fmt.Errorf("invalid --address %q: %w", address, err)
}
if host == "" {
host = "127.0.0.1"
}
scheme := "http"
if isFile(filepath.Join(certsDir, publicCertFile)) && isFile(filepath.Join(certsDir, privateKeyFile)) {
scheme = "https"
}
return (&url.URL{Scheme: scheme, Host: net.JoinHostPort(host, port)}).String(), nil
}
// probeHealth performs one bounded, strictly anonymous GET against the
// health endpoint for check. Anonymity is load-bearing: a credentialed
// request is rejected by the reserved-path guard instead of answered.
func probeHealth(baseURL, check string, maintenance bool, timeout time.Duration) healthcheckResult {
res := healthcheckResult{Check: check}
probeURL := baseURL + healthCheckPathPrefix + healthcheckChecks[check]
if maintenance {
probeURL += "?maintenance=true"
}
// Proxy is nil on purpose: a loopback probe must never be routed through
// an HTTP_PROXY inherited from the container environment. Certificate
// verification is skipped to match the kubelet's HTTPS probe behavior.
client := &http.Client{
Transport: &http.Transport{
Proxy: nil,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
DisableKeepAlives: true,
},
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, probeURL, nil)
if err != nil {
res.Err = err.Error()
return res
}
req.Header.Set("User-Agent", "silo-healthcheck/"+ReleaseTag)
started := time.Now()
resp, err := client.Do(req)
res.DurationMS = time.Since(started).Milliseconds()
if err != nil {
res.Err = err.Error()
return res
}
defer resp.Body.Close()
io.Copy(io.Discard, io.LimitReader(resp.Body, 4096))
res.StatusCode = resp.StatusCode
res.Healthy = resp.StatusCode == http.StatusOK
res.ServerStatus = resp.Header.Get(xhttp.MinIOServerStatus)
res.WriteQuorum = resp.Header.Get(xhttp.MinIOWriteQuorum)
res.ReadQuorum = resp.Header.Get(xhttp.MinIOReadQuorum)
res.HealingDrives = resp.Header.Get(xhttp.MinIOHealingDrives)
return res
}
// healthcheckCertsDir mirrors the server's certs-dir resolution without its
// side effects: an explicit --certs-dir wins, an explicit --config-dir
// implies <config-dir>/certs, and the shared default applies otherwise.
func healthcheckCertsDir(ctx *cli.Context) string {
switch {
case ctx.IsSet("certs-dir"):
return ctx.String("certs-dir")
case ctx.GlobalIsSet("certs-dir"):
return ctx.GlobalString("certs-dir")
case ctx.IsSet("config-dir"):
return filepath.Join(ctx.String("config-dir"), certsDir)
case ctx.GlobalIsSet("config-dir"):
return filepath.Join(ctx.GlobalString("config-dir"), certsDir)
}
return defaultCertsDir.Get()
}
func healthcheckMain(ctx *cli.Context) {
fail := func(format string, args ...any) {
fmt.Fprintf(os.Stderr, "healthcheck: "+format+"\n", args...)
os.Exit(1)
}
if len(ctx.Args()) > 1 {
fail("too many arguments, expected at most one CHECK")
}
check := "live"
if arg := ctx.Args().First(); arg != "" {
check = arg
}
if _, ok := healthcheckChecks[check]; !ok {
fail("unknown check %q, expected one of: live, ready, cluster, cluster-read", check)
}
if ctx.Bool("maintenance") && check != "cluster" {
fail("--maintenance applies to the cluster check only")
}
timeout := ctx.Duration("timeout")
if !ctx.IsSet("timeout") {
timeout = healthcheckLocalTimeout
if strings.HasPrefix(check, "cluster") {
timeout = healthcheckClusterTimeout
}
}
baseURL, err := healthcheckTarget(ctx.String("url"), ctx.String("address"), healthcheckCertsDir(ctx))
if err != nil {
fail("%v", err)
}
res := probeHealth(baseURL, check, ctx.Bool("maintenance"), timeout)
quiet := ctx.Bool("quiet") || ctx.GlobalBool("quiet")
switch {
case ctx.Bool("json") || ctx.GlobalBool("json"):
buf, jerr := json.Marshal(res)
if jerr != nil {
fail("%v", jerr)
}
fmt.Println(string(buf))
case !res.Healthy:
fmt.Fprintln(os.Stderr, res.line())
case !quiet:
fmt.Println(res.line())
}
if !res.Healthy {
os.Exit(1)
}
}
-202
View File
@@ -1,202 +0,0 @@
// Copyright (c) 2015-2026 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 (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"time"
xhttp "github.com/minio/minio/internal/http"
)
func TestHealthcheckTarget(t *testing.T) {
plainDir := t.TempDir()
tlsDir := t.TempDir()
for _, name := range []string{publicCertFile, privateKeyFile} {
if err := os.WriteFile(filepath.Join(tlsDir, name), []byte("test"), 0o600); err != nil {
t.Fatal(err)
}
}
// A lone public.crt without its key must not flip the scheme.
halfDir := t.TempDir()
if err := os.WriteFile(filepath.Join(halfDir, publicCertFile), []byte("test"), 0o600); err != nil {
t.Fatal(err)
}
tests := []struct {
name string
rawURL string
address string
certsDir string
want string
wantErr bool
}{
{name: "default address", address: ":9000", certsDir: plainDir, want: "http://127.0.0.1:9000"},
{name: "explicit host", address: "10.0.0.7:9010", certsDir: plainDir, want: "http://10.0.0.7:9010"},
{name: "ipv6 address", address: "[::1]:9000", certsDir: plainDir, want: "http://[::1]:9000"},
{name: "ipv6 zone is escaped", address: "[fe80::1%eth0]:9000", certsDir: plainDir, want: "http://[fe80::1%25eth0]:9000"},
{name: "ipv6 zone in url", rawURL: "http://[fe80::1%25eth0]:9000", certsDir: plainDir, want: "http://[fe80::1%25eth0]:9000"},
{name: "tls certs present", address: ":9000", certsDir: tlsDir, want: "https://127.0.0.1:9000"},
{name: "cert without key stays http", address: ":9000", certsDir: halfDir, want: "http://127.0.0.1:9000"},
{name: "url override wins", rawURL: "https://silo.internal:9000", address: ":9000", certsDir: plainDir, want: "https://silo.internal:9000"},
{name: "url path is dropped", rawURL: "http://silo.internal:9000/minio/health/live", address: ":9000", certsDir: plainDir, want: "http://silo.internal:9000"},
{name: "address without port", address: "localhost", certsDir: plainDir, wantErr: true},
{name: "url without scheme", rawURL: "silo.internal:9000", certsDir: plainDir, wantErr: true},
{name: "url with bad scheme", rawURL: "ftp://silo.internal:9000", certsDir: plainDir, wantErr: true},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := healthcheckTarget(test.rawURL, test.address, test.certsDir)
if (err != nil) != test.wantErr {
t.Fatalf("healthcheckTarget() error = %v, wantErr = %v", err, test.wantErr)
}
if err == nil && got != test.want {
t.Fatalf("healthcheckTarget() = %q, want %q", got, test.want)
}
})
}
}
func TestProbeHealthChecksAndVerdicts(t *testing.T) {
var gotPath, gotQuery, gotAuth string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotPath = r.URL.Path
gotQuery = r.URL.RawQuery
gotAuth = r.Header.Get("Authorization")
switch r.URL.Path {
case "/minio/health/live", "/minio/health/ready":
w.WriteHeader(http.StatusOK)
case "/minio/health/cluster":
if r.URL.Query().Get("maintenance") == "true" {
w.Header().Set(xhttp.MinIOWriteQuorum, "3")
w.Header().Set(xhttp.MinIOHealingDrives, "2")
w.WriteHeader(http.StatusPreconditionFailed)
return
}
w.Header().Set(xhttp.MinIOServerStatus, "iam-offline")
w.Header().Set(xhttp.MinIOWriteQuorum, "3")
w.WriteHeader(http.StatusServiceUnavailable)
case "/minio/health/cluster/read":
w.Header().Set(xhttp.MinIOReadQuorum, "2")
w.WriteHeader(http.StatusOK)
default:
w.WriteHeader(http.StatusNotFound)
}
}))
defer srv.Close()
res := probeHealth(srv.URL, "live", false, time.Second)
if !res.Healthy || res.StatusCode != http.StatusOK {
t.Fatalf("live: expected healthy 200, got %+v", res)
}
if gotPath != "/minio/health/live" {
t.Fatalf("live: probed %q", gotPath)
}
if gotAuth != "" {
t.Fatalf("probe must be anonymous, sent Authorization %q", gotAuth)
}
res = probeHealth(srv.URL, "cluster", false, time.Second)
if res.Healthy || res.StatusCode != http.StatusServiceUnavailable {
t.Fatalf("cluster: expected unhealthy 503, got %+v", res)
}
if res.ServerStatus != "iam-offline" || res.WriteQuorum != "3" {
t.Fatalf("cluster: headers not decoded, got %+v", res)
}
if gotQuery != "" {
t.Fatalf("cluster without --maintenance sent query %q", gotQuery)
}
res = probeHealth(srv.URL, "cluster", true, time.Second)
if res.Healthy || res.StatusCode != http.StatusPreconditionFailed {
t.Fatalf("cluster maintenance: expected 412, got %+v", res)
}
if res.HealingDrives != "2" {
t.Fatalf("cluster maintenance: headers not decoded, got %+v", res)
}
if gotQuery != "maintenance=true" {
t.Fatalf("cluster --maintenance sent query %q", gotQuery)
}
res = probeHealth(srv.URL, "cluster-read", false, time.Second)
if !res.Healthy || res.ReadQuorum != "2" {
t.Fatalf("cluster-read: expected healthy with read quorum, got %+v", res)
}
if gotPath != "/minio/health/cluster/read" {
t.Fatalf("cluster-read: probed %q", gotPath)
}
}
func TestProbeHealthTLSSkipsVerification(t *testing.T) {
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()
res := probeHealth(srv.URL, "live", false, time.Second)
if !res.Healthy {
t.Fatalf("self-signed TLS probe must succeed, got %+v", res)
}
}
func TestProbeHealthUnreachableAndTimeout(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
deadURL := srv.URL
srv.Close()
res := probeHealth(deadURL, "live", false, time.Second)
if res.Healthy || res.Err == "" {
t.Fatalf("probe of a closed server must report unreachable, got %+v", res)
}
slow := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(500 * time.Millisecond)
w.WriteHeader(http.StatusOK)
}))
defer slow.Close()
res = probeHealth(slow.URL, "live", false, 50*time.Millisecond)
if res.Healthy || res.Err == "" {
t.Fatalf("probe past its deadline must fail, got %+v", res)
}
}
func TestHealthcheckResultLine(t *testing.T) {
tests := []struct {
res healthcheckResult
want string
}{
{healthcheckResult{Check: "live", Healthy: true, StatusCode: 200, DurationMS: 2}, "live: ok (200, 2ms)"},
{healthcheckResult{Check: "cluster", StatusCode: 503, ServerStatus: "iam-offline", WriteQuorum: "3", HealingDrives: "2"}, "cluster: unhealthy (503) server-status=iam-offline write-quorum=3 healing-drives=2"},
{healthcheckResult{Check: "cluster", StatusCode: 412, WriteQuorum: "3"}, "cluster: not safe for maintenance (412) write-quorum=3"},
{healthcheckResult{Check: "ready", Err: "connection refused"}, "ready: unreachable (connection refused)"},
}
for _, test := range tests {
if got := test.res.line(); got != test.want {
t.Fatalf("line() = %q, want %q", got, test.want)
}
}
}
+2 -2
View File
@@ -390,7 +390,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
if err := saveIAMFormat(retryCtx, sys.store); err != nil {
if configRetriableErrors(err) {
retryInterval := time.Duration(r.Float64() * float64(time.Second))
logger.Info("Waiting for the Silo IAM sub-system to be initialized.. possible cause (%v) (retrying in %s)", err, retryInterval)
logger.Info("Waiting for all MinIO IAM sub-system to be initialized.. possible cause (%v) (retrying in %s)", err, retryInterval)
time.Sleep(retryInterval)
continue
}
@@ -410,7 +410,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
if err := sys.Load(retryCtx, true); err != nil {
if configRetriableErrors(err) {
retryInterval := time.Duration(r.Float64() * float64(time.Second))
logger.Info("Waiting for the Silo IAM sub-system to be initialized.. possible cause (%v) (retrying in %s)", err, retryInterval)
logger.Info("Waiting for all MinIO IAM sub-system to be initialized.. possible cause (%v) (retrying in %s)", err, retryInterval)
time.Sleep(retryInterval)
continue
}
-78
View File
@@ -1,78 +0,0 @@
// 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)
}
}
+11 -12
View File
@@ -25,7 +25,9 @@ import (
"runtime"
"runtime/debug"
"sort"
"strconv"
"strings"
"time"
"github.com/minio/cli"
"github.com/minio/minio/internal/color"
@@ -138,7 +140,6 @@ func newApp(name string) *cli.App {
// Register all commands.
registerCommand(serverCmd)
registerCommand(fmtGenCmd)
registerCommand(healthcheckCmd)
// Set up app.
cli.HelpFlag = cli.BoolFlag{
@@ -149,16 +150,16 @@ func newApp(name string) *cli.App {
app := cli.NewApp()
app.Name = name
app.Author = "Silo contributors"
app.Author = "MinIO, Inc."
app.Version = ReleaseTag
app.Usage = "S3-compatible object storage"
app.Description = `Run independently maintained, S3-compatible object storage with Silo`
app.Usage = "High Performance Object Storage"
app.Description = `Build high performance data infrastructure for machine learning, analytics and application data workloads with MinIO`
app.Flags = GlobalFlags
app.HideHelpCommand = true // Hide `help, h`; the top-level `silo --help` already covers it.
app.HideHelpCommand = true // Hide `help, h` command, we already have `minio --help`.
app.Commands = commands
app.CustomAppHelpTemplate = minioHelpTemplate
app.CommandNotFound = func(ctx *cli.Context, command string) {
console.Printf("%s is not a %s sub-command. See %s --help.\n", command, ctx.App.Name, ctx.App.Name)
console.Printf("%s is not a minio sub-command. See minio --help.\n", command)
closestCommands := findClosestCommands(command)
if len(closestCommands) > 0 {
console.Println()
@@ -175,8 +176,8 @@ func newApp(name string) *cli.App {
}
func startupBanner(banner io.Writer) {
fmt.Fprintln(banner, color.Blue("Copyright:")+color.Bold(" 2015-%s MinIO, Inc.", upstreamCopyrightEndYear))
fmt.Fprintln(banner, color.Blue("Modifications:")+color.Bold(" Copyright %s-%s PGSTY", forkCopyrightStartYear, copyrightEndYear()))
CopyrightYear = strconv.Itoa(time.Now().Year())
fmt.Fprintln(banner, color.Blue("Copyright:")+color.Bold(" 2015-%s MinIO, Inc.", CopyrightYear))
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))
}
@@ -186,9 +187,7 @@ func versionBanner(c *cli.Context) io.Reader {
fmt.Fprintln(banner, color.Bold("%s version %s (commit-id=%s)", c.App.Name, c.App.Version, CommitID))
fmt.Fprintln(banner, color.Blue("Runtime:")+color.Bold(" %s %s/%s", runtime.Version(), runtime.GOOS, runtime.GOARCH))
fmt.Fprintln(banner, color.Blue("License:")+color.Bold(" GNU AGPLv3 - https://www.gnu.org/licenses/agpl-3.0.html"))
fmt.Fprintln(banner, color.Blue("Copyright:")+color.Bold(" 2015-%s MinIO, Inc.", upstreamCopyrightEndYear))
fmt.Fprintln(banner, color.Blue("Modifications:")+color.Bold(" Copyright %s-%s PGSTY", forkCopyrightStartYear, copyrightEndYear()))
fmt.Fprintln(banner, color.Blue("Source compatibility:")+color.Bold(" based on MinIO technology"))
fmt.Fprintln(banner, color.Blue("Copyright:")+color.Bold(" 2015-%s MinIO, Inc.", CopyrightYear))
return strings.NewReader(banner.String())
}
@@ -198,7 +197,7 @@ func printMinIOVersion(c *cli.Context) {
var debugNoExit = env.Get("_MINIO_DEBUG_NO_EXIT", "") != ""
// Main is the Silo server entry point.
// Main main for minio server.
func Main(args []string) {
// Set the minio app name.
appName := filepath.Base(args[0])
-8
View File
@@ -610,10 +610,6 @@ func (s *peerRESTServer) VerifyBinaryHandler(w http.ResponseWriter, r *http.Requ
s.writeErrorResponse(w, errors.New("Invalid request"))
return
}
if globalInplaceUpdateDisabled {
s.writeErrorResponse(w, errInplaceUpdateDisabled)
return
}
if r.ContentLength < 0 {
s.writeErrorResponse(w, errInvalidArgument)
@@ -663,10 +659,6 @@ func (s *peerRESTServer) CommitBinaryHandler(w http.ResponseWriter, r *http.Requ
s.writeErrorResponse(w, errors.New("Invalid request"))
return
}
if globalInplaceUpdateDisabled {
s.writeErrorResponse(w, errInplaceUpdateDisabled)
return
}
if err := commitBinary(); err != nil {
s.writeErrorResponse(w, err)
+2 -2
View File
@@ -81,7 +81,7 @@ func bgFormatErasureCleanupTmp(diskPath string) {
// |__ e870a2c1-d09c-450c-a69c-6eaa54a89b3e
//
// In this example, `33a58b40-aecc-4c9f-a22f-ff17bfa33b62` directory contains
// temporary objects from one of the previous runs of Silo.
// temporary objects from one of the previous runs of minio server.
tmpID := mustGetUUID()
tmpOld := pathJoin(diskPath, minioMetaTmpBucket+"-old", tmpID)
if err := renameAll(pathJoin(diskPath, minioMetaTmpBucket),
@@ -263,7 +263,7 @@ func waitForFormatErasure(firstDisk bool, endpoints Endpoints, poolCount, setCou
defer func() {
if err == nil && format != nil {
// Assign globalDeploymentID() on first run for the
// Silo server managing the first disk
// minio server managing the first disk
globalDeploymentIDPtr.Store(&format.ID)
// Set the deployment ID here to avoid races.
+23 -13
View File
@@ -114,7 +114,7 @@ var ServerFlags = []cli.Flag{
},
cli.StringFlag{
Name: "interface",
Usage: "bind to the VRF device used by Silo services",
Usage: "bind to right VRF device for MinIO services",
Hidden: true,
EnvVar: "MINIO_INTERFACE",
},
@@ -220,20 +220,20 @@ FLAGS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
EXAMPLES:
1. Start Silo server on "/home/shared" directory.
1. Start MinIO server on "/home/shared" directory.
{{.Prompt}} {{.HelpName}} /home/shared
2. Start single node server with 64 local drives "/mnt/data1" to "/mnt/data64".
{{.Prompt}} {{.HelpName}} /mnt/data{1...64}
3. Start distributed Silo server on a 32-node setup with 32 drives each; run the following command on all nodes.
3. Start distributed MinIO server on an 32 node setup with 32 drives each, run following command on all the nodes
{{.Prompt}} {{.HelpName}} http://node{1...32}.example.com/mnt/export{1...32}
4. Start distributed Silo server in an expanded setup; run the following command on all nodes.
4. Start distributed MinIO server in an expanded setup, run the following command on all the nodes
{{.Prompt}} {{.HelpName}} http://node{1...16}.example.com/mnt/export{1...32} \
http://node{17...64}.example.com/mnt/export{1...64}
5. Start distributed Silo server with FTP and SFTP on ports 8021 and 8022.
5. Start distributed MinIO server, with FTP and SFTP servers on all interfaces via port 8021, 8022 respectively
{{.Prompt}} {{.HelpName}} http://node{1...4}.example.com/mnt/export{1...4} \
--ftp="address=:8021" --ftp="passive-port-range=30000-40000" \
--sftp="address=:8022" --sftp="ssh-private-key=${HOME}/.ssh/id_rsa"
@@ -597,7 +597,7 @@ func initServerConfig(ctx context.Context, newObject ObjectLayer) error {
// These messages only meant primarily for distributed setup, so only log during distributed setup.
if globalIsDistErasure {
logger.Info("Waiting for all Silo subsystems to initialize...")
logger.Info("Waiting for all MinIO sub-systems to be initialize...")
}
// Upon success migrating the config, initialize all sub-systems
@@ -607,13 +607,13 @@ func initServerConfig(ctx context.Context, newObject ObjectLayer) error {
// All successful return.
if globalIsDistErasure {
// These messages only meant primarily for distributed setup, so only log during distributed setup.
logger.Info("All Silo subsystems initialized successfully in %s", time.Since(t1))
logger.Info("All MinIO sub-systems initialized successfully in %s", time.Since(t1))
}
return nil
}
if configRetriableErrors(err) {
logger.Info("Waiting for all Silo subsystems to initialize; possible cause: %v", err)
logger.Info("Waiting for all MinIO sub-systems to be initialized.. possible cause (%v)", err)
time.Sleep(time.Duration(r.Float64() * float64(5*time.Second)))
continue
}
@@ -742,7 +742,7 @@ func initializeLogRotate(ctx *cli.Context) (io.WriteCloser, error) {
return output, nil
}
// serverMain handles the 'silo server' command.
// serverMain handler called for 'minio server' command.
func serverMain(ctx *cli.Context) {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
@@ -851,6 +851,16 @@ func serverMain(ctx *cli.Context) {
getCert = globalTLSCerts.GetCertificate
}
// Check for updates in non-blocking manner.
go func() {
if !globalServerCtxt.Quiet && !globalInplaceUpdateDisabled {
// Check for new updates from dl.min.io.
bootstrapTrace("checkUpdate", func() {
checkUpdate(getMinioMode())
})
}
}()
// Set system resources to maximum.
bootstrapTrace("setMaxResources", func() {
_ = setMaxResources(globalServerCtxt)
@@ -858,13 +868,13 @@ func serverMain(ctx *cli.Context) {
// Verify kernel release and version.
if oldLinux() {
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"))
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"))
}
maxProcs := runtime.GOMAXPROCS(0)
cpuProcs := runtime.NumCPU()
if maxProcs < cpuProcs {
warnings = append(warnings, color.YellowBold("Detected GOMAXPROCS(%d) < NumCPU(%d); provide all processors to Silo for optimal performance",
warnings = append(warnings, color.YellowBold("Detected GOMAXPROCS(%d) < NumCPU(%d), please make sure to provide all PROCS to MinIO for optimal performance",
maxProcs, cpuProcs))
}
@@ -1151,7 +1161,7 @@ func serverMain(ctx *cli.Context) {
Transport: globalRemoteTargetTransport,
Region: region,
})
logger.FatalIf(err, "Unable to initialize the internal S3 client")
logger.FatalIf(err, "Unable to initialize MinIO client")
})
go bootstrapTrace("startResourceMetricsCollection", func() {
@@ -1159,7 +1169,7 @@ func serverMain(ctx *cli.Context) {
})
// Add User-Agent to differentiate the requests.
globalMinioClient.SetAppInfo("silo-perf-test", ReleaseTag)
globalMinioClient.SetAppInfo("minio-perf-test", ReleaseTag)
if serverDebugLog {
fmt.Println("== DEBUG Mode enabled ==")
+2 -2
View File
@@ -61,7 +61,7 @@ func printStartupMessage(apiEndpoints []string, err error) {
// Prints `mc` cli configuration message chooses
// first endpoint as default.
printCLIAccessMsg(strippedAPIEndpoints[0], "mysilo")
printCLIAccessMsg(strippedAPIEndpoints[0], "myminio")
// Prints documentation message.
printObjectAPIMsg()
@@ -186,7 +186,7 @@ func printCLIAccessMsg(endPoint string, alias string) {
const mcQuickStartGuide = "https://silo.pgsty.com/reference/minio-mc/#quickstart"
// Configure mc and print platform-specific connection information.
// Configure 'mc', following block prints platform specific information for minio client.
if color.IsTerminal() && (!globalServerCtxt.Anonymous && globalAPIConfig.permitRootAccess()) {
logger.Startup(color.Blue("\nCLI: ") + mcQuickStartGuide)
mcMessage := fmt.Sprintf("$ mc alias set '%s' '%s' '%s' '%s'", alias,
+1 -1
View File
@@ -80,7 +80,7 @@ func TestPrintCLIAccessMsg(t *testing.T) {
}
apiEndpoints := []string{"http://127.0.0.1:9000"}
printCLIAccessMsg(apiEndpoints[0], "mysilo")
printCLIAccessMsg(apiEndpoints[0], "myminio")
}
// Test print startup message.
+1 -1
View File
@@ -26,7 +26,7 @@ import (
var errMaxVersionsExceeded = StorageErr("maximum versions exceeded, please delete few versions to proceed")
// errUnexpected - unexpected error, requires manual intervention.
var errUnexpected = StorageErr("unexpected error, please report this issue at https://github.com/pgsty/silo/issues")
var errUnexpected = StorageErr("unexpected error, please report this issue at https://github.com/minio/minio/issues")
// errCorruptedFormat - corrupted format.
var errCorruptedFormat = StorageErr("corrupted format")
+4 -4
View File
@@ -1112,17 +1112,17 @@ func logFatalErrs(err error, endpoint Endpoint, exit bool) {
case errors.Is(err, errUnsupportedDisk):
var hint string
if endpoint.URL != nil {
hint = fmt.Sprintf("Drive '%s' does not support O_DIRECT flags, Silo erasure coding requires filesystems with O_DIRECT support", endpoint.Path)
hint = fmt.Sprintf("Drive '%s' does not support O_DIRECT flags, MinIO erasure coding requires filesystems with O_DIRECT support", endpoint.Path)
} else {
hint = "Drives do not support O_DIRECT flags, Silo erasure coding requires filesystems with O_DIRECT support"
hint = "Drives do not support O_DIRECT flags, MinIO erasure coding requires filesystems with O_DIRECT support"
}
logger.Fatal(config.ErrUnsupportedBackend(err).Hint("%s", hint), "Unable to initialize backend")
case errors.Is(err, errDiskNotDir):
var hint string
if endpoint.URL != nil {
hint = fmt.Sprintf("Drive '%s' is not a directory, Silo erasure coding needs a directory", endpoint.Path)
hint = fmt.Sprintf("Drive '%s' is not a directory, MinIO erasure coding needs a directory", endpoint.Path)
} else {
hint = "Drives are not directories, Silo erasure coding needs directories"
hint = "Drives are not directories, MinIO erasure coding needs directories"
}
logger.Fatal(config.ErrUnableToWriteInBackend(err).Hint("%s", hint), "Unable to initialize backend")
case errors.Is(err, errDiskAccessDenied):

Some files were not shown because too many files have changed in this diff Show More