Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 55 additions & 9 deletions .github/workflows/_ci-relay.yml
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,12 @@ 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
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
Expand Down Expand Up @@ -281,12 +282,34 @@ 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"
wait_healthy "Redis" "buzz-redis"
wait_healthy "MinIO" "buzz-minio"
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
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"
- name: Download relay binary
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
Expand Down Expand Up @@ -420,11 +443,12 @@ 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
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
Expand All @@ -443,12 +467,34 @@ 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"
wait_healthy "Redis" "buzz-redis"
wait_healthy "MinIO" "buzz-minio"
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
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"
- name: Download relay artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
Expand Down Expand Up @@ -604,7 +650,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.
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/mesh-lifecycle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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'
Expand Down Expand Up @@ -83,11 +85,12 @@ 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
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
Expand Down
2 changes: 1 addition & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/` |
Expand Down Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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) │ └─────────────┘ └─────────────┘
└──────────────┘
Expand Down
2 changes: 1 addition & 1 deletion VISION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 5 additions & 5 deletions crates/buzz-media/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
Expand Down Expand Up @@ -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").
Expand All @@ -60,11 +60,11 @@ 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,
/// 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.
Expand Down
4 changes: 2 additions & 2 deletions crates/buzz-media/src/storage.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! S3/MinIO storage client.
//! S3-compatible storage client.

use std::collections::HashMap;
use std::path::Path;
Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions crates/buzz-media/tests/static_creds_minio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down
Loading
Loading