From 4ce7d06ae9c39c30a9ef6635230358c059e61c44 Mon Sep 17 00:00:00 2001 From: Zoorpha Date: Thu, 24 Sep 2026 21:40:12 +0200 Subject: [PATCH 1/8] fix: replace MinIO Compose storage with RustFS Signed-off-by: Zoorpha --- .github/workflows/_ci-relay.yml | 10 ++-- .github/workflows/mesh-lifecycle.yml | 2 +- ARCHITECTURE.md | 2 +- CONTRIBUTING.md | 4 +- Justfile | 2 +- README.md | 4 +- VISION.md | 2 +- crates/buzz-media/src/config.rs | 8 +-- crates/buzz-media/src/storage.rs | 4 +- crates/buzz-media/tests/static_creds_minio.rs | 8 +-- crates/buzz-relay/src/api/git/store.rs | 2 +- crates/buzz-relay/src/config.rs | 4 +- crates/buzz-relay/src/state.rs | 2 +- crates/buzz-test-client/tests/e2e_git.rs | 10 ++-- crates/buzz-test-client/tests/e2e_media.rs | 2 +- .../buzz-test-client/tests/e2e_media_video.rs | 2 +- deploy/compose/README.md | 8 +-- deploy/compose/compose.dev.yml | 2 +- deploy/compose/compose.yml | 48 +++++++++-------- deploy/compose/run.sh | 2 +- docker-compose.ci.yml | 13 +++-- docker-compose.harness.yml | 44 ++++++++------- docker-compose.yml | 54 +++++++++++-------- docs/admin/README.md | 2 +- scripts/dev-reset.sh | 2 +- scripts/e2e-git-perms.sh | 2 +- scripts/seed-admin-dashboard.sh | 23 +++++--- scripts/start-isolated-test-relay.sh | 6 +-- scripts/start-relay-for-tests.sh | 4 +- 29 files changed, 154 insertions(+), 124 deletions(-) diff --git a/.github/workflows/_ci-relay.yml b/.github/workflows/_ci-relay.yml index b616b8e7f35..bd371a5289b 100644 --- a/.github/workflows/_ci-relay.yml +++ b/.github/workflows/_ci-relay.yml @@ -225,7 +225,7 @@ jobs: - name: Start integration services run: | for attempt in 1 2 3; do - if docker compose up -d postgres redis minio minio-init; then + if docker compose up -d postgres redis rustfs rustfs-init; then break fi if [ "$attempt" -eq 3 ]; then @@ -286,7 +286,7 @@ jobs: } wait_healthy "Postgres" "buzz-postgres" wait_healthy "Redis" "buzz-redis" - wait_healthy "MinIO" "buzz-minio" + wait_healthy "RustFS" "buzz-rustfs" - name: Download relay binary uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -420,7 +420,7 @@ jobs: - name: Start integration services run: | for attempt in 1 2 3; do - if docker compose up -d postgres redis minio minio-init; then + if docker compose up -d postgres redis rustfs rustfs-init; then break fi if [ "$attempt" -eq 3 ]; then @@ -448,7 +448,7 @@ jobs: } wait_healthy "Postgres" "buzz-postgres" wait_healthy "Redis" "buzz-redis" - wait_healthy "MinIO" "buzz-minio" + wait_healthy "RustFS" "buzz-rustfs" - name: Download relay artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -604,7 +604,7 @@ jobs: # Reads require kind:24242 `t=get` auth, so these binaries are the only # coverage that a real relay rejects bare reads and honours host- and # hash-scoped tokens. They were #[ignore]d and selected by no CI job, so - # the lane never ran; select it here, where MinIO and the seeded + # the lane never ran; select it here, where RustFS and the seeded # 'localhost:3000' community already exist. # --no-fail-fast: without it cargo stops after the first failing binary, # so one broken case hides every later binary's result. diff --git a/.github/workflows/mesh-lifecycle.yml b/.github/workflows/mesh-lifecycle.yml index 0666e98c327..ee16f1142dd 100644 --- a/.github/workflows/mesh-lifecycle.yml +++ b/.github/workflows/mesh-lifecycle.yml @@ -83,7 +83,7 @@ jobs: - name: Start integration services run: | for attempt in 1 2 3; do - if docker compose up -d postgres redis minio minio-init; then + if docker compose up -d postgres redis rustfs rustfs-init; then break fi if [ "$attempt" -eq 3 ]; then diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index ae82c131ec7..46fed20c87a 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -802,7 +802,7 @@ Docker Compose provides the full local development stack. All services include h | Postgres | `postgres:17-alpine` | 5432 | Primary event store — events, channels, tokens, workflows, audit; full-text search (`search_tsv` GIN) | | Redis | `redis:7-alpine` | 6379 | Pub/sub fan-out, presence (SET EX), typing (sorted sets) | | Adminer | `adminer` | 8082 | DB web UI (dev only) | -| MinIO | `quay.io/minio/minio` | 9000 (API), 9001 (console) | S3-compatible object storage (media) | +| RustFS | `rustfs/rustfs` | 9000 (API), 9001 (console) | S3-compatible object storage (media) | | Prometheus | `prom/prometheus` | 9090 | Metrics collection | ### Postgres Schema (key tables) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dbe4ba5dd2b..88f7dc08f17 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -84,7 +84,7 @@ We review as capacity allows — focused PRs that follow this guide move fastest | Node.js | 24+ | Required for desktop app commands and `just ci` | | pnpm | 10+ | Required for desktop app commands and `just ci` | | Flutter | 3.41+ | Required for mobile app — install via [flutter.dev](https://docs.flutter.dev/get-started/install) | -| Docker | 24+ | For Postgres, Redis, MinIO | +| Docker | 24+ | For Postgres, Redis, RustFS | | `just` | latest | Task runner — `cargo install just` | | `lefthook` | 2.1.3 (Hermit-pinned) | Auto-installed by `just hooks` — no manual install needed | | `sqlx` migrations | workspace crate | `just migrate` applies embedded migrations from `migrations/` | @@ -157,7 +157,7 @@ cached thereafter). You can also run `just bootstrap` independently at any time; it is safe to re-run. `just setup` then starts Docker services (Postgres on `:5432`, Redis on `:6379`, -Adminer on `:8082`, Keycloak on `:8180` for local OAuth/OIDC testing, MinIO on +Adminer on `:8082`, Keycloak on `:8180` for local OAuth/OIDC testing, RustFS on `:9000` for media storage, and Prometheus on `:9090` for metrics) and runs all pending database migrations. diff --git a/Justfile b/Justfile index c53ebb70e73..113d147a54c 100644 --- a/Justfile +++ b/Justfile @@ -642,7 +642,7 @@ dev *ARGS: bootstrap _ensure-sidecar-stubs _ensure-migrations done fi cargo build -p buzz-acp -p buzz-agent -p buzz-backend-kubernetes -p buzz-dev-mcp -p buzz-cli -p git-credential-nostr -p buzz-relay - # Docker Desktop's forwarded MinIO port can stall under the deployment + # Docker Desktop's forwarded RustFS port can stall under the deployment # probe's 32 concurrent writers. Keep the gate enabled in local dev, using # the bounded profile already used by the relay test launcher. export BUZZ_GIT_PROBE_WRITERS="${BUZZ_GIT_PROBE_WRITERS:-8}" diff --git a/README.md b/README.md index 56439f00bc1..a37b9f455ab 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ Relay on `ws://localhost:3000`. Desktop app pops up. You're in. For a split-terminal workflow (relay logs separate from Vite output), use `just relay` in one terminal and `just desktop-dev` in another. -Want a single-node / VPS relay instead of the local-dev stack? Use the production Compose bundle in [`deploy/compose/`](deploy/compose/README.md) (`docker compose` + Postgres, Redis, MinIO, optional Caddy/TLS). The root [`docker-compose.yml`](docker-compose.yml) is for day-to-day development only. +Want a single-node / VPS relay instead of the local-dev stack? Use the production Compose bundle in [`deploy/compose/`](deploy/compose/README.md) (`docker compose` + Postgres, Redis, RustFS, optional Caddy/TLS). The root [`docker-compose.yml`](docker-compose.yml) is for day-to-day development only. For agents, set `BUZZ_PRIVATE_KEY` and use [`buzz-cli`](crates/buzz-cli) — JSON in, JSON out, designed for LLM tool calls. @@ -212,7 +212,7 @@ If you'd rather point buzz at a different bash-compatible shell, set `BUZZ_SHELL └───┬──────────────────────────┬──────────────────────────┬───────────────┘ │ │ │ ┌──▼───────────┐ ┌──────▼──────┐ ┌───────▼─────┐ - │ Postgres │ │ Redis │ │ S3/MinIO │ + │ Postgres │ │ Redis │ │ S3/RustFS │ │ (events + │ │ (pub/sub) │ │ (Blossom) │ │ FTS search) │ └─────────────┘ └─────────────┘ └──────────────┘ diff --git a/VISION.md b/VISION.md index 900e5a9475b..286e826b8aa 100644 --- a/VISION.md +++ b/VISION.md @@ -141,7 +141,7 @@ See [VISION_ACTIVITY.md](VISION_ACTIVITY.md) for the agent activity feed in dept Beyond chat: channels are workspaces. - **Canvases** — a shared document per channel. Read and write via the desktop or MCP tools. -- **Media uploads** — paste, drop, or attach files. Stored via the [Blossom](https://github.com/hzrd149/blossom) protocol (BUD-01/BUD-02) on S3/MinIO. Thumbnails generated server-side. +- **Media uploads** — paste, drop, or attach files. Stored via the [Blossom](https://github.com/hzrd149/blossom) protocol (BUD-01/BUD-02) on S3/RustFS. Thumbnails generated server-side. - **Message editing and deletion** — with confirmation. Soft-deleted events remain in the audit log. - **Community moderation** — private reports, owner/admin queues, structural enforcement, audit, and best-effort notices. See [VISION_MODERATION.md](VISION_MODERATION.md) for the full governance model. - **Typing indicators** — real-time. Agents broadcast them too. diff --git a/crates/buzz-media/src/config.rs b/crates/buzz-media/src/config.rs index 3c70e4afe19..60941a142e4 100644 --- a/crates/buzz-media/src/config.rs +++ b/crates/buzz-media/src/config.rs @@ -8,8 +8,8 @@ use std::str::FromStr; pub enum S3AddressingStyle { /// Put the bucket in the request path (`https://endpoint/bucket/key`). /// - /// This preserves compatibility with the bundled MinIO deployments, whose - /// internal DNS only resolves the endpoint hostname. + /// This preserves compatibility with bundled S3-compatible deployments, + /// whose internal DNS only resolves the endpoint hostname. #[default] Path, /// Put the bucket in the hostname (`https://bucket.endpoint/key`). @@ -45,7 +45,7 @@ fn default_s3_region() -> String { "us-east-1".to_string() } -/// Configuration for media storage (S3/MinIO). +/// Configuration for media storage (S3-compatible). #[derive(Debug, Clone, serde::Deserialize)] pub struct MediaConfig { /// S3-compatible endpoint URL (e.g. "http://localhost:9000"). @@ -64,7 +64,7 @@ pub struct MediaConfig { /// the value is not meaningfully checked. #[serde(default = "default_s3_region")] pub s3_region: String, - /// S3 URL addressing style. Defaults to path style for MinIO compatibility. + /// S3 URL addressing style. Defaults to path style for local compatibility. #[serde(default)] pub s3_addressing_style: S3AddressingStyle, /// Maximum upload size for images (bytes). Default: 50 MB. diff --git a/crates/buzz-media/src/storage.rs b/crates/buzz-media/src/storage.rs index abe6bdd40ea..95374582ca5 100644 --- a/crates/buzz-media/src/storage.rs +++ b/crates/buzz-media/src/storage.rs @@ -1,4 +1,4 @@ -//! S3/MinIO storage client. +//! S3-compatible storage client. use std::collections::HashMap; use std::path::Path; @@ -211,7 +211,7 @@ impl MediaStorage { /// /// Credential selection: /// - If both `s3_access_key` and `s3_secret_key` are non-empty, use them as - /// static credentials (MinIO/local/dev, or any static-key deployment). + /// static credentials (local/dev, or any static-key deployment). /// - Otherwise, fall back to the AWS default credential chain via /// [`Credentials::default`]: environment, shared profile, web-identity /// token (IRSA on EKS — `AssumeRoleWithWebIdentity`), container, and diff --git a/crates/buzz-media/tests/static_creds_minio.rs b/crates/buzz-media/tests/static_creds_minio.rs index 4c8c10702cb..9cdd04ab75a 100644 --- a/crates/buzz-media/tests/static_creds_minio.rs +++ b/crates/buzz-media/tests/static_creds_minio.rs @@ -6,17 +6,17 @@ //! exists to prove that adding the IRSA/credential-chain fallback did **not** //! regress hardcoded credentials. //! -//! Run it against the docker-compose MinIO (creds `buzz_dev`/`buzz_dev_secret`, +//! Run it against the docker-compose RustFS (creds `buzz_dev`/`buzz_dev_secret`, //! bucket `buzz-media`, endpoint `http://localhost:9000`): //! //! ```bash -//! docker compose up -d minio minio-init +//! docker compose up -d rustfs rustfs-init //! cargo test -p buzz-media --test static_creds_minio -- --ignored //! ``` //! //! Overridable via `BUZZ_S3_ENDPOINT` / `BUZZ_S3_ACCESS_KEY` / //! `BUZZ_S3_SECRET_KEY` / `BUZZ_S3_BUCKET` / `BUZZ_S3_REGION` / -//! `BUZZ_S3_ADDRESSING_STYLE`. The default remains `path` for MinIO. +//! `BUZZ_S3_ADDRESSING_STYLE`. The default remains `path` for RustFS. use buzz_media::config::MediaConfig; use buzz_media::storage::MediaStorage; @@ -47,7 +47,7 @@ fn minio_config() -> MediaConfig { } #[tokio::test] -#[ignore = "requires a live MinIO (docker compose up -d minio minio-init)"] +#[ignore = "requires a live RustFS (docker compose up -d rustfs rustfs-init)"] async fn static_creds_round_trip_against_minio() { let storage = MediaStorage::new(&minio_config()).expect("static creds should build a storage client"); diff --git a/crates/buzz-relay/src/api/git/store.rs b/crates/buzz-relay/src/api/git/store.rs index bdfca8dcf2d..d6a1b106e4f 100644 --- a/crates/buzz-relay/src/api/git/store.rs +++ b/crates/buzz-relay/src/api/git/store.rs @@ -1024,7 +1024,7 @@ mod probe { //! BUZZ_GIT_S3_PROBE=1 cargo test -p buzz-relay --lib \ //! api::git::store::probe -- --nocapture --test-threads=1 //! - //! Pre-req: `docker compose up minio` and the `buzz-git` bucket exists. + //! Pre-req: `docker compose up rustfs` and the `buzz-git` bucket exists. use super::*; diff --git a/crates/buzz-relay/src/config.rs b/crates/buzz-relay/src/config.rs index 5d831b3f651..f2517314c1c 100644 --- a/crates/buzz-relay/src/config.rs +++ b/crates/buzz-relay/src/config.rs @@ -290,7 +290,7 @@ pub struct Config { /// and its proxy routes return 404. pub klipy: Option, - /// Media storage configuration (S3/MinIO). + /// Media storage configuration (S3-compatible). pub media: buzz_media::MediaConfig, /// Maximum concurrent media uploads handled by one relay process. pub media_max_concurrent_uploads: usize, @@ -1382,7 +1382,7 @@ mod tests { assert_eq!( config.media.s3_addressing_style, buzz_media::config::S3AddressingStyle::Path, - "S3 addressing must default to path style for bundled MinIO compatibility" + "S3 addressing must default to path style for bundled S3 compatibility" ); assert!( config.join_policy.is_none(), diff --git a/crates/buzz-relay/src/state.rs b/crates/buzz-relay/src/state.rs index cbfe7b7b304..69f658af1ba 100644 --- a/crates/buzz-relay/src/state.rs +++ b/crates/buzz-relay/src/state.rs @@ -701,7 +701,7 @@ pub struct AppState { /// Bounded channel for audit logging, absent when audit logging is disabled. pub audit_tx: Option>, - /// Media storage client (S3/MinIO). + /// Media storage client (S3-compatible). pub media_storage: Arc, /// Cached worker snapshot and storage metric emission bookkeeping. See /// `storage_sweep` module docs; shared with the usage-metrics tick via diff --git a/crates/buzz-test-client/tests/e2e_git.rs b/crates/buzz-test-client/tests/e2e_git.rs index 3c82e317649..feb90b11359 100644 --- a/crates/buzz-test-client/tests/e2e_git.rs +++ b/crates/buzz-test-client/tests/e2e_git.rs @@ -2,10 +2,10 @@ //! //! Drives the real `git` binary (clone / push / fetch / force-push / tag, //! plus a best-effort concurrent push race) against a running relay backed by -//! S3/MinIO, exercising the full manifest-pointer CAS commit path described in +//! S3/RustFS, exercising the full manifest-pointer CAS commit path described in //! `docs/git-on-object-storage.md`. //! -//! Requires: relay at localhost:3000 with git + S3/MinIO configured, `git` on +//! Requires: relay at localhost:3000 with git + S3/RustFS configured, `git` on //! PATH, and the `git-credential-nostr` helper built. All tests are `#[ignore]` //! so they don't run in CI by default. //! @@ -161,7 +161,7 @@ impl GitS3Probe { fn from_env() -> Self { // These E2E assertions inspect the relay's backing bucket directly, so // they must receive the same provider connection and URL style as the - // relay. Unit/live MinIO probes in buzz-relay keep explicit local + // relay. Unit/live RustFS probes in buzz-relay keep explicit local // fixtures and do not need provider overrides. let endpoint = std::env::var("BUZZ_S3_ENDPOINT") .unwrap_or_else(|_| "http://localhost:9000".to_string()); @@ -266,7 +266,7 @@ fn git_s3_probe_builds_both_addressing_styles() { } #[tokio::test] -#[ignore = "requires live relay + MinIO + git"] +#[ignore = "requires live relay + RustFS + git"] async fn git_clone_push_fetch_force_roundtrip() { use nostr::ToBech32; @@ -409,7 +409,7 @@ async fn git_clone_push_fetch_force_roundtrip() { } #[tokio::test] -#[ignore = "requires live relay + MinIO + git"] +#[ignore = "requires live relay + RustFS + git"] async fn git_concurrent_push_one_wins_and_repo_recovers() { use nostr::ToBech32; diff --git a/crates/buzz-test-client/tests/e2e_media.rs b/crates/buzz-test-client/tests/e2e_media.rs index 690fd9c8a50..918eb654c65 100644 --- a/crates/buzz-test-client/tests/e2e_media.rs +++ b/crates/buzz-test-client/tests/e2e_media.rs @@ -1,6 +1,6 @@ //! End-to-end media upload tests (Blossom / NIP-96 style). //! -//! Requires: relay running at localhost:3000, MinIO running at localhost:9000. +//! Requires: relay running at localhost:3000, RustFS running at localhost:9000. //! All tests are `#[ignore]` so they don't run in CI by default. //! //! # Running diff --git a/crates/buzz-test-client/tests/e2e_media_video.rs b/crates/buzz-test-client/tests/e2e_media_video.rs index 2ec0b1e6986..25719115365 100644 --- a/crates/buzz-test-client/tests/e2e_media_video.rs +++ b/crates/buzz-test-client/tests/e2e_media_video.rs @@ -1,6 +1,6 @@ //! End-to-end video upload tests (Blossom protocol, MP4/H.264). //! -//! Requires: relay running at localhost:3000, MinIO running at localhost:9000. +//! Requires: relay running at localhost:3000, RustFS running at localhost:9000. //! All tests are `#[ignore]` so they don't run in CI by default. //! //! # Running diff --git a/deploy/compose/README.md b/deploy/compose/README.md index b12f9ff48c6..d97e023389c 100644 --- a/deploy/compose/README.md +++ b/deploy/compose/README.md @@ -36,15 +36,15 @@ keypair. `buzz-admin migrate` before starting the relay when bootstrapping a fresh database. Auto-migration requires an image that includes embedded SQLx migrations. -- The stack uses Postgres, Redis, MinIO, and a git data volume because +- The stack uses Postgres, Redis, RustFS, and a git data volume because those are real Buzz dependencies today. Minimal mode can simplify this later. - Mobile push remains off by default. To use the public gateway, keep the template's explicit `BUZZ_PUSH_GATEWAY_DELIVERY_URL` and set `BUZZ_PUSH_ENABLED=true`. To use another gateway, replace the exact HTTPS `/v1/deliveries/apns` URL before enabling push. -- The bundled Compose stack fixes the relay endpoint to `http://minio:9000` and - `BUZZ_S3_ADDRESSING_STYLE=path`: Docker DNS resolves `minio`, not - `.minio`. It is not configurable for an external S3 provider through +- The bundled Compose stack fixes the relay endpoint to `http://rustfs:9000` and + `BUZZ_S3_ADDRESSING_STYLE=path`: Docker DNS resolves `rustfs`, not + `.rustfs`. It is not configurable for an external S3 provider through `.env`; use the Helm chart or a custom Compose configuration for providers such as new Railway Storage Buckets that require `virtual` addressing. diff --git a/deploy/compose/compose.dev.yml b/deploy/compose/compose.dev.yml index 00ce14c1f34..bb8a28c4218 100644 --- a/deploy/compose/compose.dev.yml +++ b/deploy/compose/compose.dev.yml @@ -7,7 +7,7 @@ services: ports: - "${REDIS_PORT:-6379}:6379" - minio: + rustfs: ports: - "${MINIO_API_PORT:-9000}:9000" - "${MINIO_CONSOLE_PORT:-9001}:9001" diff --git a/deploy/compose/compose.yml b/deploy/compose/compose.yml index bcdad7296c8..a4bf4c57ca0 100644 --- a/deploy/compose/compose.yml +++ b/deploy/compose/compose.yml @@ -11,8 +11,8 @@ services: BUZZ_METRICS_PORT: "9102" DATABASE_URL: postgres://${POSTGRES_USER:-buzz}:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-buzz} REDIS_URL: redis://:${REDIS_PASSWORD:?set REDIS_PASSWORD}@redis:6379 - BUZZ_S3_ENDPOINT: http://minio:9000 - # Docker DNS resolves `minio`, not arbitrary `.minio` hosts. + BUZZ_S3_ENDPOINT: http://rustfs:9000 + # Docker DNS resolves `rustfs`, not arbitrary `.rustfs` hosts. BUZZ_S3_ADDRESSING_STYLE: path BUZZ_S3_ACCESS_KEY: ${BUZZ_S3_ACCESS_KEY:?set BUZZ_S3_ACCESS_KEY} BUZZ_S3_SECRET_KEY: ${BUZZ_S3_SECRET_KEY:?set BUZZ_S3_SECRET_KEY} @@ -29,9 +29,9 @@ services: condition: service_healthy redis: condition: service_healthy - minio: + rustfs: condition: service_healthy - minio-init: + rustfs-init: condition: service_completed_successfully # Probe /_readiness over /dev/tcp because the runtime image has bash but no curl/wget/socat. healthcheck: @@ -84,16 +84,18 @@ services: networks: - buzz-net - minio: - image: quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z@sha256:14cea493d9a34af32f524e538b8346cf79f3321eff8e708c1e2960462bd8936e - command: server /data --console-address ":9001" + rustfs: + image: rustfs/rustfs@sha256:8cc9801755448b71a786705ce76692c77e14936cccd87cf2fc31842e58f4d1ff # 1.0.0 environment: - MINIO_ROOT_USER: ${BUZZ_S3_ACCESS_KEY:?set BUZZ_S3_ACCESS_KEY} - MINIO_ROOT_PASSWORD: ${BUZZ_S3_SECRET_KEY:?set BUZZ_S3_SECRET_KEY} + RUSTFS_ADDRESS: 0.0.0.0:9000 + RUSTFS_CONSOLE_ADDRESS: 0.0.0.0:9001 + RUSTFS_VOLUMES: /data + RUSTFS_ACCESS_KEY: ${BUZZ_S3_ACCESS_KEY:?set BUZZ_S3_ACCESS_KEY} + RUSTFS_SECRET_KEY: ${BUZZ_S3_SECRET_KEY:?set BUZZ_S3_SECRET_KEY} volumes: - - buzz-minio-data:/data + - buzz-rustfs-data:/data healthcheck: - test: ["CMD", "curl", "-f", "http://127.0.0.1:9000/minio/health/live"] + test: ["CMD", "curl", "-f", "http://127.0.0.1:9000/health"] interval: 5s timeout: 5s retries: 12 @@ -102,21 +104,23 @@ services: networks: - buzz-net - minio-init: - image: quay.io/minio/mc:RELEASE.2025-08-13T08-35-41Z@sha256:a7fe349ef4bd8521fb8497f55c6042871b2ae640607cf99d9bede5e9bdf11727 + rustfs-init: + image: rustfs/rc@sha256:ab024bfebee49a750ce886b4c70963ccd9ddaa03f491704a90710641d7a26699 # v0.1.36 depends_on: - minio: + rustfs: condition: service_healthy environment: BUZZ_S3_ACCESS_KEY: ${BUZZ_S3_ACCESS_KEY:?set BUZZ_S3_ACCESS_KEY} BUZZ_S3_SECRET_KEY: ${BUZZ_S3_SECRET_KEY:?set BUZZ_S3_SECRET_KEY} BUZZ_S3_BUCKET: ${BUZZ_S3_BUCKET:-buzz-media} - entrypoint: > - /bin/sh -euc ' - mc alias set local http://minio:9000 "$${BUZZ_S3_ACCESS_KEY}" "$${BUZZ_S3_SECRET_KEY}" - mc mb --ignore-existing "local/$${BUZZ_S3_BUCKET}" - mc anonymous set none "local/$${BUZZ_S3_BUCKET}" - ' + entrypoint: /bin/sh + command: + - -ec + - >- + rc alias set local http://rustfs:9000 + "$${BUZZ_S3_ACCESS_KEY}" "$${BUZZ_S3_SECRET_KEY}" && + rc bucket create "local/$${BUZZ_S3_BUCKET}" --ignore-existing --quiet && + rc bucket anonymous set private "local/$${BUZZ_S3_BUCKET}" restart: "no" networks: - buzz-net @@ -128,9 +132,9 @@ volumes: buzz-redis-data: labels: com.buzz.volume: redis - buzz-minio-data: + buzz-rustfs-data: labels: - com.buzz.volume: minio + com.buzz.volume: rustfs buzz-git-data: labels: com.buzz.volume: git diff --git a/deploy/compose/run.sh b/deploy/compose/run.sh index d5465ea1f5d..c0eb0d9e972 100755 --- a/deploy/compose/run.sh +++ b/deploy/compose/run.sh @@ -42,7 +42,7 @@ Back up these before upgrades and on a regular schedule: - deploy/compose/.env, especially BUZZ_RELAY_PRIVATE_KEY, DB/Redis/S3 secrets, and BUZZ_GIT_HOOK_HMAC_SECRET - The owner private key if bootstrap generated one for RELAY_OWNER_PUBKEY - Postgres data (prefer pg_dump or a quiesced volume snapshot) -- MinIO/S3 bucket contents for media and git objects +- RustFS/S3 bucket contents for media and git objects - buzz-git-data volume (BUZZ_GIT_REPO_PATH=/data/git) - Caddy data/config volumes if using compose.caddy.yml diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml index 9fe6e7a9a8e..29c2741c5a5 100644 --- a/docker-compose.ci.yml +++ b/docker-compose.ci.yml @@ -1,12 +1,11 @@ -# CI pulls the published image; only minio-image.yml builds it. -# MINIO_CI_IMAGE lets the publisher smoke-test an unpublished image or an -# operator reproduce a run with its recorded run-specific tag/digest. +# CI explicitly selects the same digest-pinned object-storage images as the +# development and deployment Compose files. services: - minio: - image: ${MINIO_CI_IMAGE:-ghcr.io/block/buzz-minio:latest} + rustfs: + image: rustfs/rustfs@sha256:8cc9801755448b71a786705ce76692c77e14936cccd87cf2fc31842e58f4d1ff # 1.0.0 platform: linux/amd64 pull_policy: always - minio-init: - image: ${MINIO_CI_IMAGE:-ghcr.io/block/buzz-minio:latest} + rustfs-init: + image: rustfs/rc@sha256:ab024bfebee49a750ce886b4c70963ccd9ddaa03f491704a90710641d7a26699 # v0.1.36 platform: linux/amd64 pull_policy: always diff --git a/docker-compose.harness.yml b/docker-compose.harness.yml index 0164a68e274..946ec3b93cd 100644 --- a/docker-compose.harness.yml +++ b/docker-compose.harness.yml @@ -7,7 +7,7 @@ # isolation pattern. # # Bring up: docker compose -p buzz-harness -f docker-compose.harness.yml up -d -# Ports: postgres 5471 · redis 6471 · minio 9471/9472 +# Ports: postgres 5471 · redis 6471 · RustFS 9471/9472 # Relay (run separately by scripts/start-isolated-test-relay.sh): # main 3030 · health 8088 · metrics 9202 # ============================================================================= @@ -41,37 +41,45 @@ services: retries: 10 start_period: 5s - minio: - image: quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z@sha256:14cea493d9a34af32f524e538b8346cf79f3321eff8e708c1e2960462bd8936e - command: server /data --console-address ":9001" + rustfs: + image: rustfs/rustfs@sha256:8cc9801755448b71a786705ce76692c77e14936cccd87cf2fc31842e58f4d1ff # 1.0.0 environment: - MINIO_ROOT_USER: buzz_dev - MINIO_ROOT_PASSWORD: buzz_dev_secret + RUSTFS_ADDRESS: 0.0.0.0:9000 + RUSTFS_CONSOLE_ADDRESS: 0.0.0.0:9001 + RUSTFS_VOLUMES: /data + RUSTFS_ACCESS_KEY: buzz_dev + RUSTFS_SECRET_KEY: buzz_dev_secret ports: - "9471:9000" - "9472:9001" volumes: - - harness-minio-data:/data + - harness-rustfs-data:/data healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + test: ["CMD", "curl", "-f", "http://localhost:9000/health"] interval: 5s timeout: 5s retries: 10 start_period: 10s - minio-init: - image: quay.io/minio/mc:RELEASE.2025-08-13T08-35-41Z@sha256:a7fe349ef4bd8521fb8497f55c6042871b2ae640607cf99d9bede5e9bdf11727 + rustfs-init: + image: rustfs/rc@sha256:ab024bfebee49a750ce886b4c70963ccd9ddaa03f491704a90710641d7a26699 # v0.1.36 depends_on: - minio: + rustfs: condition: service_healthy - entrypoint: > - /bin/sh -c " - mc alias set local http://minio:9000 buzz_dev buzz_dev_secret && - mc mb --ignore-existing local/buzz-media && - mc anonymous set none local/buzz-media - " + environment: + BUZZ_S3_ACCESS_KEY: buzz_dev + BUZZ_S3_SECRET_KEY: buzz_dev_secret + BUZZ_S3_BUCKET: buzz-media + entrypoint: /bin/sh + command: + - -ec + - >- + rc alias set local http://rustfs:9000 + "$${BUZZ_S3_ACCESS_KEY}" "$${BUZZ_S3_SECRET_KEY}" && + rc bucket create "local/$${BUZZ_S3_BUCKET}" --ignore-existing --quiet && + rc bucket anonymous set private "local/$${BUZZ_S3_BUCKET}" restart: "no" volumes: harness-postgres-data: - harness-minio-data: + harness-rustfs-data: diff --git a/docker-compose.yml b/docker-compose.yml index 995adea1561..8fc1349dffe 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -100,22 +100,24 @@ services: com.buzz.env: "dev" restart: unless-stopped - minio: - image: quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z@sha256:14cea493d9a34af32f524e538b8346cf79f3321eff8e708c1e2960462bd8936e - container_name: buzz-minio - command: server /data --console-address ":9001" + rustfs: + image: rustfs/rustfs@sha256:8cc9801755448b71a786705ce76692c77e14936cccd87cf2fc31842e58f4d1ff # 1.0.0 + container_name: buzz-rustfs environment: - MINIO_ROOT_USER: buzz_dev - MINIO_ROOT_PASSWORD: buzz_dev_secret + RUSTFS_ADDRESS: 0.0.0.0:9000 + RUSTFS_CONSOLE_ADDRESS: 0.0.0.0:9001 + RUSTFS_VOLUMES: /data + RUSTFS_ACCESS_KEY: buzz_dev + RUSTFS_SECRET_KEY: buzz_dev_secret ports: - "127.0.0.1:9000:9000" - "127.0.0.1:9001:9001" volumes: - - minio-data:/data + - rustfs-data:/data networks: - buzz-net healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + test: ["CMD", "curl", "-f", "http://localhost:9000/health"] interval: 5s timeout: 5s retries: 10 @@ -125,26 +127,32 @@ services: limits: memory: 256m labels: - com.buzz.service: "minio" + com.buzz.service: "rustfs" com.buzz.env: "dev" restart: unless-stopped - minio-init: - image: quay.io/minio/mc:RELEASE.2025-08-13T08-35-41Z@sha256:a7fe349ef4bd8521fb8497f55c6042871b2ae640607cf99d9bede5e9bdf11727 - container_name: buzz-minio-init + rustfs-init: + image: rustfs/rc@sha256:ab024bfebee49a750ce886b4c70963ccd9ddaa03f491704a90710641d7a26699 # v0.1.36 + container_name: buzz-rustfs-init depends_on: - minio: + rustfs: condition: service_healthy networks: - buzz-net - entrypoint: > - /bin/sh -c " - mc alias set local http://minio:9000 buzz_dev buzz_dev_secret && - mc mb --ignore-existing local/buzz-media && - mc anonymous set none local/buzz-media - " + environment: + BUZZ_S3_ACCESS_KEY: buzz_dev + BUZZ_S3_SECRET_KEY: buzz_dev_secret + BUZZ_S3_BUCKET: buzz-media + entrypoint: /bin/sh + command: + - -ec + - >- + rc alias set local http://rustfs:9000 + "$${BUZZ_S3_ACCESS_KEY}" "$${BUZZ_S3_SECRET_KEY}" && + rc bucket create "local/$${BUZZ_S3_BUCKET}" --ignore-existing --quiet && + rc bucket anonymous set private "local/$${BUZZ_S3_BUCKET}" labels: - com.buzz.service: "minio-init" + com.buzz.service: "rustfs-init" com.buzz.env: "dev" restart: "no" @@ -174,10 +182,10 @@ volumes: name: buzz-postgres-data labels: com.buzz.volume: "postgres" - minio-data: - name: buzz-minio-data + rustfs-data: + name: buzz-rustfs-data labels: - com.buzz.volume: "minio" + com.buzz.volume: "rustfs" prometheus-data: name: buzz-prometheus-data labels: diff --git a/docs/admin/README.md b/docs/admin/README.md index 62d8b7102ff..5ebb9c76373 100644 --- a/docs/admin/README.md +++ b/docs/admin/README.md @@ -269,7 +269,7 @@ rolling. For local review, run `just admin-seed` before `just admin`. `just admin` defaults to `BUZZ_ADMIN_AUTH=disabled`, so the dashboard renders without a credential. The seed command also uploads real image and diagnostic fixtures to -local MinIO. Feedback search and filters run over the bounded browser result +local RustFS. Feedback search and filters run over the bounded browser result set. The feedback **status** control (`new`/`reviewed`/`archived`) is server-backed: in `nip98` mode it `PATCH`es the relay and adopts the returned status, so every operator sees the same state; in `disabled` mode it renders as diff --git a/scripts/dev-reset.sh b/scripts/dev-reset.sh index ea5acc047ae..c5670ba154a 100755 --- a/scripts/dev-reset.sh +++ b/scripts/dev-reset.sh @@ -30,7 +30,7 @@ cd "${REPO_ROOT}" # ---- Confirm ---------------------------------------------------------------- if [[ "${1:-}" != "--yes" ]]; then - echo -e "${YELLOW}WARNING: This will DELETE all development data (desktop state, postgres, minio volumes).${NC}" + echo -e "${YELLOW}WARNING: This will DELETE all development data (desktop state, postgres, rustfs volumes).${NC}" echo -e " Installed Buzz app state and its production keyring are preserved." echo -e " Redis data is ephemeral and always wiped on restart." echo "" diff --git a/scripts/e2e-git-perms.sh b/scripts/e2e-git-perms.sh index bc507a7ff31..2da51594f06 100755 --- a/scripts/e2e-git-perms.sh +++ b/scripts/e2e-git-perms.sh @@ -5,7 +5,7 @@ # Two bots collaborate on a simple web page via the Buzz relay's git server. # # Prerequisites: -# - Docker services running (postgres, redis, minio) +# - Docker services running (postgres, redis, rustfs) # - Relay built: cargo build --release --bin buzz-relay # - Credential helper built: cargo build --release --bin git-credential-nostr # - Signing program built: cargo build --release --bin git-sign-nostr diff --git a/scripts/seed-admin-dashboard.sh b/scripts/seed-admin-dashboard.sh index e9ff10644ed..89d7abf1772 100755 --- a/scripts/seed-admin-dashboard.sh +++ b/scripts/seed-admin-dashboard.sh @@ -69,13 +69,25 @@ upload_fixture() { size="$(fixture_size "${path}")" sidecar="$(printf '{"dim":"%s","blurhash":"","thumb_url":"","ext":"%s","mime_type":"%s","size":%s,"uploaded_at":0}' \ "${dimensions}" "${extension}" "${mime}" "${size}")" - docker exec -i buzz-minio mc pipe --quiet --attr "Content-Type=${mime}" \ + rustfs_rc pipe --quiet --content-type "${mime}" \ "local/${BUZZ_S3_BUCKET:-buzz-media}/${hash}.${extension}" < "${path}" - printf '%s' "${sidecar}" | docker exec -i buzz-minio mc pipe --quiet \ - --attr "Content-Type=application/json" \ + printf '%s' "${sidecar}" | rustfs_rc pipe --quiet \ + --content-type application/json \ "local/${BUZZ_S3_BUCKET:-buzz-media}/_meta/${community_id}/${hash}.json" } +RUSTFS_RC_IMAGE="rustfs/rc@sha256:ab024bfebee49a750ce886b4c70963ccd9ddaa03f491704a90710641d7a26699" +rustfs_rc() { + docker run --rm -i --network buzz-net \ + -e "BUZZ_S3_ACCESS_KEY=${BUZZ_S3_ACCESS_KEY:-buzz_dev}" \ + -e "BUZZ_S3_SECRET_KEY=${BUZZ_S3_SECRET_KEY:-buzz_dev_secret}" \ + --entrypoint /bin/sh "${RUSTFS_RC_IMAGE}" -c ' + set -eu + rc alias set local http://rustfs:9000 "$BUZZ_S3_ACCESS_KEY" "$BUZZ_S3_SECRET_KEY" >/dev/null + exec rc "$@" + ' -- "$@" +} + fixture_dir="$(mktemp -d "${TMPDIR:-/tmp}/buzz-admin-feedback.XXXXXX")" search_image="${REPO_ROOT}/docs/assets/screenshots/media-comments.png" workspace_image="${REPO_ROOT}/docs/assets/screenshots/channel-thread.png" @@ -96,9 +108,8 @@ quality_image_hash="$(fixture_hash "${quality_image}")" composer_diagnostics_hash="$(fixture_hash "${composer_diagnostics}")" workspace_diagnostics_hash="$(fixture_hash "${workspace_diagnostics}")" -if ! docker exec buzz-minio mc alias set local http://localhost:9000 \ - "${BUZZ_S3_ACCESS_KEY:-buzz_dev}" "${BUZZ_S3_SECRET_KEY:-buzz_dev_secret}" >/dev/null; then - echo "error: local MinIO is unavailable; run just setup first" >&2 +if ! rustfs_rc ping local --quiet >/dev/null; then + echo "error: local RustFS is unavailable; run just setup first" >&2 exit 1 fi diff --git a/scripts/start-isolated-test-relay.sh b/scripts/start-isolated-test-relay.sh index b737a73ea9c..8a2cfa14412 100755 --- a/scripts/start-isolated-test-relay.sh +++ b/scripts/start-isolated-test-relay.sh @@ -12,7 +12,7 @@ # compose project : buzz-harness # postgres : localhost:5471 (db=buzz, user=buzz, pass=buzz_dev) # redis : localhost:6471 -# minio : localhost:9471 (console 9472) +# RustFS : localhost:9471 (console 9472) # relay main : localhost:3030 ← BUZZ_E2E_RELAY_URL=http://localhost:3030 # relay health : localhost:8088 # relay metrics : localhost:9202 @@ -57,7 +57,7 @@ COMPOSE_FILE="docker-compose.harness.yml" # evaperf :5470/:6470/:9470/:3170 stack). PG_PORT=5471 REDIS_PORT=6471 -MINIO_PORT=9471 +RUSTFS_PORT=9471 RELAY_MAIN=3030 RELAY_HEALTH=8088 RELAY_METRICS=9202 @@ -148,7 +148,7 @@ tmux new-session -d -s "${TMUX_SESSION}" "cd '${REPO_ROOT}' && env \ BUZZ_BIND_ADDR=0.0.0.0:${RELAY_MAIN} \ BUZZ_HEALTH_PORT=${RELAY_HEALTH} \ BUZZ_METRICS_PORT=${RELAY_METRICS} \ - BUZZ_S3_ENDPOINT=http://localhost:${MINIO_PORT} \ + BUZZ_S3_ENDPOINT=http://localhost:${RUSTFS_PORT} \ BUZZ_S3_ACCESS_KEY=buzz_dev \ BUZZ_S3_SECRET_KEY=buzz_dev_secret \ BUZZ_S3_BUCKET=buzz-media \ diff --git a/scripts/start-relay-for-tests.sh b/scripts/start-relay-for-tests.sh index 272bafd7057..7f75d45afdd 100755 --- a/scripts/start-relay-for-tests.sh +++ b/scripts/start-relay-for-tests.sh @@ -61,7 +61,7 @@ err() { echo -e "${RED}[relay-test]${NC} $*" >&2; } cd "${REPO_ROOT}" log "Starting docker compose services..." -docker compose up -d postgres redis minio minio-init +docker compose up -d postgres redis rustfs rustfs-init # ── Wait for services to be healthy ────────────────────────────────────────── @@ -84,7 +84,7 @@ wait_healthy() { wait_healthy "Postgres" "buzz-postgres" wait_healthy "Redis" "buzz-redis" -wait_healthy "MinIO" "buzz-minio" +wait_healthy "RustFS" "buzz-rustfs" # ── Apply database schema ──────────────────────────────────────────────────── From 3e903b8ab920ee799458703cd251137adef113f7 Mon Sep 17 00:00:00 2001 From: Zoorpha Date: Thu, 24 Sep 2026 22:08:50 +0200 Subject: [PATCH 2/8] fix: update remaining RustFS smoke references Signed-off-by: Zoorpha --- crates/buzz-media/tests/versioned_minio.rs | 83 +++++++++++++--------- scripts/run-desktop-release-smoke.sh | 4 +- 2 files changed, 53 insertions(+), 34 deletions(-) diff --git a/crates/buzz-media/tests/versioned_minio.rs b/crates/buzz-media/tests/versioned_minio.rs index 1e0db4b481f..39dc2ca7425 100644 --- a/crates/buzz-media/tests/versioned_minio.rs +++ b/crates/buzz-media/tests/versioned_minio.rs @@ -1,20 +1,21 @@ -//! Live destructive versioned-bucket deletion coverage against docker-compose MinIO. +//! Live destructive versioned-bucket deletion coverage against docker-compose RustFS. //! //! This exercises the S3-compatible path that community deletion relies on when //! a bucket has versioning enabled: list object versions/delete markers with //! dual markers, delete exact `(Key, VersionId)` identifiers, retry an already //! deleted version, and prove final `ListObjectVersions` emptiness. //! -//! Run it against the docker-compose MinIO (creds `buzz_dev`/`buzz_dev_secret`): +//! Run it against the docker-compose RustFS (creds `buzz_dev`/`buzz_dev_secret`): //! //! ```bash -//! docker compose up -d minio minio-init +//! docker compose up -d rustfs rustfs-init //! cargo test -p buzz-media --test versioned_minio -- --ignored --nocapture //! ``` //! -//! The test creates and removes its own bucket. The MinIO container name is -//! overridable with `BUZZ_MINIO_CONTAINER`; credentials/endpoint/region/addressing -//! use the same `BUZZ_S3_*` env vars as `static_creds_minio`. +//! The test creates and removes its own bucket. The RustFS CLI image is run on +//! the Compose network; override that network with `BUZZ_S3_DOCKER_NETWORK`. +//! Credentials/endpoint/region/addressing use the same `BUZZ_S3_*` env vars as +//! `static_creds_minio`. use std::process::Command; @@ -25,7 +26,7 @@ fn env_or(name: &str, default: &str) -> String { std::env::var(name).unwrap_or_else(|_| default.to_string()) } -fn minio_config(bucket: String) -> MediaConfig { +fn s3_config(bucket: String) -> MediaConfig { MediaConfig { s3_endpoint: env_or("BUZZ_S3_ENDPOINT", "http://localhost:9000"), s3_access_key: env_or("BUZZ_S3_ACCESS_KEY", "buzz_dev"), @@ -46,20 +47,38 @@ fn minio_config(bucket: String) -> MediaConfig { } } -fn run_mc(args: &[String]) -> Result<(), String> { - let container = env_or("BUZZ_MINIO_CONTAINER", "buzz-minio"); +fn run_rc(args: &[String]) -> Result<(), String> { + const RUSTFS_RC_IMAGE: &str = + "rustfs/rc@sha256:ab024bfebee49a750ce886b4c70963ccd9ddaa03f491704a90710641d7a26699"; + let network = env_or("BUZZ_S3_DOCKER_NETWORK", "buzz-net"); let output = Command::new("docker") - .arg("exec") - .arg(container) - .arg("mc") + .args(["run", "--rm", "-i", "--network", &network]) + .args([ + "-e", + &format!( + "BUZZ_S3_ACCESS_KEY={}", + env_or("BUZZ_S3_ACCESS_KEY", "buzz_dev") + ), + "-e", + &format!( + "BUZZ_S3_SECRET_KEY={}", + env_or("BUZZ_S3_SECRET_KEY", "buzz_dev_secret") + ), + "--entrypoint", + "/bin/sh", + RUSTFS_RC_IMAGE, + "-c", + "set -eu; rc alias set local http://rustfs:9000 \"$BUZZ_S3_ACCESS_KEY\" \"$BUZZ_S3_SECRET_KEY\" >/dev/null; exec rc \"$@\"", + "--", + ]) .args(args) .output() - .map_err(|err| format!("failed to execute docker/mc: {err}"))?; + .map_err(|err| format!("failed to execute docker/rustfs-rc: {err}"))?; if output.status.success() { Ok(()) } else { Err(format!( - "mc {:?} failed with status {}\nstdout:\n{}\nstderr:\n{}", + "rustfs-rc {:?} failed with status {}\nstdout:\n{}\nstderr:\n{}", args, output.status, String::from_utf8_lossy(&output.stdout), @@ -68,12 +87,12 @@ fn run_mc(args: &[String]) -> Result<(), String> { } } -fn mc_alias(access_key: &str, secret_key: &str) -> Result<(), String> { - run_mc(&[ +fn rc_alias(access_key: &str, secret_key: &str) -> Result<(), String> { + run_rc(&[ "alias".to_string(), "set".to_string(), "local".to_string(), - "http://localhost:9000".to_string(), + "http://rustfs:9000".to_string(), access_key.to_string(), secret_key.to_string(), ]) @@ -128,20 +147,20 @@ fn refs_from(entries: &[buzz_media::storage::ObjectVersionEntry]) -> Vec/dev/null || true)" == "healthy" ]] && break sleep 1 From 21e119d9b9723771516679e5f5ecffef8313d08e Mon Sep 17 00:00:00 2001 From: Zoorpha Date: Thu, 24 Sep 2026 22:14:26 +0200 Subject: [PATCH 3/8] fix: guard MinIO to RustFS deployment upgrades Signed-off-by: Zoorpha --- deploy/compose/README.md | 4 ++++ deploy/compose/compose.dev.yml | 4 ++-- deploy/compose/run.sh | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/deploy/compose/README.md b/deploy/compose/README.md index d97e023389c..6f3fc5017c4 100644 --- a/deploy/compose/README.md +++ b/deploy/compose/README.md @@ -47,6 +47,10 @@ keypair. `.rustfs`. It is not configurable for an external S3 provider through `.env`; use the Helm chart or a custom Compose configuration for providers such as new Railway Storage Buckets that require `virtual` addressing. +- RustFS does not read the previous MinIO filesystem layout. Existing MinIO + deployments must migrate objects through the S3 API and verify a backup before + switching; `./run.sh start`, `restart`, and `upgrade` refuse to create the new + RustFS volume automatically while only the legacy MinIO volume exists. Run `./run.sh backup-hint` for the backup checklist. diff --git a/deploy/compose/compose.dev.yml b/deploy/compose/compose.dev.yml index bb8a28c4218..cc6a8c6e86c 100644 --- a/deploy/compose/compose.dev.yml +++ b/deploy/compose/compose.dev.yml @@ -9,8 +9,8 @@ services: rustfs: ports: - - "${MINIO_API_PORT:-9000}:9000" - - "${MINIO_CONSOLE_PORT:-9001}:9001" + - "${RUSTFS_API_PORT:-9000}:9000" + - "${RUSTFS_CONSOLE_PORT:-9001}:9001" adminer: image: adminer:latest diff --git a/deploy/compose/run.sh b/deploy/compose/run.sh index c0eb0d9e972..d62882c92b9 100755 --- a/deploy/compose/run.sh +++ b/deploy/compose/run.sh @@ -35,6 +35,20 @@ MSG fi } +guard_legacy_minio_volume() { + local legacy_volume="buzz-prod_buzz-minio-data" + local rustfs_volume="buzz-prod_buzz-rustfs-data" + if docker volume inspect "${legacy_volume}" >/dev/null 2>&1 && + ! docker volume inspect "${rustfs_volume}" >/dev/null 2>&1; then + cat >&2 <<'MSG' +The previous MinIO data volume exists, but the RustFS data volume does not. +Buzz will not create a new empty RustFS store and hide existing objects. +Migrate the objects through the S3 API, verify the backup, then retry. +MSG + return 1 + fi +} + backup_hint() { cat <<'MSG' Back up these before upgrades and on a regular schedule: @@ -53,6 +67,7 @@ MSG case "${1:-help}" in start|up) require_env + guard_legacy_minio_volume compose up -d --wait ;; stop|down) @@ -60,6 +75,7 @@ case "${1:-help}" in ;; restart) require_env + guard_legacy_minio_volume compose up -d --wait --force-recreate relay ;; pull) @@ -68,6 +84,7 @@ case "${1:-help}" in ;; upgrade) require_env + guard_legacy_minio_volume compose pull compose up -d --wait backup_hint From 863c5289fa779647b40641ce9436c2b44bab37ef Mon Sep 17 00:00:00 2001 From: Zoorpha Date: Thu, 24 Sep 2026 22:19:01 +0200 Subject: [PATCH 4/8] docs: clarify RustFS migration compatibility Signed-off-by: Zoorpha --- deploy/compose/README.md | 11 +++++++---- deploy/compose/run.sh | 3 ++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/deploy/compose/README.md b/deploy/compose/README.md index 6f3fc5017c4..ba1427d771c 100644 --- a/deploy/compose/README.md +++ b/deploy/compose/README.md @@ -47,10 +47,13 @@ keypair. `.rustfs`. It is not configurable for an external S3 provider through `.env`; use the Helm chart or a custom Compose configuration for providers such as new Railway Storage Buckets that require `virtual` addressing. -- RustFS does not read the previous MinIO filesystem layout. Existing MinIO - deployments must migrate objects through the S3 API and verify a backup before - switching; `./run.sh start`, `restart`, and `upgrade` refuse to create the new - RustFS volume automatically while only the legacy MinIO volume exists. +- The bundled Compose stack uses RustFS. The Helm quickstart remains the + separate MinIO option, and external S3 providers require the Helm chart or a + custom Compose configuration. The pinned RustFS build is not validated here + for direct reuse of a MinIO data directory: existing MinIO deployments must + migrate objects through the S3 API and verify a backup before switching; + `./run.sh start`, `restart`, and `upgrade` refuse to create the new RustFS + volume automatically while only the legacy MinIO volume exists. Run `./run.sh backup-hint` for the backup checklist. diff --git a/deploy/compose/run.sh b/deploy/compose/run.sh index d62882c92b9..a48ad31aee0 100755 --- a/deploy/compose/run.sh +++ b/deploy/compose/run.sh @@ -42,7 +42,8 @@ guard_legacy_minio_volume() { ! docker volume inspect "${rustfs_volume}" >/dev/null 2>&1; then cat >&2 <<'MSG' The previous MinIO data volume exists, but the RustFS data volume does not. -Buzz will not create a new empty RustFS store and hide existing objects. +The pinned RustFS build has not been validated for direct MinIO data-directory +reuse. Buzz will not create a new empty RustFS store and hide existing objects. Migrate the objects through the S3 API, verify the backup, then retry. MSG return 1 From 2c5ca5e9b440f7954bb9a02742f87bb7097b577b Mon Sep 17 00:00:00 2001 From: Zoorpha Date: Thu, 24 Sep 2026 22:26:24 +0200 Subject: [PATCH 5/8] fix: wait for RustFS bucket initialization Signed-off-by: Zoorpha --- .github/workflows/_ci-relay.yml | 40 ++++++++++++++++++++++++++++ scripts/run-desktop-release-smoke.sh | 16 +++++++++++ scripts/start-isolated-test-relay.sh | 23 ++++++++++++++++ scripts/start-relay-for-tests.sh | 24 +++++++++++++++++ 4 files changed, 103 insertions(+) diff --git a/.github/workflows/_ci-relay.yml b/.github/workflows/_ci-relay.yml index bd371a5289b..2f1a97d8243 100644 --- a/.github/workflows/_ci-relay.yml +++ b/.github/workflows/_ci-relay.yml @@ -287,6 +287,26 @@ jobs: wait_healthy "Postgres" "buzz-postgres" wait_healthy "Redis" "buzz-redis" wait_healthy "RustFS" "buzz-rustfs" + wait_completed() { + local service="$1" + local container="$2" + for attempt in $(seq 1 60); do + status=$(docker inspect --format='{{.State.Status}}' "${container}" 2>/dev/null || echo "not_found") + if [ "${status}" = "exited" ]; then + exit_code=$(docker inspect --format='{{.State.ExitCode}}' "${container}" 2>/dev/null || echo "unknown") + if [ "${exit_code}" = "0" ]; then + echo "${service} completed" + return 0 + fi + docker logs "${container}" || true + return 1 + fi + sleep 2 + done + docker logs "${container}" || true + return 1 + } + wait_completed "RustFS bucket initialization" "buzz-rustfs-init" - name: Download relay binary uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -449,6 +469,26 @@ jobs: wait_healthy "Postgres" "buzz-postgres" wait_healthy "Redis" "buzz-redis" wait_healthy "RustFS" "buzz-rustfs" + wait_completed() { + local service="$1" + local container="$2" + for attempt in $(seq 1 60); do + status=$(docker inspect --format='{{.State.Status}}' "${container}" 2>/dev/null || echo "not_found") + if [ "${status}" = "exited" ]; then + exit_code=$(docker inspect --format='{{.State.ExitCode}}' "${container}" 2>/dev/null || echo "unknown") + if [ "${exit_code}" = "0" ]; then + echo "${service} completed" + return 0 + fi + docker logs "${container}" || true + return 1 + fi + sleep 2 + done + docker logs "${container}" || true + return 1 + } + wait_completed "RustFS bucket initialization" "buzz-rustfs-init" - name: Download relay artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: diff --git a/scripts/run-desktop-release-smoke.sh b/scripts/run-desktop-release-smoke.sh index 023c058cb0f..e960ea8ee4f 100755 --- a/scripts/run-desktop-release-smoke.sh +++ b/scripts/run-desktop-release-smoke.sh @@ -79,6 +79,22 @@ for container in buzz-postgres buzz-redis buzz-rustfs; do exit 1 } done +for _ in $(seq 1 60); do + init_status="$(docker inspect --format='{{.State.Status}}' buzz-rustfs-init 2>/dev/null || true)" + if [[ "${init_status}" == "exited" ]]; then + init_exit_code="$(docker inspect --format='{{.State.ExitCode}}' buzz-rustfs-init 2>/dev/null || true)" + if [[ "${init_exit_code}" == "0" ]]; then + break + fi + docker logs buzz-rustfs-init || true + exit 1 + fi + sleep 1 +done +[[ "$(docker inspect --format='{{.State.Status}}' buzz-rustfs-init 2>/dev/null || true)" == "exited" ]] || { + docker logs buzz-rustfs-init || true + exit 1 +} phase services "${phase_start}" phase_start="$(date +%s)" diff --git a/scripts/start-isolated-test-relay.sh b/scripts/start-isolated-test-relay.sh index 8a2cfa14412..2c99f719dce 100755 --- a/scripts/start-isolated-test-relay.sh +++ b/scripts/start-isolated-test-relay.sh @@ -84,6 +84,29 @@ wait_pg() { } wait_pg +wait_rustfs_init() { + local container status exit_code + for _ in $(seq 1 60); do + container="$(docker compose -p "${PROJECT}" -f "${COMPOSE_FILE}" ps -q rustfs-init 2>/dev/null || true)" + status="$(docker inspect --format='{{.State.Status}}' "${container}" 2>/dev/null || true)" + if [[ "${status}" == "exited" ]]; then + exit_code="$(docker inspect --format='{{.State.ExitCode}}' "${container}" 2>/dev/null || true)" + if [[ "${exit_code}" == "0" ]]; then + ok "RustFS bucket initialization completed" + return 0 + fi + err "RustFS bucket initialization failed with exit code ${exit_code}" + docker logs "${container}" || true + return 1 + fi + sleep 2 + done + err "RustFS bucket initialization did not complete" + docker logs "${container}" || true + return 1 +} +wait_rustfs_init + # ── Schema + partitions ────────────────────────────────────────────────────── export PGPASSWORD=buzz_dev psql_h() { docker compose -p "${PROJECT}" -f "${COMPOSE_FILE}" exec -T postgres \ diff --git a/scripts/start-relay-for-tests.sh b/scripts/start-relay-for-tests.sh index 7f75d45afdd..b102b427f55 100755 --- a/scripts/start-relay-for-tests.sh +++ b/scripts/start-relay-for-tests.sh @@ -82,9 +82,33 @@ wait_healthy() { return 1 } +wait_completed() { + local service="$1" + local container="$2" + log "Waiting for ${service} to complete..." + for attempt in $(seq 1 60); do + status=$(docker inspect --format='{{.State.Status}}' "${container}" 2>/dev/null || echo "not_found") + if [ "${status}" = "exited" ]; then + exit_code=$(docker inspect --format='{{.State.ExitCode}}' "${container}" 2>/dev/null || echo "unknown") + if [ "${exit_code}" = "0" ]; then + ok "${service} completed" + return 0 + fi + err "${service} failed with exit code ${exit_code}" + docker logs "${container}" || true + return 1 + fi + sleep 2 + done + err "${service} did not complete within 120s" + docker logs "${container}" || true + return 1 +} + wait_healthy "Postgres" "buzz-postgres" wait_healthy "Redis" "buzz-redis" wait_healthy "RustFS" "buzz-rustfs" +wait_completed "RustFS bucket initialization" "buzz-rustfs-init" # ── Apply database schema ──────────────────────────────────────────────────── From 5a72e79f44e55e634641ba13de75c30d490b642d Mon Sep 17 00:00:00 2001 From: Zoorpha Date: Thu, 24 Sep 2026 22:29:32 +0200 Subject: [PATCH 6/8] fix: include exited RustFS init containers Signed-off-by: Zoorpha --- scripts/start-isolated-test-relay.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/start-isolated-test-relay.sh b/scripts/start-isolated-test-relay.sh index 2c99f719dce..81548bf92d7 100755 --- a/scripts/start-isolated-test-relay.sh +++ b/scripts/start-isolated-test-relay.sh @@ -87,7 +87,7 @@ wait_pg wait_rustfs_init() { local container status exit_code for _ in $(seq 1 60); do - container="$(docker compose -p "${PROJECT}" -f "${COMPOSE_FILE}" ps -q rustfs-init 2>/dev/null || true)" + container="$(docker compose -p "${PROJECT}" -f "${COMPOSE_FILE}" ps -a -q rustfs-init 2>/dev/null || true)" status="$(docker inspect --format='{{.State.Status}}' "${container}" 2>/dev/null || true)" if [[ "${status}" == "exited" ]]; then exit_code="$(docker inspect --format='{{.State.ExitCode}}' "${container}" 2>/dev/null || true)" From 09793d7e3923d155d5c3cd0975e32380d2f84b3d Mon Sep 17 00:00:00 2001 From: Zoorpha Date: Thu, 24 Sep 2026 22:46:13 +0200 Subject: [PATCH 7/8] docs: align RustFS runtime diagnostics Signed-off-by: Zoorpha --- crates/buzz-media/src/config.rs | 2 +- crates/buzz-relay/src/api/git/cas_publish.rs | 2 +- crates/buzz-relay/src/api/git/hydrate.rs | 4 ++-- crates/buzz-relay/src/api/git/settings_tests.rs | 8 ++++---- crates/buzz-relay/src/api/git/store.rs | 16 ++++++++-------- crates/buzz-relay/src/api/git/transport.rs | 8 ++++---- crates/buzz-relay/src/main.rs | 2 +- deploy/compose/.env.example | 6 +++--- scripts/test-postgres-test-discovery.sh | 4 ++-- 9 files changed, 26 insertions(+), 26 deletions(-) diff --git a/crates/buzz-media/src/config.rs b/crates/buzz-media/src/config.rs index 60941a142e4..af783e08d71 100644 --- a/crates/buzz-media/src/config.rs +++ b/crates/buzz-media/src/config.rs @@ -60,7 +60,7 @@ pub struct MediaConfig { /// /// Must match the region of `s3_endpoint` for real AWS S3, otherwise /// requests are signed with the wrong credential scope and AWS rejects - /// them. Defaults to "us-east-1" to preserve MinIO/local behavior, where + /// them. Defaults to "us-east-1" to preserve local S3-compatible behavior, where /// the value is not meaningfully checked. #[serde(default = "default_s3_region")] pub s3_region: String, diff --git a/crates/buzz-relay/src/api/git/cas_publish.rs b/crates/buzz-relay/src/api/git/cas_publish.rs index 50bb36d818c..cce6d72dcb1 100644 --- a/crates/buzz-relay/src/api/git/cas_publish.rs +++ b/crates/buzz-relay/src/api/git/cas_publish.rs @@ -1600,7 +1600,7 @@ mod tests { "us-east-1", buzz_media::config::S3AddressingStyle::Path, ) - .expect("connect local MinIO") + .expect("connect local RustFS") } fn tenant() -> TenantContext { diff --git a/crates/buzz-relay/src/api/git/hydrate.rs b/crates/buzz-relay/src/api/git/hydrate.rs index 080bbd5c1cc..5f92bed871a 100644 --- a/crates/buzz-relay/src/api/git/hydrate.rs +++ b/crates/buzz-relay/src/api/git/hydrate.rs @@ -577,7 +577,7 @@ mod tests { assert!(hydrated.path().starts_with(scratch.path())); } - // -------- Live MinIO + real git roundtrip ---------------------------------- + // -------- Live RustFS + real git roundtrip --------------------------------- // // Run manually: // BUZZ_GIT_S3_PROBE=1 cargo test -p buzz-relay --lib \ @@ -596,7 +596,7 @@ mod tests { "us-east-1", buzz_media::config::S3AddressingStyle::Path, ) - .expect("connect local MinIO") + .expect("connect local RustFS") } /// Build a tiny on-disk repo, return (pack bytes, head_oid). diff --git a/crates/buzz-relay/src/api/git/settings_tests.rs b/crates/buzz-relay/src/api/git/settings_tests.rs index 55c04dddcf7..fe917ba63af 100644 --- a/crates/buzz-relay/src/api/git/settings_tests.rs +++ b/crates/buzz-relay/src/api/git/settings_tests.rs @@ -336,7 +336,7 @@ mod external_infra { } #[tokio::test] - #[ignore = "requires isolated Postgres, Redis and MinIO"] + #[ignore = "requires isolated Postgres, Redis and S3-compatible object storage"] async fn default_branch_route_permissions_and_protocol() { let f = Fixture::new().await; let before = f.snapshot().await; @@ -514,7 +514,7 @@ mod external_infra { } #[tokio::test] - #[ignore = "requires isolated Postgres, Redis and MinIO"] + #[ignore = "requires isolated Postgres, Redis and S3-compatible object storage"] async fn default_branch_delegation_and_revocation() { let f = Fixture::new().await; let agent = Keys::generate(); @@ -614,7 +614,7 @@ mod external_infra { } #[tokio::test] - #[ignore = "requires isolated Postgres, Redis and MinIO"] + #[ignore = "requires isolated Postgres, Redis and S3-compatible object storage"] async fn default_branch_push_races_and_fresh_clone() { let f = Fixture::new().await; let a = f.snapshot().await; @@ -844,7 +844,7 @@ mod external_infra { } #[tokio::test] - #[ignore = "requires isolated Postgres, Redis and MinIO"] + #[ignore = "requires isolated Postgres, Redis and S3-compatible object storage"] async fn default_branch_replay_outage_and_deletion_fail_closed() { let mut f = Fixture::new().await; let before = f.snapshot().await.digest; diff --git a/crates/buzz-relay/src/api/git/store.rs b/crates/buzz-relay/src/api/git/store.rs index d6a1b106e4f..05d40a218b4 100644 --- a/crates/buzz-relay/src/api/git/store.rs +++ b/crates/buzz-relay/src/api/git/store.rs @@ -172,15 +172,15 @@ pub struct GitStore { } impl GitStore { - /// Build a client against an S3-compatible endpoint (e.g. MinIO). + /// Build a client against an S3-compatible endpoint (e.g. RustFS). /// /// `addressing_style` is shared with media storage so both paths sign and - /// route requests consistently. Path style supports the bundled MinIO DNS; + /// route requests consistently. Path style supports the bundled RustFS DNS; /// virtual-hosted style supports standard S3 and providers such as Railway. /// /// Credential selection mirrors [`buzz_media::MediaStorage::new`]: /// - both `access_key` and `secret_key` non-empty → static credentials - /// (MinIO/local/dev, or any static-key deployment); + /// (local/dev, or any static-key deployment); /// - both empty → the AWS default credential chain via /// [`Credentials::default`] (env, profile, web-identity/IRSA, container, /// instance metadata), so the relay can use its pod IAM role; @@ -1035,7 +1035,7 @@ mod probe { fn store() -> GitStore { // This is the dedicated backend conformance path, so all connection and // signing inputs are overridable for a real provider such as Railway. - // The hydrate/CAS live tests use explicit local MinIO fixtures instead. + // The hydrate/CAS live tests use explicit local RustFS fixtures instead. let endpoint = std::env::var("BUZZ_S3_ENDPOINT").unwrap_or_else(|_| "http://localhost:9000".into()); let access_key = std::env::var("BUZZ_S3_ACCESS_KEY").unwrap_or_else(|_| "buzz_dev".into()); @@ -1067,7 +1067,7 @@ mod probe { #[tokio::test] async fn probe_412_surfacing() { if !probe_enabled() { - eprintln!("skipping: set BUZZ_GIT_S3_PROBE=1 to run against live MinIO"); + eprintln!("skipping: set BUZZ_GIT_S3_PROBE=1 to run against live RustFS"); return; } let st = store(); @@ -1142,9 +1142,9 @@ mod probe { assert_eq!(r, CasOutcome::LostRace, "second INM* must lose"); // Chain CAS directly on the PUT-returned ETag (no HEAD round-trip). - // MinIO returns the ETag in the PUT response; this proves callers can + // RustFS returns the ETag in the PUT response; this proves callers can // chain `Won → IfMatch → Won` without re-reading the pointer. - assert!(!e1.0.is_empty(), "MinIO should populate PUT response ETag"); + assert!(!e1.0.is_empty(), "RustFS should populate PUT response ETag"); let p2 = br#"{"manifest":"d2"}"#; let r = st .put_pointer(&pkey, p2, Precond::IfMatch(e1.clone())) @@ -1171,7 +1171,7 @@ mod probe { let _ = st.bucket.delete_object(&key).await; } - /// End-to-end conformance probe against MinIO. This is the same code path + /// End-to-end conformance probe against RustFS. This is the same code path /// that will run at relay startup as a deployment gate. #[tokio::test] async fn probe_conformance() { diff --git a/crates/buzz-relay/src/api/git/transport.rs b/crates/buzz-relay/src/api/git/transport.rs index 704bbf1c1d6..3ffac18b61c 100644 --- a/crates/buzz-relay/src/api/git/transport.rs +++ b/crates/buzz-relay/src/api/git/transport.rs @@ -2663,21 +2663,21 @@ mod track_c_tests { pool.close().await; } - mod external_infra_minio_tests { + mod external_infra_s3_tests { #[tokio::test] - #[ignore = "requires Postgres and MinIO"] + #[ignore = "requires Postgres and S3-compatible object storage"] async fn repo_announcement_holds_serving_lease_until_pointer_is_seeded() { super::repo_announcement_holds_serving_lease_until_pointer_is_seeded().await; } #[tokio::test] - #[ignore = "requires Postgres and MinIO"] + #[ignore = "requires Postgres and S3-compatible object storage"] async fn finalize_push_holds_serving_lease_through_post_cas_publication() { super::finalize_push_holds_serving_lease_through_post_cas_publication().await; } #[tokio::test] - #[ignore = "requires Postgres and MinIO"] + #[ignore = "requires Postgres and S3-compatible object storage"] async fn finalize_push_db_failure_after_cas_is_not_success_and_releases_lease() { super::finalize_push_db_failure_after_cas_is_not_success_and_releases_lease().await; } diff --git a/crates/buzz-relay/src/main.rs b/crates/buzz-relay/src/main.rs index 114b51ee7f2..de1c5b517a9 100644 --- a/crates/buzz-relay/src/main.rs +++ b/crates/buzz-relay/src/main.rs @@ -557,7 +557,7 @@ async fn run_relay_main(boot: BootTracker) -> anyhow::Result<()> { info!(runtime_id = %runtime_id, "Inter-relay mesh started"); } - // Git-on-object-storage: admit the configured S3/MinIO backend against the + // Git-on-object-storage: admit the configured S3-compatible backend against the // linearizable conditional-write axiom (A3) before serving git traffic. // Failure is fatal: a backend that cannot satisfy pointer CAS invalidates // the manifest-pointer protocol. This is a deployment gate, not a proof. diff --git a/deploy/compose/.env.example b/deploy/compose/.env.example index 100ec893c3f..218fd338ff6 100644 --- a/deploy/compose/.env.example +++ b/deploy/compose/.env.example @@ -37,7 +37,7 @@ REDIS_PASSWORD=CHANGE_ME_RANDOM_PASSWORD BUZZ_S3_ACCESS_KEY=CHANGE_ME_RANDOM_ACCESS_KEY BUZZ_S3_SECRET_KEY=CHANGE_ME_RANDOM_SECRET_KEY BUZZ_S3_BUCKET=buzz-media -# Bundled MinIO uses path-style URLs; deploy/compose/compose.yml pins this. +# Bundled RustFS uses path-style URLs; deploy/compose/compose.yml pins this. BUZZ_S3_ADDRESSING_STYLE=path # Optional private moderation dashboard. Set BUZZ_ADMIN_HOST plus BUZZ_ADMIN_AUTH: @@ -71,7 +71,7 @@ CADDY_HTTPS_PORT=443 # Dev override ports. Only used with compose.dev.yml. POSTGRES_PORT=5432 REDIS_PORT=6379 -MINIO_API_PORT=9000 -MINIO_CONSOLE_PORT=9001 +RUSTFS_API_PORT=9000 +RUSTFS_CONSOLE_PORT=9001 ADMINER_PORT=8082 PROMETHEUS_PORT=9090 diff --git a/scripts/test-postgres-test-discovery.sh b/scripts/test-postgres-test-discovery.sh index 8b6b6c32183..3bef0b9b91e 100755 --- a/scripts/test-postgres-test-discovery.sh +++ b/scripts/test-postgres-test-discovery.sh @@ -198,7 +198,7 @@ cat >"$fixture_root/src/hybrid.rs" <<'RS' #[cfg(test)] mod postgres_tests { #[test] - #[ignore = "requires Postgres and MinIO"] + #[ignore = "requires Postgres and S3-compatible object storage"] fn hybrid_without_external_module() {} } RS @@ -214,7 +214,7 @@ cat >"$fixture_root/src/name_is_not_classification.rs" <<'RS' #[cfg(test)] mod postgres_tests { #[test] - #[ignore = "requires Postgres and MinIO"] + #[ignore = "requires Postgres and S3-compatible object storage"] fn external_infra_prefix_is_not_enough() {} } RS From c31d01b7e83e9e731903bef6b878e11d3f90ed1c Mon Sep 17 00:00:00 2001 From: Zoorpha Date: Thu, 24 Sep 2026 22:56:57 +0200 Subject: [PATCH 8/8] ci: diagnose Compose dependency failures Signed-off-by: Zoorpha --- .github/workflows/_ci-relay.yml | 18 ++++++++++++------ .github/workflows/ci.yml | 2 ++ .github/workflows/mesh-lifecycle.yml | 5 ++++- scripts/diagnose-compose-runtime.sh | 16 ++++++++++++++++ scripts/run-desktop-release-smoke.sh | 3 +++ scripts/start-relay-for-tests.sh | 12 +++++++++--- 6 files changed, 46 insertions(+), 10 deletions(-) create mode 100755 scripts/diagnose-compose-runtime.sh diff --git a/.github/workflows/_ci-relay.yml b/.github/workflows/_ci-relay.yml index 2f1a97d8243..c52a6827770 100644 --- a/.github/workflows/_ci-relay.yml +++ b/.github/workflows/_ci-relay.yml @@ -229,7 +229,8 @@ jobs: break fi if [ "$attempt" -eq 3 ]; then - echo "docker compose up failed after 3 attempts" >&2 + echo "::error::dependency image/startup failure: docker compose up failed after 3 attempts" >&2 + ./scripts/diagnose-compose-runtime.sh postgres redis rustfs rustfs-init || true exit 1 fi echo "docker compose up failed (attempt $attempt), retrying in $((attempt * 5))s..." >&2 @@ -281,7 +282,8 @@ jobs: fi sleep 2 done - docker logs "${container}" || true + echo "::error::dependency readiness failure: ${service}" >&2 + ./scripts/diagnose-compose-runtime.sh postgres redis rustfs rustfs-init || true return 1 } wait_healthy "Postgres" "buzz-postgres" @@ -303,7 +305,8 @@ jobs: fi sleep 2 done - docker logs "${container}" || true + echo "::error::dependency initialization failure: ${service}" >&2 + ./scripts/diagnose-compose-runtime.sh postgres redis rustfs rustfs-init || true return 1 } wait_completed "RustFS bucket initialization" "buzz-rustfs-init" @@ -444,7 +447,8 @@ jobs: break fi if [ "$attempt" -eq 3 ]; then - echo "docker compose up failed after 3 attempts" >&2 + echo "::error::dependency image/startup failure: docker compose up failed after 3 attempts" >&2 + ./scripts/diagnose-compose-runtime.sh postgres redis rustfs rustfs-init || true exit 1 fi echo "docker compose up failed (attempt $attempt), retrying in $((attempt * 5))s..." >&2 @@ -463,7 +467,8 @@ jobs: fi sleep 2 done - docker logs "${container}" || true + echo "::error::dependency readiness failure: ${service}" >&2 + ./scripts/diagnose-compose-runtime.sh postgres redis rustfs rustfs-init || true return 1 } wait_healthy "Postgres" "buzz-postgres" @@ -485,7 +490,8 @@ jobs: fi sleep 2 done - docker logs "${container}" || true + echo "::error::dependency initialization failure: ${service}" >&2 + ./scripts/diagnose-compose-runtime.sh postgres redis rustfs rustfs-init || true return 1 } wait_completed "RustFS bucket initialization" "buzz-rustfs-init" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94336011cb3..7bf9d29e380 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,6 +52,8 @@ jobs: - 'scripts/check-postgres-test-discovery.py' - 'scripts/test-postgres-test-discovery.sh' - 'scripts/test-postgres-test-wrapper.sh' + - 'scripts/start-relay-for-tests.sh' + - 'scripts/diagnose-compose-runtime.sh' - 'deny.toml' - '.github/workflows/ci.yml' - '.github/workflows/_ci-*.yml' diff --git a/.github/workflows/mesh-lifecycle.yml b/.github/workflows/mesh-lifecycle.yml index ee16f1142dd..172c09a8fa9 100644 --- a/.github/workflows/mesh-lifecycle.yml +++ b/.github/workflows/mesh-lifecycle.yml @@ -21,6 +21,7 @@ on: - 'desktop/src-tauri/src/mesh_llm/**' - 'scripts/ci-mesh-lifecycle-smoke.sh' - 'scripts/start-relay-for-tests.sh' + - 'scripts/diagnose-compose-runtime.sh' - '.github/workflows/mesh-lifecycle.yml' - 'docker-compose.yml' - 'docker-compose.ci.yml' @@ -35,6 +36,7 @@ on: - 'desktop/src-tauri/src/mesh_llm/**' - 'scripts/ci-mesh-lifecycle-smoke.sh' - 'scripts/start-relay-for-tests.sh' + - 'scripts/diagnose-compose-runtime.sh' - '.github/workflows/mesh-lifecycle.yml' - 'docker-compose.yml' - 'docker-compose.ci.yml' @@ -87,7 +89,8 @@ jobs: break fi if [ "$attempt" -eq 3 ]; then - echo "docker compose up failed after 3 attempts" >&2 + echo "::error::dependency image/startup failure: docker compose up failed after 3 attempts" >&2 + ./scripts/diagnose-compose-runtime.sh postgres redis rustfs rustfs-init || true exit 1 fi echo "docker compose up failed (attempt $attempt), retrying in $((attempt * 5))s..." >&2 diff --git a/scripts/diagnose-compose-runtime.sh b/scripts/diagnose-compose-runtime.sh new file mode 100755 index 00000000000..4ab46942e57 --- /dev/null +++ b/scripts/diagnose-compose-runtime.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Print secret-safe state and recent logs for Compose dependencies. +set -u + +services=("$@") +if [[ "${#services[@]}" -eq 0 ]]; then + services=(postgres redis rustfs rustfs-init) +fi + +echo "::group::Buzz dependency container state" +docker compose ps -a "${services[@]}" 2>&1 || true +echo "::endgroup::" + +echo "::group::Buzz dependency logs (last 200 lines)" +docker compose logs --no-color --tail=200 "${services[@]}" 2>&1 || true +echo "::endgroup::" diff --git a/scripts/run-desktop-release-smoke.sh b/scripts/run-desktop-release-smoke.sh index e960ea8ee4f..af3c654ac94 100755 --- a/scripts/run-desktop-release-smoke.sh +++ b/scripts/run-desktop-release-smoke.sh @@ -35,6 +35,9 @@ phase() { cleanup() { local status=$? trap - EXIT INT TERM + if [[ "${status}" -ne 0 ]]; then + "${ROOT}/scripts/diagnose-compose-runtime.sh" postgres redis rustfs rustfs-init || true + fi if [[ -n "${RELAY_PID}" ]]; then kill "${RELAY_PID}" 2>/dev/null || true for _ in $(seq 1 50); do diff --git a/scripts/start-relay-for-tests.sh b/scripts/start-relay-for-tests.sh index b102b427f55..012989f41f5 100755 --- a/scripts/start-relay-for-tests.sh +++ b/scripts/start-relay-for-tests.sh @@ -61,7 +61,11 @@ err() { echo -e "${RED}[relay-test]${NC} $*" >&2; } cd "${REPO_ROOT}" log "Starting docker compose services..." -docker compose up -d postgres redis rustfs rustfs-init +if ! docker compose up -d postgres redis rustfs rustfs-init; then + err "dependency image/startup failure while starting Compose services" + "${SCRIPT_DIR}/diagnose-compose-runtime.sh" postgres redis rustfs rustfs-init || true + exit 1 +fi # ── Wait for services to be healthy ────────────────────────────────────────── @@ -78,7 +82,8 @@ wait_healthy() { sleep 2 done err "${service} did not become healthy within 120s" - docker logs "${container}" || true + err "dependency readiness failure: ${service}" + "${SCRIPT_DIR}/diagnose-compose-runtime.sh" postgres redis rustfs rustfs-init || true return 1 } @@ -101,7 +106,8 @@ wait_completed() { sleep 2 done err "${service} did not complete within 120s" - docker logs "${container}" || true + err "dependency initialization failure: ${service}" + "${SCRIPT_DIR}/diagnose-compose-runtime.sh" postgres redis rustfs rustfs-init || true return 1 }