Compare commits

...

7 Commits

Author SHA1 Message Date
マルコメ 42dd76d3b2 ci(docker): sync the Docker Hub overview from README.md on release (#8034)
Docker Hub's overview is a separate `full_description` field that
`docker push` never touches, so it had drifted into an 8 KB snapshot of
an old README.md that still linked to
https://docs.rustfs.com/introduction.html (now 404).

Add a `sync-dockerhub-description` job to docker.yml that runs after the
images are pushed and republishes README.md from the same commit via
peter-evans/dockerhub-description (pinned to v5.0.0). It reuses the
existing DOCKERHUB_USERNAME / DOCKERHUB_TOKEN credentials, so no new
secrets are needed. Relative links (docs/, CONTRIBUTING.md) are
rewritten to github.com URLs so they resolve on Docker Hub.

Docker Hub caps the field at 25,000 bytes and the action truncates to
fit with only a warning; README.md is at 22,879 bytes today. Read the
published overview back after the sync and fail the job if it hit the
cap, so a truncated overview cannot be published silently.

Fixes #7995

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Hauser <housemecn@gmail.com>
2026-09-21 07:04:47 +08:00
Chris 9aeecad4a9 docs(security): add encrypted ETag advisory lesson (#8040) 2026-09-20 23:52:47 +08:00
Hauser 3738b32df1 chore(deps): update flake.lock (#8033)
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/aff8a0b' (2026-09-10)
  → 'github:NixOS/nixpkgs/a32edd7' (2026-09-17)
• Updated input 'rust-overlay':
    'github:oxalica/rust-overlay/228ecef' (2026-09-12)
  → 'github:oxalica/rust-overlay/26a71e6' (2026-09-19)
2026-09-20 14:54:45 +08:00
Hauser d997b07505 fix(get): skip futile resume for single-disk objects (#8031)
* chore: pin s3s to upstream git revision

Use the upstream s3s git repository at 50c94aeb1e5ea9ef8e7393ef6bbc87b72f6891c6 for both s3s and s3s-sigv4.

Allow the official s3s git source in cargo-deny so source checks continue to pass.

Co-Authored-By: heihutu <heihutu@gmail.com>

Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* fix(get): skip futile resume for single-disk objects

Co-Authored-By: heihutu <heihutu@gmail.com>

Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

---------

Co-authored-by: zhi22915 <qiuzgang@gmail.com>
2026-09-20 13:25:08 +08:00
Chris 0632a64f91 fix(ecstore): allow server-side copy at normal read quorum (#8029) 2026-09-20 12:36:53 +08:00
hector cbf6b02905 ci(chain): fall back to auto-testing main HEAD when the pinned harness goes stale (#8026)
The functional chain pins the test harness to the revision recorded in
.config/functional-script-revision.txt, which is only refreshed by
release syncs - it went stale on Sep 16 and the nightly chain has been
replaying a Sep-17-era harness ever since. Last night that meant:
the performance lane died on a dpkg conffile prompt (fix merged as
#88 but never reached the chain), the FT lane died on 'unknown option:
--results-file' (harness/workflow version mismatch), and the
STS-105 signing fix (#94) sat untested.

When the pinned revision's commit is older than 24 hours, fall back to
auto-testing main HEAD so the chain always runs the current harness.
Any staleness-probe failure keeps the pinned revision (fail-safe).
The revision file stays in place as the audit trail and the release
sync continues to manage it.
2026-09-20 11:27:56 +08:00
hector ed2c3eaf77 fix(package): preserve service state across upgrades (#8024)
* fix(package): preserve service state across upgrades

* fix(package): match legacy DEB versions in tilde form

Published prerelease packages carry ~ in the dpkg control version
(package_versions.sh maps the SemVer prerelease - to ~), so the
legacy fallback list written with dots never matched 1.0.0~rc.x and
upgrades away from those DEBs still left the service stopped (#8011).

Fix the legacy glob to the tilde form and update the contract test,
which had enshrined the dot form. Verified on Ubuntu 24.04 systemd
containers: DEB upgrade 1.0.0~rc.5 -> 1.0.1 now keeps the service
running; 1.0.0 -> 1.0.1 and 1.0.1 -> 1.0.2 marker path still pass.

* docs(package): expand /etc/default/rustfs example template

Document the commonly used RUSTFS_* settings as commented examples in
the packaged conffile and point to docs.rustfs.com.
2026-09-20 11:27:41 +08:00
17 changed files with 595 additions and 166 deletions
@@ -101,6 +101,7 @@ Update this file only when an advisory adds or changes a reusable lesson, affect
### SSE and on-disk storage invariants
- `GHSA-xrrf-67jm-3c2r`: SSE metadata reported encryption while reader composition bypassed `EncryptReader` and stored plaintext. Lesson: test actual bytes on disk and wrapper order, not only API metadata.
- `GHSA-wqmc-vjgv-jrpw`: SSE-C persisted and listed the plaintext MD5 as the ETag, allowing list-only users to confirm or brute-force low-entropy content and correlate equal plaintext across keys. Lesson: encrypted-object ETags must not expose deterministic plaintext fingerprints; protect them with per-object key material and keep listing, conditional, copy, and multipart semantics consistent.
### Object Lock and retention invariants
@@ -126,7 +127,7 @@ rg -n "normalize_extract_entry_key|Snowball|auto-extract|PathBuf::join|canonical
rg -n "DEFAULT_SECRET|DEFAULT_ACCESS|TEST_PRIVATE_KEY|rustfs rpc|RUSTFS_RPC_SECRET" rustfs crates
rg -n "TONIC_RPC_PREFIX|verify_rpc_signature|check_auth|NodeServiceServer|x-rustfs-signature" rustfs crates
rg -n "debug!|trace!|info!|error!|\\?resp|\\?merged_config|session_token|secret_key" rustfs crates
rg -n "HashReader|EncryptReader|SSE|server-side encryption|Access-Control-Allow-Credentials|Origin" rustfs crates
rg -n "HashReader|EncryptReader|try_resolve_etag|ETag|SSE|server-side encryption|Access-Control-Allow-Credentials|Origin" rustfs crates
rg -n "ObjectLock|object_lock|retention|COMPLIANCE|GOVERNANCE|delete_prefix|lifecycle|scanner" rustfs crates
rg -n "deny_unknown_fields|serde.default|as u32|as usize|as i32" rustfs crates
```
@@ -145,5 +146,5 @@ rg -n "deny_unknown_fields|serde.default|as u32|as usize|as i32" rustfs crates
- IAM export fixes: assert exported archives omit plaintext user and service-account secrets unless the format deliberately encrypts or seals them.
- RPC auth fixes: include captured metadata replay across two concrete methods, stale timestamps, wrong path, wrong method surrogate, wrong secret, and valid same-method calls.
- Browser/CORS fixes: assert no credentials on reflected/default origins, correct behavior for explicit allowlists, and no same-origin script execution for previewed object content.
- SSE fixes: inspect stored bytes and verify API metadata, read-back behavior, and on-disk ciphertext together.
- SSE fixes: inspect stored bytes and verify API metadata, read-back behavior, and on-disk ciphertext together; for ETags, upload equal plaintext under different keys and assert list-only callers cannot derive or correlate the plaintext digest, including multipart objects.
- Object Lock fixes: include unreadable metadata, fabricated metadata defaults, unparsable config, confirmed absent config, COMPLIANCE/GOVERNANCE retention, lifecycle expiry, scanner sweeps, and force-delete paths.
+1
View File
@@ -29,6 +29,7 @@ script-tests: ## Run shell script tests
./scripts/test_entrypoint_credentials.sh
./scripts/test_internode_grpc_ab_bench.sh
./scripts/test_object_batch_bench_enhanced.sh
./scripts/test_package_service_scripts.sh
./scripts/test_hotpath_warp_ab_gate.sh
./scripts/test_hotpath_warp_abba.sh
./scripts/test_scanner_validation_harness.sh
+7
View File
@@ -24,7 +24,9 @@ on:
- '.github/actions/**'
- '.github/workflows/**'
- 'scripts/release/create_or_update_release.sh'
- 'scripts/release/package_service_scripts.sh'
- 'scripts/release/package_versions.sh'
- 'scripts/test_package_service_scripts.sh'
- 'scripts/test_package_versions.sh'
- 'scripts/security/check_performance_ab_workflow.sh'
- 'scripts/security/check_preview_release_workflow.sh'
@@ -40,7 +42,9 @@ on:
- '.github/actions/**'
- '.github/workflows/**'
- 'scripts/release/create_or_update_release.sh'
- 'scripts/release/package_service_scripts.sh'
- 'scripts/release/package_versions.sh'
- 'scripts/test_package_service_scripts.sh'
- 'scripts/test_package_versions.sh'
- 'scripts/security/check_performance_ab_workflow.sh'
- 'scripts/security/check_preview_release_workflow.sh'
@@ -158,6 +162,9 @@ jobs:
- name: Check package version contract
run: ./scripts/test_package_versions.sh
- name: Check package service lifecycle contract
run: ./scripts/test_package_service_scripts.sh
dependency-review:
name: Dependency Review
runs-on: ubuntu-latest
+48
View File
@@ -542,6 +542,54 @@ jobs:
if-no-files-found: error
retention-days: 30
# Docker Hub's overview is a separate `full_description` field that
# `docker push` never touches, so it drifted from README.md and kept
# linking to pages that no longer exist (#7995). Republish it from the
# commit the images were built from, only when images were pushed.
sync-dockerhub-description:
name: Sync Docker Hub Description
needs: [ build-check, build-docker ]
if: needs.build-check.outputs.should_build == 'true' && needs.build-check.outputs.should_push == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
persist-credentials: false
ref: ${{ needs.build-check.outputs.source_ref }}
sparse-checkout: README.md
sparse-checkout-cone-mode: false
- name: Sync Docker Hub description
uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5.0.0
with:
username: ${{ env.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
repository: ${{ env.REGISTRY_DOCKERHUB }}
readme-filepath: ./README.md
# README.md links to docs/ and CONTRIBUTING.md by relative path;
# rewrite those to github.com URLs so they resolve on Docker Hub.
enable-url-completion: true
# Docker Hub caps `full_description` at 25,000 bytes and the action
# truncates to fit with only a warning, which would publish a README
# cut off mid-sentence. README.md is already near the cap, so read the
# published overview back and fail if it hit the limit.
- name: Check published description was not truncated
env:
REPOSITORY: ${{ env.REGISTRY_DOCKERHUB }}
run: |
limit=25000
published=$(curl -sSf "https://hub.docker.com/v2/repositories/${REPOSITORY}/" \
| jq -j '.full_description' | wc -c | tr -d ' ')
echo "Published overview: ${published} bytes (Docker Hub limit: ${limit})"
# Truncation lands within one UTF-8 character of the cap.
if [ "$published" -ge $((limit - 4)) ]; then
echo "::error file=README.md::Docker Hub overview was truncated at ${limit} bytes; trim README.md before the next release."
exit 1
fi
# Docker build summary
docker-summary:
name: Docker Build Summary
+26 -62
View File
@@ -117,10 +117,15 @@ jobs:
cat > "${PKG_DIR}/etc/default/rustfs" << 'ENVEOF'
# RustFS Environment Configuration
# See https://rustfs.com/docs/ for more information
# See https://docs.rustfs.com for more information
# RUSTFS_ACCESS_KEY=<changeme>
# RUSTFS_SECRET_KEY=<changeme>
# RUSTFS_VOLUMES=""
# RUSTFS_ROOT_USER=""
# RUSTFS_ROOT_PASSWORD=""
# RUSTFS_ADDRESS=":9000"
# RUSTFS_CONSOLE_ADDRESS=":9001"
# RUSTFS_CONSOLE_ENABLE=true
# RUSTFS_OBS_LOGGER_LEVEL=error
# RUSTFS_OBS_LOG_DIRECTORY="/var/log/rustfs/"
ENVEOF
# dpkg versions must start with a digit and cannot contain hyphens;
@@ -146,37 +151,13 @@ jobs:
/etc/default/rustfs
CONFFILES
cat > "${PKG_DIR}/DEBIAN/postinst" << 'POSTINST'
#!/bin/bash
set -e
if ! getent passwd rustfs > /dev/null 2>&1; then
useradd -r -s /bin/false -d /opt/rustfs rustfs
fi
mkdir -p /opt/rustfs /data/rustfs /var/log/rustfs
chown rustfs:rustfs /opt/rustfs /data/rustfs /var/log/rustfs
if [ -d /run/systemd/system ]; then
systemctl daemon-reload
fi
echo "RustFS installed. Configure /etc/default/rustfs then: systemctl start rustfs"
POSTINST
./scripts/release/package_service_scripts.sh after-install > "${PKG_DIR}/DEBIAN/postinst"
chmod 755 "${PKG_DIR}/DEBIAN/postinst"
cat > "${PKG_DIR}/DEBIAN/prerm" << 'PRERM'
#!/bin/bash
set -e
if [ -d /run/systemd/system ] && systemctl is-active --quiet rustfs; then
systemctl stop rustfs
fi
PRERM
./scripts/release/package_service_scripts.sh before-remove > "${PKG_DIR}/DEBIAN/prerm"
chmod 755 "${PKG_DIR}/DEBIAN/prerm"
cat > "${PKG_DIR}/DEBIAN/postrm" << 'POSTRM'
#!/bin/bash
set -e
if [ -d /run/systemd/system ]; then
systemctl daemon-reload
fi
POSTRM
./scripts/release/package_service_scripts.sh after-remove > "${PKG_DIR}/DEBIAN/postrm"
chmod 755 "${PKG_DIR}/DEBIAN/postrm"
cp LICENSE "${PKG_DIR}/usr/share/doc/rustfs/"
@@ -214,10 +195,15 @@ jobs:
mkdir -p ./tmp-pkg/etc/default
cat > ./tmp-pkg/etc/default/rustfs << 'ENVEOF'
# RustFS Environment Configuration
# See https://rustfs.com/docs/ for more information
# See https://docs.rustfs.com for more information
# RUSTFS_ACCESS_KEY=<changeme>
# RUSTFS_SECRET_KEY=<changeme>
# RUSTFS_VOLUMES=""
# RUSTFS_ROOT_USER=""
# RUSTFS_ROOT_PASSWORD=""
# RUSTFS_ADDRESS=":9000"
# RUSTFS_CONSOLE_ADDRESS=":9001"
# RUSTFS_CONSOLE_ENABLE=true
# RUSTFS_OBS_LOGGER_LEVEL=error
# RUSTFS_OBS_LOG_DIRECTORY="/var/log/rustfs/"
ENVEOF
fpm -s dir -t rpm \
@@ -231,35 +217,13 @@ jobs:
--description "High-performance distributed object storage" \
--url "https://rustfs.com" \
--license "Apache-2.0" \
--after-install <(cat << 'POSTINST'
#!/bin/bash
set -e
if ! getent passwd rustfs > /dev/null 2>&1; then
useradd -r -s /bin/false -d /opt/rustfs rustfs
fi
mkdir -p /opt/rustfs /data/rustfs /var/log/rustfs
chown rustfs:rustfs /opt/rustfs /data/rustfs /var/log/rustfs
if [ -d /run/systemd/system ]; then
systemctl daemon-reload
fi
POSTINST
) \
--before-remove <(cat << 'PRERM'
#!/bin/bash
set -e
if [ -d /run/systemd/system ] && systemctl is-active --quiet rustfs; then
systemctl stop rustfs
fi
PRERM
) \
--after-remove <(cat << 'POSTRM'
#!/bin/bash
set -e
if [ -d /run/systemd/system ]; then
systemctl daemon-reload
fi
POSTRM
) \
--before-install <(./scripts/release/package_service_scripts.sh rpm-before-install) \
--before-upgrade <(./scripts/release/package_service_scripts.sh rpm-before-upgrade) \
--after-install <(./scripts/release/package_service_scripts.sh after-install) \
--after-upgrade <(./scripts/release/package_service_scripts.sh after-install) \
--before-remove <(./scripts/release/package_service_scripts.sh before-remove) \
--after-remove <(./scripts/release/package_service_scripts.sh after-remove) \
--rpm-posttrans <(./scripts/release/package_service_scripts.sh rpm-posttrans) \
--config-files /etc/default/rustfs \
"rustfs-nightly-${DEB_DATE}/usr/bin/rustfs=/usr/bin/rustfs" \
./tmp-pkg/etc/default/rustfs=/etc/default/rustfs \
+26 -62
View File
@@ -328,10 +328,15 @@ jobs:
cat > "${PKG_DIR}/etc/default/rustfs" << 'ENVEOF'
# RustFS Environment Configuration
# See https://rustfs.com/docs/ for more information
# See https://docs.rustfs.com for more information
# RUSTFS_ACCESS_KEY=<changeme>
# RUSTFS_SECRET_KEY=<changeme>
# RUSTFS_VOLUMES=""
# RUSTFS_ROOT_USER=""
# RUSTFS_ROOT_PASSWORD=""
# RUSTFS_ADDRESS=":9000"
# RUSTFS_CONSOLE_ADDRESS=":9001"
# RUSTFS_CONSOLE_ENABLE=true
# RUSTFS_OBS_LOGGER_LEVEL=error
# RUSTFS_OBS_LOG_DIRECTORY="/var/log/rustfs/"
ENVEOF
cat > "${PKG_DIR}/DEBIAN/control" << EOF
@@ -354,37 +359,13 @@ jobs:
/etc/default/rustfs
CONFFILES
cat > "${PKG_DIR}/DEBIAN/postinst" << 'POSTINST'
#!/bin/bash
set -e
if ! getent passwd rustfs > /dev/null 2>&1; then
useradd -r -s /bin/false -d /opt/rustfs rustfs
fi
mkdir -p /opt/rustfs /data/rustfs /var/log/rustfs
chown rustfs:rustfs /opt/rustfs /data/rustfs /var/log/rustfs
if [ -d /run/systemd/system ]; then
systemctl daemon-reload
fi
echo "RustFS installed. Configure /etc/default/rustfs then: systemctl start rustfs"
POSTINST
./scripts/release/package_service_scripts.sh after-install > "${PKG_DIR}/DEBIAN/postinst"
chmod 755 "${PKG_DIR}/DEBIAN/postinst"
cat > "${PKG_DIR}/DEBIAN/prerm" << 'PRERM'
#!/bin/bash
set -e
if [ -d /run/systemd/system ] && systemctl is-active --quiet rustfs; then
systemctl stop rustfs
fi
PRERM
./scripts/release/package_service_scripts.sh before-remove > "${PKG_DIR}/DEBIAN/prerm"
chmod 755 "${PKG_DIR}/DEBIAN/prerm"
cat > "${PKG_DIR}/DEBIAN/postrm" << 'POSTRM'
#!/bin/bash
set -e
if [ -d /run/systemd/system ]; then
systemctl daemon-reload
fi
POSTRM
./scripts/release/package_service_scripts.sh after-remove > "${PKG_DIR}/DEBIAN/postrm"
chmod 755 "${PKG_DIR}/DEBIAN/postrm"
cp LICENSE "${PKG_DIR}/usr/share/doc/rustfs/"
@@ -422,10 +403,15 @@ jobs:
mkdir -p ./tmp-pkg/etc/default
cat > ./tmp-pkg/etc/default/rustfs << 'ENVEOF'
# RustFS Environment Configuration
# See https://rustfs.com/docs/ for more information
# See https://docs.rustfs.com for more information
# RUSTFS_ACCESS_KEY=<changeme>
# RUSTFS_SECRET_KEY=<changeme>
# RUSTFS_VOLUMES=""
# RUSTFS_ROOT_USER=""
# RUSTFS_ROOT_PASSWORD=""
# RUSTFS_ADDRESS=":9000"
# RUSTFS_CONSOLE_ADDRESS=":9001"
# RUSTFS_CONSOLE_ENABLE=true
# RUSTFS_OBS_LOGGER_LEVEL=error
# RUSTFS_OBS_LOG_DIRECTORY="/var/log/rustfs/"
ENVEOF
fpm -s dir -t rpm \
@@ -439,35 +425,13 @@ jobs:
--description "High-performance distributed object storage" \
--url "https://rustfs.com" \
--license "Apache-2.0" \
--after-install <(cat <<'POSTINST'
#!/bin/bash
set -e
if ! getent passwd rustfs > /dev/null 2>&1; then
useradd -r -s /bin/false -d /opt/rustfs rustfs
fi
mkdir -p /opt/rustfs /data/rustfs /var/log/rustfs
chown rustfs:rustfs /opt/rustfs /data/rustfs /var/log/rustfs
if [ -d /run/systemd/system ]; then
systemctl daemon-reload
fi
POSTINST
) \
--before-remove <(cat <<'PRERM'
#!/bin/bash
set -e
if [ -d /run/systemd/system ] && systemctl is-active --quiet rustfs; then
systemctl stop rustfs
fi
PRERM
) \
--after-remove <(cat <<'POSTRM'
#!/bin/bash
set -e
if [ -d /run/systemd/system ]; then
systemctl daemon-reload
fi
POSTRM
) \
--before-install <(./scripts/release/package_service_scripts.sh rpm-before-install) \
--before-upgrade <(./scripts/release/package_service_scripts.sh rpm-before-upgrade) \
--after-install <(./scripts/release/package_service_scripts.sh after-install) \
--after-upgrade <(./scripts/release/package_service_scripts.sh after-install) \
--before-remove <(./scripts/release/package_service_scripts.sh before-remove) \
--after-remove <(./scripts/release/package_service_scripts.sh after-remove) \
--rpm-posttrans <(./scripts/release/package_service_scripts.sh rpm-posttrans) \
--config-files /etc/default/rustfs \
./bin/rustfs=/usr/bin/rustfs \
./tmp-pkg/etc/default/rustfs=/etc/default/rustfs \
Generated
+26 -17
View File
@@ -1984,9 +1984,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.4.6"
version = "1.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3eb0f42d6c360dc3f8a821f6bf2fdea7f72bfd36b3076eb0e6d1e9e0752fff4"
checksum = "54413ede23c2daf518f35156dfde027feb2374004d63bd497f983c8db9c0e313"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -4410,9 +4410,9 @@ dependencies = [
[[package]]
name = "find-msvc-tools"
version = "0.1.12"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
checksum = "ef25905e51abafe4dcea6c15fec58c57b601cdbd0ee53d22ea1d3016c587d39b"
[[package]]
name = "findshlibs"
@@ -11258,9 +11258,8 @@ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "s3s"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87d22507a8ebd0ea64972c76dfedbc477418dc70afd98bfce928263894067d2f"
version = "0.16.0"
source = "git+https://github.com/s3s-project/s3s.git?rev=50c94aeb1e5ea9ef8e7393ef6bbc87b72f6891c6#50c94aeb1e5ea9ef8e7393ef6bbc87b72f6891c6"
dependencies = [
"arc-swap",
"arrayvec",
@@ -11278,7 +11277,6 @@ dependencies = [
"http 1.5.0",
"http-body 1.1.0",
"http-body-util",
"httparse",
"hyper",
"itoa",
"jiff",
@@ -11291,6 +11289,7 @@ dependencies = [
"pin-project-lite",
"quick-xml",
"regex",
"s3s-multipart",
"s3s-rfc2047",
"s3s-sigv2",
"s3s-sigv4",
@@ -11315,11 +11314,23 @@ dependencies = [
"zeroize",
]
[[package]]
name = "s3s-multipart"
version = "0.17.0-alpha.1"
source = "git+https://github.com/s3s-project/s3s.git?rev=50c94aeb1e5ea9ef8e7393ef6bbc87b72f6891c6#50c94aeb1e5ea9ef8e7393ef6bbc87b72f6891c6"
dependencies = [
"arrayvec",
"bytes",
"futures-core",
"httparse",
"memchr",
"thiserror 2.0.20",
]
[[package]]
name = "s3s-rfc2047"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9d3e6b36be7cca3da377b4cabcd668ffe5b16c3b73a83f7a8f7b63ff6a178ee"
version = "0.16.0"
source = "git+https://github.com/s3s-project/s3s.git?rev=50c94aeb1e5ea9ef8e7393ef6bbc87b72f6891c6#50c94aeb1e5ea9ef8e7393ef6bbc87b72f6891c6"
dependencies = [
"base64-simd",
"thiserror 2.0.20",
@@ -11327,9 +11338,8 @@ dependencies = [
[[package]]
name = "s3s-sigv2"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d96880b5ad8d639c9224155126741708aeb62ad3682e0476d2fd14290af808ef"
version = "0.16.0"
source = "git+https://github.com/s3s-project/s3s.git?rev=50c94aeb1e5ea9ef8e7393ef6bbc87b72f6891c6#50c94aeb1e5ea9ef8e7393ef6bbc87b72f6891c6"
dependencies = [
"base64-simd",
"hmac 0.13.0",
@@ -11341,9 +11351,8 @@ dependencies = [
[[package]]
name = "s3s-sigv4"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed605cbaaa1cf4d82e8ee15098fb3e42d49c92bb6215eb1359f63bfc0c6b020"
version = "0.16.0"
source = "git+https://github.com/s3s-project/s3s.git?rev=50c94aeb1e5ea9ef8e7393ef6bbc87b72f6891c6#50c94aeb1e5ea9ef8e7393ef6bbc87b72f6891c6"
dependencies = [
"arrayvec",
"base64-simd",
+2 -2
View File
@@ -313,8 +313,8 @@ rustify = { version = "0.7", default-features = false }
rustix = { version = "1.1.5" }
rust-embed = { version = "8.12.0" }
rustc-hash = { version = "2.1.3" }
s3s = { version = "0.16.1", features = ["minio"] }
s3s-sigv4 = { version = "0.16.1" }
s3s = { git = "https://github.com/s3s-project/s3s.git", rev = "50c94aeb1e5ea9ef8e7393ef6bbc87b72f6891c6", features = ["minio"] }
s3s-sigv4 = { git = "https://github.com/s3s-project/s3s.git", rev = "50c94aeb1e5ea9ef8e7393ef6bbc87b72f6891c6" }
serial_test = "4.0.1"
shadow-rs = { default-features = false, version = "2.0.0" }
siphasher = "1.0.3"
+151 -3
View File
@@ -2573,6 +2573,11 @@ impl Erasure {
.with_deferred_parity_reopeners(deferred_reopeners);
let integrity = reader.integrity.clone();
// Copy sources use demand-bound scheduling for backpressure, but retain
// the ordinary GET read quorum. Only the opt-in GET rollout requires a
// surplus source; available surplus shards and integrity proofs are
// still verified before any reconstructed copy bytes are emitted.
let require_surplus_source = reader.demand_bound_lockstep && matches!(decode_read_policy(), DecodeReadPolicy::Default);
let start = offset / self.block_size;
let end = end_offset.saturating_sub(1) / self.block_size;
@@ -2667,7 +2672,6 @@ impl Erasure {
// `shards` are borrowed again below. In the `Stop` case that
// drop is what cancels the still-in-flight prefetch read.
let (flow, next): (Option<StripeFlow>, Option<StripeReadOutput>) = {
let require_surplus_source = reader.demand_bound_lockstep;
let read_fut = read_stripe_timed(&mut reader, stage_metrics_enabled);
let emit_fut = self.emit_decoded_stripe(
writer,
@@ -2728,7 +2732,7 @@ impl Erasure {
&mut written,
&mut ret_err,
stage_metrics_enabled,
reader.demand_bound_lockstep,
require_surplus_source,
integrity.as_ref(),
offset,
)
@@ -2771,7 +2775,7 @@ impl Erasure {
&mut written,
&mut ret_err,
stage_metrics_enabled,
reader.demand_bound_lockstep,
require_surplus_source,
integrity.as_ref(),
offset,
)
@@ -3449,6 +3453,150 @@ mod tests {
}
}
async fn copy_source_bitrot_shards(erasure: &Erasure, payload: &[u8], corrupt_parity: bool) -> Vec<Vec<u8>> {
let mut writers: Vec<_> = (0..erasure.data_shards + erasure.parity_shards)
.map(|_| BitrotWriter::new(Cursor::new(Vec::new()), erasure.shard_size(), HashAlgorithm::HighwayHash256))
.collect();
for block in payload.chunks(erasure.block_size) {
let shards = erasure.encode_data(block).expect("copy source should encode");
for (index, shard) in shards.iter().enumerate() {
let mut bytes = shard.to_vec();
if corrupt_parity && index == erasure.data_shards {
// A stale source can have a valid per-shard bitrot checksum.
// Reframe the changed parity to exercise reconstruction verification.
bytes[0] ^= 0x80;
}
writers[index].write(&bytes).await.expect("copy source should frame");
}
}
writers.into_iter().map(|writer| writer.into_inner().into_inner()).collect()
}
#[tokio::test]
#[serial_test::serial]
async fn copy_source_reads_exact_quorum_across_stripes_and_ranges() {
const BLOCK_SIZE: usize = 768;
let payload: Vec<_> = (0..BLOCK_SIZE * 3 + 37)
.map(|index| (index.wrapping_mul(31) % 251) as u8)
.collect();
for uses_legacy in [false, true] {
let erasure = Erasure::new_with_options(12, 4, BLOCK_SIZE, uses_legacy);
let buffers = copy_source_bitrot_shards(&erasure, &payload, false).await;
for offline in [[0, 1, 2, 3], [0, 5, 12, 15]] {
for (offset, length) in [(0, payload.len()), (BLOCK_SIZE + 17, payload.len() - BLOCK_SIZE - 17)] {
let frame_offset = offset / BLOCK_SIZE * (erasure.shard_size() + HashAlgorithm::HighwayHash256.size());
let positioned: Vec<_> = buffers.iter().map(|bytes| bytes[frame_offset..].to_vec()).collect();
let (mut readers, handles) =
readers_with_deferred_parity(&positioned, 12, erasure.shard_size(), &HashAlgorithm::HighwayHash256, &[]);
for index in offline {
readers[index] = None;
}
let mut output = Vec::new();
let (written, error) = crate::set_disk::with_get_object_read_policy(
crate::set_disk::GetObjectReadPolicy::CopySource,
erasure.decode_with_stripe_handles(&mut output, readers, offset, length, payload.len(), None, handles),
)
.await;
assert!(error.is_none(), "legacy={uses_legacy}, offline={offline:?}, offset={offset}: {error:?}");
assert_eq!(written, length);
assert_eq!(output, payload[offset..offset + length]);
}
}
}
}
#[tokio::test]
#[serial_test::serial]
async fn copy_source_recovers_exact_quorum_after_first_stripe() {
const BLOCK_SIZE: usize = 768;
let payload: Vec<_> = (0..BLOCK_SIZE * 3 + 37)
.map(|index| (index.wrapping_mul(31) % 251) as u8)
.collect();
for uses_legacy in [false, true] {
let erasure = Erasure::new_with_options(12, 4, BLOCK_SIZE, uses_legacy);
let buffers = copy_source_bitrot_shards(&erasure, &payload, false).await;
let first_frame = erasure.shard_size() + HashAlgorithm::HighwayHash256.size();
let (readers, handles) = readers_with_deferred_parity(
&buffers,
12,
erasure.shard_size(),
&HashAlgorithm::HighwayHash256,
&[(0, first_frame), (1, first_frame), (2, first_frame), (3, first_frame)],
);
let mut output = Vec::new();
let (written, error) = crate::set_disk::with_get_object_read_policy(
crate::set_disk::GetObjectReadPolicy::CopySource,
erasure.decode_with_stripe_handles(&mut output, readers, 0, payload.len(), payload.len(), None, handles),
)
.await;
assert!(error.is_none(), "legacy={uses_legacy}: late shard loss should recover: {error:?}");
assert_eq!(written, payload.len());
assert_eq!(output, payload);
}
}
#[tokio::test]
#[serial_test::serial]
async fn copy_source_below_read_quorum_fails_before_output() {
const BLOCK_SIZE: usize = 768;
let payload = vec![0x5a; BLOCK_SIZE * 2 + 37];
for uses_legacy in [false, true] {
let erasure = Erasure::new_with_options(12, 4, BLOCK_SIZE, uses_legacy);
let buffers = copy_source_bitrot_shards(&erasure, &payload, false).await;
let (mut readers, handles) =
readers_with_deferred_parity(&buffers, 12, erasure.shard_size(), &HashAlgorithm::HighwayHash256, &[]);
for reader in readers.iter_mut().take(5) {
*reader = None;
}
let mut output = Vec::new();
let (written, error) = crate::set_disk::with_get_object_read_policy(
crate::set_disk::GetObjectReadPolicy::CopySource,
erasure.decode_with_stripe_handles(&mut output, readers, 0, payload.len(), payload.len(), None, handles),
)
.await;
assert_eq!(written, 0);
assert!(output.is_empty());
assert_eq!(
error
.expect("eleven shards cannot reconstruct twelve data shards")
.to_string(),
Error::ErasureReadQuorum.to_string()
);
}
}
#[tokio::test]
#[serial_test::serial]
async fn copy_source_rejects_corrupt_surplus_parity_before_output() {
const BLOCK_SIZE: usize = 768;
let payload = vec![0x5a; BLOCK_SIZE * 2 + 37];
for uses_legacy in [false, true] {
let erasure = Erasure::new_with_options(12, 4, BLOCK_SIZE, uses_legacy);
let buffers = copy_source_bitrot_shards(&erasure, &payload, true).await;
let (mut readers, handles) =
readers_with_deferred_parity(&buffers, 12, erasure.shard_size(), &HashAlgorithm::HighwayHash256, &[]);
for reader in readers.iter_mut().take(3) {
*reader = None;
}
let mut output = Vec::new();
let (written, error) = crate::set_disk::with_get_object_read_policy(
crate::set_disk::GetObjectReadPolicy::CopySource,
erasure.decode_with_stripe_handles(&mut output, readers, 0, payload.len(), payload.len(), None, handles),
)
.await;
assert_eq!(written, 0);
assert!(output.is_empty());
let error = error.expect("copy must reject inconsistent surplus parity despite valid bitrot framing");
assert_eq!(error.kind(), ErrorKind::InvalidData);
assert!(error.to_string().contains("inconsistent read source shards"));
}
}
/// Guards the shard-buffer reuse in `ParallelReader`: a multi-stripe
/// `Erasure::decode` that reconstructs missing data shards on every stripe
/// must still return byte-exact output. Reconstructed and parity buffers are
+1 -1
View File
@@ -39,7 +39,7 @@ allow-git = [
# Official s3s repository. Temporarily pinned to the merged generic REST
# SigV4 payload-checksum fix until it is available in a crates.io release.
# owner: marshawcoco review: 2026-10
# "https://github.com/s3s-project/s3s.git",
"https://github.com/s3s-project/s3s.git",
# RustFS fork carrying presigned expiry and constant-time authentication fixes.
# owner: rustfs-maintainers review: 2026-10
# "https://github.com/rustfs/s3s.git",
+1 -1
View File
@@ -108,7 +108,7 @@ Post-merge and tag-driven; not a substitute for a PR gate.
|---|---|---|
| Push to `main`, weekly schedule, dispatch | `build.yml` `build-rustfs` (a development build on a main push restricts the matrix to the Linux targets) | build artifacts; no release publication |
| Valid release or preview tag | `build.yml` `build-rustfs`, `create-release`, `upload-release-assets`, `publish-release` | draft release, checksummed assets, publish |
| Successful non-preview release-tag build (`workflow_run`) | `docker.yml` `build-docker`, `scan-docker-image` | multi-architecture images and vulnerability report |
| Successful non-preview release-tag build (`workflow_run`) | `docker.yml` `build-docker`, `scan-docker-image`, `sync-dockerhub-description` | multi-architecture images, vulnerability report, and the Docker Hub overview republished from `README.md` |
| Successful release-tag build (`workflow_run`) | `package.yml` `package` | DEB/RPM packages and checksums uploaded to the release |
| Successful non-preview release-tag build (`workflow_run`) | `helm-package.yml` `build-helm-package`, `publish-helm-package` | versioned chart and repository index |
| Final tag's release published | `build.yml` `cleanup-preview-releases` | deletes every `<target>-preview.<N>` Release for that target; the tags are kept |
Generated
+6 -6
View File
@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1789073787,
"narHash": "sha256-xfX/toC2QV707s06GbP4II/TxYF0fNQj7s5/LClNDKc=",
"lastModified": 1789632929,
"narHash": "sha256-RjR8AyvGlWuw16XRj7C1YDEw4E27ciuyPeB2nDMzgTU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "aff8a0b28396750446e5537a96461bc4facdb287",
"rev": "a32edd7654519351e48e80372a928df336394670",
"type": "github"
},
"original": {
@@ -29,11 +29,11 @@
]
},
"locked": {
"lastModified": 1789196581,
"narHash": "sha256-yJr1Bt4fKkKIpPYbsKGqJ0VFdoDnURgRwuffmBQ2WzY=",
"lastModified": 1789802016,
"narHash": "sha256-QVFfArbYaQMGK4dFV+5RUL72Ci+hNHVhaF/R9qJfDhs=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "228ecefb6329d5a531b77b46b581a2f0c26ee056",
"rev": "26a71e661c47bd21a05d06fec749f3f7c75e9d12",
"type": "github"
},
"original": {
+48 -10
View File
@@ -499,6 +499,14 @@ fn get_object_stream_failure_reason(error_class: &'static str) -> &'static str {
}
}
/// A mid-stream reopen can recover only when another representation of the
/// committed object may exist. A single-disk, no-parity object has no alternate
/// shard after a bitrot/read-quorum failure, so retrying the same read only
/// repeats the failure and adds latency.
fn should_attach_get_object_resume(info: &ObjectInfo) -> bool {
info.parity_blocks > 0 || info.data_blocks > 1 || !info.transitioned_object.tier.is_empty()
}
fn record_get_object_reader_stream_failure(
reason: &'static str,
error_class: &'static str,
@@ -4188,16 +4196,18 @@ impl DefaultObjectUsecase {
part_number,
lifecycle,
|info| {
Some(get_object_resume_control(GetObjectResumeContext::new(
store,
&bucket,
&key,
opts,
&req.headers,
info,
resume_range_start,
resume_range_end,
)))
should_attach_get_object_resume(info).then(|| {
get_object_resume_control(GetObjectResumeContext::new(
store,
&bucket,
&key,
opts,
&req.headers,
info,
resume_range_start,
resume_range_end,
))
})
},
)
.await;
@@ -8365,6 +8375,34 @@ mod tests {
assert_eq!(reopen_count.load(Ordering::Relaxed), 0);
}
#[test]
fn get_object_resume_only_attaches_for_recoverable_layouts() {
let single_disk = ObjectInfo {
data_blocks: 1,
parity_blocks: 0,
..Default::default()
};
assert!(!should_attach_get_object_resume(&single_disk));
let erasure = ObjectInfo {
data_blocks: 2,
parity_blocks: 1,
..Default::default()
};
assert!(should_attach_get_object_resume(&erasure));
let transitioned = ObjectInfo {
data_blocks: 1,
parity_blocks: 0,
transitioned_object: crate::storage_api::server::event::contract::lifecycle::TransitionedObject {
tier: "remote-tier".to_string(),
..Default::default()
},
..Default::default()
};
assert!(should_attach_get_object_resume(&transitioned));
}
#[test]
fn get_object_resume_identity_requires_same_version() {
let version_id = Uuid::from_u128(0x1234);
+1
View File
@@ -90,6 +90,7 @@ their issue closes.
| `test_build_rustfs_options.sh` | dev-tool | Shell test for rustfs build-option wiring | `make test` (script-tests) |
| `test_entrypoint_credentials.sh` | dev-tool | Container entrypoint credential-handling test | `make test` (script-tests) |
| `test_helm_chart_version.sh` | dev-tool | Test for `helm_chart_version.sh` | — |
| `test_package_service_scripts.sh` | ci-gate | Verifies DEB/RPM install, upgrade, removal, and service restart scriptlets | audit.yml `workflow-pin-report` |
| `test_package_versions.sh` | ci-gate | Exact-output and fail-closed tests for DEB/RPM package version normalization | audit.yml `workflow-pin-report`; package.yml RPM build |
| `windows-sftp-listener-smoke.sh` | dev-tool | Confirms `rustfs.exe --features sftp` binds an SFTP listener on Windows | — |
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env bash
set -euo pipefail
case ${1:-} in
after-install)
cat <<'SCRIPT'
#!/bin/sh
set -e
if ! getent passwd rustfs > /dev/null 2>&1; then
useradd -r -s /bin/false -d /opt/rustfs rustfs
fi
mkdir -p /opt/rustfs /data/rustfs /var/log/rustfs
chown rustfs:rustfs /opt/rustfs /data/rustfs /var/log/rustfs
if [ -d /run/systemd/system ]; then
systemctl daemon-reload
if [ "${1:-}" = configure ]; then
marker=/run/rustfs-package-upgrade-was-active
if [ -z "${2:-}" ]; then
rm -f "$marker"
else
# Versions published before this fix had no upgrade-aware prerm:
# the old prerm stopped the service and left no marker, so the
# marker check below cannot see it. Fall back to restarting for
# upgrades from those versions. NOTE: these are dpkg control-file
# versions - package_versions.sh maps the SemVer prerelease "-"
# to "~", so 1.0.0-rc.5 was published as 1.0.0~rc.5. Drop this list once
# upgrades from <= 1.0.0 no longer need support.
case $2 in
1.0.0|1.0.0~rc.[1-6]|1.0.0~rc.[1-6]~preview.*) legacy_upgrade=true ;;
*) legacy_upgrade=false ;;
esac
if [ -f "$marker" ] || [ "$legacy_upgrade" = true ]; then
rm -f "$marker"
if command -v deb-systemd-invoke > /dev/null 2>&1; then
deb-systemd-invoke restart rustfs.service
else
systemctl restart rustfs.service
fi
fi
fi
fi
fi
SCRIPT
;;
before-remove)
cat <<'SCRIPT'
#!/bin/sh
set -e
case ${1:-} in
upgrade)
marker=/run/rustfs-package-upgrade-was-active
rm -f "$marker"
if [ -d /run/systemd/system ] && systemctl is-active --quiet rustfs; then
touch "$marker"
fi
exit 0
;;
0|remove|deconfigure) ;;
*) exit 0 ;;
esac
if [ -d /run/systemd/system ] && systemctl is-active --quiet rustfs; then
systemctl stop rustfs
fi
SCRIPT
;;
after-remove)
cat <<'SCRIPT'
#!/bin/sh
set -e
if [ -d /run/systemd/system ]; then
systemctl daemon-reload
fi
if [ "${1:-}" != upgrade ]; then
rm -f /run/rustfs-package-upgrade-was-active
fi
SCRIPT
;;
rpm-before-install)
cat <<'SCRIPT'
#!/bin/sh
set -e
rm -f /run/rustfs-package-upgrade-was-active
SCRIPT
;;
rpm-before-upgrade)
cat <<'SCRIPT'
#!/bin/sh
set -e
marker=/run/rustfs-package-upgrade-was-active
rm -f "$marker"
if [ -d /run/systemd/system ] && systemctl is-active --quiet rustfs; then
touch "$marker"
fi
SCRIPT
;;
rpm-posttrans)
cat <<'SCRIPT'
#!/bin/sh
set -e
marker=/run/rustfs-package-upgrade-was-active
if [ -f "$marker" ]; then
rm -f "$marker"
systemctl restart rustfs
fi
SCRIPT
;;
*)
echo "usage: $0 {after-install|before-remove|after-remove|rpm-before-install|rpm-before-upgrade|rpm-posttrans}" >&2
exit 2
;;
esac
+20
View File
@@ -5,6 +5,7 @@ from __future__ import annotations
import hashlib
import io
import json
from datetime import datetime, timezone
import os
from pathlib import Path
import re
@@ -112,6 +113,25 @@ def prepare():
candidate = resolve(run_id, attempt)
revision = (ROOT / ".config/functional-script-revision.txt").read_text().strip()
require(sha(revision), "private test script revision must be pinned")
# The revision file is refreshed only by release syncs and goes stale
# within days, so the nightly chain keeps replaying an old harness while
# fixes (conffile prompts, FT --results-file, tier retries, STS-105
# signing) sit untested on auto-testing main. When the pinned revision is
# older than a day, fall back to auto-testing main HEAD. Any probe
# failure keeps the pinned revision - the chain still runs, just on the
# old harness.
try:
head = api("repos/rustfs/auto-testing/git/ref/heads/main")["object"]["sha"]
pinned_date = api(f"repos/rustfs/auto-testing/commits/{revision}")["commit"]["committer"]["date"]
pinned_at = datetime.fromisoformat(pinned_date.replace("Z", "+00:00"))
age_hours = (datetime.now(timezone.utc) - pinned_at).total_seconds() / 3600
if age_hours > 24:
print(f"functional-script revision {revision[:8]} is {age_hours:.0f}h old; falling back to auto-testing main HEAD {head[:8]}")
revision = head
else:
print(f"functional-script revision {revision[:8]} is {age_hours:.0f}h old; keeping pin")
except Exception as error:
print(f"staleness probe failed ({error}); keeping pinned revision {revision[:8]}")
chain = {"schema": 1, "run_id": int(os.environ["GITHUB_RUN_ID"]), "attempt": int(os.environ["GITHUB_RUN_ATTEMPT"]),
"workflow_sha": os.environ["GITHUB_SHA"], "testing_sha": revision, "candidate": candidate}
encoded = json.dumps(chain, sort_keys=True, separators=(",", ":"))
+116
View File
@@ -0,0 +1,116 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
generator="${repo_root}/scripts/release/package_service_scripts.sh"
test_tmp=$(mktemp -d)
trap 'rm -rf "$test_tmp"' EXIT
mkdir -p "${test_tmp}/bin"
mkdir -p "${test_tmp}/run/systemd/system"
cat > "${test_tmp}/bin/getent" <<'SCRIPT'
#!/bin/sh
exit 0
SCRIPT
cat > "${test_tmp}/bin/mkdir" <<'SCRIPT'
#!/bin/sh
exit 0
SCRIPT
cat > "${test_tmp}/bin/chown" <<'SCRIPT'
#!/bin/sh
exit 0
SCRIPT
cat > "${test_tmp}/bin/systemctl" <<'SCRIPT'
#!/bin/sh
printf 'systemctl %s\n' "$*" >> "$PACKAGE_SCRIPT_LOG"
case "$1" in
is-active) exit "${SYSTEMCTL_ACTIVE_EXIT:-0}" ;;
esac
SCRIPT
cat > "${test_tmp}/bin/deb-systemd-invoke" <<'SCRIPT'
#!/bin/sh
printf 'deb-systemd-invoke %s\n' "$*" >> "$PACKAGE_SCRIPT_LOG"
SCRIPT
chmod +x "${test_tmp}/bin/"*
export PATH="${test_tmp}/bin:${PATH}"
export PACKAGE_SCRIPT_LOG="${test_tmp}/calls"
run_script() {
local name=$1
shift
"$generator" "$name" |
sed \
-e "s#/run/systemd/system#${test_tmp}/run/systemd/system#g" \
-e "s#/run/rustfs-package-upgrade-was-active#${test_tmp}/run/rustfs-package-upgrade-was-active#g" \
> "${test_tmp}/${name}"
chmod +x "${test_tmp}/${name}"
"${test_tmp}/${name}" "$@"
}
assert_log() {
local expected=$1
printf '%s\n' "$expected" > "${test_tmp}/expected"
diff -u "${test_tmp}/expected" "$PACKAGE_SCRIPT_LOG"
}
: > "$PACKAGE_SCRIPT_LOG"
run_script before-remove upgrade 1.0.0
assert_log 'systemctl is-active --quiet rustfs'
marker="${test_tmp}/run/rustfs-package-upgrade-was-active"
[[ -f $marker ]]
: > "$PACKAGE_SCRIPT_LOG"
run_script after-remove upgrade 1.0.1
[[ -f $marker ]]
run_script after-install configure 1.0.1
[[ ! -e $marker ]]
assert_log $'systemctl daemon-reload\nsystemctl daemon-reload\ndeb-systemd-invoke restart rustfs.service'
: > "$PACKAGE_SCRIPT_LOG"
touch "$marker"
SYSTEMCTL_ACTIVE_EXIT=1 run_script before-remove upgrade 1.0.1
[[ ! -e $marker ]]
run_script after-remove upgrade 1.0.2
run_script after-install configure 1.0.1
assert_log $'systemctl is-active --quiet rustfs\nsystemctl daemon-reload\nsystemctl daemon-reload'
: > "$PACKAGE_SCRIPT_LOG"
run_script after-install configure 1.0.0~rc.5
assert_log $'systemctl daemon-reload\ndeb-systemd-invoke restart rustfs.service'
: > "$PACKAGE_SCRIPT_LOG"
run_script after-install configure 0~dev.9000.0123456789abcdef
assert_log 'systemctl daemon-reload'
: > "$PACKAGE_SCRIPT_LOG"
run_script before-remove remove
assert_log $'systemctl is-active --quiet rustfs\nsystemctl stop rustfs'
: > "$PACKAGE_SCRIPT_LOG"
touch "$marker"
run_script after-remove remove
[[ ! -e $marker ]]
assert_log 'systemctl daemon-reload'
: > "$PACKAGE_SCRIPT_LOG"
touch "$marker"
run_script after-install configure
[[ ! -e $marker ]]
assert_log 'systemctl daemon-reload'
: > "$PACKAGE_SCRIPT_LOG"
run_script rpm-before-upgrade
[[ -f $marker ]]
run_script rpm-posttrans
[[ ! -e $marker ]]
assert_log $'systemctl is-active --quiet rustfs\nsystemctl restart rustfs'
: > "$PACKAGE_SCRIPT_LOG"
SYSTEMCTL_ACTIVE_EXIT=1 run_script rpm-before-upgrade
[[ ! -e $marker ]]
run_script rpm-posttrans
assert_log 'systemctl is-active --quiet rustfs'
echo "PASS package service script contract"