GitHub generates no contributor graph for forks, so this fork had no
attribution record at all. CONTRIBUTORS.md becomes that record, in three
tiers: the four contributors with code merged into main, each listed with
the pull request and the commit that carries their authorship; the five who
opened pull requests; and the twenty-two who filed the bug reports and
compatibility findings that shaped the releases.
Both READMEs are condensed and gain a Contributors section that shows the
avatars and points at the full record, with README_ZH realigned to README
section for section.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Feng Ruohang <rh@vonng.com>
GPG-signing the RPMs rewrites their bytes after release.yml has already
generated SBOMs, the packages checksum manifest, and attestations from
the as-built files. The new workflow_dispatch-only finalize-release lane
runs between signing and publishing: it refuses non-Draft releases,
verifies the signed RPMs against their sha256 sidecars and the committed
PGSTY public key, confirms every other package still matches the
original manifest, regenerates the RPM SBOMs and the manifest from the
published bytes, cosign-signs the manifest under the workflow identity,
and attests the finalized set before replacing the assets in place.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The substring grep for the archive line also captured the archive's
.sbom.json entry, whose file is deliberately not downloaded in this
lane, so the sha256sum check failed on every run. awk now matches the
manifest filename column exactly.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rpm carried a bare -1 release while the deb carried none. Both now
ship the PGDG-style 1PGSTY segment: silo-VER-1PGSTY.arch.rpm and
silo_VER-1PGSTY_arch.deb, with nfpm rendering it as the RPM Release tag
and the Debian revision respectively. The apk stays bare because Alpine
pkgrel admits only -r<integer>.
sign-release-rpms.sh declares the value once as expected_release;
package-release.sh names artifacts from its own PKG_RELEASE copy, and
test-release.yml evals the signing script's value and asserts both the
download names and the packaged RPM Release/DEB Version against it, so
the copies cannot drift apart silently.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The go list -deps closure is GOOS-specific, so the CREDITS generated on
darwin (carrying go-m1cpu) could never match a Linux regeneration
(ethtool, numcpus), and the check-gen guard failed on its first CI run.
Generation now pins linux/amd64 with the release build tags, so the
output is identical on every host.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bundle mcli RELEASE.2026-08-06T00-00-00Z in the container image (pin
and both architecture digests; interop with the new server was
verified end to end during release acceptance) and align the CI
fixture default in install-mcli.sh.
Point the Helm chart defaults at the first real silo image tag:
docker.io/pgsty/silo is a fresh repository, so the inherited
RELEASE.2026-08-04 default could never pull. Chart version moves to
7.0.1 with appVersion RELEASE.2026-08-06T00-00-00Z; the packaging
tripwire in verify-helm-migration.sh follows.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
gocritic's ifElseChain check fails make lint on the --json/--quiet
branch introduced by b6d47b739; the quality job runs make lint on
every push to main. Behavior is unchanged (verified by re-running the
json/quiet success and failure cases byte-for-byte).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Findings from an adversarial review (Codex, gpt-5.6-sol at max effort)
of 2ff594f4b and 4c34d2309, each independently verified before fixing:
- SBOM generation: buildx attaches a provenance attestation, so every
per-arch digest names an OCI index; Syft's platform default on an
amd64 runner cannot resolve an arm64-only index and the step dies.
Pass --platform explicitly on all four Syft calls (the two classic
lanes had the same latent defect - the renamed workflow has not run
yet, which is why it never fired).
- Release ordering: the HEALTHCHECK survival check now runs against
the pushed architecture image before the versioned and rolling
multi-arch manifests are created, so a broken health config blocks
their promotion; the comment now states honestly that the
arch-suffixed tags are already public at that point.
- Gate assertions: tar's member-argument mode exits non-zero on any
missing name, which under pipefail masked a found forbidden file
when exactly one of them existed; -tv prints symlinks as
'name -> target', defeating $-anchored greps; and the licenses
check proved only one-of-three. Export the rootfs once and assert
every required and forbidden entry individually (busybox/sh and
usr/bin/mc[li] now covered), and match the image healthcheck as an
exact array instead of a substring.
- Probe target vs CLI-configured servers: a probe process cannot see
PID 1's argv, so --url gains EnvVar MINIO_HEALTHCHECK_URL as the
documented way to point the baked-in HEALTHCHECK at a server whose
address/TLS comes from command-line arguments (verified end to end:
server on --address :9010, env var alone turns the container
healthy). Baseline regenerated for the new env token.
- IPv6 zone identifiers: serialize probe URLs via url.URL.String()
so [fe80::1%eth0]:9000 becomes a valid %25-escaped URL (tests added).
- Boolean flags: read --json/--quiet via Bool() so --json=false is
false, instead of IsSet() which treats any occurrence as true.
- Docker's HEALTHCHECK timeout raised to 10s: an outer deadline equal
to the probe's own 5s always SIGKILLed the probe before it could
print its diagnostic line.
- test-release path filter now also triggers on cmd/healthcheck-main.go
and cmd/main.go, so subcommand regressions run the image gate.
Not adopted: require_text's comment-insensitivity in verify-rebrand.sh
(snapshot-tripwire by design, consistent with its other assertions -
the semantic check lives in the CI gate now), and full
staging-then-promote tag publishing (a workflow-wide redesign shared
with the classic lanes, tracked as follow-up).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Publish pgsty/silo:<RELEASE>-distroless (and a rolling :distroless tag)
alongside the classic image: gcr.io/distroless/static-debian12 plus
exactly one program, the silo binary. No shell, no mc, no curl, no
entrypoint script - the binary is the ENTRYPOINT and the baked-in
exec-form HEALTHCHECK runs 'silo healthcheck ready'. The classic image
and its mc-based health checks are deliberately unchanged.
Design: silo.pgsty.com/compatibility/feature/healthcheck/
/data is created in the image layer, world-writable, because Docker
seeds fresh volumes from the layer mountpoint and no entrypoint exists
to repair ownership at runtime (issue #55); the parent directory is
copied from a throwaway busybox stage since COPY of a directory copies
contents rather than the entry itself, which would silently leave
/data at root:0755 and break every non-root run. The MINIO_USERNAME
drop-user path is not supported in this variant; use --user.
test-release.yml now builds the real Dockerfile.distroless on every
gate run (it has no download stages, so it stays offline) and asserts:
the HEALTHCHECK survives into the image config, /data ships 0777, no
shell and no /usr/bin/minio are present, Docker's health state turns
healthy from the baked probe alone, the probe binary execs without a
shell, and SIGTERM still stops the server gracefully - as root and as
--user 1001:1001. docker-release.yml gains the distroless build lanes,
multi-arch manifests, SBOM and provenance attestations, and a
release-blocking check that the pushed manifest still carries the
HEALTHCHECK (a Docker extension absent from the OCI image spec).
Verified locally on linux/arm64: full gate assertions plus bare
'docker run ... healthcheck' exit-code semantics and --version
passthrough.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add 'silo healthcheck [live|ready|cluster|cluster-read]', a thin
anonymous HTTP client for the server's own /minio/health/* endpoints,
so containers without a shell, curl, or mc can still run health
checks. Design: silo.pgsty.com/compatibility/feature/healthcheck/
The check vocabulary maps 1:1 onto the health API paths; the probe
target is derived from the server's own --address/MINIO_ADDRESS
contract with HTTPS auto-detected from the certs directory, and can
be overridden with --url. Exit codes are 0/1 only (Docker reserves 2);
diagnostics (x-minio-server-status, quorum headers) go into a single
output line for docker inspect. The request is strictly anonymous (a
credentialed request would be rejected by the reserved-path guard),
the transport bypasses HTTP_PROXY, and certificate verification is
skipped to match kubelet HTTPS probe behavior. Cluster checks default
to a 15s deadline so the server's 10s cluster_deadline can elapse.
Compatibility notes: the preserved /minio/health/* path literals and
the MINIO_ADDRESS env var are upstream wire/config surface, reused on
purpose; the rebrand-guard baseline is regenerated for the new route
literals (tests included) with zero new exported symbols. The docker
entrypoint argv translation learns the new command name.
Verified: unit tests, entrypoint tests, go vet, plus an end-to-end
run against a live server covering all four checks, --maintenance
(412), --json, usage errors, unreachable and timeout paths.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The RPM, DEB and APK payloads carried no license material at all, and
the OCI image copied LICENSE and CREDITS but not NOTICE. For an AGPL
distribution that is not defensible packaging. Every delivery channel
now carries the same license set:
- nfpm packages install LICENSE and NOTICE under /usr/share/doc/silo/.
The entries are declared once per packager because nfpm only honors
type: license on rpm and silently drops such entries from deb and apk
-- the rpm keeps its %license flag, deb and apk carry plain files at
the same path. CREDITS stays out of the OS packages deliberately: at
1.8MB it would dominate an otherwise ~40MB payload.
- The OCI image gains /licenses/NOTICE next to the existing LICENSE and
CREDITS, in the Dockerfile and in both context-staging paths (the
make docker target and docker-release.yml).
- The GoReleaser archives list their files explicitly; the default
globs would keep missing NOTICE.
NOTICE's first line now reads 2015-2025 to match the banner constants
fixed in b57275be3 -- it had kept upstream's stale 2015-2023 claim --
and verify-rebrand.sh pins both NOTICE copyright lines.
The payload locks move in the same commit: sign-release-rpms.sh expects
the six-file listing, and the test-release validation asserts presence,
count, the rpm %license flag, and sha256 equality of the packaged
LICENSE and NOTICE against the repository files across all three
formats and both architectures.
Also dropped the unit's WorkingDirectory=/usr/local, a leftover from
upstream's /usr/local/bin install layout. Debian policy 9.1.2 keeps
packages out of /usr/local entirely; the binary already installs to
/usr/bin and the service does not depend on its working directory.
Verified locally with nfpm v2.47.0 (the CI-pinned version): built all
six packages via package-release.sh, then ran the test-release
validation step verbatim (extracted from the workflow YAML) and the
signing script's payload comparison against them in an Ubuntu 24.04
container -- both pass. Built the arm64 image from the real
Dockerfile.goreleaser and confirmed /licenses holds CREDITS, LICENSE
and NOTICE. make rebrand-guard passes with the new NOTICE pins.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Feng Ruohang <rh@vonng.com>
The shipped CREDITS predated the fork's dependency work: 38 of the
modules in the current go.mod, among them lestrrat-go/jwx, go-tpm,
go-spiffe and the charmbracelet family, had no entry at all, while
entries lingered for modules no longer in the build. An attribution
file that silently drifts from go.mod is worse than none, so it is now
generated, not curated.
buildscripts/gen-credits.sh (make credits) rebuilds the file from the
licenses of every module go list -deps reports for the main package --
the set actually linked into the silo binary, so test-only and tool
dependencies stay out. Entries keep the established name / URL /
license-text layout. New over the old file:
- Modules replaced in go.mod are annotated with the repository that
actually serves them, so the pgsty/silo-console, pgsty/mc and
pgsty/silo-pkg forks are named next to their upstream import paths.
- Bundled NOTICE files are reproduced after the license text, which
Apache License 2.0 section 4(d) requires when redistributing; 23
modules carry one.
- minio/colorjson, minio/csvparser and minio/filepath publish no
license file at all; they repackage Go standard library code and
their sources carry the Go Authors' BSD-style header, so the Go
project license is reproduced for them with a note saying why.
- The Go license text itself comes from the pinned golang.org/x/sys
module rather than GOROOT, because Homebrew's Go omits
GOROOT/LICENSE and the module copy is version-locked.
check-gen now runs the generator and fails on a CREDITS diff, the same
treatment go.mod and go.sum already get, so dependency changes cannot
leave stale attributions behind. Output is deterministic: two runs are
byte-identical, and LC_ALL=C sorting plus version-pinned inputs keep it
that way across machines.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Feng Ruohang <rh@vonng.com>
Pins pgsty/silo-console at 72fc0a5e, the v2.1.1 tag, replacing the
v2.1.0 commit from earlier today. The pseudo-version form stays because
the compatibility module path github.com/minio/console carries no /v2
suffix, so a v2.x semver tag is not directly selectable.
v2.1.1 is 5 commits over v2.1.0. The one substantive change for the
server is a fix that keeps unresolved i18n placeholders out of the
metrics dashboard legends; the rest is the v2.1.1 changelog, dropping
the container-image advertising from the Console docs, and regenerated
embedded web assets.
Verified: go mod tidy -compat=1.26 is a clean no-op afterwards, which is
what make check-gen asserts in CI, and go build ./... and go vet ./...
pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Feng Ruohang <rh@vonng.com>
Brings the server in line with the contribution and copyright policy
already adopted in pgsty/mc.
Copyright terms were derived from the clock: startupBanner overwrote the
ldflags-injected CopyrightYear with time.Now().Year() and printed it as
the end of MinIO, Inc.'s term, so every January would have extended the
upstream copyright claim past the 2025 end of upstream development, and
release builds silently discarded the injected year. Both banners now
credit MinIO, Inc. for 2015-2025 and PGSTY from 2025 through the
release-stamped year, falling back to the current year in source builds.
NOTICE names PGSTY as the holder of the fork's modifications, matching
the banners and the packaging vendor field.
Contributions are accepted inbound=outbound under AGPL-3.0-or-later with
no CLA - the core is Copyright MinIO, Inc., so the combined work can
never be relicensed and a CLA would buy nothing. What a fork carrying a
downstream delta does need is provenance, so DCO 1.1 sign-off is now
mandatory and enforced by a workflow that rejects unsigned non-bot
commits. CONTRIBUTING documents sign-off, repair, cherry-pick provenance,
dual copyright headers, trailer preservation across squash merges, and
that assistive-tooling trailers carry no authorship or copyright claim.
verify-rebrand.sh pins the copyright split and the policy files so
neither can regress.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Feng Ruohang <rh@vonng.com>
actions/checkout@v4 and actions/setup-go@v5 declare runs.using: node20, so
every Go CI job printed the runner's Node 20 deprecation warning. The
release, test-release and docker-release workflows carried six more node20
actions that never appeared in that warning only because those workflows
run on a tag or a dispatch rather than on every push: upload-artifact v4,
goreleaser-action v6, build-push v6 and the docker qemu/buildx/login v3
line. All of them move to the current majors, which declare node24. The
SHA-pinned rest already qualified -- attest and sbom-action are node24,
cosign-installer is composite.
Checked against what those majors removed. build-push v7 dropped
DOCKER_BUILD_NO_SUMMARY and DOCKER_BUILD_EXPORT_RETENTION_DAYS and
setup-buildx v4 dropped its deprecated inputs, none of which are used here.
checkout v7 refuses to check out fork PRs under pull_request_target and
workflow_run, and no workflow here triggers on either.
setup-go v6 exports GOTOOLCHAIN=local, the one change with teeth. go.mod
declares `go 1.26.5` and carries no toolchain directive, every job resolves
that same version through go-version-file, and nothing in the module graph
asks for more, so there is nothing left for the go command to fetch:
`go build ./...` passes under GOTOOLCHAIN=local. The tools installed from
outside the module are in range too -- nfpm v2.47.0 requires 1.26.4 and
govulncheck v1.6.0 requires 1.25.0 -- and golangci-lint arrives as a
prebuilt binary from upstream's install script, not through the toolchain.
The runner floor that comes with node24 (v2.327.1) is met by GitHub-hosted
runners, and every job here is ubuntu-latest.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pins pgsty/silo-console at 343d9442, the v2.1.0 tag, replacing the v2.0.0 commit
from 2026-08-04. The pseudo-version is still the form used here because the
compatibility module path github.com/minio/console carries no /v2 suffix, so a
v2.x semver tag is not directly selectable; the comment above the directive now
names the release the commit actually corresponds to.
v2.1.0 is 23 commits over v2.0.0. The substantive ones for the server are a
hand-rolled EN/ZH i18n layer with a language toggle covering login, navigation,
dashboard, buckets and object browser, identity and configuration, event
destinations, observability and admin tools; a dashboard migration to MinIO
Metrics V3; and removal of the remaining SUBNET references from health
reporting, which matches this repository's own decision to close the upstream
telemetry surfaces. The rest is UI polish and regenerated embedded web assets.
Verified: go mod tidy -compat=1.26 is a clean no-op afterwards, which is what
make check-gen asserts in CI, and go build ./... and go vet ./... pass.
Not included here: the GitHub Action version bumps and the .gitignore entry
sitting in the working tree are someone else's change and stay uncommitted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous commit added a rename note to both READMEs and broke the gate I
added one commit earlier. That gate rejected the string pgsty/minio anywhere
outside three guard files, which was the wrong invariant: the point is that the
old name must never be a live target, not that it must never be spoken. A README
explaining where the MinIO-named artifacts went has to name them, and doing so
is the opposite of stranding a reader on a dead repository.
Split into two rules that say what is actually meant:
- No live URL may resolve to the old repository anywhere, READMEs included -
github.com/pgsty/minio and hub.docker.com/r/pgsty/minio are rejected outright.
This is the rule that protects users.
- The bare name is allowed only in five files: the pinned pre-rebrand image
digest in the upgrade test, the two guards that refuse a legacy image, and the
two READMEs.
Both halves are negative-tested: adding a github.com/pgsty/minio link to
README.md fails rule one, and adding a bare pgsty/minio to docs/docker/README.md
fails rule two.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Someone arriving at main after the rename has one question the README did not
answer: where did the MinIO-named builds go. Both READMEs now say it directly —
the archived minio branch and the releases up to RELEASE.2026-08-04T00-00-00Z
hold the artifacts maintained under the original identity, and those assets and
the docker.io/pgsty/minio image stay published and unmodified.
The note also records the rename itself with its date, and repeats the one fact
that determines whether an operator has any work to do: only product and
artifact names changed. MINIO_* variables, minio_* metrics, x-minio-* headers,
/minio/* routes and the on-disk format are the same.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The transitional references land in one commit, because they are only correct
together: the repository is pgsty/silo, its default branch is main, and nothing
in the tree should point a user at the old names.
Changed:
- Workflow branch filters. go.yml and vulncheck.yml gated on `branches: master`
for both push and pull_request, so renaming the default branch would have
taken automatic CI offline with no error and no signal - the workflows would
simply never trigger again. They now name main.
- Release target. goreleaser's `release.github.name` becomes silo, which is
what actually decides where a tagged build publishes. sign-release-rpms.sh's
GH_REPO default follows.
- The OCI `image.source` label, the Helm chart `sources` entry, the security
advisory link in the issue-template config, and the go.mod comment citing the
LDAP TLS fix.
- 115 occurrences across README, README_ZH, SECURITY, CONTRIBUTING and 30 docs
pages, including 72 links that also carried the master branch in their path.
Those matter most: GitHub redirects clone, fetch, push and web URLs after a
rename, but raw.githubusercontent.com does not, and neither follows a branch
rename - every one of those links would 404 twice over.
- Three error strings in cmd/erasure-sets.go, cmd/storage-errors.go and
internal/config/errors.go that print an issue URL to operators. These are Go
string literals inside rebrand-guard's brand allowlist, so the baseline is
regenerated. The regeneration removes exactly those three entries and adds
none; all twelve other protected sets, including the 9014 exported symbols,
are byte-identical.
- The transitional-naming disclaimers in README, README_ZH, SECURITY and
CONTRIBUTING are dropped, since they no longer describe anything.
Deliberately unchanged, all three because they exist to reject or freeze the old
name rather than to point at it:
- buildscripts/minio-upgrade.sh pins pgsty/minio@sha256:b6bfe72... - the frozen
pre-rebrand image is the control group for the MinIO-to-Silo upgrade test.
- helm-migration-guard rejects any rendered container still pulling pgsty/minio.
- verify-rebrand.sh rejects the same in the delivery surfaces.
Also unchanged: docs/config/README.md links to pgsty/mc/blob/master, and that
repository's default branch really is still master. It moves when mc does.
verify-rebrand.sh gains three assertions so this cannot silently regress: no
source reference may name pgsty/minio outside the three allowlisted guards, no
link may target pgsty/silo's master branch, and go.yml and vulncheck.yml must
filter on main. Both new rejections were negative-tested - reintroducing a
master branch filter and adding a pgsty/minio URL each fail the gate with the
specific message.
This commit assumes the rename actually happens. Until the GitHub branch and
repository renames are executed, the links it introduces do not resolve.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
README, README_ZH, SECURITY, COMPLIANCE, CONTRIBUTING, NOTICE,
code_of_conduct, the vulnerability and PR-etiquette documents, the GitHub issue
and pull request templates, and the docs/ tree all present Silo as the product.
The Grafana dashboards under docs/metrics/prometheus/grafana/ have their panel
titles and descriptions rebranded while every minio_* query, label and
expression is left alone, so existing alerts and recording rules keep matching.
The distinction the review demanded is applied per hit rather than by
search-and-replace:
- Product and command text becomes Silo and silo: install and run instructions,
systemd examples, compose services, download links, badges.
- Protocol and interface text keeps MinIO: MINIO_* variables, minio_* metrics,
x-minio-* headers, /minio/* routes, .minio.sys, arn:minio, and API field and
error names.
- Attribution keeps MinIO and gains the fork's own: the AGPL obligations,
original copyright, CREDITS and NOTICE stay, with the modification notice
added alongside rather than replacing them.
- Historical and third-party references are left as facts, not rewritten for
brand tidiness.
README and README_ZH each carry an explicit non-affiliation notice, document
the side-by-side package migration including the
/etc/systemd/system/silo.service.d/10-legacy-user.conf drop-in for keeping a
legacy UID/GID, and state that recursive chown is never performed. The trademark
attribution uses the policy's approved "based on MinIO technology" wording, not
the shortened form the policy rejects.
github.com/pgsty/minio links are left in place and labelled transitional. The
repository has not been renamed, and rewriting them now would produce documented
URLs that 404 until the cutover; they change in the cutover commit together with
the goreleaser release target, the OCI source label and the raw-content branch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The test and verification scripts invoked ./minio and pulled their tooling from
upstream infrastructure with no integrity check. Every `curl | tar` of a client
or an old server binary was an unverified execution path in a script that
regularly runs as a privileged user, and several fetched a floating "latest".
Two installers replace all of it:
- install-mcli.sh resolves a pinned pgsty/mc release, downloads the archive and
its checksum manifest, requires exactly one valid manifest entry for the
asset, verifies it, and installs. MCLI_BIN with a mandatory MCLI_SHA256 lets
an offline or air-gapped run supply its own binary, still checksum-checked.
- install-verified-fixture.sh takes source, expected SHA-256 and target, and
refuses anything that does not match. Sources may be a URL or a local file.
Every script that previously downloaded mc now calls install-mcli.sh. The three
places that genuinely need an upstream artifact - the old MinIO server binary
for the LDAP IAM upgrade-import test, the 2021 mc for the three-site
replication test, and the functional-tests.sh fixture - go through
install-verified-fixture.sh with the digest recorded inline. Those dl.min.io
URLs remain on purpose: they are historical upstream artifacts needed to prove
upgrade compatibility, and they are now pinned and verified rather than
trusted.
The scripts otherwise switch to ./silo, silo.service, the silo container and
compose service names, and SILO_CONFIG_DIR. run-multi-site-minio-idp.sh is
renamed to run-multi-site-silo-idp.sh with the Makefile target following.
buildscripts/minio-upgrade.sh keeps its name and its `minio server` argv - it
exists to test the MinIO-to-Silo upgrade, so the old side must stay old - but
it is now pinned to an image digest rather than a tag, and its `docker system
prune` and `docker volume prune` calls are removed. Those ran unfiltered
against the developer's whole Docker installation; the resiliency tests had the
same problem and lose their prune and `docker ps -q` sweeps too.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A rebrand of this size cannot be reviewed by reading the diff. The risk is not
what was renamed but what was renamed by accident: an environment variable, a
metric, a header, a route, a storage marker or an exported symbol that some
deployment depends on. So the invariants get a machine check.
rebrand-guard extracts the identifiers a product rename must not touch - the
go.mod module path, github.com/minio/* imports, MINIO_* variables, minio_*
metric literals, x-minio-* headers, route string literals, the hardcoded grid
routes in internal/grid/manager.go, the reserved bucket root, .minio.sys
markers, arn:minio and minio:s3 policy values, every exported symbol in cmd/
and internal/, and every branded string literal - and compares them to a
checked-in baseline. Any removal or addition fails the build, in both
directions, so a future change cannot quietly drop one either.
The recorded delta against the pre-rebrand tree is: module path, 137 imports,
19 metrics, 15 storage markers, 58 policy values and all 9014 exported symbols
byte-identical; environment 437 -> 436, headers 85 -> 84, routes 331 -> 330.
Each of those three is accounted for. MINIO_CONFIG_DIR and MINIO_VERSION were a
shell local and a compose variable in test scripts, never server settings.
x-minio-common was a docker-compose extension key, never an HTTP header.
/api/health/upload was the SUBNET upload path in the deleted callhome.go.
verify-rebrand.sh covers what the identifier guard cannot see: that the retired
upstream delivery paths stay deleted, that the packaging and container files
name silo, that silo.service keeps its Conflicts and both EnvironmentFile
lines, that silo.env contains no active assignment that would shadow the legacy
file, that no active delivery surface still publishes pgsty/minio, and that no
non-test Go source contains a dl.min.io, subnet.min.io, api.min.io,
slack.min.io or play.min.io URL.
Both, plus the entrypoint argv test, run in go.yml, release.yml and
test-release.yml. test-release.yml also gets the path filters right: it was
watching minio.service, which no longer exists, so the packaging gate would
have gone silently dormant. It now watches the unit, defaults, sysusers,
lifecycle scripts, helm chart and both guards, and asserts the four-file
payload, the absence of any cross-name Provides/Obsoletes/Conflicts, and that a
container started with the legacy `minio server` argv really runs silo as PID 1.
release.yml and docker-release.yml additionally verify that the checked-out
revision matches GITHUB_SHA before building. GitHub's OIDC certificate records
GITHUB_SHA, not the ref passed to actions/checkout, so a manual dispatch from
the wrong ref would have produced provenance describing different source from
the bytes being published. They now emit SBOMs and attestations for archives,
packages and images, and docker-release verifies the release workflow's
attestation on every artifact it consumes. Images publish to pgsty/silo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
helm/minio becomes helm/silo: chart name silo, version 6.0.0 -> 7.0.0, the
MinIO wordmark icon replaced with the project's own, image.repository and
mcImage.repository pointing at pgsty/silo, and the container command changed to
silo. User-visible titles, comments and documentation links are rebranded. The
MINIO_* environment variables and every existing values key are kept - the
first Silo chart is a rename, not a values-schema migration.
The hard problem is that a chart rename normally rewrites Kubernetes resource
identity, and a StatefulSet's selector and volumeClaimTemplate are immutable.
An existing release upgraded carelessly would either fail or orphan its PVCs.
Two things address that:
- Templates no longer derive the container name from .Chart.Name. It comes from
a helper, so nameOverride can pin it, which means an existing release can be
upgraded with nameOverride=minio, fullnameOverride=<existing-fullname> and
serviceAccount.name=minio-sa and render byte-stable identity while switching
chart and image.
- helm-migration-guard and verify-helm-migration.sh make that a gate rather
than a documented hope. The script lints the chart, renders it in distributed
and standalone modes plus the optional templates, then renders the legacy
chart from a pinned commit and the new chart with those three overrides and
compares resource identity. The guard additionally rejects any rendered
container still pulling pgsty/minio or invoking /usr/bin/minio. It runs
through a pinned alpine/helm image when helm is not installed locally, so the
gate does not depend on the developer's machine. Currently green over 7
compared resources.
Rollback is asymmetric and the README says so: the old chart with the new image
survives via the entrypoint argv shim, but the new chart with an old MinIO
image does not, because `silo server` is not a command that binary knows. Only
`helm rollback` is supported, never an image-only downgrade.
Not addressed here: the default image tag is pgsty/silo:RELEASE.2026-08-04T00-00-00Z,
which does not exist yet. The chart must not be published until the first Silo
image is pushed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dockerfile.goreleaser now copies silo to /usr/bin/silo, defaults to
CMD ["silo"], and labels the image as Silo. MINIO_UPDATE_MINISIGN_PUBKEY is
removed from the image environment: with the updater permanently disabled it
was dead weight, and leaving upstream's verification key in a Silo image
implied a trust relationship that does not exist. The MINIO_* runtime
environment variables, ports, volume and health endpoints are unchanged.
The image keeps shipping mcli with an /usr/bin/mc symlink. That is the client,
not a MinIO-branded alias for the server binary, and the Helm post-install job
and existing container scripts call it by name.
docker-entrypoint.sh translates a legacy first argument: `minio server /data`
becomes `silo server /data`, so an existing `command: minio server ...` in
compose or a Pod spec keeps working across the image swap. The translation is
argv-level only - no file named minio is installed, and an explicitly
overridden `entrypoint: /usr/bin/minio` still fails, which is the honest
outcome since that path genuinely no longer exists.
The entrypoint also fixes an unrelated startup hazard it was already carrying:
when the image runs under an arbitrary UID, HOME points at an unreadable /root
and the server probes its default config directory during initialization. It
now falls back to /tmp when HOME is unset, /root, missing or unwritable.
docker-entrypoint_test.sh pins all of it - empty argv, legacy minio, native
silo, bare flags, and an explicit shell command that must stay explicit. It is
wired into make rebrand-guard and into the go.yml, release.yml and
test-release.yml gates, so removing the shim breaks CI rather than breaking
users. Passing locally.
download-static-curl.sh gains checksum verification for the curl it fetches
into the build stage.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Everything a user installs is renamed, and the package finally installs enough
to be startable on a clean host.
Artifact names
- goreleaser.yml: build id, binary, archive and checksum manifest become silo_*.
release.github.name stays "minio" with a comment - the GitHub repository has
not been renamed yet, and pointing at pgsty/silo before the rename would 404.
Also adds per-archive SPDX SBOMs and a keyless cosign signature over the
checksum manifest, so the signed manifest covers archives and SBOMs together.
- nfpm.yml: package name silo, and the binary moves from /usr/local/bin/minio
to /usr/bin/silo. /usr/local is not on the default PATH of a systemd unit and
is not FHS-correct for a distribution package.
- package-release.sh, sign-release-rpms.sh and verify-build-provenance.sh follow
the new names; the RPM signing script asserts NAME=silo and the new four-file
payload. nfpm is now invoked from the repository root so relative script paths
in the config resolve regardless of the caller's directory.
Package relationships are deliberately empty
No Provides, Obsoletes, Replaces or package-level Conflicts. Obsoletes: minio
cannot distinguish a pgsty package from upstream's own identically named one,
so an unattended dnf upgrade could silently swap a different vendor's product
for this one. With no relationships, both packages coexist, their file sets do
not overlap, and migration and rollback are single explicit commands. The
mutual exclusion lives in the unit instead: silo.service carries
Conflicts=minio.service plus After=minio.service.
Payload, from two files to four
- /usr/bin/silo
- /usr/lib/systemd/system/silo.service
- /etc/default/silo, installed config|noreplace
- /usr/lib/sysusers.d/silo.conf
The old package shipped a unit referencing an account nothing created, so a
clean install could not start. postinstall.sh now creates the silo system
account through systemd-sysusers, useradd or BusyBox adduser in that order and
runs daemon-reload. It never stops a service, never chowns data and never
touches /etc/default/minio. preremove.sh disables silo.service only on a real
removal - Debian "remove", RPM 0, Alpine's dotted version - so upgrades leave
the running service alone. lifecycle_test.sh exercises both against a stubbed
PATH, so a green run cannot create an account or touch the host.
silo.service reads /etc/default/minio then /etc/default/silo, in that order, so
an existing node's MINIO_* values keep working and the new file overrides them.
The packaged silo.env therefore ships comments only: any active assignment
would shadow the legacy file with an empty value.
Makefile: build/install/install-race produce ./silo, and the docker target now
assembles a context from a locally built linux binary plus Dockerfile.goreleaser
instead of the deleted Dockerfile. The hotfix, hotfix-push, docker-hotfix and
docker-hotfix-push targets are gone - they downloaded upstream's pkger, signed
with upstream's minisign key and scp'd to dl-N.minio.io. verifiers now depends
on a new rebrand-guard target.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two coupled changes that must land together, because the same files carry both:
the server now identifies itself as Silo, and every path that would have called
home to a MinIO-operated service is closed.
Product identity
- build-constants.go: store name, UA name and startup banner become Silo. The
Go identifiers (MinioStoreName, MinioBannerName, ...) keep their names on
purpose - renaming exported symbols would churn the compatibility surface for
a cosmetic gain, and the rebrand guard freezes that surface.
- main.go, server-startup-msg.go, ftp-server.go and the user-visible log, help
and error strings across cmd/ and internal/ switch to Silo. Original MinIO
copyright, LICENSE, NOTICE and CREDITS are untouched; --version now prints
the upstream copyright, the pgsty modification notice, and the trademark
policy's approved "based on MinIO technology" attribution.
- api-headers.go: the HTTP Server header becomes "Silo". This is the one
externally observable identity change, so TestCommonHeadersUseSiloProductName
pins it - probes that sniff for "MinIO" must move to capability detection.
- Prometheus metric HELP strings keep their MinIO wording. They are part of the
metrics contract the guard protects, not product copy.
Configuration directory
- config-dir.go: new installs use ~/.silo. If only ~/.minio exists it is still
read, with a one-time notice and no files moved. If both exist ~/.silo wins
and an ambiguity warning is emitted; an explicit --config-dir always wins.
Covered by TestSelectDefaultConfigDir. The internal .minio.sys layout is
never renamed - this rule applies to the user config directory only.
Upstream service lockdown
- globalInplaceUpdateDisabled is now true at initialization rather than being
set from MINIO_UPDATE. common-main.go still parses MINIO_UPDATE so upgrading
nodes do not fail on an unknown key, but warns that the value is ignored;
there is no way to re-enable the updater. TestInplaceUpdateCannotBeEnabled
guards that. Without this, an admin with mc could have overwritten
/usr/bin/silo with an upstream MinIO binary.
- verifyBinary and commitBinary refuse early; the ServerUpdate v1/v2 admin
routes and the peer-rest update endpoints stay registered and keep returning
the existing programmatic error, so clients see a stable failure rather than
a 404.
- MinioReleaseBaseURL and defaultMinisignPubkey are emptied: no dl.min.io
download root, and upstream's minisign key is no longer a trust root for
anything this fork ships.
- cmd/callhome.go is deleted and internal/config/subnet/ is reduced to parsing
its old keys and reporting that the integration is disabled. config-current.go
warns instead of failing when callhome or SUBNET settings are present, so an
upgraded node with those keys still starts.
- internal/config/errors.go replaces the MinIO Slack and support entry points
with Silo documentation and issue links. Error codes and programmatic fields
are unchanged.
Verified: the compatibility baseline is unchanged except for the deliberate
removal of the /api/health/upload SUBNET route; go build, go vet and the full
cmd/ and internal/ unit suites pass; a locally built binary starts, serves
S3/Admin/metrics on the unchanged /minio/* routes, answers with Server: Silo,
and falls back to a pre-existing ~/.minio with the expected notice.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The tree still carried a complete second publishing pipeline inherited from
upstream that pgsty has never operated, plus MinIO-branded artwork the fork has
no right to redistribute. None of it is a pgsty release artifact, so the
history-is-immutable rule does not protect it: nothing here was ever published
under this repository's name, and git history plus the pre-cutover tag keep the
provenance.
Removed, by category:
- charts.min.io machinery: CNAME (charts.min.io), _config.yml, index.yaml,
helm-reindex.sh and 90 helm-releases/minio-*.tgz. GitHub Pages is not enabled
on this repository, so this was dormant - but enabling Pages for any reason
would have stood up a convincing impostor of the official MinIO chart
repository from our own domain.
- Dockerfiles that consume upstream binaries: Dockerfile, Dockerfile.cicd,
Dockerfile.hotfix, Dockerfile.release, Dockerfile.release.old_cpu,
Dockerfile.scratch and docker-buildx.sh. These fetch or expect a MinIO-built
binary; a Silo image must only ever contain an artifact this repository built
from this commit. Dockerfile.goreleaser, the one the release workflow
actually uses, stays.
- buildscripts/upgrade-tests/ (compose.yml, minio.env, nginx.conf), which
pinned minio/minio images through a MINIO_VERSION compose variable. The
upgrade test is rebuilt on image digests in a later commit.
- docs/hotfixes.md, describing an upstream hotfix distribution channel that
does not exist for this fork.
- MinIO trademark artwork: .github/logo.svg (a recoloured MinIO wordmark), the
docs/screenshots/ set, the Grafana dashboard screenshots and
docs/federation/lookup/bucket-lookup.png. The dashboards themselves are kept
and rebranded separately; only the images carrying MinIO chrome go.
Deliberately not touched: published releases, tags, image digests and package
signatures remain byte-identical. This commit only removes paths that were
never part of a pgsty release.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The release image (Dockerfile.goreleaser) was only ever built by
docker-release.yml, which is workflow_dispatch only - so the runtime layer and
entrypoint were never exercised by CI until an actual publish, where a broken
COPY path or a signal-handling regression would surface at the worst possible
moment. MC already covers this in its test-release; the server did not.
Add an offline, deterministic smoke: assemble a minimal image from the
linux/amd64 binary goreleaser already built and the real entrypoint, then start
the server and `docker stop` it on both the default and the MINIO_USERNAME
drop-privilege paths, asserting PID 1 is minio, the exit is 0, the shutdown is
sub-timeout, and the "Exiting on signal" log is present. The mcli-download build
stage is skipped deliberately - it hits the GitHub API and would make this gate
flaky and non-reproducible; its simple, checksum-guarded logic still runs at
publish time.
This is the regression guard for the exec-into-chroot entrypoint fix. Also add
dockerscripts/docker-entrypoint.sh to the pull_request paths so an entrypoint
change actually triggers this pipeline. Verified locally end to end: the fixed
entrypoint passes both paths; reverting the exec makes the drop-privilege path
time out to exit 137 and the step fails.
Co-authored-by: Claude <noreply@anthropic.com>
Three gate-correctness fixes:
- lint: `command typos && typos ./ || echo skipping` ran typos twice (POSIX
`command` executes it) and, via `&& ... || echo`, turned a real typo finding
(nonzero exit) into the "not installed" message with exit 0 - so spelling
issues could never fail the gate. Use `command -v` to test presence and run
typos once, letting its findings surface.
- getdeps: `curl -sSfL ... | sh` took the pipeline's exit from sh, and make's
/bin/sh has no pipefail, so a failed or partial download of the golangci-lint
installer was reported as success and a truncated script could run. Download
to a temp file under `set -e` (with a trap to clean it up) and execute that,
so a curl failure aborts the target. Verified: a 404 now exits nonzero
instead of silently continuing.
- release: workflow_dispatch checked out github.ref (the branch it ran from)
while naming artifacts after the input tag, so a release could be built from
one ref and published under another. Pin checkout to the requested tag.
Not addressed here: the server's release container image is still only built
by the publish-time docker-release workflow, never in CI (MC covers this in
its test-release). Flagged for a separate decision.
Co-authored-by: Claude <noreply@anthropic.com>
nFPM placed minio.service at /lib/systemd/system. On merged-usr EL/Fedora the
file lands correctly through the symlink, but the RPM database records /lib/...,
so `rpm -qf` disowns it and - more importantly - systemd's
%transfiletriggerin watches /usr/lib/systemd/system, so the path recorded as
/lib/... never fires the automatic `daemon-reload` on install or removal. Move
the unit to the canonical /usr/lib/systemd/system, which both restores that
trigger and makes the package own the path it ships.
The destination is asserted verbatim in several places, all updated in lockstep
so the packaging gate still passes: the six name/payload/sha checks in
test-release.yml (rpm/deb/apk) and expected_payload in sign-release-rpms.sh.
Verified by building a real deb with the new config: the unit is at
./usr/lib/systemd/system/minio.service.
Also drop the `# Built for ${project.name}...` line from minio.service. nFPM
expands variables in content src paths, not file bodies, so that pkger-era
placeholder was being written verbatim into every installed unit.
Not addressed here, deliberately: the package still does not create the
minio-user account the unit references. That matches upstream MinIO's own
packages (both rely on the documented manual useradd) and is not a fork
regression, so it stays a documentation step rather than a scriptlet.
Co-authored-by: Claude <noreply@anthropic.com>
A gitignored .goreleaser.yml sat in the repo root, so a bare `goreleaser`
invocation - the default discovery path - picked it up instead of the
.github/goreleaser.yml that CI passes with --config. The two had diverged
completely: the shadow published the GitHub release directly (draft: false),
pushed pgsty/minio:latest from a dockers section, and stamped a different
vendor and artifact naming. Since RPM signing runs on the maintainer's own
machine, that shadow config was one stray `goreleaser release` away from
publishing a mislabelled release and overwriting the Docker latest tag.
Delete the file and drop its .gitignore entry so any future .goreleaser.yml
shows up as untracked and is caught by the release workflow's clean-checkout
check, rather than silently steering local builds. CI is unaffected: it names
its config explicitly. (A copy of the removed file is preserved out of tree
for reference.)
Co-authored-by: Claude <noreply@anthropic.com>
gen-ldflags injected -X cmd.GOPATH / cmd.GOROOT from the builder's environment,
baking absolute paths like /Users/<user>/go into every released binary. That
defeats -trimpath and makes the build unreproducible: a third party rebuilding
the same tag gets different bytes and cannot verify checksums.txt.
The values only seed logger.Init's source-path trim list, and under -trimpath
the binary's paths are already relative, so there is no build-machine prefix
left to trim - the trim list also still gets runtime.GOROOT() and
build.Default.GOPATH at run time. Dropping the two stamps changes no observable
logging behaviour; cmd.GOPATH/GOROOT keep the empty defaults a plain go build
leaves.
Verified: gen-ldflags output no longer contains cmd.GOPATH/GOROOT; a
-trimpath release build has zero occurrences of the builder path (was 1);
Version, ReleaseTag and CommitID stamps are intact.
Co-authored-by: Claude <noreply@anthropic.com>
The compute step spliced ${{ github.event.inputs.tag }} straight into the
run: body, so a dispatch tag containing shell metacharacters was parsed as
script - arbitrary code in a job holding a contents:write token. Pass the
input through the environment instead (INPUT_TAG), the same shape
docker-release.yml already uses, so the value reaches bash as data.
Second vector: the old format check compared the sed output against the input
to decide validity, and sed anchors ^...$ per line. A tag carrying a newline
passed the check on its first line and the remaining lines flowed into
$GITHUB_ENV, setting arbitrary variables (PATH, LD_PRELOAD, ...) for every
later step. Replace it with a bash =~ whitelist anchored to the whole string,
which rejects any multi-line value up front.
Verified: a legal RELEASE.* tag is accepted and yields the right PKG_VERSION;
a `"; touch ...; #` injection and a newline-carrying PATH/LD_PRELOAD payload
are both rejected; an empty input falls back to the git ref name.
Co-authored-by: Claude <noreply@anthropic.com>
The crosscompile gate built 15 OS/arch combinations, ten of which we never
ship (ppc64le, mips64, s390x, mips, riscv64, 386, arm, freebsd, netbsd,
openbsd). On a cold CI cache each target costs ~80s, so the full set ran ~21
min and tripped the job's 20-min timeout, cancelling the only CI run for the
release HEAD. Trim the list to the exact goos/goarch matrix the release
actually produces (see .github/goreleaser.yml): linux, darwin, windows on
amd64 and arm64.
This also closes a coverage gap: windows/arm64 is published but was not being
compile-checked. The trimmed set builds all six in well under the timeout.
Note: netbsd is no longer compile-checked here, so the go-systemd v22.6.0 pin
(kept because v22.7.0 does not build on netbsd) loses its CI guard. The pin is
retained deliberately - it is harmless on the platforms we ship and upgrading
go-systemd is a separate decision - but a netbsd regression in a dependency
would now surface only if that build is exercised out of band.
Co-authored-by: Claude <noreply@anthropic.com>
The two chroot branches that drop privileges when MINIO_USERNAME/GROUPNAME
(and optionally MINIO_UID/GID) are set ran chroot as a child of the entry
shell, leaving the shell as PID 1. A SIGTERM from `docker stop` or an
orchestrator then went to the shell, which does not forward it, so MinIO was
never asked to shut down and was killed after the stop timeout (exit 137) with
no "Exiting on signal" log - risking in-flight requests and data at the flush
boundary. The default branch already exec's; these two now do too, so MinIO
runs as PID 1 and receives the signal directly.
Verified in a faithful reproduction of the release runtime layer: all three
paths (default, USERNAME only, USERNAME+UID/GID) now stop in ~0.2s with exit 0
and log the graceful shutdown, where the two drop-privilege paths previously
timed out to exit 137.
Co-authored-by: Claude <noreply@anthropic.com>
Make the repository's actual quality contract visible in CI. Check formatting, build and vet the tree, run cmd and internal tests, enforce pinned lint and generated-file cleanliness, exercise S3 Select under the race detector, and cross-compile every supported target.
Keep each concern in a separate job so failures identify the missing guarantee instead of hiding behind a single aggregate test result.
Co-authored-by: ChatGPT <noreply@openai.com>
Co-authored-by: Claude <noreply@anthropic.com>
SelectResults spawns a parser goroutine that drains and closes the
response body when the stream ends; deferring res.Close() had the test
drain and close the same bytes.Reader concurrently, which the race
detector catches reliably. Give the test body a close signal and wait
for the parser to finish instead of competing with it.
The double-close lives in minio-go's client parser, which no server
code path uses; it remains worth an upstream report.
Co-authored-by: ChatGPT <noreply@openai.com>
Co-authored-by: Claude <noreply@anthropic.com>
Expand the generated-file gate beyond *_gen.go and go.sum to cover generated tests, msgp output, stringer files, go.mod, and untracked generated artifacts.
Regenerate the 19 stale stringer outputs with the go.mod-pinned x/tools version. Runtime String output is unchanged, and a repeated generation pass now leaves the tree byte-identical.
Co-authored-by: ChatGPT <noreply@openai.com>
Co-authored-by: Claude <noreply@anthropic.com>
Installing golangci-lint from the moving master branch made local and CI results depend on the day they ran. Pin v2.11.3 and install it into a versioned path so version changes cannot accidentally reuse a stale binary.
Co-authored-by: ChatGPT <noreply@openai.com>
Co-authored-by: Claude <noreply@anthropic.com>
The release workflow built and pushed the container images in the same
run that created the draft, so pgsty/minio:latest moved to a build
nobody had signed or published yet. Abandoning that draft left latest
pointing at it with no way to notice, and the images were the one
artifact of a release that escaped the draft gate entirely.
Image publishing moves to its own dispatch-triggered workflow that
takes a tag and refuses to touch anything that is not a published,
non-prerelease release, and that is not the latest one - since it moves
the latest image tag, running it for an older release would silently
roll users back. It builds from the archives attached to that release,
checked against the published checksums, rather than rebuilding from
source: the image then contains the same binary the tarball does, by
construction rather than by assumption.
GoReleaser loses its dockers and docker_manifests sections along with
the QEMU, buildx and registry-login steps that only existed to serve
them, and the release job drops packages: write, which was granted for
a GHCR push that never happened.
Releasing now has one more manual step. That is the point: tagging no
longer moves docker latest, so the tag can be cut, inspected, signed
and published before anything reaches users who pull by tag.
Co-authored-by: ChatGPT <noreply@openai.com>
Co-authored-by: Claude <noreply@anthropic.com>
Refuse dirty release checkouts and verify that every GoReleaser binary records the tagged revision with vcs.modified=false before packaging or publication.
Exercise the complete nFPM output in the release test pipeline: assert the six public names, validate every checksum and package identity field, and prove that RPM, DEB, and APK payloads contain the exact source binary and systemd unit. Validate release scripts and make their identity expectations the single source of truth.
Co-authored-by: ChatGPT <noreply@openai.com>
Co-authored-by: Claude <noreply@anthropic.com>
Replace minio/pkger's hard-coded upstream identity with an in-tree nFPM configuration. Packages now name PGSTY as vendor and maintainer, use the SILO homepage and SPDX license, and preserve the established package names, versions, payload paths, modes, and checksum format.
Resolve both the release binary and systemd unit independently of the caller's working directory. The release and test workflows share the same package script, while the signing script consumes the same final metadata contract.
Co-authored-by: ChatGPT <noreply@openai.com>
Co-authored-by: Claude <noreply@anthropic.com>
GitHub Actions cannot hold the Pigsty RPM signing key, so the release workflow must stop before publication. Create releases as append-only drafts, refuse to overwrite existing assets, and make publication an explicit action after local signing and review.
Add a maintainer-side signing command that downloads the two RPMs, verifies checksums and package identity, signs them with the Pigsty key, regenerates checksums, and only replaces draft assets when --upload is explicitly requested. Published releases are never modified.
Co-authored-by: ChatGPT <noreply@openai.com>
Co-authored-by: Claude <noreply@anthropic.com>
Consolidate the release-cycle security ledger after the implementation commits are stable. Document internode containment, policy-condition source hardening, the opt-in trusted-proxy boundary, and the bucket/object authorization tightening with fork-local identifiers where no CVE exists.
Use silo-pkg v3.11.0 as the maintained dependency reference, explain compatibility and migration behavior, and link each entry to the rewritten commit that actually carries the fix.
Co-authored-by: ChatGPT <noreply@openai.com>
Co-authored-by: Claude <noreply@anthropic.com>
Keep unpublished security working notes, local agent instructions, and maintainer scratch material out of the public source tree. Already tracked security documentation remains tracked; the ignore rules only prevent accidental additions.
Co-authored-by: ChatGPT <noreply@openai.com>
Co-authored-by: Claude <noreply@anthropic.com>
Point both the server and bundled client jobs at the release tag being cut, keep chart appVersion aligned, and retain the maintained pgsty/minio image selected by the public chart baseline.
Stop creating the console/console123 administrator by default. Leave an explicit change-me example while rendering no user-creation job unless an operator opts in. Chart packaging and repository indexing remain separate release steps.
Co-authored-by: ChatGPT <noreply@openai.com>
Co-authored-by: Claude <noreply@anthropic.com>
ReadParts built its disk-health trace path from partMetaPaths[0] with
no length check, so a caller passing no paths at all indexed an empty
slice. xlStorage.ReadParts handles an empty list perfectly well - it
returns an empty result - so the panic came entirely from the metrics
bookkeeping wrapped around it.
The reachable caller is ReadPartsHandler, which decodes its path list
from a msgpack request body that an authenticated peer controls, and
neither the handler, the storage-REST client, nor the path guard
rejects an empty one: guardPaths ranges over the slice, so an empty
slice passes vacuously.
net/http recovers a panicking handler, so this is not a crash - which
is what makes it worth fixing rather than merely tidy. ReadPartsHandler
calls keepHTTPResponseAlive before it calls ReadParts, and that helper
spawns a goroutine whose only exit is receiving from the channel done()
writes. Panicking in between skips both done(err) and done(nil), so the
process survives and the keep-alive goroutine and its ten-second ticker
stay parked forever - one per request, driven by a request body the
caller chooses. Repeating one malformed frame exhausts the node.
The fix follows DeleteVersions in the same decorator, which already
guards the identical "merely for tracing" lookup; ReadParts was the one
method missing the pattern. It also covers the second entry point, the
per-disk errgroup in readParts, where a panic has no recover at all and
would take the process down. That path is screened at the S3 boundary
today, so this is defence in depth there.
No error is returned for the empty case. The storage layer's answer to
an empty list is an empty result, and turning that into an error would
be a behaviour change on a path that is merely degenerate.
Co-authored-by: ChatGPT <noreply@openai.com>
Co-authored-by: Claude <noreply@anthropic.com>