From f34ab2e27f10e0dc55a2fbe43e3398e6182b1c7c Mon Sep 17 00:00:00 2001 From: xiaomage Date: Mon, 20 Jul 2026 15:48:46 +0800 Subject: [PATCH 01/13] refactor rustfs docs --- .agents/skills/rustfs-docs/SKILL.md | 4 +- AGENTS.md | 2 +- STYLE.md | 2 +- content/administration/iam/meta.json | 1 - content/administration/index.md | 16 ++ content/administration/meta.json | 12 ++ content/administration/security/audit-logs.md | 8 + .../security/encryption/index.md | 14 ++ .../administration/security/encryption/kms.md | 12 ++ .../security/encryption/meta.json | 8 + .../security/encryption/sse-c.md | 12 ++ .../security/encryption/sse-s.md | 12 ++ .../administration/security/oidc/meta.json | 7 + .../security/sts-token-service/index.md | 16 ++ .../security/sts-token-service/meta.json | 4 + content/developer/index.md | 2 +- content/developer/meta.json | 8 + content/index.mdx | 6 +- content/installation/checklists/meta.json | 10 -- .../installation/cloud-native/helm-chart.md | 60 +++++++ content/installation/cloud-native/index.md | 6 +- content/installation/cloud-native/meta.json | 7 + content/installation/cloud-native/operator.md | 99 ++++++++++++ content/installation/console-first-steps.md | 16 +- content/installation/container/docker.md | 55 +++++++ .../{docker => container}/index.mdx | 0 content/installation/container/meta.json | 7 + content/installation/container/podman.md | 54 +++++++ content/installation/index.md | 10 +- content/installation/linux/index.md | 2 +- .../linux/multiple-node-multiple-disk.md | 4 +- content/installation/linux/quick-start.md | 4 +- .../linux/single-node-multiple-disk.md | 2 +- .../linux/single-node-single-disk.md | 2 +- content/installation/meta.json | 11 ++ .../checklists/hardware-checklists.md | 0 .../checklists/hardware-selection.md | 0 .../{ => requirement}/checklists/index.md | 4 +- .../requirement/checklists/meta.json | 10 ++ .../checklists/network-checklists.md | 0 .../checklists/security-checklists.md | 0 .../checklists/software-checklists.md | 0 .../requirement/disk-preparation.md | 72 +++++++++ content/installation/requirement/meta.json | 9 ++ content/management/bucket/index.md | 11 -- content/management/bucket/meta.json | 7 - content/management/cors/index.md | 48 ++++++ .../management/{ => data}/bucket/creation.md | 4 +- .../management/{ => data}/bucket/deletion.md | 2 +- .../bucket/images/bucket-creation-by-ui.png | Bin .../bucket/images/bucket-deletion-on-ui.png | Bin content/management/data/bucket/meta.json | 10 ++ content/management/data/bucket/policy.md | 12 ++ content/management/data/bucket/quota.md | 12 ++ content/management/data/bucket/replication.md | 12 ++ content/management/data/data-tiering.md | 12 ++ .../management/data/lifecycle-management.md | 12 ++ content/management/data/meta.json | 9 ++ .../management/{ => data}/object/creation.md | 6 +- .../management/{ => data}/object/deletion.md | 2 +- .../object/images/delete_file_from_ui.png | Bin .../object/images/object_details_info.png | Bin .../object/images/upload_file_from_ui.png | Bin content/management/data/object/meta.json | 11 ++ .../data/object/multipart-upload.md | 12 ++ content/management/data/object/object-lock.md | 12 ++ .../management/{ => data}/object/scanner.md | 2 +- content/management/data/object/versioning.md | 12 ++ content/management/index.md | 11 +- content/management/meta.json | 10 ++ content/management/object/index.md | 13 -- content/management/object/meta.json | 7 - content/management/protocols/ftps.md | 12 ++ content/management/protocols/meta.json | 9 ++ content/management/protocols/s3.md | 8 + content/management/protocols/webdav.md | 12 ++ content/meta.json | 69 ++------ content/operations/cluster-health-check.md | 21 +++ content/operations/cold-start.md | 88 ---------- content/operations/event-notifications.md | 18 +++ content/operations/high-availability/index.md | 7 + .../operations/high-availability/meta.json | 7 + .../high-availability/node-healing.md} | 2 +- .../high-availability/site-replication.md | 12 ++ content/operations/index.md | 16 ++ content/operations/meta.json | 12 ++ .../{monitoring.md => observability.md} | 4 +- .../operations/scaling/data-rebalancing.md | 8 + content/operations/scaling/index.md | 10 ++ content/operations/scaling/meta.json | 8 + .../storage-pool-decommission.md} | 2 +- .../scaling/storage-pool-expansion.md} | 4 +- content/operations/upgrade/binary/index.md | 50 ++++++ content/operations/upgrade/binary/meta.json | 4 + content/operations/upgrade/container/index.md | 30 ++++ .../operations/upgrade/container/meta.json | 4 + content/operations/upgrade/index.md | 14 ++ .../operations/upgrade/kubernetes/index.md | 34 ++++ .../operations/upgrade/kubernetes/meta.json | 4 + content/operations/upgrade/meta.json | 8 + content/reference/environment-variables.md | 9 ++ content/reference/index.md | 15 ++ content/reference/meta.json | 11 ++ content/reference/metrics.md | 2 +- content/troubleshooting/index.md | 2 +- content/troubleshooting/meta.json | 7 + content/upgrade-scale/index.md | 12 -- content/upgrade-scale/upgrade.md | 150 ------------------ 108 files changed, 1115 insertions(+), 409 deletions(-) create mode 100644 content/administration/index.md create mode 100644 content/administration/meta.json create mode 100644 content/administration/security/audit-logs.md create mode 100644 content/administration/security/encryption/index.md create mode 100644 content/administration/security/encryption/kms.md create mode 100644 content/administration/security/encryption/meta.json create mode 100644 content/administration/security/encryption/sse-c.md create mode 100644 content/administration/security/encryption/sse-s.md create mode 100644 content/administration/security/oidc/meta.json create mode 100644 content/administration/security/sts-token-service/index.md create mode 100644 content/administration/security/sts-token-service/meta.json create mode 100644 content/developer/meta.json delete mode 100644 content/installation/checklists/meta.json create mode 100644 content/installation/cloud-native/helm-chart.md create mode 100644 content/installation/cloud-native/meta.json create mode 100644 content/installation/cloud-native/operator.md create mode 100644 content/installation/container/docker.md rename content/installation/{docker => container}/index.mdx (100%) create mode 100644 content/installation/container/meta.json create mode 100644 content/installation/container/podman.md create mode 100644 content/installation/meta.json rename content/installation/{ => requirement}/checklists/hardware-checklists.md (100%) rename content/installation/{ => requirement}/checklists/hardware-selection.md (100%) rename content/installation/{ => requirement}/checklists/index.md (94%) create mode 100644 content/installation/requirement/checklists/meta.json rename content/installation/{ => requirement}/checklists/network-checklists.md (100%) rename content/installation/{ => requirement}/checklists/security-checklists.md (100%) rename content/installation/{ => requirement}/checklists/software-checklists.md (100%) create mode 100644 content/installation/requirement/disk-preparation.md create mode 100644 content/installation/requirement/meta.json delete mode 100644 content/management/bucket/index.md delete mode 100644 content/management/bucket/meta.json create mode 100644 content/management/cors/index.md rename content/management/{ => data}/bucket/creation.md (87%) rename content/management/{ => data}/bucket/deletion.md (94%) rename content/management/{ => data}/bucket/images/bucket-creation-by-ui.png (100%) rename content/management/{ => data}/bucket/images/bucket-deletion-on-ui.png (100%) create mode 100644 content/management/data/bucket/meta.json create mode 100644 content/management/data/bucket/policy.md create mode 100644 content/management/data/bucket/quota.md create mode 100644 content/management/data/bucket/replication.md create mode 100644 content/management/data/data-tiering.md create mode 100644 content/management/data/lifecycle-management.md create mode 100644 content/management/data/meta.json rename content/management/{ => data}/object/creation.md (88%) rename content/management/{ => data}/object/deletion.md (93%) rename content/management/{ => data}/object/images/delete_file_from_ui.png (100%) rename content/management/{ => data}/object/images/object_details_info.png (100%) rename content/management/{ => data}/object/images/upload_file_from_ui.png (100%) create mode 100644 content/management/data/object/meta.json create mode 100644 content/management/data/object/multipart-upload.md create mode 100644 content/management/data/object/object-lock.md rename content/management/{ => data}/object/scanner.md (97%) create mode 100644 content/management/data/object/versioning.md create mode 100644 content/management/meta.json delete mode 100644 content/management/object/index.md delete mode 100644 content/management/object/meta.json create mode 100644 content/management/protocols/ftps.md create mode 100644 content/management/protocols/meta.json create mode 100644 content/management/protocols/s3.md create mode 100644 content/management/protocols/webdav.md create mode 100644 content/operations/cluster-health-check.md delete mode 100644 content/operations/cold-start.md create mode 100644 content/operations/event-notifications.md create mode 100644 content/operations/high-availability/index.md create mode 100644 content/operations/high-availability/meta.json rename content/{troubleshooting/healing.md => operations/high-availability/node-healing.md} (98%) create mode 100644 content/operations/high-availability/site-replication.md create mode 100644 content/operations/index.md create mode 100644 content/operations/meta.json rename content/operations/{monitoring.md => observability.md} (98%) create mode 100644 content/operations/scaling/data-rebalancing.md create mode 100644 content/operations/scaling/index.md create mode 100644 content/operations/scaling/meta.json rename content/operations/{decommission.md => scaling/storage-pool-decommission.md} (99%) rename content/{upgrade-scale/availability-and-resiliency.md => operations/scaling/storage-pool-expansion.md} (98%) create mode 100644 content/operations/upgrade/binary/index.md create mode 100644 content/operations/upgrade/binary/meta.json create mode 100644 content/operations/upgrade/container/index.md create mode 100644 content/operations/upgrade/container/meta.json create mode 100644 content/operations/upgrade/index.md create mode 100644 content/operations/upgrade/kubernetes/index.md create mode 100644 content/operations/upgrade/kubernetes/meta.json create mode 100644 content/operations/upgrade/meta.json create mode 100644 content/reference/index.md create mode 100644 content/reference/meta.json create mode 100644 content/troubleshooting/meta.json delete mode 100644 content/upgrade-scale/index.md delete mode 100644 content/upgrade-scale/upgrade.md diff --git a/.agents/skills/rustfs-docs/SKILL.md b/.agents/skills/rustfs-docs/SKILL.md index 25806e04..bf8c0c66 100644 --- a/.agents/skills/rustfs-docs/SKILL.md +++ b/.agents/skills/rustfs-docs/SKILL.md @@ -12,8 +12,8 @@ description: >- # Writing RustFS documentation This is a **FumaPress** site (Waku + Fumadocs). Pages live in `content/`; each -page's URL mirrors its path (`content/management/bucket/creation.md` → -`/management/bucket/creation`). Navigation is defined by `content/meta.json` +page's URL mirrors its path (`content/management/data/bucket/creation.md` → +`/management/data/bucket/creation`). Navigation is defined by `content/meta.json` (root sidebar) and per-folder `meta.json` files. Read `STYLE.md` (repo root) for the full style guide and `AGENTS.md` for repo diff --git a/AGENTS.md b/AGENTS.md index b7997898..c6dab144 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,7 @@ This playbook directs AI agents working in the RustFS documentation repository s - Framework: **FumaPress** (static-site generator powered by Waku + Fumadocs). Content lives in `content/`; site configuration is in `press.config.tsx`, `source.config.ts`, and `waku.config.ts`. - Goal: produce documentation for a distributed object storage product aimed at a global audience, currently English-first with room for other locales. - Navigation: `content/meta.json` (root sidebar: section order, labels, links) and per-folder `content/**/meta.json` (nested group titles/order) define the site structure. New pages must be reflected there immediately. -- Routing: a page's URL mirrors its path under `content/` (e.g. `content/management/bucket/index.md` → `/management/bucket`). `.md` links between pages are resolved automatically; you can write `./sibling.md` or `/absolute/path`. +- Routing: a page's URL mirrors its path under `content/` (e.g. `content/management/data/bucket/creation.md` → `/management/data/bucket/creation`). `.md` links between pages are resolved automatically; you can write `./sibling.md` or `/absolute/path`. ## 2. Core Principles 1. **Accuracy**: Data, APIs, and commands must be reproducible; cite third-party information with a concise source note. diff --git a/STYLE.md b/STYLE.md index 68b5a2cf..7c9b4733 100644 --- a/STYLE.md +++ b/STYLE.md @@ -94,6 +94,6 @@ RUSTFS_VOLUMES="/data" ## Product Terminology - The product is **RustFS** (capital R, capital FS) — never "rustFS", "Rustfs", or "RUSTFS". -- RustFS ships as a single binary and an official **Helm chart** for Kubernetes. There is **no Operator, no Tenant CRD, and no KES component** — do not reference them. +- RustFS ships as a single binary and an official **Helm chart** for Kubernetes. The separate official **RustFS Operator** repository provides a pre-release `Tenant` CRD; verify its current release status before documenting it. RustFS has no KES component. - Key management is the **built-in KMS** with `local`, `vault`, and `vault-transit` backends. - Observability is **OpenTelemetry (OTLP)**-based: metrics, logs, and traces export through an OTLP endpoint. diff --git a/content/administration/iam/meta.json b/content/administration/iam/meta.json index 8987166c..5c55fb55 100644 --- a/content/administration/iam/meta.json +++ b/content/administration/iam/meta.json @@ -1,7 +1,6 @@ { "title": "IAM", "pages": [ - "[Overview](/administration/iam)", "[Users, Groups, and Policies](/administration/iam/policies)", "[Service Accounts and STS](/administration/iam/sts)", "[External Identity (OIDC)](/administration/iam/oidc)", diff --git a/content/administration/index.md b/content/administration/index.md new file mode 100644 index 00000000..589b709e --- /dev/null +++ b/content/administration/index.md @@ -0,0 +1,16 @@ +--- +title: "Security & Compliance" +description: "Configure identity, encryption, transport security, audit logging, and credentials for RustFS." +--- + +Use this section to secure access to RustFS and configure the controls required by your environment. + +## Security areas + +- [IAM Management](./iam/index.md) covers users, groups, policies, and access keys. +- [Data Encryption](./security/encryption/index.md) covers server-side encryption options and key management. +- [STS Token Service](./security/sts-token-service/index.md) covers temporary credentials. +- [OIDC](./iam/oidc.md) covers external identity providers. +- [TLS Configuration](/integration/tls-configured) covers encrypted client and node connections. +- [Audit Logs](./security/audit-logs.md) covers audit target configuration. +- [Credential Management](/operations/credentials) covers root credentials and secret injection. \ No newline at end of file diff --git a/content/administration/meta.json b/content/administration/meta.json new file mode 100644 index 00000000..43b82022 --- /dev/null +++ b/content/administration/meta.json @@ -0,0 +1,12 @@ +{ + "title": "Security & Compliance", + "pages": [ + "[IAM Management](/administration/iam)", + "security/encryption", + "security/sts-token-service", + "security/oidc", + "[TLS Configuration](/integration/tls-configured)", + "[Audit Logs](/administration/security/audit-logs)", + "[Credential Management](/operations/credentials)" + ] +} \ No newline at end of file diff --git a/content/administration/security/audit-logs.md b/content/administration/security/audit-logs.md new file mode 100644 index 00000000..49a57623 --- /dev/null +++ b/content/administration/security/audit-logs.md @@ -0,0 +1,8 @@ +--- +title: "Audit Logs" +description: "Find the verified RustFS environment variables for configuring audit log targets." +--- + +RustFS audit logging is configured through the `RUSTFS_AUDIT_*` environment-variable family. + +See the [environment variable reference](/reference/environment-variables#audit-and-notification-targets) for the verified target pattern and supported target families. End-to-end collector examples will be added after runtime validation. \ No newline at end of file diff --git a/content/administration/security/encryption/index.md b/content/administration/security/encryption/index.md new file mode 100644 index 00000000..fa390c58 --- /dev/null +++ b/content/administration/security/encryption/index.md @@ -0,0 +1,14 @@ +--- +title: "Data Encryption" +description: "Find the verified RustFS server-side encryption and key management references." +--- + +RustFS provides server-side encryption options for data stored in the object storage service. Use this section to choose the key-management model that matches your security requirements. + +## Encryption options + +- [SSE-S](./sse-s.md) covers server-managed server-side encryption. +- [SSE-C](./sse-c.md) covers server-side encryption with customer-provided keys. +- [KMS](./kms.md) covers server-side encryption backed by a Key Management Service (KMS). + +For the currently verified KMS configuration surface, see the [CLI reference](/reference/cli) and [environment variable reference](/reference/environment-variables). \ No newline at end of file diff --git a/content/administration/security/encryption/kms.md b/content/administration/security/encryption/kms.md new file mode 100644 index 00000000..851739ac --- /dev/null +++ b/content/administration/security/encryption/kms.md @@ -0,0 +1,12 @@ +--- +title: "KMS" +description: "Find verified references for Key Management Service integration in RustFS." +--- + +RustFS exposes KMS-backed server-side encryption settings through its command-line interface and environment variables. + +Use the [CLI reference](/reference/cli) and [environment variable reference](/reference/environment-variables) for the currently verified configuration surface. A complete key-management workflow will be added after runtime validation. + +## Next steps + +Compare [SSE-S](./sse-s.md) and [SSE-C](./sse-c.md) before selecting an encryption model. \ No newline at end of file diff --git a/content/administration/security/encryption/meta.json b/content/administration/security/encryption/meta.json new file mode 100644 index 00000000..a288b978 --- /dev/null +++ b/content/administration/security/encryption/meta.json @@ -0,0 +1,8 @@ +{ + "title": "Data Encryption", + "pages": [ + "sse-s", + "sse-c", + "kms" + ] +} \ No newline at end of file diff --git a/content/administration/security/encryption/sse-c.md b/content/administration/security/encryption/sse-c.md new file mode 100644 index 00000000..1923bacc --- /dev/null +++ b/content/administration/security/encryption/sse-c.md @@ -0,0 +1,12 @@ +--- +title: "SSE-C" +description: "Learn about server-side encryption with customer-provided keys in RustFS." +--- + +SSE-C is the section for server-side encryption where you provide the encryption key with the object request. + +Detailed configuration guidance will be added after the workflow has been validated against a current RustFS release. + +## Next steps + +Compare [SSE-S](./sse-s.md) and [KMS](./kms.md) before selecting an encryption model. \ No newline at end of file diff --git a/content/administration/security/encryption/sse-s.md b/content/administration/security/encryption/sse-s.md new file mode 100644 index 00000000..8a8ba1a1 --- /dev/null +++ b/content/administration/security/encryption/sse-s.md @@ -0,0 +1,12 @@ +--- +title: "SSE-S" +description: "Learn about server-managed server-side encryption in RustFS." +--- + +SSE-S is the section for server-side encryption where RustFS manages the encryption keys used for stored objects. + +Detailed configuration guidance will be added after the workflow has been validated against a current RustFS release. + +## Next steps + +Compare [SSE-C](./sse-c.md) and [KMS](./kms.md) before selecting an encryption model. \ No newline at end of file diff --git a/content/administration/security/oidc/meta.json b/content/administration/security/oidc/meta.json new file mode 100644 index 00000000..414b32c6 --- /dev/null +++ b/content/administration/security/oidc/meta.json @@ -0,0 +1,7 @@ +{ + "title": "OIDC", + "pages": [ + "[Keycloak](/administration/iam/oidc#example-keycloak)", + "[Authing](/administration/iam/oidc#example-authing)" + ] +} \ No newline at end of file diff --git a/content/administration/security/sts-token-service/index.md b/content/administration/security/sts-token-service/index.md new file mode 100644 index 00000000..54c0db57 --- /dev/null +++ b/content/administration/security/sts-token-service/index.md @@ -0,0 +1,16 @@ +--- +title: "STS Token Service" +description: "Learn how RustFS issues temporary credentials through the Security Token Service." +--- + +The RustFS Security Token Service (STS) issues temporary credentials for existing IAM identities and external OpenID Connect (OIDC) identities. + +## STS workflows + +RustFS supports temporary credential workflows through `AssumeRole` and `AssumeRoleWithWebIdentity`. See [Service Accounts and STS](/administration/iam/sts) for the verified request parameters, credential behavior, and usage guidance. + +For identity-provider configuration, see [External Identity (OIDC)](/administration/security/oidc). + +## Next steps + +Review [IAM Management](/administration/iam) to understand how RustFS evaluates policies for temporary credentials. \ No newline at end of file diff --git a/content/administration/security/sts-token-service/meta.json b/content/administration/security/sts-token-service/meta.json new file mode 100644 index 00000000..d82ca4cd --- /dev/null +++ b/content/administration/security/sts-token-service/meta.json @@ -0,0 +1,4 @@ +{ + "title": "STS Token Service", + "pages": [] +} \ No newline at end of file diff --git a/content/developer/index.md b/content/developer/index.md index 3e4eeafb..a33f9180 100644 --- a/content/developer/index.md +++ b/content/developer/index.md @@ -1,5 +1,5 @@ --- -title: RustFS Developer Guide +title: Developer description: Guide to using STS, MCP, MinIO Client, SDKs, and APIs with RustFS. --- diff --git a/content/developer/meta.json b/content/developer/meta.json new file mode 100644 index 00000000..fce91b2b --- /dev/null +++ b/content/developer/meta.json @@ -0,0 +1,8 @@ +{ + "title": "Developer", + "pages": [ + "mc", + "sdk", + "license" + ] +} \ No newline at end of file diff --git a/content/index.mdx b/content/index.mdx index 140242e3..5de0e36c 100644 --- a/content/index.mdx +++ b/content/index.mdx @@ -11,7 +11,7 @@ RustFS is a high-performance, distributed object storage system written in Rust, } title="Quick Start" href="/installation/linux/quick-start"> One command to install, log in to the Console, and store your first object. - } title="Run with Docker" href="/installation/docker"> + } title="Run with Docker" href="/installation/container"> A single `docker run` for local evaluation — no server required. @@ -38,10 +38,10 @@ RustFS is a high-performance, distributed object storage system written in Rust, } title="Installation Guide" href="/installation/"> Linux (single-node to multi-node), Docker, Kubernetes, Windows, macOS. - } title="Production Checklists" href="/installation/checklists/hardware-checklists"> + } title="Checklist" href="/installation/requirement/checklists/hardware-checklists"> Hardware, network, software, and security prep. - } title="Operations" href="/upgrade-scale/upgrade"> + } title="Operations" href="/operations/upgrade"> Rolling upgrades, scaling, troubleshooting, and data healing. diff --git a/content/installation/checklists/meta.json b/content/installation/checklists/meta.json deleted file mode 100644 index 97bdf2a8..00000000 --- a/content/installation/checklists/meta.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "title": "Production Checklists", - "pages": [ - "[Hardware Checklist](/installation/checklists/hardware-checklists)", - "[Hardware Selection](/installation/checklists/hardware-selection)", - "[Network Checklist](/installation/checklists/network-checklists)", - "[Software Checklist](/installation/checklists/software-checklists)", - "[Security Checklist](/installation/checklists/security-checklists)" - ] -} diff --git a/content/installation/cloud-native/helm-chart.md b/content/installation/cloud-native/helm-chart.md new file mode 100644 index 00000000..c7a96aba --- /dev/null +++ b/content/installation/cloud-native/helm-chart.md @@ -0,0 +1,60 @@ +--- +title: "Helm Chart Installation" +description: "Deploy a standalone or distributed RustFS cluster on Kubernetes with the official Helm chart." +--- + +Use the official RustFS Helm chart when you want Helm to manage one RustFS deployment directly. You need a Kubernetes cluster, `kubectl`, Helm 3, and a StorageClass that can provision the required persistent volumes. + +## 1. Get the chart + +The chart is stored in the RustFS source repository under `helm/rustfs`: + +```bash +git clone https://github.com/rustfs/rustfs.git +cd rustfs/helm/rustfs +``` + +## 2. Install RustFS + +Install the chart in a dedicated namespace and replace the credential placeholders before running the command: + +```bash +helm install rustfs . \ + --namespace rustfs \ + --create-namespace \ + --set secret.rustfs.access_key= \ + --set secret.rustfs.secret_key= \ + --set storageclass.dataStorageSize=100Gi \ + --set storageclass.logStorageSize=1Gi +``` + +The chart deploys distributed mode by default. For a development-only standalone deployment, add these values: + +```bash +--set mode.standalone.enabled=true \ +--set mode.distributed.enabled=false +``` + +## 3. Verify the deployment + +Wait for the RustFS pods to become ready: + +```bash +kubectl -n rustfs get pods -w +``` + +Without an Ingress, forward the S3 API and Console services to your workstation: + +```bash +kubectl -n rustfs port-forward svc/rustfs 9000:9000 9001:9001 +``` + +The S3 API is available at `http://localhost:9000`, and the Console is available at `http://localhost:9001`. + +For storage sizing, probes, ingress, TLS, server pools, and uninstall steps, see the [complete Kubernetes Helm guide](/installation/cloud-native). + +## Next steps + +- [Install with the RustFS Operator](/installation/cloud-native/operator) +- [Configure TLS](/integration/tls-configured) +- [Observe RustFS](/operations/observability) \ No newline at end of file diff --git a/content/installation/cloud-native/index.md b/content/installation/cloud-native/index.md index f55a2a6b..c756353e 100644 --- a/content/installation/cloud-native/index.md +++ b/content/installation/cloud-native/index.md @@ -146,7 +146,7 @@ Log in to the Console with the access key and secret key you set at install time ## 7. Scaling out with server pools -In distributed mode the chart can run multiple **server pools** — independent StatefulSets whose drives together form one cluster. This is the chart-level equivalent of adding a Server Pool as described in [Availability and Scalability](../../upgrade-scale/availability-and-resiliency.md). +In distributed mode the chart can run multiple **server pools** — independent StatefulSets whose drives together form one cluster. This is the chart-level equivalent of adding a Server Pool as described in [Pool Expansion](../../operations/scaling/storage-pool-expansion.md). To expand an existing deployment, enable pools and describe the current layout as pool 0 plus your new capacity: @@ -195,6 +195,6 @@ Helm does not delete PVCs created by StatefulSet volume claim templates. If you ## Next steps -- [Availability and Scalability](../../upgrade-scale/availability-and-resiliency.md) — how Server Pool expansion works at the cluster level -- [Upgrade](../../upgrade-scale/upgrade.md) — zero-downtime rolling upgrades +- [Pool Expansion](../../operations/scaling/storage-pool-expansion.md) — how Server Pool expansion works at the cluster level +- [Kubernetes Upgrade](../../operations/upgrade/kubernetes/index.md) — upgrade Helm- and Operator-managed deployments - [TLS configuration](../../integration/tls-configured.md) — end-to-end TLS options diff --git a/content/installation/cloud-native/meta.json b/content/installation/cloud-native/meta.json new file mode 100644 index 00000000..8e024ab7 --- /dev/null +++ b/content/installation/cloud-native/meta.json @@ -0,0 +1,7 @@ +{ + "title": "Cloud Native", + "pages": [ + "[Helm Chart](/installation/cloud-native/helm-chart)", + "[Operator](/installation/cloud-native/operator)" + ] +} diff --git a/content/installation/cloud-native/operator.md b/content/installation/cloud-native/operator.md new file mode 100644 index 00000000..b4f25404 --- /dev/null +++ b/content/installation/cloud-native/operator.md @@ -0,0 +1,99 @@ +--- +title: "Operator Installation" +description: "Install the RustFS Kubernetes Operator and create a development Tenant custom resource." +--- + +Use the RustFS Kubernetes Operator when you want to manage RustFS clusters as namespaced `Tenant` custom resources. The Operator reconciles the RBAC, Services, StatefulSets, and persistent volume claims required by each Tenant. + +:::warning[Pre-release software] + +The RustFS Operator is currently `v0.1.0` pre-release and under active development. Evaluate it in a non-production cluster and review the [upstream Operator repository](https://github.com/rustfs/operator) before adopting it. + +::: + +You need Kubernetes 1.30 or newer, Helm 3, `kubectl` access to the target cluster, and a StorageClass that can satisfy Tenant persistent volume claims. + +## 1. Get the Operator chart + +Clone the official Operator repository. Its Helm chart is stored under `deploy/rustfs-operator`: + +```bash +git clone https://github.com/rustfs/operator.git +cd operator +``` + +## 2. Install the Operator + +Install the Operator and its custom resource definitions in the `rustfs-system` namespace: + +```bash +helm install rustfs-operator deploy/rustfs-operator/ \ + --namespace rustfs-system \ + --create-namespace +``` + +Verify the Operator and Console pods: + +```bash +kubectl get pods -n rustfs-system +kubectl logs -n rustfs-system \ + -l app.kubernetes.io/name=rustfs-operator,app.kubernetes.io/component=operator +``` + +## 3. Create a development Tenant + +Create a minimal single-node Tenant for evaluation: + +```yaml title="tenant.yaml" +apiVersion: rustfs.com/v1alpha1 +kind: Tenant +metadata: + name: dev-minimal + namespace: default +spec: + image: rustfs/rustfs:latest + pools: + - name: dev-pool + servers: 1 + persistence: + volumesPerServer: 1 +``` + +:::warning[Development credentials] + +This minimal manifest does not configure a credential Secret and is suitable only for local evaluation. For production-style testing, create a Kubernetes Secret containing `accesskey` and `secretkey`, then reference it with `spec.credsSecret.name`. + +::: + +Apply the Tenant and wait for its pod to become ready: + +```bash +kubectl apply -f tenant.yaml +kubectl get tenant dev-minimal +kubectl get pods,pvc,svc -l rustfs.tenant=dev-minimal +kubectl wait --for=condition=ready pod \ + -l rustfs.tenant=dev-minimal \ + --timeout=300s +``` + +## 4. Access the Tenant + +Forward the Tenant S3 API: + +```bash +kubectl port-forward svc/dev-minimal-io 9000:9000 +``` + +In another terminal, forward the Tenant Console: + +```bash +kubectl port-forward svc/dev-minimal-console 9001:9001 +``` + +The S3 API is available at `http://localhost:9000`, and the Tenant Console is available at `http://localhost:9001`. + +## Next steps + +- Review the [Operator user guide](https://github.com/rustfs/operator/blob/main/docs/operator-user-guide.md) +- Review the [Tenant examples](https://github.com/rustfs/operator/tree/main/examples) +- [Configure TLS](/integration/tls-configured) \ No newline at end of file diff --git a/content/installation/console-first-steps.md b/content/installation/console-first-steps.md index 4221a41d..687510e2 100644 --- a/content/installation/console-first-steps.md +++ b/content/installation/console-first-steps.md @@ -20,9 +20,9 @@ For a local test install, that is `http://127.0.0.1:9001`. Sign in with the credentials configured at install time: - If you used the [Linux quick-start script](./linux/quick-start.md), the access key and secret key were printed at the end of the installation output. -- If you started RustFS via Docker or a systemd unit, use the values of `RUSTFS_ACCESS_KEY` and `RUSTFS_SECRET_KEY` from your configuration (see the [Docker guide](./docker/index.md)). +- If you started RustFS via Docker or a systemd unit, use the values of `RUSTFS_ACCESS_KEY` and `RUSTFS_SECRET_KEY` from your configuration (see the [container guide](./container/index.mdx)). -> For production deployments, change any default credentials before exposing the console. See the [Security Checklist](./checklists/security-checklists.md). +> For production deployments, change any default credentials before exposing the console. See the [Security Checklist](./requirement/checklists/security-checklists.md). ## 3. A Quick Tour @@ -37,9 +37,9 @@ After signing in you land on the console home page, which shows: 1. On the Buckets page, in the top right corner, select **Create Bucket**. 2. Enter a bucket name (for example `my-first-bucket`) and click **Create**. -![bucket creation](../management/bucket/images/bucket-creation-by-ui.png) +![bucket creation](../management/data/bucket/images/bucket-creation-by-ui.png) -Full details, including `mc` and API alternatives, are in [Bucket Creation](../management/bucket/creation.md). +Full details, including `mc` and API alternatives, are in [Bucket Creation](../management/data/bucket/creation.md). ## 5. Upload Your First Object @@ -47,13 +47,13 @@ Full details, including `mc` and API alternatives, are in [Bucket Creation](../m 2. In the top right corner, select **Upload File/Folder**. 3. Choose one or more local files and click **Start Upload**. -![object creation from ui](../management/object/images/upload_file_from_ui.png) +![object creation from ui](../management/data/object/images/upload_file_from_ui.png) Click the uploaded object to view its details — size, ETag, content type, and a shareable link. -![object details info](../management/object/images/object_details_info.png) +![object details info](../management/data/object/images/object_details_info.png) -More options are covered in [Object Creation](../management/object/creation.md). +More options are covered in [Object Creation](../management/data/object/creation.md). ## 6. Create Access Keys for Applications @@ -63,4 +63,4 @@ The console sign-in credentials are administrator credentials — applications s - Point any S3-compatible client at `http://:9000` (the S3 API port) with your new access keys. - Explore [IAM management](../administration/iam/index.md) to add users and policies. -- Review the [production checklists](./checklists/index.md) before going live. +- Review the [production checklists](./requirement/checklists/index.md) before going live. diff --git a/content/installation/container/docker.md b/content/installation/container/docker.md new file mode 100644 index 00000000..ba31ccca --- /dev/null +++ b/content/installation/container/docker.md @@ -0,0 +1,55 @@ +--- +title: "Docker Installation" +description: "Run a single-node RustFS instance with Docker and persistent container storage." +--- + +Use the official RustFS image to start a single-node instance with persistent storage. You need a working Docker Engine and permission to run containers. + +## 1. Pull the image + +```bash +docker pull rustfs/rustfs:latest +``` + +## 2. Create persistent storage + +Create a named volume so object data remains available when you replace the container: + +```bash +docker volume create rustfs-data +``` + +## 3. Start RustFS + +Replace the credential placeholders before running the container: + +```bash +docker run -d \ + --name rustfs \ + --restart unless-stopped \ + -p 9000:9000 \ + -p 9001:9001 \ + -v rustfs-data:/data \ + -e RUSTFS_ACCESS_KEY="" \ + -e RUSTFS_SECRET_KEY="" \ + -e RUSTFS_CONSOLE_ENABLE=true \ + rustfs/rustfs:latest \ + /data +``` + +## 4. Verify the deployment + +Check the container and the S3 API health endpoint: + +```bash +docker ps --filter name=rustfs +curl --fail http://localhost:9000/health +``` + +The S3 API is available at `http://localhost:9000`, and the Console is available at `http://localhost:9001`. + +## Next steps + +- [Console first steps](../console-first-steps.md) +- [Configure an S3 client](../../developer/examples/aws-cli.md) +- [TLS configuration](../../integration/tls-configured.md) \ No newline at end of file diff --git a/content/installation/docker/index.mdx b/content/installation/container/index.mdx similarity index 100% rename from content/installation/docker/index.mdx rename to content/installation/container/index.mdx diff --git a/content/installation/container/meta.json b/content/installation/container/meta.json new file mode 100644 index 00000000..be4e8d9a --- /dev/null +++ b/content/installation/container/meta.json @@ -0,0 +1,7 @@ +{ + "title": "Container", + "pages": [ + "docker", + "podman" + ] +} \ No newline at end of file diff --git a/content/installation/container/podman.md b/content/installation/container/podman.md new file mode 100644 index 00000000..74636433 --- /dev/null +++ b/content/installation/container/podman.md @@ -0,0 +1,54 @@ +--- +title: "Podman Installation" +description: "Run a single-node RustFS instance with Podman and persistent container storage." +--- + +Podman can run the official RustFS Open Container Initiative (OCI) image without a daemon. You need a working Podman installation and permission to create containers. + +## 1. Pull the image + +```bash +podman pull docker.io/rustfs/rustfs:latest +``` + +## 2. Create persistent storage + +Create a named volume so object data remains available when you replace the container: + +```bash +podman volume create rustfs-data +``` + +## 3. Start RustFS + +Replace the credential placeholders before running the container: + +```bash +podman run -d \ + --name rustfs \ + -p 9000:9000 \ + -p 9001:9001 \ + -v rustfs-data:/data \ + -e RUSTFS_ACCESS_KEY="" \ + -e RUSTFS_SECRET_KEY="" \ + -e RUSTFS_CONSOLE_ENABLE=true \ + docker.io/rustfs/rustfs:latest \ + /data +``` + +## 4. Verify the deployment + +Check the container and the S3 API health endpoint: + +```bash +podman ps --filter name=rustfs +curl --fail http://localhost:9000/health +``` + +The S3 API is available at `http://localhost:9000`, and the Console is available at `http://localhost:9001`. + +## Next steps + +- [Console first steps](../console-first-steps.md) +- [Configure an S3 client](../../developer/examples/aws-cli.md) +- [TLS configuration](../../integration/tls-configured.md) \ No newline at end of file diff --git a/content/installation/index.md b/content/installation/index.md index 815916eb..4e36b849 100644 --- a/content/installation/index.md +++ b/content/installation/index.md @@ -1,5 +1,5 @@ --- -title: "RustFS Installation Guide" +title: "Installation" description: "Choose the right RustFS deployment path: local trial, single-node or multi-node production, containers, and Kubernetes." --- @@ -9,9 +9,9 @@ RustFS is a distributed object storage system written in Rust, fully compatible | Your goal | Recommended path | Guide | | - | - | - | -| Try RustFS on a local machine | One-command install script, or a container | [Linux Quick Start](./linux/quick-start.md) · [Docker](./docker/index.md) | +| Try RustFS on a local machine | One-command install script, or a container | [Linux Quick Start](./linux/quick-start.md) · [Container](./container/index.mdx) | | Single-server production | SNSD (one disk) or SNMD (multiple disks) | [Installing RustFS on Linux](./linux/index.md) | -| Multi-server production cluster | MNMD, after completing the production checklists | [Multiple Node Multiple Disk](./linux/multiple-node-multiple-disk.md) · [Checklists](./checklists/index.md) | +| Multi-server production cluster | MNMD, after completing the production checklists | [Multiple Node Multiple Disk](./linux/multiple-node-multiple-disk.md) · [Checklists](./requirement/checklists/index.md) | | Kubernetes / cloud-native | Container orchestration deployment | [Cloud Native](./cloud-native/index.md) | | Windows or macOS host | Native installation | [Windows](./windows/index.md) · [macOS](./macos/index.md) | @@ -23,6 +23,6 @@ RustFS is a distributed object storage system written in Rust, fully compatible | [SNMD](./linux/single-node-multiple-disk.md) | 1 | Multiple | Up to M parity disks within the node | Medium, non-critical business on a single server | | [MNMD](./linux/multiple-node-multiple-disk.md) | 4+ | Multiple per node | Disk- and node-level via erasure coding across servers | Production workloads | -## Production Checklists +## Checklist -Before any production deployment, work through the [Pre-Installation Checklists](./checklists/index.md) — hardware, network, software, and security — to make sure your environment meets production guidance. +Before any production deployment, work through the [Pre-Installation Checklists](./requirement/checklists/index.md) — hardware, network, software, and security — to make sure your environment meets production guidance. diff --git a/content/installation/linux/index.md b/content/installation/linux/index.md index 505105d5..87ab19a5 100644 --- a/content/installation/linux/index.md +++ b/content/installation/linux/index.md @@ -25,4 +25,4 @@ Four or more servers, each with one or more disks. Erasure coding spans servers, ## Before Production -Work through the [Pre-Installation Checklists](../checklists/index.md) — hardware, network, software, and security — before deploying to production. If you don't need production standards, you can skip them. +Work through the [Pre-Installation Checklists](../requirement/checklists/index.md) — hardware, network, software, and security — before deploying to production. If you don't need production standards, you can skip them. diff --git a/content/installation/linux/multiple-node-multiple-disk.md b/content/installation/linux/multiple-node-multiple-disk.md index 30ec6981..ed60d884 100644 --- a/content/installation/linux/multiple-node-multiple-disk.md +++ b/content/installation/linux/multiple-node-multiple-disk.md @@ -29,7 +29,7 @@ flowchart TD class N1D1,N1D2,N1D3,N1D4,N2D1,N2D2,N2D3,N2D4,N3D1,N3D2,N3D3,N3D4,N4D1,N4D2,N4D3,N4D4 disk ``` -Before installation, review the [Pre-Installation Checklists](../checklists/index.md) and ensure all items meet production guidance. +Before installation, review the [Pre-Installation Checklists](../requirement/checklists/index.md) and ensure all items meet production guidance. ## Hostnames @@ -119,4 +119,4 @@ tail -f /var/logs/rustfs/rustfs*.log - Put a load balancer in front of the cluster — see the [Nginx integration guide](../../integration/nginx.md). - Enable TLS for production traffic — see [TLS configuration](../../integration/tls-configured.md). -- Review [Availability and Resiliency](../../upgrade-scale/availability-and-resiliency.md) before scaling. +- Review [Pool Expansion](../../operations/scaling/storage-pool-expansion.md) before scaling. diff --git a/content/installation/linux/quick-start.md b/content/installation/linux/quick-start.md index 6d8efc73..3163f0e6 100644 --- a/content/installation/linux/quick-start.md +++ b/content/installation/linux/quick-start.md @@ -84,6 +84,6 @@ The quick install runs RustFS in **Single Node Single Disk (SNSD)** mode — zer - **Plan a production deployment** — choose a topology, then follow its guide: - [Single Node Single Disk (SNSD)](./single-node-single-disk.md) — dev and small workloads - [Single Node Multiple Disk (SNMD)](./single-node-multiple-disk.md) — disk-level fault tolerance on one machine - - [Multiple Node Multiple Disk (MNMD)](./multiple-node-multiple-disk.md) — production-grade availability and scale, with the [pre-installation checklists](../checklists/index.md) -- **Prefer containers?** — [Install with Docker](../docker/index.md) + - [Multiple Node Multiple Disk (MNMD)](./multiple-node-multiple-disk.md) — production-grade availability and scale, with the [pre-installation checklists](../requirement/checklists/index.md) +- **Prefer containers?** — [Install with a container](../container/index.mdx) - **Connect your application** — [SDKs and examples](../../developer/sdk/index.md) diff --git a/content/installation/linux/single-node-multiple-disk.md b/content/installation/linux/single-node-multiple-disk.md index b634a14b..95dd8f00 100644 --- a/content/installation/linux/single-node-multiple-disk.md +++ b/content/installation/linux/single-node-multiple-disk.md @@ -26,7 +26,7 @@ flowchart TD - 1 server with multiple data disks (this example uses 4 disks mounted at `/data/rustfs0` through `/data/rustfs3`). - Erasure coding spreads data and parity shards across the disks; fault tolerance is limited to disk failures within the single node. - Format each disk with XFS and mount it separately (e.g., labels `RUSTFS0` – `RUSTFS3`), as described in the prerequisites page. -- For production deployments, also review the [Pre-Installation Checklists](../checklists/index.md). +- For production deployments, also review the [Pre-Installation Checklists](../requirement/checklists/index.md). ## Prerequisites and Service Setup diff --git a/content/installation/linux/single-node-single-disk.md b/content/installation/linux/single-node-single-disk.md index 9eb18d79..cf912d2f 100644 --- a/content/installation/linux/single-node-single-disk.md +++ b/content/installation/linux/single-node-single-disk.md @@ -25,7 +25,7 @@ flowchart TD - 1 server, 1 data disk (e.g., an XFS-formatted disk mounted at `/data/rustfs0`). - No erasure coding across disks — fault tolerance depends entirely on backups. -- For production deployments, also review the [Pre-Installation Checklists](../checklists/index.md). +- For production deployments, also review the [Pre-Installation Checklists](../requirement/checklists/index.md). ## Prerequisites and Service Setup diff --git a/content/installation/meta.json b/content/installation/meta.json new file mode 100644 index 00000000..e617373b --- /dev/null +++ b/content/installation/meta.json @@ -0,0 +1,11 @@ +{ + "title": "Installation", + "pages": [ + "requirement", + "linux", + "container", + "cloud-native", + "[Windows](/installation/windows)", + "[macOS](/installation/macos)" + ] +} \ No newline at end of file diff --git a/content/installation/checklists/hardware-checklists.md b/content/installation/requirement/checklists/hardware-checklists.md similarity index 100% rename from content/installation/checklists/hardware-checklists.md rename to content/installation/requirement/checklists/hardware-checklists.md diff --git a/content/installation/checklists/hardware-selection.md b/content/installation/requirement/checklists/hardware-selection.md similarity index 100% rename from content/installation/checklists/hardware-selection.md rename to content/installation/requirement/checklists/hardware-selection.md diff --git a/content/installation/checklists/index.md b/content/installation/requirement/checklists/index.md similarity index 94% rename from content/installation/checklists/index.md rename to content/installation/requirement/checklists/index.md index 0a121882..7b1f73a8 100644 --- a/content/installation/checklists/index.md +++ b/content/installation/requirement/checklists/index.md @@ -1,5 +1,5 @@ --- -title: "Checklists" +title: "Checklist" description: "Pre-installation checklists." --- @@ -15,7 +15,7 @@ For professional support, contact us at hello@rustfs.com. For community support, submit an issue on GitHub. -## Checklists +## Checklist - [Hardware Checklist](./hardware-checklists.md) - [Hardware Selection](./hardware-selection.md) diff --git a/content/installation/requirement/checklists/meta.json b/content/installation/requirement/checklists/meta.json new file mode 100644 index 00000000..1fd22fd6 --- /dev/null +++ b/content/installation/requirement/checklists/meta.json @@ -0,0 +1,10 @@ +{ + "title": "Checklist", + "pages": [ + "[Hardware Checklist](/installation/requirement/checklists/hardware-checklists)", + "[Hardware Selection](/installation/requirement/checklists/hardware-selection)", + "[Network Checklist](/installation/requirement/checklists/network-checklists)", + "[Software Checklist](/installation/requirement/checklists/software-checklists)", + "[Security Checklist](/installation/requirement/checklists/security-checklists)" + ] +} diff --git a/content/installation/checklists/network-checklists.md b/content/installation/requirement/checklists/network-checklists.md similarity index 100% rename from content/installation/checklists/network-checklists.md rename to content/installation/requirement/checklists/network-checklists.md diff --git a/content/installation/checklists/security-checklists.md b/content/installation/requirement/checklists/security-checklists.md similarity index 100% rename from content/installation/checklists/security-checklists.md rename to content/installation/requirement/checklists/security-checklists.md diff --git a/content/installation/checklists/software-checklists.md b/content/installation/requirement/checklists/software-checklists.md similarity index 100% rename from content/installation/checklists/software-checklists.md rename to content/installation/requirement/checklists/software-checklists.md diff --git a/content/installation/requirement/disk-preparation.md b/content/installation/requirement/disk-preparation.md new file mode 100644 index 00000000..da847fac --- /dev/null +++ b/content/installation/requirement/disk-preparation.md @@ -0,0 +1,72 @@ +--- +title: "Disk Preparation" +description: "Identify, format, mount, and verify dedicated data disks before installing RustFS on Linux." +--- + +Prepare each RustFS data disk as an independent XFS file system with a stable label and mount point. Complete these steps on every Linux node before configuring `RUSTFS_VOLUMES`. + +:::warning[Formatting destroys data] + +The `mkfs.xfs` command erases the selected device. Confirm the device name, back up any required data, and ensure the disk is not used by the operating system before continuing. + +::: + +## 1. Identify the data disks + +List block devices, file systems, labels, and current mount points: + +```bash +sudo lsblk -o NAME,SIZE,TYPE,FSTYPE,LABEL,UUID,MOUNTPOINTS +``` + +Choose only dedicated data disks. Do not format the operating-system disk or a device that contains data you need to retain. + +## 2. Format a disk with XFS + +The following example prepares `/dev/sdb` and assigns the label `RUSTFS0`. Replace the device name for your environment and use a unique label for each disk. + +```bash +sudo mkfs.xfs -f -i size=512 -n ftype=1 -L RUSTFS0 /dev/sdb +``` + +Repeat the command for additional disks with labels such as `RUSTFS1`, `RUSTFS2`, and `RUSTFS3`. + +## 3. Create a persistent mount + +Create the mount point: + +```bash +sudo mkdir -p /data/rustfs0 +``` + +Add an entry to `/etc/fstab` so the disk is mounted by its stable file-system label after a restart: + +```ini title="/etc/fstab" +LABEL=RUSTFS0 /data/rustfs0 xfs defaults,noatime,nodiratime 0 0 +``` + +Mount all entries and confirm the result: + +```bash +sudo mount -a +findmnt /data/rustfs0 +``` + +For multiple disks, create one mount point and one `/etc/fstab` entry per label. + +## 4. Verify the prepared disks + +Confirm that every expected mount uses XFS and has sufficient free space: + +```bash +df -hT /data/rustfs0 +sudo xfs_info /data/rustfs0 +``` + +Use the verified mount paths when you configure `RUSTFS_VOLUMES`. Keep disk labels and mount paths consistent across nodes in a distributed deployment. + +## Next steps + +- [Choose a deployment topology](/installation#deployment-mode-comparison) +- [Review erasure coding](/concepts/principle/erasure-coding) +- [Continue with Linux prerequisites and service setup](/installation/linux/prerequisites-and-service) \ No newline at end of file diff --git a/content/installation/requirement/meta.json b/content/installation/requirement/meta.json new file mode 100644 index 00000000..d212099e --- /dev/null +++ b/content/installation/requirement/meta.json @@ -0,0 +1,9 @@ +{ + "title": "Requirement", + "pages": [ + "[Topology Selection](/installation#deployment-mode-comparison)", + "[EC Configuration](/concepts/principle/erasure-coding)", + "disk-preparation", + "checklists" + ] +} \ No newline at end of file diff --git a/content/management/bucket/index.md b/content/management/bucket/index.md deleted file mode 100644 index 0e21f346..00000000 --- a/content/management/bucket/index.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "RustFS Bucket Management" -description: "Buckets are the fundamental containers for data in RustFS. This section covers bucket creation, deletion, and management." ---- - -Buckets organize and manage data. Each bucket has a unique name and can contain multiple objects, providing logical grouping for easier access and management. - -- [Bucket Creation](./creation.md) -- [Bucket Deletion](./deletion.md) -- Bucket Copying - diff --git a/content/management/bucket/meta.json b/content/management/bucket/meta.json deleted file mode 100644 index 2f9e3b72..00000000 --- a/content/management/bucket/meta.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "title": "Bucket Management", - "pages": [ - "[Bucket Creation](/management/bucket/creation)", - "[Bucket Deletion](/management/bucket/deletion)" - ] -} diff --git a/content/management/cors/index.md b/content/management/cors/index.md new file mode 100644 index 00000000..15bc2ad5 --- /dev/null +++ b/content/management/cors/index.md @@ -0,0 +1,48 @@ +--- +title: "CORS Configuration" +description: "Configure allowed browser origins for the RustFS S3 API and Console." +--- + +Cross-Origin Resource Sharing (CORS) controls which browser origins can access the RustFS S3 API and Console. Configure each listener separately, then restart RustFS to apply the environment changes. + +## S3 API origins + +Set `RUSTFS_CORS_ALLOWED_ORIGINS` to a comma-separated list of trusted origins: + +```ini title="/etc/default/rustfs" +RUSTFS_CORS_ALLOWED_ORIGINS="https://app.example.com,https://admin.example.com" +``` + +When this variable is unset or empty, the S3 endpoint does not add generic CORS response headers. A list of explicit origins allows credentialed browser requests from matching origins. + +You can set the value to `*` to allow requests from any origin. Wildcard mode does not allow browser credentials. + +:::warning[Use explicit origins in production] +Use a comma-separated allowlist for applications that send credentials. Reserve `*` for public resources that do not require credentialed browser requests. +::: + +## Console origins + +The Console uses a separate variable: + +```ini title="/etc/default/rustfs" +RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="https://admin.example.com" +``` + +Use a comma-separated list when more than one browser origin must access the Console. Set `*` only when any origin must be allowed. + +## Verify the response + +Send a request with an `Origin` header and inspect the CORS response headers: + +```bash +curl -i \ + -H "Origin: https://app.example.com" \ + http://localhost:9000/ +``` + +Confirm that `Access-Control-Allow-Origin` contains the expected origin. Repeat the check with an unlisted origin and confirm that it is not allowed. + +## Next steps + +See the [environment variable reference](/reference/environment-variables#cors) for the verified defaults and configuration formats. \ No newline at end of file diff --git a/content/management/bucket/creation.md b/content/management/data/bucket/creation.md similarity index 87% rename from content/management/bucket/creation.md rename to content/management/data/bucket/creation.md index 5efe2f15..fc7d6124 100644 --- a/content/management/bucket/creation.md +++ b/content/management/data/bucket/creation.md @@ -9,7 +9,7 @@ This guide explains how to create buckets using the RustFS UI, `mc` (MinIO Clien Prerequisites: -- A running RustFS instance (see [Installation Guide](../../installation/index.md)). +- A running RustFS instance (see [Installation Guide](../../../installation/index.md)). ## Using the RustFS UI @@ -21,7 +21,7 @@ Prerequisites: ### Using `mc` -> See the [`mc` Usage Guide](../../developer/mc.md) for installation and configuration. +> See the [`mc` Usage Guide](../../../developer/mc.md) for installation and configuration. Create a bucket: diff --git a/content/management/bucket/deletion.md b/content/management/data/bucket/deletion.md similarity index 94% rename from content/management/bucket/deletion.md rename to content/management/data/bucket/deletion.md index 90eb7a23..a81a397d 100644 --- a/content/management/bucket/deletion.md +++ b/content/management/data/bucket/deletion.md @@ -18,7 +18,7 @@ This guide explains how to delete buckets using the RustFS UI, `mc` (MinIO Clien ## Using `mc` -See the [`mc` Usage Guide](../../developer/mc.md) for installation and configuration. +See the [`mc` Usage Guide](../../../developer/mc.md) for installation and configuration. Delete a bucket: diff --git a/content/management/bucket/images/bucket-creation-by-ui.png b/content/management/data/bucket/images/bucket-creation-by-ui.png similarity index 100% rename from content/management/bucket/images/bucket-creation-by-ui.png rename to content/management/data/bucket/images/bucket-creation-by-ui.png diff --git a/content/management/bucket/images/bucket-deletion-on-ui.png b/content/management/data/bucket/images/bucket-deletion-on-ui.png similarity index 100% rename from content/management/bucket/images/bucket-deletion-on-ui.png rename to content/management/data/bucket/images/bucket-deletion-on-ui.png diff --git a/content/management/data/bucket/meta.json b/content/management/data/bucket/meta.json new file mode 100644 index 00000000..39583c1f --- /dev/null +++ b/content/management/data/bucket/meta.json @@ -0,0 +1,10 @@ +{ + "title": "Bucket", + "pages": [ + "creation", + "deletion", + "quota", + "replication", + "policy" + ] +} \ No newline at end of file diff --git a/content/management/data/bucket/policy.md b/content/management/data/bucket/policy.md new file mode 100644 index 00000000..d2682230 --- /dev/null +++ b/content/management/data/bucket/policy.md @@ -0,0 +1,12 @@ +--- +title: "Bucket Policy" +description: "Plan documentation for creating and applying access policies to RustFS buckets." +--- + +This page is reserved for the verified RustFS bucket policy workflow. + +:::note[Documentation status] + +Bucket-specific examples will be added after validation. For the current policy document model and supported conditions, see [Users, Groups, and Policies](/administration/iam/policies). + +::: \ No newline at end of file diff --git a/content/management/data/bucket/quota.md b/content/management/data/bucket/quota.md new file mode 100644 index 00000000..81169dde --- /dev/null +++ b/content/management/data/bucket/quota.md @@ -0,0 +1,12 @@ +--- +title: "Bucket Quota" +description: "Plan documentation for configuring and monitoring RustFS bucket quotas." +--- + +This page is reserved for the verified RustFS bucket quota workflow. + +:::note[Documentation status] + +Quota configuration and enforcement behavior will be added after validation against the current RustFS release. Review [Usage Limits](/concepts/limit) for global limits that are already documented. + +::: \ No newline at end of file diff --git a/content/management/data/bucket/replication.md b/content/management/data/bucket/replication.md new file mode 100644 index 00000000..50bd92f3 --- /dev/null +++ b/content/management/data/bucket/replication.md @@ -0,0 +1,12 @@ +--- +title: "Bucket Replication" +description: "Plan documentation for configuring and operating RustFS bucket replication." +--- + +This page is reserved for the verified RustFS bucket replication workflow. + +:::note[Documentation status] + +Configuration, monitoring, and recovery steps will be added after validation against the current RustFS release. See the [glossary](/concepts/glossary) for replication terminology. + +::: \ No newline at end of file diff --git a/content/management/data/data-tiering.md b/content/management/data/data-tiering.md new file mode 100644 index 00000000..78cdcc5b --- /dev/null +++ b/content/management/data/data-tiering.md @@ -0,0 +1,12 @@ +--- +title: "Data Tiering" +description: "Plan documentation for configuring and operating RustFS data tiering." +--- + +This page is reserved for the verified RustFS data tiering workflow. + +:::note[Documentation status] + +Configuration and operational guidance will be added after validation against the current RustFS release. See the [glossary](/concepts/glossary) for current storage-tier terminology. + +::: \ No newline at end of file diff --git a/content/management/data/lifecycle-management.md b/content/management/data/lifecycle-management.md new file mode 100644 index 00000000..3e830632 --- /dev/null +++ b/content/management/data/lifecycle-management.md @@ -0,0 +1,12 @@ +--- +title: "Lifecycle Management" +description: "Plan documentation for configuring and monitoring RustFS object lifecycle rules." +--- + +This page is reserved for the verified RustFS lifecycle management workflow. + +:::note[Documentation status] + +Rule configuration will be added after validation against the current RustFS release. The [Object Scanner](/management/data/object/scanner) page describes the background process that evaluates lifecycle work. + +::: \ No newline at end of file diff --git a/content/management/data/meta.json b/content/management/data/meta.json new file mode 100644 index 00000000..fe9504bb --- /dev/null +++ b/content/management/data/meta.json @@ -0,0 +1,9 @@ +{ + "title": "Data Management", + "pages": [ + "object", + "bucket", + "data-tiering", + "lifecycle-management" + ] +} \ No newline at end of file diff --git a/content/management/object/creation.md b/content/management/data/object/creation.md similarity index 88% rename from content/management/object/creation.md rename to content/management/data/object/creation.md index aa82ab35..f7a272d5 100644 --- a/content/management/object/creation.md +++ b/content/management/data/object/creation.md @@ -5,13 +5,13 @@ description: "Create objects using the RustFS UI, MinIO Client, or API." Objects are the fundamental storage units in RustFS, containing data, metadata, and a unique key. This guide covers object creation (upload). -> For concepts related to Objects, see [Core Concepts](../../concepts/glossary.md). +> For concepts related to Objects, see [Core Concepts](../../../concepts/glossary.md). ## Creating Objects Prerequisites: -- A running RustFS instance (see [Installation Guide](../../installation/index.md)). +- A running RustFS instance (see [Installation Guide](../../../installation/index.md)). [Create a bucket](../bucket/creation.md), then upload files to it. @@ -30,7 +30,7 @@ Click on an object to view its details. ### Using `mc` -> See the [`mc` Usage Guide](../../developer/mc.md) for installation and configuration. +> See the [`mc` Usage Guide](../../../developer/mc.md) for installation and configuration. Upload a file: diff --git a/content/management/object/deletion.md b/content/management/data/object/deletion.md similarity index 93% rename from content/management/object/deletion.md rename to content/management/data/object/deletion.md index 9cd993b9..fa50fb76 100644 --- a/content/management/object/deletion.md +++ b/content/management/data/object/deletion.md @@ -5,7 +5,7 @@ description: "Delete objects using the RustFS UI, MinIO Client, or API." This guide covers object deletion. -> For concepts related to objects, see [Core Concepts](../../concepts/glossary.md). +> For concepts related to objects, see [Core Concepts](../../../concepts/glossary.md). ## Using the RustFS UI diff --git a/content/management/object/images/delete_file_from_ui.png b/content/management/data/object/images/delete_file_from_ui.png similarity index 100% rename from content/management/object/images/delete_file_from_ui.png rename to content/management/data/object/images/delete_file_from_ui.png diff --git a/content/management/object/images/object_details_info.png b/content/management/data/object/images/object_details_info.png similarity index 100% rename from content/management/object/images/object_details_info.png rename to content/management/data/object/images/object_details_info.png diff --git a/content/management/object/images/upload_file_from_ui.png b/content/management/data/object/images/upload_file_from_ui.png similarity index 100% rename from content/management/object/images/upload_file_from_ui.png rename to content/management/data/object/images/upload_file_from_ui.png diff --git a/content/management/data/object/meta.json b/content/management/data/object/meta.json new file mode 100644 index 00000000..5c8d87c5 --- /dev/null +++ b/content/management/data/object/meta.json @@ -0,0 +1,11 @@ +{ + "title": "Object", + "pages": [ + "creation", + "deletion", + "object-lock", + "versioning", + "multipart-upload", + "scanner" + ] +} \ No newline at end of file diff --git a/content/management/data/object/multipart-upload.md b/content/management/data/object/multipart-upload.md new file mode 100644 index 00000000..a0b798a5 --- /dev/null +++ b/content/management/data/object/multipart-upload.md @@ -0,0 +1,12 @@ +--- +title: "Multipart Upload" +description: "Plan documentation for uploading large objects with the S3 multipart upload workflow." +--- + +This page is reserved for a client-neutral multipart upload workflow. + +:::note[Documentation status] + +Detailed steps will be added after the examples are validated across supported clients. For current request limits, see [Usage Limits](/concepts/limit). + +::: \ No newline at end of file diff --git a/content/management/data/object/object-lock.md b/content/management/data/object/object-lock.md new file mode 100644 index 00000000..a0f45015 --- /dev/null +++ b/content/management/data/object/object-lock.md @@ -0,0 +1,12 @@ +--- +title: "Object Lock" +description: "Plan documentation for protecting object versions from deletion or overwrite." +--- + +This page is reserved for the verified RustFS Object Lock workflow. + +:::note[Documentation status] + +Operational steps will be added after the Console and S3 API behavior is verified against the current RustFS release. Until then, review the [security checklist](/installation/requirement/checklists/security-checklists) before planning retention controls. + +::: \ No newline at end of file diff --git a/content/management/object/scanner.md b/content/management/data/object/scanner.md similarity index 97% rename from content/management/object/scanner.md rename to content/management/data/object/scanner.md index ac0b560d..6330c38f 100644 --- a/content/management/object/scanner.md +++ b/content/management/data/object/scanner.md @@ -29,4 +29,4 @@ Similar to the `osd_scrub_begin_hour` configuration in Ceph, administrators can ## Monitoring and Metrics Scanner statistics include total task count, failure count, and time distribution, exposing metrics through the Prometheus data model such as `rustfs_scanner_jobs_total`, `rustfs_scanner_failures_total`, and `rustfs_scanner_duration_seconds`. -Combined with monitoring systems, alerts can be set based on scanning failure rates and duration to promptly discover and locate potential issues at the storage or network levels. +Combined with monitoring systems, alerts can be set based on scanning failure rates and duration to promptly discover and locate potential issues at the storage or network levels. \ No newline at end of file diff --git a/content/management/data/object/versioning.md b/content/management/data/object/versioning.md new file mode 100644 index 00000000..cd497c47 --- /dev/null +++ b/content/management/data/object/versioning.md @@ -0,0 +1,12 @@ +--- +title: "Versioning" +description: "Plan documentation for enabling and operating object versioning in RustFS buckets." +--- + +This page is reserved for the verified RustFS object versioning workflow. + +:::note[Documentation status] + +Configuration, recovery, and deletion behavior will be documented after validation against the current RustFS release. See the [glossary](/concepts/glossary) for the current terminology. + +::: \ No newline at end of file diff --git a/content/management/index.md b/content/management/index.md index 4565e162..509d73f2 100644 --- a/content/management/index.md +++ b/content/management/index.md @@ -1,10 +1,11 @@ --- -title: "Management Guide" -description: "Guide to managing RustFS buckets, objects, and permissions." +title: "Administration" +description: "Guide to administering RustFS data, protocols, browser access, and permissions." --- -This section covers: +This section covers data, protocol, browser access, and identity administration: -- [Bucket Management](./bucket/index.md) -- [Object Management](./object/index.md) +- [Data Management](./data/object/object-lock.md) +- [Protocol Support](./protocols/s3.md) +- [CORS Configuration](./cors/index.md) - [IAM Management](../administration/iam/index.md) \ No newline at end of file diff --git a/content/management/meta.json b/content/management/meta.json new file mode 100644 index 00000000..7e436f7e --- /dev/null +++ b/content/management/meta.json @@ -0,0 +1,10 @@ +{ + "title": "Administration", + "pages": [ + "data", + "protocols", + "cors", + "[Nginx Reverse Proxy](/integration/nginx)", + "[Virtual-Host Access](/integration/virtual)" + ] +} \ No newline at end of file diff --git a/content/management/object/index.md b/content/management/object/index.md deleted file mode 100644 index d3dbacd5..00000000 --- a/content/management/object/index.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: "RustFS Object Management" -description: "Guide to RustFS object management, including creation, deletion, versioning, locking, and scanning." ---- - -Objects are the fundamental storage units in RustFS, consisting of data, metadata, and a unique key. This section covers: - -- [Object Upload](./creation.md) -- [Object Deletion](./deletion.md) -- Object Versioning -- Object Locking -- Object Sharing -- [Object Scanning](./scanner.md) diff --git a/content/management/object/meta.json b/content/management/object/meta.json deleted file mode 100644 index 3e501e72..00000000 --- a/content/management/object/meta.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "title": "Object Management", - "pages": [ - "[Object Upload](/management/object/creation)", - "[Object Deletion](/management/object/deletion)" - ] -} diff --git a/content/management/protocols/ftps.md b/content/management/protocols/ftps.md new file mode 100644 index 00000000..54f0b86a --- /dev/null +++ b/content/management/protocols/ftps.md @@ -0,0 +1,12 @@ +--- +title: "FTP(S)" +description: "Plan documentation for accessing RustFS through its FTP and FTPS services." +--- + +This page is reserved for the verified RustFS FTP and FTPS workflows. + +:::note[Documentation status] + +Startup, TLS, authentication, port, and client commands will be added after they are verified against the current RustFS source and release artifacts. + +::: \ No newline at end of file diff --git a/content/management/protocols/meta.json b/content/management/protocols/meta.json new file mode 100644 index 00000000..3b269ae9 --- /dev/null +++ b/content/management/protocols/meta.json @@ -0,0 +1,9 @@ +{ + "title": "Protocol Support", + "pages": [ + "s3", + "webdav", + "ftps", + "[MCP Server](/developer/mcp)" + ] +} \ No newline at end of file diff --git a/content/management/protocols/s3.md b/content/management/protocols/s3.md new file mode 100644 index 00000000..f7c37332 --- /dev/null +++ b/content/management/protocols/s3.md @@ -0,0 +1,8 @@ +--- +title: "S3" +description: "Find verified client examples for accessing RustFS through its S3-compatible API." +--- + +RustFS exposes an S3-compatible API for object storage clients. Use the verified [client examples](/developer/examples) to configure an endpoint, credentials, region, and path-style addressing. + +For API boundaries and request limits, see [Usage Limits](/concepts/limit). \ No newline at end of file diff --git a/content/management/protocols/webdav.md b/content/management/protocols/webdav.md new file mode 100644 index 00000000..3c4a741d --- /dev/null +++ b/content/management/protocols/webdav.md @@ -0,0 +1,12 @@ +--- +title: "WebDAV" +description: "Plan documentation for accessing RustFS through its WebDAV service." +--- + +This page is reserved for the verified RustFS WebDAV workflow. + +:::note[Documentation status] + +Startup, authentication, endpoint, and client commands will be added after they are verified against the current RustFS source and release artifacts. + +::: \ No newline at end of file diff --git a/content/meta.json b/content/meta.json index ee1b30ab..f21a1b99 100644 --- a/content/meta.json +++ b/content/meta.json @@ -1,62 +1,19 @@ { "root": true, "pages": [ - "---Getting Started---", - "[What is RustFS?](/concepts/introduction)", - "[Quick Start](/installation/linux/quick-start)", - "[Console First Steps](/installation/console-first-steps)", + "!concepts/introduction", + "!installation/linux/quick-start", + "!installation/console-first-steps", + "!concepts/architecture", + "!concepts/principle/erasure-coding", + "!concepts/comparison", + "!developer/examples", - "---Core Concepts---", - "[Design Architecture](/concepts/architecture)", - "[Erasure Coding](/concepts/principle/erasure-coding)", - "[Performance Comparison](/concepts/comparison)", - - "---Installation---", - "[Installation Guide](/installation)", - "installation/checklists", - "installation/linux", - "[Docker](/installation/docker)", - "[Kubernetes (Helm)](/installation/cloud-native)", - "[Windows](/installation/windows)", - "[macOS](/installation/macos)", - - "---Administration---", - "[Management Guide](/management)", - "management/bucket", - "management/object", - "administration/iam", - "[Nginx Reverse Proxy](/integration/nginx)", - "[TLS Configuration](/integration/tls-configured)", - "[Virtual-Host Access](/integration/virtual)", - - "---Operations---", - "[Scaling & Availability](/upgrade-scale)", - "[Monitoring & Alerting](/operations/monitoring)", - "[Rolling Upgrades](/upgrade-scale/upgrade)", - "[Adding a Storage Pool](/upgrade-scale/availability-and-resiliency)", - "[Pool Decommission & Rebalance](/operations/decommission)", - "[Credential Management](/operations/credentials)", - "[Object Scanner](/management/object/scanner)", - "[Troubleshooting](/troubleshooting)", - "[Cold Start & Quorum Loss](/operations/cold-start)", - "[Disk Failures](/troubleshooting/driver)", - "[Node Failures](/troubleshooting/node)", - "[Data Healing](/troubleshooting/healing)", - - "---Developer---", - "[Developer Guide](/developer)", - "developer/sdk", - "developer/examples", - "[MinIO Client (mc)](/developer/mc)", - "[MCP Server](/developer/mcp)", - "[Open Source License](/developer/license)", - - "---Reference---", - "[Environment Variables](/reference/environment-variables)", - "[CLI](/reference/cli)", - "[Ports & Health Endpoints](/reference/ports)", - "[Metrics](/reference/metrics)", - "[Usage Limits](/concepts/limit)", - "[Glossary](/concepts/glossary)" + "installation", + "management", + "administration", + "operations", + "developer", + "reference" ] } diff --git a/content/operations/cluster-health-check.md b/content/operations/cluster-health-check.md new file mode 100644 index 00000000..60bb3e64 --- /dev/null +++ b/content/operations/cluster-health-check.md @@ -0,0 +1,21 @@ +--- +title: "Cluster Health Check" +description: "Check RustFS node readiness and cluster read and write health endpoints." +--- + +RustFS serves health endpoints from the S3 listener on port `9000`. + +| Endpoint | Purpose | +| --- | --- | +| `GET /health/live` | Confirms that the process is running. | +| `GET /health/ready` | Confirms storage, IAM, and peer readiness. | +| `GET /minio/health/cluster` | Checks cluster write health and lock quorum. | +| `GET /minio/health/cluster/read` | Checks cluster read health and lock quorum. | + +Check every node before maintenance: + +```bash +curl -fsS http://:9000/health/ready +``` + +A ready node returns HTTP `200`. For telemetry configuration and alerting guidance, see [Observability](./observability.md). \ No newline at end of file diff --git a/content/operations/cold-start.md b/content/operations/cold-start.md deleted file mode 100644 index f5099e9b..00000000 --- a/content/operations/cold-start.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -title: "Cold Start and Quorum Loss" -description: "This article explains how RustFS nodes behave when they start before the cluster has quorum, how to read the degraded-mode signals, and how to diagnose the most common startup failures." ---- - -## Degraded Startup in One Paragraph - -When several nodes of an erasure-coded cluster went down together (power loss, host maintenance, a crash-looping rollout) and come back one at a time, the early nodes cannot reach the storage read quorum. They do **not** exit. The process stays alive in **degraded mode**, answers S3 requests with `503`, and recovers **automatically** as soon as enough peers are online. Do not restart-loop degraded nodes — just keep starting the remaining ones. - -## Why One Node Cannot Serve Alone - -Erasure coding shards every object — including internal metadata such as IAM users and policies under `.rustfs.sys` — across the drives of a set. Reading anything back requires a read quorum of shards. With a set's drives spread over several nodes, a single node can never satisfy that quorum by itself; the cluster becomes readable once roughly half the nodes of a set are up (internal configuration objects are written with maximum parity). Distributed locking similarly needs a majority of nodes' lock RPC endpoints. - -## The 503 Contract - -While a node is not yet ready, S3 requests receive: - -- status `503 Service Unavailable`; -- header `Retry-After: 5` — clients should retry, the condition is temporary; -- header `x-rustfs-readiness-pending` naming the blocking dependency: - - `storage_quorum` — waiting for enough nodes/disks for the erasure read quorum; - - `iam` — storage is up, the IAM cache is still loading; - - `startup_finalization` — the last startup steps are being published. - -Probe the readiness endpoint for per-dependency detail: - -```bash -curl -s http://:9000/health/ready | jq -``` - -A degraded response includes a `details` object (`storage` / `iam` / `lock`, plus `kms` when configured) and a `degradedReasons` array. Verified reason values: - -| `degradedReasons` value | Meaning | -| --- | --- | -| `storage_quorum_unavailable` | Erasure read quorum not met | -| `iam_not_ready` | IAM cache still loading | -| `lock_quorum_unavailable` | Lock RPC majority not reachable (cluster probes only) | -| `kms_not_ready` | Configured KMS not reachable | -| `peer_health_unavailable` | Peer health check failing | -| `cluster_health_timeout` | Cluster health evaluation timed out | -| `storage_and_iam_unavailable`, `storage_and_lock_unavailable`, `iam_and_lock_unavailable`, `storage_iam_and_lock_unavailable` | Combined variants of the above | - -`GET /health` and `GET /health/live` stay `200` throughout — the process is alive, only readiness is pending. Point Kubernetes liveness probes at `/health` and readiness probes at `/health/ready`, or a degraded-but-recovering node will be killed mid-recovery. - -## Why You Should Not Restart-Loop - -1. **Recovery is automatic.** As soon as enough peers are online, pending nodes finish IAM bootstrap on the next retry and flip `/health/ready` to `200` on their own. Restarting does not speed this up — it throws away retry progress. -2. **Logs tell you what is missing.** The IAM recovery loop logs `event="iam_bootstrap_retry_failed"` with an actionable `hint` field (for example, "storage read quorum not met yet; waiting for enough cluster nodes/disks to come online"). After repeated failures the level escalates from WARN to ERROR — this still does not kill the process. -3. **A node process exiting during startup is a bug, not the design.** The fatal IAM/lock startup path was removed after v1.0.0-beta.5; upgrade if you still see nodes exit while waiting for quorum. - -## Tuning - -- `RUSTFS_STARTUP_READINESS_MAX_WAIT_SECS` (default `120`): how long startup waits for full readiness before continuing in degraded mode with background recovery. Raising it delays the listener during genuinely slow starts; lowering it surfaces degraded mode sooner. Recovery retries continue regardless of this limit. - -## Recommended Cold-Start Procedure - -1. Start all nodes (order does not matter). Early nodes sit in degraded mode. -2. Watch readiness converge: - - ```bash - for n in node1 node2 node3 node4; do - echo -n "$n: "; curl -s http://$n:9000/health/ready | jq -r '.status' - done - ``` - -3. Once every node reports `ok`, the cluster is fully serving. If a node is still degraded **after** all peers are up, check network reachability between nodes (peer RPC ports), compare per-node clocks, then read `degradedReasons` and the `hint` field of the IAM retry logs. - -## Common Startup Failures - -These fail fast at container start (checks performed by the image entrypoint) or during listener setup, before any degraded-mode logic applies: - -| Symptom | Cause | Fix | -| --- | --- | --- | -| `ERROR: RUSTFS_ACCESS_KEY must not be empty.` | Credential env var set to an empty string (often an unexpanded compose interpolation like `${VAR}`) | Set a real value or remove the variable | -| `ERROR: Set either RUSTFS_ACCESS_KEY or RUSTFS_ACCESS_KEY_FILE, not both.` | Both direct and file-based credential sources configured | Keep exactly one source | -| `ERROR: RUSTFS_ACCESS_KEY_FILE points to an unreadable file.` | Secret file missing or wrong permissions | Mount the secret and check file modes | -| `WARNING: ... uses the default rustfsadmin credential.` | Running with default credentials | Not fatal, but set real credentials before exposing the listener — see [Credential Management](./credentials) | -| Startup aborts with `VolumeNotFound` | The local path component of a distributed volume URL does not exist on disk — RustFS does not auto-create disk roots | Create the data directories (the container entrypoint creates local paths listed in `RUSTFS_VOLUMES`, but verify host mounts) | -| `HTTP listener bind failed` in logs | Port 9000 (or 9001 for the console) already in use, or address unavailable | Free the port or change `RUSTFS_ADDRESS` / `RUSTFS_CONSOLE_ADDRESS` | - -:::note -Keep node clocks synchronized (NTP/chrony). S3 request signing is time-sensitive, and the rolling-restart runbook explicitly lists per-node clock drift as a cause of nodes staying degraded after the cluster is otherwise back. -::: - -## Related - -- Rolling restarts without downtime (restart one node at a time, wait for `/health/ready` = `200` before the next) follow the same readiness signals — see the availability documentation in [Availability and Resiliency](../upgrade-scale/availability-and-resiliency). -- [Monitoring and Alerting](./monitoring) shows how to alert on `rustfs_runtime_readiness_ready` so a stuck-degraded node pages you. diff --git a/content/operations/event-notifications.md b/content/operations/event-notifications.md new file mode 100644 index 00000000..18885a14 --- /dev/null +++ b/content/operations/event-notifications.md @@ -0,0 +1,18 @@ +--- +title: "Event Notifications" +description: "Find the verified RustFS environment variables for configuring bucket event notification targets." +--- + +RustFS bucket event notifications send object and bucket events to configured external targets. Use this page to find the currently verified configuration surface. + +## Enable event notifications + +Set `RUSTFS_NOTIFY_ENABLE` to enable the bucket event notification module. Notification targets use the `RUSTFS_NOTIFY__` environment-variable pattern. + +RustFS provides configuration families for webhook, Kafka, MQTT, MySQL, PostgreSQL, NATS, Redis, AMQP, and Pulsar targets. + +See the [environment variable reference](/reference/environment-variables#audit-and-notification-targets) for the verified module switch, target pattern, and webhook settings. End-to-end delivery examples will be added after runtime validation. + +## Next steps + +Review [Observability](/operations/observability) to configure metrics, logs, and traces for your deployment. \ No newline at end of file diff --git a/content/operations/high-availability/index.md b/content/operations/high-availability/index.md new file mode 100644 index 00000000..d274c255 --- /dev/null +++ b/content/operations/high-availability/index.md @@ -0,0 +1,7 @@ +--- +title: "High Availability" +description: "Review RustFS site replication and node recovery guidance." +--- + +- [Site Replication](./site-replication.md) +- [Node Healing](./node-healing.md) \ No newline at end of file diff --git a/content/operations/high-availability/meta.json b/content/operations/high-availability/meta.json new file mode 100644 index 00000000..350cc1f0 --- /dev/null +++ b/content/operations/high-availability/meta.json @@ -0,0 +1,7 @@ +{ + "title": "High Availability", + "pages": [ + "site-replication", + "node-healing" + ] +} \ No newline at end of file diff --git a/content/troubleshooting/healing.md b/content/operations/high-availability/node-healing.md similarity index 98% rename from content/troubleshooting/healing.md rename to content/operations/high-availability/node-healing.md index 00eccb76..589b4a2c 100644 --- a/content/troubleshooting/healing.md +++ b/content/operations/high-availability/node-healing.md @@ -1,5 +1,5 @@ --- -title: "Object Inspection and Auto-Recovery" +title: "Node Healing" description: "How RustFS object self-healing works: design principles, trigger paths, scrub and repair process, and usage notes." --- diff --git a/content/operations/high-availability/site-replication.md b/content/operations/high-availability/site-replication.md new file mode 100644 index 00000000..0fab72f1 --- /dev/null +++ b/content/operations/high-availability/site-replication.md @@ -0,0 +1,12 @@ +--- +title: "Site Replication" +description: "Track documentation for the verified RustFS site replication workflow." +--- + +This page is reserved for the verified RustFS site replication workflow. + +:::note[Documentation status] + +Configuration steps will be added after site replication behavior and supported administration interfaces are validated against the current RustFS release. + +::: \ No newline at end of file diff --git a/content/operations/index.md b/content/operations/index.md new file mode 100644 index 00000000..c3ab2610 --- /dev/null +++ b/content/operations/index.md @@ -0,0 +1,16 @@ +--- +title: "Operations & Observability" +description: "Operate, monitor, scale, upgrade, and troubleshoot RustFS deployments." +--- + +Use this section to keep RustFS deployments healthy through upgrades, scaling, availability planning, monitoring, and incident response. + +## Operational areas + +- [Upgrade](./upgrade/index.md) covers binary, container, and Kubernetes upgrades. +- [Scaling](./scaling/index.md) covers storage expansion, rebalancing, and decommissioning. +- [High Availability](./high-availability/index.md) covers node healing and site replication. +- [Observability](./observability.md) covers metrics, logs, traces, and health signals. +- [Cluster Health Check](./cluster-health-check.md) covers deployment health validation. +- [Event Notifications](./event-notifications.md) covers bucket event targets. +- [Troubleshooting](/troubleshooting) provides diagnostic guidance. \ No newline at end of file diff --git a/content/operations/meta.json b/content/operations/meta.json new file mode 100644 index 00000000..980f07d3 --- /dev/null +++ b/content/operations/meta.json @@ -0,0 +1,12 @@ +{ + "title": "Operations & Observability", + "pages": [ + "upgrade", + "scaling", + "high-availability", + "observability", + "cluster-health-check", + "event-notifications", + "[Troubleshooting](/troubleshooting)" + ] +} \ No newline at end of file diff --git a/content/operations/monitoring.md b/content/operations/observability.md similarity index 98% rename from content/operations/monitoring.md rename to content/operations/observability.md index 8f565bd1..2eae87f0 100644 --- a/content/operations/monitoring.md +++ b/content/operations/observability.md @@ -1,5 +1,5 @@ --- -title: "Monitoring and Alerting" +title: "Observability" description: "This article explains how RustFS exports metrics, traces, and logs over OTLP, how to wire the pipeline into Prometheus and Grafana, which health endpoints to probe, and which signals to alert on." --- @@ -89,7 +89,7 @@ A ready node answers `200` with `"ready": true`. A degraded node answers `503` w curl -s http://:9000/health/ready | jq ``` -The `details` object reports `storage` / `iam` / `lock` (and `kms` when configured), and `degradedReasons` lists machine-readable causes such as `storage_quorum_unavailable`, `iam_not_ready`, or `lock_quorum_unavailable`. See [Cold Start and Quorum Loss](./cold-start) for the full degraded-startup semantics. +The `details` object reports `storage` / `iam` / `lock` (and `kms` when configured), and `degradedReasons` lists machine-readable causes such as `storage_quorum_unavailable`, `iam_not_ready`, or `lock_quorum_unavailable`. ## Key Metrics diff --git a/content/operations/scaling/data-rebalancing.md b/content/operations/scaling/data-rebalancing.md new file mode 100644 index 00000000..fedec2df --- /dev/null +++ b/content/operations/scaling/data-rebalancing.md @@ -0,0 +1,8 @@ +--- +title: "Rebalancing" +description: "Redistribute existing objects across RustFS storage pools after expanding a cluster." +--- + +After you add a storage pool, new writes prefer pools with more free space, but existing objects remain in their current pools. Rebalancing redistributes existing objects across all active pools. + +See [Rebalance After Expansion](./storage-pool-decommission.md#rebalance-after-expansion) for the verified admin API endpoints, progress fields, and stop behavior. \ No newline at end of file diff --git a/content/operations/scaling/index.md b/content/operations/scaling/index.md new file mode 100644 index 00000000..ffa65989 --- /dev/null +++ b/content/operations/scaling/index.md @@ -0,0 +1,10 @@ +--- +title: "Scaling" +description: "Expand, rebalance, and retire RustFS storage pools." +--- + +Choose the workflow that matches the topology change: + +- [Pool Expansion](./storage-pool-expansion.md) +- [Rebalancing](./data-rebalancing.md) +- [Pool Decommission](./storage-pool-decommission.md) \ No newline at end of file diff --git a/content/operations/scaling/meta.json b/content/operations/scaling/meta.json new file mode 100644 index 00000000..69f00562 --- /dev/null +++ b/content/operations/scaling/meta.json @@ -0,0 +1,8 @@ +{ + "title": "Scaling", + "pages": [ + "storage-pool-expansion", + "data-rebalancing", + "storage-pool-decommission" + ] +} \ No newline at end of file diff --git a/content/operations/decommission.md b/content/operations/scaling/storage-pool-decommission.md similarity index 99% rename from content/operations/decommission.md rename to content/operations/scaling/storage-pool-decommission.md index 8a7c529c..c946f734 100644 --- a/content/operations/decommission.md +++ b/content/operations/scaling/storage-pool-decommission.md @@ -1,5 +1,5 @@ --- -title: "Pool Decommission and Rebalance" +title: "Pool Decommission" description: "This article describes how to retire a server pool with the decommission workflow and how to spread existing data onto new pools with rebalance, including the admin API endpoints, progress monitoring, and abort semantics." --- diff --git a/content/upgrade-scale/availability-and-resiliency.md b/content/operations/scaling/storage-pool-expansion.md similarity index 98% rename from content/upgrade-scale/availability-and-resiliency.md rename to content/operations/scaling/storage-pool-expansion.md index efeee9b8..092991cb 100644 --- a/content/upgrade-scale/availability-and-resiliency.md +++ b/content/operations/scaling/storage-pool-expansion.md @@ -1,6 +1,6 @@ --- -title: "Availability and Scalability Description" -description: "This article will detail the technology and description related to RustFS scaling." +title: "Pool Expansion" +description: "Learn how to expand a RustFS cluster by adding a storage pool and verifying the new topology." --- > Note: This document is based on the latest RustFS version. Please perform full data backup before scaling operations. For production environments, it's recommended to contact RustFS technical support engineers for solution review. diff --git a/content/operations/upgrade/binary/index.md b/content/operations/upgrade/binary/index.md new file mode 100644 index 00000000..a8fc13c0 --- /dev/null +++ b/content/operations/upgrade/binary/index.md @@ -0,0 +1,50 @@ +--- +title: "Binary Upgrade" +description: "Upgrade RustFS binary deployments one node at a time while preserving cluster availability." +--- + +Use a rolling upgrade to replace the RustFS executable on each node without taking the entire cluster offline. Before you begin, read the target release notes and confirm every node is healthy. + +## Before you upgrade + +Check readiness on every node: + +```bash +curl -fsS http://:9000/health/ready +``` + +Back up the service configuration and retain the currently running executable for rollback: + +```bash +sudo cp /etc/default/rustfs /etc/default/rustfs.bak +sudo cp /usr/local/bin/rustfs /usr/local/bin/rustfs.previous +``` + +## Upgrade each node + +Upgrade one node at a time. Do not continue until the restarted node reports ready. + +```bash +sudo systemctl stop rustfs +sudo cp rustfs-new /usr/local/bin/rustfs +sudo chmod +x /usr/local/bin/rustfs +sudo systemctl start rustfs +curl -fsS http://:9000/health/ready +``` + +Repeat the procedure for each remaining node. + +## Roll back + +If the new version fails validation, restore the previous executable on one node at a time and wait for readiness before continuing: + +```bash +sudo systemctl stop rustfs +sudo cp /usr/local/bin/rustfs.previous /usr/local/bin/rustfs +sudo systemctl start rustfs +curl -fsS http://:9000/health/ready +``` + +## Next steps + +Review [Cluster Health Check](/operations/cluster-health-check) for additional post-upgrade validation. \ No newline at end of file diff --git a/content/operations/upgrade/binary/meta.json b/content/operations/upgrade/binary/meta.json new file mode 100644 index 00000000..96896cd9 --- /dev/null +++ b/content/operations/upgrade/binary/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Binary", + "pages": [] +} \ No newline at end of file diff --git a/content/operations/upgrade/container/index.md b/content/operations/upgrade/container/index.md new file mode 100644 index 00000000..4eac0a6c --- /dev/null +++ b/content/operations/upgrade/container/index.md @@ -0,0 +1,30 @@ +--- +title: "Container Upgrade" +description: "Upgrade containerized RustFS deployments while preserving persistent data and cluster availability." +--- + +Upgrade a container deployment by replacing the RustFS container image while preserving its data volumes, environment configuration, ports, and restart policy. + +## Before you upgrade + +Record the current image tag and container configuration, then confirm the S3 API is healthy: + +```bash +curl -fsS http://localhost:9000/health/ready +``` + +Use an explicit image tag for both the upgrade and rollback. Do not remove or recreate the persistent volume that contains `/data`. + +## Replace the container + +Pull the target image, stop the existing container, and recreate it with the same volume mounts and configuration. For a multi-node deployment, replace one node at a time and wait for its readiness endpoint to return successfully before continuing. + +See [Docker Installation](/installation/container/docker) for the verified container ports, persistent volume mount, and startup arguments. + +## Roll back + +Recreate the container with the previous image tag and the unchanged persistent volume and environment configuration. Verify readiness before rolling back another node. + +## Next steps + +Review [Cluster Health Check](/operations/cluster-health-check) for additional post-upgrade validation. \ No newline at end of file diff --git a/content/operations/upgrade/container/meta.json b/content/operations/upgrade/container/meta.json new file mode 100644 index 00000000..bd4d22bf --- /dev/null +++ b/content/operations/upgrade/container/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Container", + "pages": [] +} \ No newline at end of file diff --git a/content/operations/upgrade/index.md b/content/operations/upgrade/index.md new file mode 100644 index 00000000..b9521a4a --- /dev/null +++ b/content/operations/upgrade/index.md @@ -0,0 +1,14 @@ +--- +title: "Upgrade" +description: "Plan and perform RustFS upgrades while preserving cluster availability." +--- + +Choose the upgrade workflow that matches how you deploy RustFS. For multi-node clusters, replace one node or pod at a time and wait for it to report ready before continuing. + +## Deployment methods + +- [Binary Upgrade](./binary/index.md) covers systemd-managed executable replacement and rollback. +- [Container Upgrade](./container/index.md) covers image replacement with persistent data volumes. +- [Kubernetes Upgrade](./kubernetes/index.md) covers Helm- and Operator-managed workloads. + +Before any upgrade, read the target release notes and confirm the cluster is healthy. \ No newline at end of file diff --git a/content/operations/upgrade/kubernetes/index.md b/content/operations/upgrade/kubernetes/index.md new file mode 100644 index 00000000..7ed46d7c --- /dev/null +++ b/content/operations/upgrade/kubernetes/index.md @@ -0,0 +1,34 @@ +--- +title: "Kubernetes Upgrade" +description: "Upgrade RustFS deployments managed by Kubernetes and verify workload readiness." +--- + +Upgrade a Kubernetes deployment by updating the RustFS image through the same controller or package manager that owns the workload. Preserve the existing persistent volume claims and configuration. + +## Before you upgrade + +Read the target release notes, record the current image tag and Helm values or Tenant manifest, and verify that all RustFS pods are ready: + +```bash +kubectl -n get pods +``` + +## Apply the upgrade + +For a Helm-managed deployment, update the image value in your saved values and apply it with `helm upgrade`. For an Operator-managed deployment, update the Tenant image field and apply the manifest through `kubectl`. + +Monitor the pods until the workload becomes ready: + +```bash +kubectl -n get pods -w +``` + +See the [Helm Chart Installation](/installation/cloud-native/helm-chart) and [Operator Installation](/installation/cloud-native/operator) guides for the verified deployment layouts. + +## Roll back + +Restore the previous image value through Helm or the Tenant manifest, then wait for all pods to become ready. Do not delete persistent volume claims during rollback. + +## Next steps + +Review [Observability](/operations/observability) to monitor the deployment after the rollout. \ No newline at end of file diff --git a/content/operations/upgrade/kubernetes/meta.json b/content/operations/upgrade/kubernetes/meta.json new file mode 100644 index 00000000..8dc32445 --- /dev/null +++ b/content/operations/upgrade/kubernetes/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Kubernetes", + "pages": [] +} \ No newline at end of file diff --git a/content/operations/upgrade/meta.json b/content/operations/upgrade/meta.json new file mode 100644 index 00000000..463253bd --- /dev/null +++ b/content/operations/upgrade/meta.json @@ -0,0 +1,8 @@ +{ + "title": "Upgrade", + "pages": [ + "binary", + "container", + "kubernetes" + ] +} \ No newline at end of file diff --git a/content/reference/environment-variables.md b/content/reference/environment-variables.md index a55287bd..a35f3da8 100644 --- a/content/reference/environment-variables.md +++ b/content/reference/environment-variables.md @@ -32,6 +32,15 @@ Boolean variables accept `true`/`false`. Values shown as "unset" have no default | `RUSTFS_CONSOLE_ENABLE` | `true` | Enable the embedded web console (served on a separate listener). | | `RUSTFS_CONSOLE_ADDRESS` | `:9001` | Bind address and port for the console listener. | +## CORS + +| Variable | Default | Description | +| --- | --- | --- | +| `RUSTFS_CORS_ALLOWED_ORIGINS` | empty | Comma-separated origins allowed to access the S3 API from a browser. Set `*` for wildcard origins without credentials. | +| `RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS` | `*` | Comma-separated origins allowed to access the Console from a browser, or `*` for all origins. | + +See [CORS Configuration](/management/cors) for configuration and verification steps. + ## TLS & KMS | Variable | Default | Description | diff --git a/content/reference/index.md b/content/reference/index.md new file mode 100644 index 00000000..8ab167ae --- /dev/null +++ b/content/reference/index.md @@ -0,0 +1,15 @@ +--- +title: "Reference" +description: "Look up RustFS configuration variables, commands, ports, metrics, limits, and terminology." +--- + +Use this section for concise technical references while configuring, operating, or integrating RustFS. + +## References + +- [Environment Variables](./environment-variables.md) +- [CLI](./cli.md) +- [Ports & Health Endpoints](./ports.md) +- [Metrics](./metrics.md) +- [Usage Limits](/concepts/limit) +- [Glossary](/concepts/glossary) \ No newline at end of file diff --git a/content/reference/meta.json b/content/reference/meta.json new file mode 100644 index 00000000..b407f39b --- /dev/null +++ b/content/reference/meta.json @@ -0,0 +1,11 @@ +{ + "title": "Reference", + "pages": [ + "environment-variables", + "cli", + "ports", + "metrics", + "[Usage Limits](/concepts/limit)", + "[Glossary](/concepts/glossary)" + ] +} \ No newline at end of file diff --git a/content/reference/metrics.md b/content/reference/metrics.md index 016daef0..d47cfd74 100644 --- a/content/reference/metrics.md +++ b/content/reference/metrics.md @@ -5,7 +5,7 @@ description: "This article lists the RustFS metric names, types, and labels that ## How to Read This Page -RustFS exports metrics via OTLP push — there is no native `/metrics` scrape endpoint. Run an OpenTelemetry Collector with a Prometheus exporter and point `RUSTFS_OBS_ENDPOINT` at it; see [Monitoring and Alerting](../operations/monitoring) for the pipeline setup. +RustFS exports metrics via OTLP push — there is no native `/metrics` scrape endpoint. Run an OpenTelemetry Collector with a Prometheus exporter and point `RUSTFS_OBS_ENDPOINT` at it; see [Observability](../operations/observability) for the pipeline setup. Every name below is verified against the server source. The list covers the operations-relevant subset exported via OTLP; internal and experimental instruments are omitted, and new releases may add metrics not listed here. Label sets are those recorded at the emission site — the Collector and Prometheus may attach additional resource labels (`service_name`, instance, and so on). diff --git a/content/troubleshooting/index.md b/content/troubleshooting/index.md index 0ae092c0..2d67143d 100644 --- a/content/troubleshooting/index.md +++ b/content/troubleshooting/index.md @@ -7,5 +7,5 @@ Contents: - [Node Failures](./node.md) - [Driver Failures](./driver.md) -- [Object Inspection and Auto-Recovery](./healing.md) +- [Node Healing](../operations/high-availability/node-healing.md) diff --git a/content/troubleshooting/meta.json b/content/troubleshooting/meta.json new file mode 100644 index 00000000..c5ac2107 --- /dev/null +++ b/content/troubleshooting/meta.json @@ -0,0 +1,7 @@ +{ + "title": "Troubleshooting", + "pages": [ + "driver", + "node" + ] +} \ No newline at end of file diff --git a/content/upgrade-scale/index.md b/content/upgrade-scale/index.md deleted file mode 100644 index b74515a1..00000000 --- a/content/upgrade-scale/index.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: "Availability and Scalability" -description: "Learn about RustFS upgrades and scaling." ---- - -Contents: - -- [Availability and Resiliency Description](./availability-and-resiliency.md) -- Upgrade -- Scaling -- Retirement -- Rebalancing diff --git a/content/upgrade-scale/upgrade.md b/content/upgrade-scale/upgrade.md deleted file mode 100644 index a1a2ddd9..00000000 --- a/content/upgrade-scale/upgrade.md +++ /dev/null @@ -1,150 +0,0 @@ ---- -title: "Upgrade" -description: "Zero-downtime rolling upgrade runbook for multi-node RustFS clusters: pre-checks, node-by-node procedure, degraded-startup semantics, rollback, and verification." ---- - -This runbook describes how to upgrade the RustFS binary (or container image) on a multi-node, erasure-coded cluster without losing availability, and how to read the degraded-mode signals if several nodes end up down at once. - -:::note[Upgrades never change the on-disk format] - -Upgrading the binary or container image never changes the on-disk data format. Replacing the executable and restarting is safe; no migration step runs on startup. - -::: - -## When a rolling restart applies - -A **rolling restart** — restarting one node at a time while the rest keep serving — covers: - -- Binary or container image upgrades -- Changes to environment variables or startup parameters on individual nodes (edits to `/etc/default/rustfs`) - -It does **not** cover topology changes. Changing `RUSTFS_VOLUMES` — for example, adding a Server Pool — must be applied to **every** node's configuration, and all nodes must be restarted so the whole cluster agrees on the new layout; expect a short interruption while the cluster converges. See [Availability and Scalability](./availability-and-resiliency.md) for the pool expansion workflow. - -## Why one node at a time - -Erasure coding shards every object — including internal metadata such as IAM users, groups, and policies under `.rustfs.sys` — across the drives of a set. Reading data back needs a read quorum of shards online. While one node is down, the rest of the cluster keeps quorum and serves all traffic. If you take a second node down before the first is back, some erasure sets may lose write or even read quorum and requests start failing — this is the situation to avoid. - -## Pre-checks - -Before touching the first node: - -1. **Confirm the cluster is fully healthy.** Every node should return `200`: - - ```bash - curl -fsS http://:9000/health/ready - ``` - - Do not start a rolling upgrade on a cluster that already has offline nodes or degraded erasure sets. - -2. **Back up each node's configuration** (generic precaution — it holds credentials and the volume layout): - - ```bash - sudo cp /etc/default/rustfs /etc/default/rustfs.bak-$(date +%F) - ``` - -3. **Keep the currently running binary** so rollback is a file copy, not a download: - - ```bash - sudo cp /usr/local/bin/rustfs /usr/local/bin/rustfs.previous - ``` - -4. **Read the release notes** of the target version for any version-specific guidance. - -## Rolling upgrade procedure - -For each node, in any order, **one at a time**: - -### 1. Replace the binary - -```bash -# stage the new binary, then swap it in -sudo systemctl stop rustfs -sudo cp rustfs-new /usr/local/bin/rustfs -sudo chmod +x /usr/local/bin/rustfs -``` - -(For container deployments, update the image tag on this node instead.) - -### 2. Restart the node - -```bash -sudo systemctl start rustfs -``` - -The bundled systemd unit uses `Type=notify` with `TimeoutStartSec=120s`: systemd waits for the server's readiness notification, and the timeout is sized to cover initialization plus readiness checks on slower disks or cold starts. Do not shorten it. - -### 3. Wait until the node reports ready - -```bash -curl -fsS http://:9000/health/ready -``` - -A ready node returns `200` with `"ready": true` in the JSON body. Only then move on to the next node. - -### 4. Repeat for the remaining nodes - -Same steps, next node — never two in parallel. - -## If a node comes up degraded - -Nodes started before the cluster has quorum (relevant when several nodes are down at once, e.g. after a power loss) come up in **degraded mode** — the process stays alive and recovers automatically: - -- S3 requests receive `503 Service Unavailable` with a `Retry-After: 5` header, an `x-rustfs-readiness-pending` header, and a body naming the blocking dependency: `storage_quorum` (waiting for the erasure read quorum), `iam` (storage is up, IAM cache still loading), or `startup_finalization` (last startup steps being published). -- The IAM recovery loop retries with backoff and logs `event="iam_bootstrap_retry_failed"` with an actionable `hint` field. After repeated failures the log level escalates from WARN to ERROR — this still does not kill the process. -- As soon as enough peers are online, pending nodes finish IAM bootstrap on the next retry and flip `/health/ready` to `200` on their own. - -:::warning[Do not restart-loop degraded nodes] - -Recovery is automatic; restarting a degraded node does not speed anything up. Just keep starting the remaining nodes and wait. - -::: - -While waiting, `/health/ready` returns per-dependency detail: - -```bash -curl -s http://:9000/health/ready | jq -``` - -The `details` object shows `storage` / `iam` / `lock` readiness, and `degradedReasons` lists machine-readable causes such as `storage_quorum_unavailable` or `lock_quorum_unavailable`. - -`RUSTFS_STARTUP_READINESS_MAX_WAIT_SECS` (default `120`) controls how long startup waits for full readiness before continuing in degraded mode with background recovery; recovery retries continue regardless of this limit. - -## Rollback - -Because no migration runs on startup, rollback is the same rolling procedure in reverse — one node at a time, with the previous binary: - -```bash -sudo systemctl stop rustfs -sudo cp /usr/local/bin/rustfs.previous /usr/local/bin/rustfs -sudo systemctl start rustfs -curl -fsS http://:9000/health/ready # wait for 200 before the next node -``` - -If you also changed `/etc/default/rustfs`, restore the backup taken during pre-checks before restarting. - -## Verification - -After the last node is back: - -1. **All nodes ready:** `curl -fsS http://:9000/health/ready` returns `200` on every node. -2. **Console check:** open the Console (`http://:9001`), confirm every server is listed online and shows the new version. -3. **Functional smoke test** with any S3 client, e.g. `mc`: - - ```bash - mc alias set rustfs http://:9000 - mc mb rustfs/upgrade-smoke-test - mc cp ./somefile rustfs/upgrade-smoke-test/ - mc cat rustfs/upgrade-smoke-test/somefile > /dev/null && echo OK - mc rb --force rustfs/upgrade-smoke-test - ``` - -## What is not normal - -- A node process **exiting** with a fatal IAM/lock error during startup — that fatal path was removed after `v1.0.0-beta.5`; upgrade if you still see it. -- A node stuck degraded **after** the whole cluster is back: check network reachability between nodes (peer RPC ports) and per-node clocks, then inspect `degradedReasons` and the `hint` field of the IAM retry logs. - -## Related - -- [Availability and Scalability](./availability-and-resiliency.md) — adding Server Pools -- [Multiple Node Multiple Disk](../installation/linux/multiple-node-multiple-disk.md) — cluster deployment layout -- [Node troubleshooting](../troubleshooting/node.md) — diagnosing offline nodes From aa1b35894eb581613452b8cc9ed2d388c9204532 Mon Sep 17 00:00:00 2001 From: xiaomage Date: Wed, 22 Jul 2026 10:48:11 +0800 Subject: [PATCH 02/13] modify sidebar --- content/administration/meta.json | 2 +- content/administration/security/sts-token-service/meta.json | 4 ---- content/operations/upgrade/binary/meta.json | 4 ---- content/operations/upgrade/container/meta.json | 4 ---- content/operations/upgrade/kubernetes/meta.json | 4 ---- content/operations/upgrade/meta.json | 6 +++--- 6 files changed, 4 insertions(+), 20 deletions(-) delete mode 100644 content/administration/security/sts-token-service/meta.json delete mode 100644 content/operations/upgrade/binary/meta.json delete mode 100644 content/operations/upgrade/container/meta.json delete mode 100644 content/operations/upgrade/kubernetes/meta.json diff --git a/content/administration/meta.json b/content/administration/meta.json index 43b82022..cd2b7b76 100644 --- a/content/administration/meta.json +++ b/content/administration/meta.json @@ -3,7 +3,7 @@ "pages": [ "[IAM Management](/administration/iam)", "security/encryption", - "security/sts-token-service", + "[STS Token Service](/administration/security/sts-token-service)", "security/oidc", "[TLS Configuration](/integration/tls-configured)", "[Audit Logs](/administration/security/audit-logs)", diff --git a/content/administration/security/sts-token-service/meta.json b/content/administration/security/sts-token-service/meta.json deleted file mode 100644 index d82ca4cd..00000000 --- a/content/administration/security/sts-token-service/meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "STS Token Service", - "pages": [] -} \ No newline at end of file diff --git a/content/operations/upgrade/binary/meta.json b/content/operations/upgrade/binary/meta.json deleted file mode 100644 index 96896cd9..00000000 --- a/content/operations/upgrade/binary/meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "Binary", - "pages": [] -} \ No newline at end of file diff --git a/content/operations/upgrade/container/meta.json b/content/operations/upgrade/container/meta.json deleted file mode 100644 index bd4d22bf..00000000 --- a/content/operations/upgrade/container/meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "Container", - "pages": [] -} \ No newline at end of file diff --git a/content/operations/upgrade/kubernetes/meta.json b/content/operations/upgrade/kubernetes/meta.json deleted file mode 100644 index 8dc32445..00000000 --- a/content/operations/upgrade/kubernetes/meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "Kubernetes", - "pages": [] -} \ No newline at end of file diff --git a/content/operations/upgrade/meta.json b/content/operations/upgrade/meta.json index 463253bd..e7d35086 100644 --- a/content/operations/upgrade/meta.json +++ b/content/operations/upgrade/meta.json @@ -1,8 +1,8 @@ { "title": "Upgrade", "pages": [ - "binary", - "container", - "kubernetes" + "[Binary](/operations/upgrade/binary)", + "[Container](/operations/upgrade/container)", + "[Kubernetes](/operations/upgrade/kubernetes)" ] } \ No newline at end of file From ac65ae8c458e6fe0f39a55ec42f26ee0b60c715e Mon Sep 17 00:00:00 2001 From: xiaomage Date: Wed, 22 Jul 2026 10:59:08 +0800 Subject: [PATCH 03/13] add console part --- content/management/console/index.md | 27 +++++++++++++++++++++++++++ content/management/meta.json | 1 + 2 files changed, 28 insertions(+) create mode 100644 content/management/console/index.md diff --git a/content/management/console/index.md b/content/management/console/index.md new file mode 100644 index 00000000..fc964df8 --- /dev/null +++ b/content/management/console/index.md @@ -0,0 +1,27 @@ +--- +title: "Console" +description: "Use the RustFS Console to administer storage resources and access settings from a web browser." +--- + +The **RustFS Console** is the web interface for routine administration. Use this section to open the Console and find the management workflows available in the documentation. + +## Open the Console + +The Console listens on port `9001` by default. Open the following address in a browser, replacing `` with the RustFS server address: + +```text +http://:9001 +``` + +Sign in with the credentials configured for your RustFS deployment. For a guided first session, see [Console: First Steps](/installation/console-first-steps). + +## Management workflows + +- [Create and manage buckets](/management/data/bucket/creation) +- [Upload and manage objects](/management/data/object/creation) +- [Manage access keys](/administration/iam/access-token) +- [Configure identity and access management](/administration/iam) + +## Next steps + +Review the [security checklist](/installation/requirement/checklists/security-checklists) before exposing the Console outside a trusted network. \ No newline at end of file diff --git a/content/management/meta.json b/content/management/meta.json index 7e436f7e..f2bdf51f 100644 --- a/content/management/meta.json +++ b/content/management/meta.json @@ -1,6 +1,7 @@ { "title": "Administration", "pages": [ + "[Console](/management/console)", "data", "protocols", "cors", From 86f2e8a27d3e7e689ac16e17f8c0807077f71a40 Mon Sep 17 00:00:00 2001 From: xiaomage Date: Thu, 23 Jul 2026 15:42:12 +0800 Subject: [PATCH 04/13] update sidecar --- content/developer/index.md | 1 + .../developer/integration/ai-workflow/dify.md | 22 +++++++++++++++++++ .../integration/ai-workflow/index.md | 13 +++++++++++ .../integration/ai-workflow/meta.json | 7 ++++++ .../developer/integration/ai-workflow/n8n.md | 22 +++++++++++++++++++ .../developer/integration/big-data/iceberg.md | 22 +++++++++++++++++++ .../developer/integration/big-data/index.md | 12 ++++++++++ .../developer/integration/big-data/meta.json | 6 +++++ content/developer/integration/index.md | 14 ++++++++++++ content/developer/integration/meta.json | 8 +++++++ .../integration/reverse-proxy/caddy.md | 19 ++++++++++++++++ .../integration/reverse-proxy/haproxy.md | 19 ++++++++++++++++ .../integration/reverse-proxy/index.md | 19 ++++++++++++++++ .../integration/reverse-proxy/meta.json | 9 ++++++++ .../integration/reverse-proxy/traefik.md | 19 ++++++++++++++++ content/developer/meta.json | 1 + content/integration/nginx.md | 6 ++--- content/management/meta.json | 1 - 18 files changed, 215 insertions(+), 5 deletions(-) create mode 100644 content/developer/integration/ai-workflow/dify.md create mode 100644 content/developer/integration/ai-workflow/index.md create mode 100644 content/developer/integration/ai-workflow/meta.json create mode 100644 content/developer/integration/ai-workflow/n8n.md create mode 100644 content/developer/integration/big-data/iceberg.md create mode 100644 content/developer/integration/big-data/index.md create mode 100644 content/developer/integration/big-data/meta.json create mode 100644 content/developer/integration/index.md create mode 100644 content/developer/integration/meta.json create mode 100644 content/developer/integration/reverse-proxy/caddy.md create mode 100644 content/developer/integration/reverse-proxy/haproxy.md create mode 100644 content/developer/integration/reverse-proxy/index.md create mode 100644 content/developer/integration/reverse-proxy/meta.json create mode 100644 content/developer/integration/reverse-proxy/traefik.md diff --git a/content/developer/index.md b/content/developer/index.md index a33f9180..3bb863f5 100644 --- a/content/developer/index.md +++ b/content/developer/index.md @@ -9,4 +9,5 @@ This guide provides instructions for integrating and interacting with RustFS usi - [MCP Usage Guide](./mcp.md) - [MinIO Client Usage Guide](./mc.md) - [SDK Usage Guide](./sdk/index.md) +- [Integration Guide](./integration/index.md) - API Usage Guide \ No newline at end of file diff --git a/content/developer/integration/ai-workflow/dify.md b/content/developer/integration/ai-workflow/dify.md new file mode 100644 index 00000000..e11826ab --- /dev/null +++ b/content/developer/integration/ai-workflow/dify.md @@ -0,0 +1,22 @@ +--- +title: "Dify" +description: "Prepare a RustFS S3-compatible endpoint for use with Dify." +--- + +Connect **Dify** to RustFS when your Dify deployment supports an S3-compatible storage backend. + +## RustFS connection values + +Prepare the following values before configuring Dify: + +- Endpoint: `http://localhost:9000` for a local deployment, or the externally reachable S3 endpoint. +- Region: `us-east-1`. +- Bucket: `my-bucket`. +- Credentials: a dedicated access key and secret key. +- Addressing: path-style. + +The exact setting names depend on the Dify version and deployment method. Match these values to the S3 storage settings documented for your Dify release. + +## Next steps + +See [Access Key Management](/administration/iam/access-token) to create dedicated credentials. \ No newline at end of file diff --git a/content/developer/integration/ai-workflow/index.md b/content/developer/integration/ai-workflow/index.md new file mode 100644 index 00000000..61969cd7 --- /dev/null +++ b/content/developer/integration/ai-workflow/index.md @@ -0,0 +1,13 @@ +--- +title: "AI Workflow" +description: "Connect AI workflow platforms to RustFS through S3-compatible storage interfaces." +--- + +Use **RustFS** as S3-compatible object storage for AI workflow platforms that accept a custom S3 endpoint. + +## Platforms + +- [Dify](./dify.md) +- [n8n](./n8n.md) + +Create dedicated access credentials and a bucket for each platform. Use path-style addressing unless the platform and RustFS deployment are both configured for virtual-hosted-style access. \ No newline at end of file diff --git a/content/developer/integration/ai-workflow/meta.json b/content/developer/integration/ai-workflow/meta.json new file mode 100644 index 00000000..71e0a93c --- /dev/null +++ b/content/developer/integration/ai-workflow/meta.json @@ -0,0 +1,7 @@ +{ + "title": "AI Workflow", + "pages": [ + "dify", + "n8n" + ] +} \ No newline at end of file diff --git a/content/developer/integration/ai-workflow/n8n.md b/content/developer/integration/ai-workflow/n8n.md new file mode 100644 index 00000000..7bc4cf00 --- /dev/null +++ b/content/developer/integration/ai-workflow/n8n.md @@ -0,0 +1,22 @@ +--- +title: "n8n" +description: "Prepare a RustFS S3-compatible endpoint for use with n8n workflows." +--- + +Connect **n8n** workflows to RustFS through an S3-compatible integration or node. + +## RustFS connection values + +Prepare the following values in the n8n credential or node configuration: + +- Endpoint: `http://localhost:9000` for a local deployment, or the externally reachable S3 endpoint. +- Region: `us-east-1`. +- Bucket: `my-bucket`. +- Credentials: a dedicated access key and secret key. +- Addressing: path-style. + +The available fields depend on the n8n version and S3 integration in use. Confirm that the selected integration accepts a custom endpoint and path-style addressing. + +## Next steps + +See [Access Key Management](/administration/iam/access-token) to create dedicated credentials. \ No newline at end of file diff --git a/content/developer/integration/big-data/iceberg.md b/content/developer/integration/big-data/iceberg.md new file mode 100644 index 00000000..58966c19 --- /dev/null +++ b/content/developer/integration/big-data/iceberg.md @@ -0,0 +1,22 @@ +--- +title: "Iceberg" +description: "Prepare RustFS as S3-compatible object storage for Apache Iceberg tables." +--- + +Use **RustFS** as the object storage layer for **Apache Iceberg** through an S3-compatible file system supported by your query engine or catalog environment. + +## RustFS connection values + +Prepare the following values in the engine that reads and writes Iceberg tables: + +- Endpoint: `http://localhost:9000` for a local deployment, or the externally reachable S3 endpoint. +- Region: `us-east-1`. +- Bucket: `my-bucket`. +- Credentials: a dedicated access key and secret key. +- Addressing: path-style. + +Iceberg does not define one universal object-store configuration. Apply these values to the S3 settings of the engine or file system implementation in your deployment. + +## Next steps + +See [Access Key Management](/administration/iam/access-token) to create dedicated credentials. \ No newline at end of file diff --git a/content/developer/integration/big-data/index.md b/content/developer/integration/big-data/index.md new file mode 100644 index 00000000..6789b966 --- /dev/null +++ b/content/developer/integration/big-data/index.md @@ -0,0 +1,12 @@ +--- +title: "Big Data" +description: "Connect big data systems to RustFS through S3-compatible object storage interfaces." +--- + +Use **RustFS** as the object storage layer for big data systems that support an S3-compatible endpoint. + +## Systems + +- [Iceberg](./iceberg.md) + +Keep table metadata and data files in a dedicated bucket, and use credentials scoped to the required bucket operations. \ No newline at end of file diff --git a/content/developer/integration/big-data/meta.json b/content/developer/integration/big-data/meta.json new file mode 100644 index 00000000..fd55ea5b --- /dev/null +++ b/content/developer/integration/big-data/meta.json @@ -0,0 +1,6 @@ +{ + "title": "Big Data", + "pages": [ + "iceberg" + ] +} \ No newline at end of file diff --git a/content/developer/integration/index.md b/content/developer/integration/index.md new file mode 100644 index 00000000..67cbcb90 --- /dev/null +++ b/content/developer/integration/index.md @@ -0,0 +1,14 @@ +--- +title: "Integration" +description: "Integrate RustFS with reverse proxies, AI workflow platforms, and big data systems." +--- + +Use this section to connect **RustFS** to infrastructure and application platforms through its S3-compatible API. + +## Integration categories + +- [Reverse Proxy](./reverse-proxy/index.md) covers Nginx, Traefik, Caddy, and HAProxy. +- [AI Workflow](./ai-workflow/index.md) covers Dify and n8n. +- [Big Data](./big-data/index.md) covers Iceberg. + +Each guide identifies the RustFS endpoint and addressing requirements to use when configuring the integrating system. \ No newline at end of file diff --git a/content/developer/integration/meta.json b/content/developer/integration/meta.json new file mode 100644 index 00000000..e720c158 --- /dev/null +++ b/content/developer/integration/meta.json @@ -0,0 +1,8 @@ +{ + "title": "Integration", + "pages": [ + "reverse-proxy", + "ai-workflow", + "big-data" + ] +} \ No newline at end of file diff --git a/content/developer/integration/reverse-proxy/caddy.md b/content/developer/integration/reverse-proxy/caddy.md new file mode 100644 index 00000000..eb7fcab5 --- /dev/null +++ b/content/developer/integration/reverse-proxy/caddy.md @@ -0,0 +1,19 @@ +--- +title: "Caddy" +description: "Connect Caddy to the RustFS S3 API and Console endpoints." +--- + +Use **Caddy** to route external traffic to the RustFS S3 API and Console. + +## Routing model + +Configure one site address for each RustFS endpoint: + +- Proxy the S3 hostname to `http://:9000`. +- Proxy the Console hostname to `http://:9001`. + +Serve the S3 API from the root path and preserve the request host. Configure certificates that match the public hostnames used by clients. + +## Next steps + +See [Virtual-Host Access](/integration/virtual) before enabling virtual-hosted-style bucket URLs. \ No newline at end of file diff --git a/content/developer/integration/reverse-proxy/haproxy.md b/content/developer/integration/reverse-proxy/haproxy.md new file mode 100644 index 00000000..e095ccbf --- /dev/null +++ b/content/developer/integration/reverse-proxy/haproxy.md @@ -0,0 +1,19 @@ +--- +title: "HAProxy" +description: "Connect HAProxy to the RustFS S3 API and Console endpoints." +--- + +Use **HAProxy** to route external traffic to the RustFS S3 API and Console. + +## Routing model + +Define separate frontends and backends for the two RustFS endpoints: + +- Send S3 API traffic to `:9000`. +- Send Console traffic to `:9001`. + +Preserve the request host, serve the S3 API from the root path, and use health checks appropriate for your deployment. Terminate TLS at HAProxy or pass encrypted traffic through to RustFS according to your certificate ownership model. + +## Next steps + +See [Virtual-Host Access](/integration/virtual) before enabling virtual-hosted-style bucket URLs. \ No newline at end of file diff --git a/content/developer/integration/reverse-proxy/index.md b/content/developer/integration/reverse-proxy/index.md new file mode 100644 index 00000000..aa225aa7 --- /dev/null +++ b/content/developer/integration/reverse-proxy/index.md @@ -0,0 +1,19 @@ +--- +title: "Reverse Proxy" +description: "Choose and configure a reverse proxy for the RustFS S3 API and Console." +--- + +Use a reverse proxy to expose the **RustFS** S3 API and Console through managed hostnames and TLS endpoints. + +We recommend using separate hostnames for the S3 API on port `9000` and the Console on port `9001`. Serve the S3 API from the root of its hostname because S3 clients sign the request path. + +## Supported guides + +- [Nginx](/integration/nginx) +- [Traefik](./traefik.md) +- [Caddy](./caddy.md) +- [HAProxy](./haproxy.md) + +## Related configuration + +See [Virtual-Host Access](/integration/virtual) when clients access buckets through virtual-hosted-style URLs. \ No newline at end of file diff --git a/content/developer/integration/reverse-proxy/meta.json b/content/developer/integration/reverse-proxy/meta.json new file mode 100644 index 00000000..a2794b5c --- /dev/null +++ b/content/developer/integration/reverse-proxy/meta.json @@ -0,0 +1,9 @@ +{ + "title": "Reverse Proxy", + "pages": [ + "[Nginx](/integration/nginx)", + "traefik", + "caddy", + "haproxy" + ] +} \ No newline at end of file diff --git a/content/developer/integration/reverse-proxy/traefik.md b/content/developer/integration/reverse-proxy/traefik.md new file mode 100644 index 00000000..e069c0a0 --- /dev/null +++ b/content/developer/integration/reverse-proxy/traefik.md @@ -0,0 +1,19 @@ +--- +title: "Traefik" +description: "Connect Traefik to the RustFS S3 API and Console endpoints." +--- + +Use **Traefik** to route external traffic to the RustFS S3 API and Console. + +## Routing model + +Create separate routers for the two RustFS endpoints: + +- Route the S3 hostname to `http://:9000`. +- Route the Console hostname to `http://:9001`. + +Keep the original `Host` header and serve the S3 API from the root path. Configure TLS on each router when the endpoints are available outside a trusted network. + +## Next steps + +See [Virtual-Host Access](/integration/virtual) before enabling virtual-hosted-style bucket URLs. \ No newline at end of file diff --git a/content/developer/meta.json b/content/developer/meta.json index fce91b2b..7fa47c38 100644 --- a/content/developer/meta.json +++ b/content/developer/meta.json @@ -3,6 +3,7 @@ "pages": [ "mc", "sdk", + "integration", "license" ] } \ No newline at end of file diff --git a/content/integration/nginx.md b/content/integration/nginx.md index ca1df934..16348b73 100644 --- a/content/integration/nginx.md +++ b/content/integration/nginx.md @@ -1,10 +1,8 @@ --- -title: "Nginx Reverse Proxy" -description: "Configuration for implementing Nginx reverse proxy with RustFS." +title: "Nginx" +description: "Configure Nginx as a reverse proxy for the RustFS S3 API and Console." --- -# Nginx Integration - Nginx integration enables: - Log collection. diff --git a/content/management/meta.json b/content/management/meta.json index f2bdf51f..adcc6da9 100644 --- a/content/management/meta.json +++ b/content/management/meta.json @@ -5,7 +5,6 @@ "data", "protocols", "cors", - "[Nginx Reverse Proxy](/integration/nginx)", "[Virtual-Host Access](/integration/virtual)" ] } \ No newline at end of file From 32b86a278cea89b13919f6a59c7e59bc0463c79b Mon Sep 17 00:00:00 2001 From: xiaomage Date: Fri, 24 Jul 2026 21:07:39 +0800 Subject: [PATCH 05/13] refactor rustfs docs --- .agents/skills/rustfs-docs/SKILL.md | 8 +- AGENTS.md | 2 +- STYLE.md | 6 +- .../console/images/rustfs-conosle-login.png | Bin 0 -> 272939 bytes content/administration/console/index.md | 69 +++++ .../cors/index.md | 0 .../data/bucket/creation.md | 25 +- .../data/bucket/deletion.md | 23 +- .../bucket/images/bucket-creation-by-ui.png | Bin .../bucket/images/bucket-deletion-on-ui.png | Bin .../data/bucket/meta.json | 0 .../data/bucket/policy.md | 2 +- .../data/bucket/quota.md | 2 +- .../data/bucket/replication.md | 2 +- .../data/data-tiering.md | 2 +- .../data/lifecycle-management.md | 2 +- .../data/meta.json | 0 .../data/object/creation.md | 25 +- .../data/object/deletion.md | 22 +- .../object/images/delete_file_from_ui.png | Bin .../object/images/object_details_info.png | Bin .../object/images/upload_file_from_ui.png | Bin .../data/object/meta.json | 0 .../data/object/multipart-upload.md | 2 +- .../data/object/object-lock.md | 0 .../data/object/scanner.md | 0 .../data/object/versioning.md | 2 +- content/administration/iam/meta.json | 9 - content/administration/iam/oidc.md | 183 ----------- content/administration/index.md | 22 +- content/administration/meta.json | 14 +- .../protocols/ftps.md | 0 .../protocols/meta.json | 0 .../protocols/s3.md | 2 - .../protocols/webdav.md | 0 .../administration/security/encryption/kms.md | 12 - .../security/encryption/sse-c.md | 12 - .../security/encryption/sse-s.md | 12 - .../administration/security/oidc/meta.json | 7 - .../security/sts-token-service/index.md | 16 - content/concepts/architecture.md | 100 ------ content/concepts/comparison.md | 52 ---- content/concepts/glossary.md | 109 ------- content/concepts/introduction.md | 28 -- content/concepts/limit.md | 67 ----- content/concepts/principle/erasure-coding.md | 142 --------- content/concepts/principle/meta.json | 6 - content/developer/examples/aws-cli.md | 4 +- content/developer/examples/aws-sdk-go.md | 4 +- content/developer/examples/aws-sdk-js.md | 4 +- content/developer/examples/boto3.md | 4 +- content/developer/examples/mc.md | 64 ---- content/developer/examples/meta.json | 2 +- content/developer/examples/rclone.md | 4 +- content/developer/examples/s3cmd.md | 4 +- content/developer/examples/terraform.md | 2 +- content/developer/index.md | 5 +- .../developer/integration/ai-workflow/dify.md | 2 +- .../developer/integration/ai-workflow/n8n.md | 2 +- .../developer/integration/big-data/iceberg.md | 2 +- content/developer/mc.md | 114 ------- content/developer/mcp.md | 2 +- content/developer/meta.json | 1 - content/developer/sdk/go.md | 2 +- content/developer/sdk/java.md | 2 +- content/developer/sdk/javascript.md | 2 +- content/developer/sdk/python.md | 2 +- content/developer/sdk/rust.md | 2 +- content/developer/sdk/typescript.md | 2 +- content/index.mdx | 130 ++++---- .../installation/cloud-native/helm-chart.md | 60 ---- .../cloud-native/helm-chart/cert-manager.md | 69 +++++ .../cloud-native/helm-chart/index.md | 21 ++ .../cloud-native/helm-chart/installation.mdx | 168 +++++++++++ .../cloud-native/helm-chart/meta.json | 8 + .../cloud-native/helm-chart/mtls.md | 66 ++++ content/installation/cloud-native/index.md | 3 +- content/installation/cloud-native/meta.json | 4 +- content/installation/cloud-native/operator.md | 99 ------ .../cloud-native/operator/index.md | 28 ++ .../cloud-native/operator/installation.md | 117 ++++++++ .../installation/cloud-native/operator/kms.md | 89 ++++++ .../cloud-native/operator/meta.json | 9 + .../cloud-native/operator/pool-expansion.md | 55 ++++ .../cloud-native/operator/tenant.md | 74 +++++ content/installation/console-first-steps.md | 14 +- content/installation/container/index.mdx | 9 +- content/installation/index.md | 2 +- content/installation/linux/quick-start.md | 15 +- content/installation/meta.json | 3 +- .../requirement/disk-preparation.md | 1 - content/installation/requirement/meta.json | 1 - content/management/console/index.md | 27 -- content/management/index.md | 11 - content/management/meta.json | 10 - content/meta.json | 11 +- content/operations/cluster-health-check.md | 21 -- content/operations/index.md | 5 +- content/operations/meta.json | 6 +- content/operations/rc.mdx | 155 ++++++++++ .../operations/scaling/data-rebalancing.md | 90 +++++- .../scaling/storage-pool-decommission.md | 159 +++++----- .../scaling/storage-pool-expansion.md | 284 ++++-------------- content/operations/status-check.md | 62 ++++ content/operations/upgrade/binary/index.md | 2 +- content/operations/upgrade/container/index.md | 2 +- content/reference/environment-variables.md | 2 +- content/reference/index.md | 6 +- content/reference/meta.json | 4 +- .../audit-logs.md | 0 .../encryption/index.md | 4 +- content/security-compliance/encryption/kms.md | 157 ++++++++++ .../encryption/meta.json | 0 .../security-compliance/encryption/sse-c.md | 84 ++++++ .../security-compliance/encryption/sse-s.md | 77 +++++ .../iam/access-token.md | 0 .../iam/images/access_token_creation.png | Bin .../iam/images/access_token_deletion.png | Bin .../iam/index.md | 1 - content/security-compliance/iam/meta.json | 8 + .../iam/policies.md | 7 +- .../iam/sts.md | 2 +- content/security-compliance/index.md | 15 + content/security-compliance/meta.json | 11 + content/security-compliance/oidc/index.md | 181 +++++++++++ content/security-compliance/oidc/meta.json | 4 + package-lock.json | 10 + package.json | 1 + press.config.tsx | 30 +- public/_redirects | 13 + src/app.css | 239 +++++++++++++++ src/components/brand-logo.tsx | 20 ++ 132 files changed, 2267 insertions(+), 1718 deletions(-) create mode 100644 content/administration/console/images/rustfs-conosle-login.png create mode 100644 content/administration/console/index.md rename content/{management => administration}/cors/index.md (100%) rename content/{management => administration}/data/bucket/creation.md (68%) rename content/{management => administration}/data/bucket/deletion.md (68%) rename content/{management => administration}/data/bucket/images/bucket-creation-by-ui.png (100%) rename content/{management => administration}/data/bucket/images/bucket-deletion-on-ui.png (100%) rename content/{management => administration}/data/bucket/meta.json (100%) rename content/{management => administration}/data/bucket/policy.md (85%) rename content/{management => administration}/data/bucket/quota.md (68%) rename content/{management => administration}/data/bucket/replication.md (72%) rename content/{management => administration}/data/data-tiering.md (70%) rename content/{management => administration}/data/lifecycle-management.md (67%) rename content/{management => administration}/data/meta.json (100%) rename content/{management => administration}/data/object/creation.md (72%) rename content/{management => administration}/data/object/deletion.md (69%) rename content/{management => administration}/data/object/images/delete_file_from_ui.png (100%) rename content/{management => administration}/data/object/images/object_details_info.png (100%) rename content/{management => administration}/data/object/images/upload_file_from_ui.png (100%) rename content/{management => administration}/data/object/meta.json (100%) rename content/{management => administration}/data/object/multipart-upload.md (77%) rename content/{management => administration}/data/object/object-lock.md (100%) rename content/{management => administration}/data/object/scanner.md (100%) rename content/{management => administration}/data/object/versioning.md (70%) delete mode 100644 content/administration/iam/meta.json delete mode 100644 content/administration/iam/oidc.md rename content/{management => administration}/protocols/ftps.md (100%) rename content/{management => administration}/protocols/meta.json (100%) rename content/{management => administration}/protocols/s3.md (80%) rename content/{management => administration}/protocols/webdav.md (100%) delete mode 100644 content/administration/security/encryption/kms.md delete mode 100644 content/administration/security/encryption/sse-c.md delete mode 100644 content/administration/security/encryption/sse-s.md delete mode 100644 content/administration/security/oidc/meta.json delete mode 100644 content/administration/security/sts-token-service/index.md delete mode 100644 content/concepts/architecture.md delete mode 100644 content/concepts/comparison.md delete mode 100644 content/concepts/glossary.md delete mode 100644 content/concepts/introduction.md delete mode 100644 content/concepts/limit.md delete mode 100644 content/concepts/principle/erasure-coding.md delete mode 100644 content/concepts/principle/meta.json delete mode 100644 content/developer/examples/mc.md delete mode 100644 content/developer/mc.md delete mode 100644 content/installation/cloud-native/helm-chart.md create mode 100644 content/installation/cloud-native/helm-chart/cert-manager.md create mode 100644 content/installation/cloud-native/helm-chart/index.md create mode 100644 content/installation/cloud-native/helm-chart/installation.mdx create mode 100644 content/installation/cloud-native/helm-chart/meta.json create mode 100644 content/installation/cloud-native/helm-chart/mtls.md delete mode 100644 content/installation/cloud-native/operator.md create mode 100644 content/installation/cloud-native/operator/index.md create mode 100644 content/installation/cloud-native/operator/installation.md create mode 100644 content/installation/cloud-native/operator/kms.md create mode 100644 content/installation/cloud-native/operator/meta.json create mode 100644 content/installation/cloud-native/operator/pool-expansion.md create mode 100644 content/installation/cloud-native/operator/tenant.md delete mode 100644 content/management/console/index.md delete mode 100644 content/management/index.md delete mode 100644 content/management/meta.json delete mode 100644 content/operations/cluster-health-check.md create mode 100644 content/operations/rc.mdx create mode 100644 content/operations/status-check.md rename content/{administration/security => security-compliance}/audit-logs.md (100%) rename content/{administration/security => security-compliance}/encryption/index.md (76%) create mode 100644 content/security-compliance/encryption/kms.md rename content/{administration/security => security-compliance}/encryption/meta.json (100%) create mode 100644 content/security-compliance/encryption/sse-c.md create mode 100644 content/security-compliance/encryption/sse-s.md rename content/{administration => security-compliance}/iam/access-token.md (100%) rename content/{administration => security-compliance}/iam/images/access_token_creation.png (100%) rename content/{administration => security-compliance}/iam/images/access_token_deletion.png (100%) rename content/{administration => security-compliance}/iam/index.md (97%) create mode 100644 content/security-compliance/iam/meta.json rename content/{administration => security-compliance}/iam/policies.md (94%) rename content/{administration => security-compliance}/iam/sts.md (98%) create mode 100644 content/security-compliance/index.md create mode 100644 content/security-compliance/meta.json create mode 100644 content/security-compliance/oidc/index.md create mode 100644 content/security-compliance/oidc/meta.json create mode 100644 public/_redirects create mode 100644 src/components/brand-logo.tsx diff --git a/.agents/skills/rustfs-docs/SKILL.md b/.agents/skills/rustfs-docs/SKILL.md index bf8c0c66..44dabda9 100644 --- a/.agents/skills/rustfs-docs/SKILL.md +++ b/.agents/skills/rustfs-docs/SKILL.md @@ -12,8 +12,8 @@ description: >- # Writing RustFS documentation This is a **FumaPress** site (Waku + Fumadocs). Pages live in `content/`; each -page's URL mirrors its path (`content/management/data/bucket/creation.md` → -`/management/data/bucket/creation`). Navigation is defined by `content/meta.json` +page's URL mirrors its path (`content/administration/data/bucket/creation.md` → +`/administration/data/bucket/creation`). Navigation is defined by `content/meta.json` (root sidebar) and per-folder `meta.json` files. Read `STYLE.md` (repo root) for the full style guide and `AGENTS.md` for repo @@ -93,8 +93,8 @@ Second person ("you"). Official recommendations as "We recommend". No superlatives or promises (fastest, perfect, 100% …). Technical pages carry instructions, not sales copy — solutions/marketing content belongs on the main site (rustfs.com), not in these docs. The docs sidebar has seven sections: -Getting Started · Core Concepts · Installation · Administration · Operations · -Developer · Reference. New pages go into one of these. +Installation · Administration · Security & Compliance · Operations · +Troubleshooting · Developer · Reference. New pages go into one of these. ## 6. Navigation diff --git a/AGENTS.md b/AGENTS.md index c6dab144..fdc0aa76 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,7 @@ This playbook directs AI agents working in the RustFS documentation repository s - Framework: **FumaPress** (static-site generator powered by Waku + Fumadocs). Content lives in `content/`; site configuration is in `press.config.tsx`, `source.config.ts`, and `waku.config.ts`. - Goal: produce documentation for a distributed object storage product aimed at a global audience, currently English-first with room for other locales. - Navigation: `content/meta.json` (root sidebar: section order, labels, links) and per-folder `content/**/meta.json` (nested group titles/order) define the site structure. New pages must be reflected there immediately. -- Routing: a page's URL mirrors its path under `content/` (e.g. `content/management/data/bucket/creation.md` → `/management/data/bucket/creation`). `.md` links between pages are resolved automatically; you can write `./sibling.md` or `/absolute/path`. +- Routing: a page's URL mirrors its path under `content/` (e.g. `content/administration/data/bucket/creation.md` → `/administration/data/bucket/creation`). `.md` links between pages are resolved automatically; you can write `./sibling.md` or `/absolute/path`. ## 2. Core Principles 1. **Accuracy**: Data, APIs, and commands must be reproducible; cite third-party information with a concise source note. diff --git a/STYLE.md b/STYLE.md index 7c9b4733..e62a3cd4 100644 --- a/STYLE.md +++ b/STYLE.md @@ -9,7 +9,7 @@ This guide defines the writing and formatting conventions for all pages under `c - No superlatives or promises: avoid *fastest*, *best-in-class*, *world-leading*, *100% secure*, *100% compatible*, *perfect*, *military-grade*. - No marketing CTAs ("Contact us immediately", "Buy now") inside technical pages. - Every factual claim must be verifiable against the [rustfs/rustfs](https://github.com/rustfs/rustfs) source or an official release. Never document hypothetical commands, flags, or components. If you are unsure whether a capability exists, remove the claim. -- Do not restate performance numbers on feature pages; link to `/concepts/comparison` instead so figures live in one place. +- Keep performance figures in one verified reference location instead of repeating them across pages. - Use USD (`$`) for any cost examples; never mix currencies. ## Page Skeleton @@ -74,7 +74,7 @@ RUSTFS_VOLUMES="/data" ## Links -- Internal links are root-relative paths without file extension: `/installation/linux/quick-start`, `/concepts/comparison`. +- Internal links are root-relative paths without file extension: `/installation/linux/quick-start`, `/operations/status-check`. - Link text describes the target ("see the cloud-native installation guide"), never "click here". ## Images and Screenshots @@ -89,7 +89,7 @@ RUSTFS_VOLUMES="/data" - Plain Markdown pages use `.md`. - Pages that need JSX components (``, ``, ``, etc.) must use the `.mdx` extension. Do not put JSX in `.md` files. - Mermaid diagrams are supported in fenced ```mermaid blocks. -- **Card icons:** when a landing grid (``) uses icons, use a single consistent set of monochrome line icons from `lucide-react` (registered in `press.config.tsx`), one semantically matched icon per card, applied to **every** card in the grid — never a partial set. Do not use emoji-as-icons and do not mix brand logos (e.g. the Docker whale) with abstract icons. Pass them via `} …>`. +- **Card icons:** use one consistent icon family within each landing grid and apply an icon to every card. Use Lucide for abstract concepts. A platform-selection grid may use brand logos from `react-icons` when every primary card icon is a brand logo. Do not mix brand logos with abstract primary icons or use emoji as icons. ## Product Terminology diff --git a/content/administration/console/images/rustfs-conosle-login.png b/content/administration/console/images/rustfs-conosle-login.png new file mode 100644 index 0000000000000000000000000000000000000000..6282809df57d9086dfd53163990a475a8ffc3e82 GIT binary patch literal 272939 zcma&O2Urtbw+4!c0ye53RZ2pyp-GhvApz+Sst|&7q&I2Os}LacA`p-cp?8%YdT%11 zROw1p5V-L_=RfB>_ug|f&oj(sX7-+0d#}A_z3*CkqBJ#B?%$!kgNKKAAFQgVjfY1Z zjfZy&M06W>q|{#93=a=q%2q)^6Re=Xr0MExZR>!*!+R2!lKjkmN|W^ci&<@m7FmgO zapX)0cH)+EbSt=(&I_g>T2}uLb7o;?)6YIX{<4Q8w=VHldf-oky}yo!^sQ*gkxF@g zTLY8&e#4AFUh$XZue@Ko1f`!@ZwOKZHNWu82%k>E(;AALx8ureY)mf5wO~a3BP=tz z@Iaj|eEb$=%@t3?LEgvNM(Uri&cN$3dN1j1JYCAQw~%b2pMu+OlgMwsTw`7s{RHRS zlwJeACT2NsV|@7Py^rCyh);bWy*2)RcD50svytzH^>gdzFOf?T zJDplNtp*eyYoW8PipqOpjE6z{;cXF-BU6lDUHOkH8rle3hn8dL*3;c}Ha2`0e7A%W zt~l+nhPF1Uf&30&$?W5Gv+!a4eUUYW&-GPwujRrB|pmJ{~^O#J*JI1Y4`C<8k52M0f-@Xz_00 zN;hzu%niE#Dl6aMz`Oac^)|9OoDZvXq2fZKl8`H%fgWH=rn?mu$e=ADQC z@2iQU^KSmTd<%p-hbO0_00!gsI&fD6!pY6X**$-{^Y?v-U#h~~@bIXBzqcD;ZNN8N z`*XIshVF*yPzkuRBcFwpvn7Jh+wtY^cJQRUC2&PYgu4Zkx1)oTn}oMC%fGIWz?FX= z=4WC0*Cp=u(kzDRnoJ7Lt_UVkJ^?-f7MVLtOiWU)R@M^Qipu|49rvF!i;cVcO9_5{ zFE1}XFCji>*BAVP;^N}`0w8`6h!=MSubYpPyM;HelN;-QH1h9u6cKK4SKF8Fw$4sW zzuUF2boOwUW?}h#qyPH-$8#dQZU6gDPHz7x7EVC^-)Hy*`2_g?t8H9WsozH>G;O^R z4lqSqM_ixb?ja*6BqSj9uL}R4Q~!O-|54TOzpH|Tg+c$b>i;_032&y)XIQHuY!?Ej-L{-dM+brjdrGIyl-|7+A_?z}sxXT%L8y{+OiUECh0 zX1{;<>9{`}|FOrF@wu{+f8OuG!;{4WE6VA5-`H)3#q!L<4q8h=_Bp3>trvC0qWv2= zM=wc8dYOWaL86Z!_wbGTSHjo{Ktx-oH4EgAg5UScKO#kbiKC9`MJ57Ck7E511IU`8 z>2dLf__eompWF^iTQ^F}OD`@Brb@9x(L=FAu@*{e&J45hL;a6t&i%Pr4@o<(I-0M4 z4$qF5hC0?rZ|YNRTrOjHH#b;z0jAiv_gnMLlH$@{491=gKCaupE^3cBieEUbZpf1M z$GmSd1uXJzE*u#x(WWthA-XLvC?l(Fbc||!3s#S80w_^V(gb?#TBZ(jV)N#q+ zIbyQpujyivB5Zxe7d3pj*92b`(b5>4%kXu7X_6>l%|83fSjNl-#*xy2y!(74ymvS}s(rm{k1%WYO+RTP6$#l`cB5A%0g};!2l!$fO6@LfVjOy zZRE4;OmDSEIt?`eTPpeGqovlapI}z7UUx^-kf)|~xZvG-+t(V`NuBle6PQj5u!!j8y6ksCTAJYnr0yr6`7P;{WXRe-F!=MxL-1lmu}yCS`SB z#};gKNqmfofvTz4F~}HR!Uu`0GlMGU$@{6w%UANjaI9zHdeiISCd$lYN2oj!%dJK` z+0HNQ7XK*`3`6z`e@@G=;vj}^8*>)r-F7a%Ltc}%Wa!v@KdJIC%{~MZvo)8LTtJyC zhgJl7IcDq@9Sqc|ccvcl42U}h&OQFKvXI)`4whG{0rH0ffxuL#X#eV4XQAp@H%fBa zNd&}zj;108quu^3e!=qz>adg+b%UI1Bzag;nFdz(+^ z-EK@o2f+)IS)CKey4)LGJ~!V3+hh0I=Zool2b>u%8q4X?fL1A-u9qxIw&e7xyQI%P zg$oZuh;6o=jdVO~q!h)}w2Hg>pTKXsE6<>6leEnLO#5x$@|1nL?`?Hb#rgpr`*%&5f-tatgt>44NlcErlB-7TcVM~4X$$=Rn35z%!& zQ%^^#W+$$Wl?T72>yKwZWG!nzZ%Rzi`>b51G$1>5$?b%ncFWkiGlGk09bVX8PFv_< zS_a>61Zo%}|7o6-#@5Jlb(h})icIlNWscpVMVfFUcM{aRKAY&ViJC^SId5O*?OIBB zHa)Nw52-ZoQq!`Fcjkk*4!YKTSbRrc99pxbEc4^XEByb%qW)Wc;apj3EvWolDWXTy z`kr}y$xjDbL3sqBWCP)aFGuC9(ZmWHlboD<>ZlWZaP6{3g5rViEW2x}a^W6;)TV?j zs^G#OBf1|5G_eUkhi#h|6InMT63{x9xxVxDudh34$oDh-#9HL_)&WfbT6zIUP+t@l zOTM%__%W60I`!(E4n>lPdB$OMU!rNJ9twoAZwjr+R^U#ut@iK!7*@1MMVN4srYkCyH5zItn!EB!vN^JC#fKQ2^qV?GH@SuGyh3L8OTwrrt zd-}nGM(+3_>r2kK^t&u?q03Q&9;Zh5YKI+=C$)|#eUS&x0$loi0sj2twmh5$CA;v+ zSM2V@$F1Md(o(()2!fwvv{sqVJ{kF|ETm+SD@c zq2kEMTkr4#wD+g#T}!&&7a8LD7aO=?HyfIMN|OodygxWm0w||ryP?6Ys8%|47JB^bGSrMKCe+9lS_{K?N<8>bS2l3Am?WJmQ*hRLlQ2@{!<2S|)abi9f!k zEJ9;4#*`_>6ixvc1@!#k_$yCAh9>(qI=0agzmv{5DU$-sgD29| zbk#aa6_R4m;`e?t-=MaiJ;5(~}Vn1HIl3XW1jRSY=?v zg#W=83l?Q+?pV{@vzDq)4X(-LU)gNCaE>hx)?sVMA^u6z#?Y8{aziKRV+CwzHnPs_P*Lma5q+!YRkur-J5ce_}14qNW#b7d}~7qr)E$W-Pl20c(>-{<<6p zT`$epkHJo$OLJH%Fdv#z3+uj|yr7=+ez+>RbC&|KM=q9Y>L6<^?gKl5t?@|47itW@ zl~YH~&R~CaBZNFmmUQ&ulmAK>CQ@0-yMQ8-ZJ&dvQ7X@k_3G95+KBfGIrz_fI$Cxa zL#uD4#MMR2`2J{rE+4MuFwTS*Y)7oBKkfm+Pw&>}1tt&sPI^G(69IH7W%PPQ+NtN# zNxhyGQtNr-VS<9w9)>h!!j*v5bsNFT$g48I2F~LhZe|G++_Geg^H8^s3g-Im4X>Bh zit5Fg^6r5bhu9$9b=eaJ`Y9eMs$!IJP4g35omc}0M*E?^gME>fIzuL) z0jgTQ@U7rFP#Oo_8*`RWb-`BL3t^fxp^nu@b++*vdpg>6`Io7P0A@n@piV?YXbzl1ir$a2$l}AsegbSA|C@qS|tm#Ptu~ zRQkV#qALSKGSa}07Nw==GQ{o1t`LrH zv(T)TRl5?tq~+u=EF+tR<&04yWrK}20h;xN<81*mqC$$VCRPl05!^uKVWTaz6dDul zbT#mkhGSTv_RB4q{E1|&C{^uq_7X5q%<+k5C$~8lIbD+%_7 z;qYn5SYqZhpfA<=Bzylt1zJn5bJ%=2^k_k!deeV?49r>(pc(DTVflMH{sFwo77#N< zqeBf40RN5Z)!JZ^03A>JZZ7^X@a@`iR;xSqimC;)p`p=k6mm#}_AL#|NCN2P&)J#P z!P(TEddGtR>Ao6s+o|s$*ov?aSV68=cAz`Xupv zwDolaUw(N z%O?2zobCclo!;#s4#kgO@7xXQ=rZPZ^y*U6j$cf^ukOMygWVU$=4vv7HS=Vn$pyHIT&YKlfEBCF=Q8%Z_*&8dxFgb*YeQgjOX~`S%a1EbVyq7S2P)D5_~a@wf}6(s7h;G!=p*wCoI;I z=`X_5m6lRe#{+MB>(s-tVR(#LBHpOtkI3vrfKW7 zWw^V2dbRalyTG!<{)0$J^#x#tnnC3Vu1OK$k<7fr19uxMRZQhSD(zciR#JL{!TsoXMK&{9$0MAHLcOoy>&Co_%>n_w>pO+mh?!U$A zEo@QnZ5Ms24ravELDxbf;h;5aHrF&(rtK zmE$i^dfs#VII5oGpy|})x`_IS)A!O=ESzb!TM-7z3BaU}$M4M`#H@AJWC?5Q>-@%S z6SL`(H9mWTKevSZr0=4CWjvhTR;D1(MzCt5xCaU$r@rH6{ORv0LsW})yg-{(SiN2@ zb#*#0u?!YeG^7!&?v?Zi4TbyZ`)NTf?W4i{D1CjD;QEzXnhEi8KFT;;@Fs&|ozv%* z#?L0dnX6umXph5UVP^p|T1my4x)%QR3}Teqa>4xlrT;)j=Ms0Hcf}e=la3!=fWGL`zA2kaX9N*~%ark^6?0kUui98X!!!oP#F{6ky z;R#z56kn%lBy{#}LnT*=qBaua7=}za-xuC%eY?&|ZvpT|Mw`R~WU zAOn*6DZk9dknZ5~HyLEYi9w;LTZ0lPE5WvYW$Tq8wCYSMS()nX^L>Epy539;8)7C? z%od8~OtFQ2H0wWRgV}6W!vnt~u*aaJ9>n}cXecX!j=N>6<9bX@%F`fu2IiQ-c6&yc zp}J;HZy~m3hfwRZ(wHq4F}%Vl%B95l!9|lU1;;7`=yIyX`468r*nRv0Q1>6p;%0VF za)0ChM+tF`6erP?KBdlWvM5oc^YK_6n3ogi1GKdACt3pB_Mnfke%sB@A2Qzt#Vi0V z4JS8j7*-W>2bq}!RVUK;U-)n)0MiHR0w$m;Fh71`_C$dc|8jAbqGm}mDjlEqitp7T zca34|41=X;1eDjRQ~S0ASVgJN6uxZGTVGI{LmszM)M;B$>?TzAi$AU-mrmkz#%^)R zaKJ3yb@aT+&s{>mguKq^T;)>3){IJM3p!|_4!a>%VIJ_SOBDJOT5Ry?$7LdZgxmyHyveem>$U6Dn)T{$3&^>h9wOSJRPP38YeP34$;aV+;~JH7SmG{$n#w_9JF zwHt;p;qwYtAW(&Hu<<;c2)08`Gh~$%!{cd1$Sp~p>l%Mq$5Nvcv)r z^dY40T+vD*J^CR1zztshSRc500jgjdMM3HAlyX)r6%x;#Psb*P z>-EVnMVJ}JnyTa|ZD-=lDks}4IuiLYS@0fvIUiYJE~%z6hpDl;Nprsp6>8GV9?0fr zJEW$k_L+ule~S45au*OA^`_EI5J5w+I8t)tn$-Pti92Sfvf1m^wr2$Z(Fp~9=ZqN= z5%??`FBkB(*X>37-cOQG*DE&}R=>#gZI}m=RGm&a$NXlpV(xE4ou+y zoio7iYDoSp*$T5}2?v|Fifft`gLGC^ZMl#$N$3fl$NbZ8IY%rCQWiPGIUFJJ8~XjE zRE5>^g&*EAkG;Zo94uO|eY0h>3&bScvVFy=+!Ak9qeu_I7|)zA0zaspvDav&s}buU zdoBAxRH1^rz;m!&l8ve2q`2oBz@0O!VFtsI&K)=|{9;RwJORXVa?iahT2_o6u5K3P zX*HbDPDUPy8t|;nzk7gwRe6xUF$uURqL2RzixDInMW9CAbNuaStg!l$HRZgtk-ms< zKZ+hD^@?B6DXa8N5a`A&P9B~&_BrX5&XH|lcm!;KYAfx*mH`QS2=4_1j>U(56%VtwB*i~JLQ0MGrD#Zy_ZEoK6FsY|6|B$X%p(# zZZChCbK-1BmuAZIl3tKsWaeZYu=uI^sL-EHiu`C|X;xbTIqKUm-eR?x%^w*UwMm4A8Y ze+=~>#T@N2<}@=5=I2M8buq?23t&6V3J*#pA96}a8v`|Wcks=cFK&`QOZIp+Z*8>k zsjj}gedbEtE~Byd^i62&6i79inu_X5eKKkj^Mo*Boo<~2W7)ARLl zs9Epi?()quYjOOur4 zHFK7z*#sx~2N;vCXa1h7X&{I$@o;Duq3A5G~r*XG5ZuO>j-%Tgq30a0y+hX0!jer z6{Z2ss-j1nF$Nj`02B4s1^Vo$9Q@4*drTr)YZg3>x%IDH`h%x)2`{pB6D$10`0IVj zD*PO^B@AE>R792}lTCTnNfo@NpY-=iP?9~C|0Bztr;QJfvBbvaYwL2Ah02yK`cJ7uo0wioYSiB4cFPw4n3sr#u&v9nxu2A@ePDYaeP5bTx!1Uz-8e zK}%Nl?*Otc9(bCxNouV8phlkL-88Eki1t=wXFonF8(^#@&J~i>ZbL$hD)Zt$U>W^e*7BPcYBDwn79;?AEUVX9~pmgmfT6@Tmo~)31R< zL#Nhz%d6)X0Zs4JatvQF5^3W-2#OshsaXKRxm(kIkl4309``A4?o;jE-mChqZWN7i zbbERLbq+Zn_?uWb6J2mHE;=Z={k9$pu@m17wr(gG+mwsFwP?DVtZXJ5Lxjc%t4k+n+_jR3zRFz^NeuzcplmJs)EO zSnc1}{Ia{ia@f!3EgP(O+gBja>J=NCK}A(dr*eIPB!mtt27_Xg2L%CeGwu9%!BS4i zxuTRF`jrYaqx_~x{Y2jCbFefiH}juBDh|S#5SdW)$e#+Nh?EY71WGrV;2ezUFme7b z-(*3l&EsyQ()}pmCOEp1T(Df`sNe+?us!itB9R4H`5{7Yd^tZQylzV^M0S-(bt+Mq zN(~l1D+5WlO6Cz&TYVSHm{55)vqKl5U5PQrOi06CBfCv%;@Y>&-a_49oaQ6MRxt;C zwja|9O%OCk`BTYwB7BGuU1%lDpusvJL1eeC0Oy&-e}Xv|tA%}Oy1H_>q@7;ae!i@( zx;)II18H?i=RvWFJ&>&02>Nk0x@U3mjpommi%IidFa-BPlLkg`#()_zQoD9PtVSbB z#}uYIHgh~4$shYqbzSXU>YR-+%ItjbHtqg^dB6QdZ2ki_f7l0?dNNBfpdsfwarjMf zJ{eRfRC@7`(CPAB5jN?FSU47d_A|AK61GO<#T6*F&1GUq%0_7o(vb=MUju-)L*$-6 zi^?q_Y};I|dW;{_urgdpvrHe;c{AucIb`ld1C(q)JRx8X&Nf1FqxI2m)vJa5!8+% zKzNc}8L}8w;5y!lKGVydEivpO2f(q^+5?%;9#X%&>z^!8(Jov1)I}ExqS^yT6t@bNG{lgvQ zdN4$*L7h@z|FKf)=${o?lO7lUzUsTY_l6*5=lwg@9p<9IcV&dFSbEf@&(1rAqh>Nq zeOhVV&oa9|B9>~+7&T}b4KbdVZJaS6)8>R0?q_q+nvh zV&N?sB~dYeR{y~}+qE`5(|d!TRplq7Rm?I8xXf>VKb%}KE4%|gg@1hUsCN0`q_w1u zxEkWKZg^W=kz{0V=1;;{hTgIlTMyPf0w4Ko`9POJ=T_e3Hv z{g&icqP&CiO|XydGQ*|;qxWO(<^FkG^B&=AMO*rMN7Cy<-=B;blDxs`wA);{_5HhU z4%2`e($)H&G`z(RZ%0Sbs>Hgb8eY_x>gfDgw^qN3r2~P7oH0#_ee+o@-23VAotLX*Mtl|j51^v zGA^G;>tI#Uuhn{;xz>2=V$v(`YtcbPYTh}|J0*%xpqYB61vLGeyR%;Na9z<4Z%*zV z!~4lkb(`{z8Fc$vZ82$}5wwUuKcnH#uUbd4yB7!k$X>#f>0O!B@O-~wtd=zc>U35d zczu@Jcrn)&{ukWaAvt%F+y$kr zRi`KsKUtNUiTCt&cObYiKz=CyQF}gcWT@lY4xc$YEFPd@)83G{>r(G{_%l-H;Z;pH z>97&8RluoP2g%NKm?GcgCh}$SfT~-&A15=L6*=}dd*))(z}RSwLxic%Ab_)`m|7Sze8sW6CUPam}III@4?t>9ct-}vyZ$UbO8%SNL3 z$%)0C$7ek3yh_bhXR9tk$djxzu|Kn0LGKL-xE}T#%YynX2Jc2n!j+u*dwrW);UwWn zNM!MTKWKkQZ8BsZmw5`)NY1X@nLjJfv-$J{n9Ly*ADTL1i=^rAobdyjQs>TpyvtO8 z`6~QCQ{(1@m+G{BRX4^gk%tHoAbF%+r%Nms1+Eb`y_uqCKf0FYpX6Dmd)jS*QRnd= zEqmPZfNOFNg`l1E&^!wUOd4j9XLnSopqMovDHqdOnCpiN?w4b^msQ2!HY1p-Rcj7O zuXn+!)1tp2&F9Ui@KFfO2ZC zSNoU~)`=}(Rk$bf;`mAT9MzwF{3HqvQ_8!pX_)XWInT4B{(*=OIbWEovEY6BoOy*M zvS-P3K~PI9nzCrZ07YzVJ485j|GnlQvyHEs%vf$}yLNZgB%k1r*#rwalL&wae(t1p z^$pV6@6}ngoxwj2RE6WXC!r*7YlZxWfnY^rHZsRFVNVBh4n!JKDpd)O%WZq>GtdI+ zKV?EZVVLOuZIhyJiM% zX0=~^c`Hq%dz*H_o#6Pr0jvuUjx#&s_lQV*zg0}w?VRHIIudg_w9x48ekOmPd}Fs8 zv!(juTh*nuYdkSwq7eKJyp+Gs2zeE*;R@*gZt+V zbIyQVz&F_$uL@oIP%i2dD7EPDSQGM8VgBe}KNE=H`r9?5kL-=NtMs@H+TKwQc@3u-Y%dCZVlj*Kk+4~=40k5fN1W?KJlv~07QX>wZh-u zN^*0*{ubDv0Wp0XZI?0Y*T*)ICc6PKzyTmkk-8&xr4R~7`gZG0q;;=qv1r4jrbtdy zK9-p6c!g~!Yn|Q9RyfS^syZnu1M5;$fx`q%#ekk9Nn{u6<&~3X9d(s)=lAojJ~Jek zLXS*3*_G8D{ilu2H&?jX0SrC)jbbGS!YK75GU|%Qt98Jnp5CPwS*x|YfA*)W(`A|S z=d_J!a?9%qrFXf%`CLJ})oC;s=R(#A*&?=XxCIs^70Hg>bR@RO5R-wY4TKG4+vS&u zTj#_M+yjf|GKKscdd^0z>e*HHJ6QNKLAg_d2uKkrS`9f0#*CS5OvDPV1o!bhfX@S>h~Z%6 ze5Z716;OVC{P^K~y><#0`;Tw9sQyclJ&6ijf+ROsS!HPwXxrmzJLXX;xdTf?aHiBl zXzC_<`O?9N1YmjGBETC=rE|M+(B0IZFp+(ucvsb8=)BlJ!lb3=E6zmdpk}Ywh_#P= zbdN5#Xv+f1I3{gTRDkZvC6#?DP)t1SRsYSJ8-LaUrkCL7TsW`#AXxLYZQBJ&{D_FjM9F(mubz_(nfuFSIViQQ-_23!utRdrea5@VU zSm|T58lf53ih6#)VX06)+KMJhw8irv&E4%e=g$oH`?CO;Uav~)EM1XJFO}jflM83- zoIGTm^Qf~^H5vnX7k7CcrenEMh-?crO$nN+nOtms{D!(*ITqp%*)kioNU1qGJ%7#r zW&Sew{Ou6B01LCr-RE(7G2?t0FfbvE%NVCqnVU*Dk<{7ztBkiviZoS9<2u4UFb+h4 zGCC+~eF>z~?z^S|LbOP{cRqqi+>^+W6@{^d#J7i%EDMS^kFnJh-BGMbA6`E~?U4o4 zT&{j4jm!>mLLmE4>gS0++S(G5JLpvGh~de)b@m_AaJdIzy2O2KA}&TkKMg)Q-<$PZ zf}}hYOpemcooZ~W&D=K1AcCKAt$!?qrk^`b(K5TEhr|4n?t1Ykm|`vX#Zn8!O6k$5 zJ#~EvH@BX6L8+2H%`6?$DdI9J9@9*Bdtb5qSa=4TRX#{r)0jL6xpI3Wk=v1!^om-A z;ct9z0eb>ajAaKwg7haHWirMYbs6ujkAZFe6{nn+k)~2PrYlpAJc|K+~~9 z_Pilpuzq#S?sIC_v<*E}#PQFwNLKm0d4eIa7Sg`THNgR^)h5~V&!tcVY2Sm2A$y&S zLP12VFd*fHKe8NP0R^RISgs9fXN(DJv?=%W#%OuQzs$+m+fy9O8E6B$uPza%4DWpS zu%)R5QOSA?jGh1Hp`!UTq8`v{cwzKZ?}3h+2;>NqBZ1hQkm{6u>N*RA7cSpjWzyad z-TS@1;E%CSHg*AB+{zp&M8D=KA;RsS@1LylfTy39I}^Hi+zcr)Hk3%aL{Xe~^%SAN zK!!BUeC_ zYSyu#Xx3IU&s4YLkA@=%P7iT?o=L0-*HJ2FqGG#o@`-3N+2kutwib(2zua*Mg-d@; zZF0Wu?Xg9B$7{(dNkiJy!g^hrN7{{ z&sE5+fH&5_Ue7<cvB(;!+=_&b9xRaMDdcIQY&a#@IEor0Vzl+vD*5Sic8lehw9_PLqZ7#26Et!K zzl}+cX7~%nZgu8E(3f-F^#(SMDi!N0eO!{8pv98F~9+|-zxjT2! ztRy^=_p}YETpex(TT>(y*5yxxdS0g_a|f$}v;vb9xWFpu*~eZctGXeyIr~DiWhjJL zO|m!Ib!>^36T*45WY6wb!3s$@)G&&M0cjxX94Rim_DLJ{Zr;}OV7(UKm+aGlPs5Xa z)deDUf00q|zQP(xXwY|SUEtC? z|CrGm>_D(_dU~q-^kmSpJbhkmG~lse12wTF_Bip7r-_OO>5Am#X15JIeTjm)PKH$& z-_f~^QYft63c`pHSVsowQWNWP{Va!q&mk1g-5nDJPRFi1-)#F`Yn^g0xdG0T{SU^a zF9ehxO-gHB8F~He0T<_uxUhijf`Di@j^0%wH+Gc%Zg7u3E;%SlpM2uB2ttbPq*bYP z*~tC^lknxqg39J77x(#LjwgKz9>etJ%Lmy87{i56B#X!V^?OfNOs}unBK~Nl-t&;j zQqr+5_;Oq?q}-^V9u+e(Jq+|o2+(&$x~|nOP60QF(>W0?0q@4 z<+4lYFKcg9+cX>Um>da;yJP4*Nw0loMNbc1jUC;Z{g)ez_~n#fadlIE1GGllZ<XC)ccG9A#zJyj5dpU*$@9+DH|d^j)fy2;5m36I6Qy>VG4xBK z&TI8SzM|cNw^1o0{GJ6F8oV?HncEAA7Sw^Nh>S_`$(9^X4dRG%4J*YoPokN>Af~<( zkoIWHmlNim@!HRooi@Vfjkafw-^0+EPUQ_tGml7MJE9{j7iHp1%sl-TTxY2DVDChK zyX_kEOWA9yTG#)K7(O2vF(&~D>0*D8TU$N(8{(Y=ld4N0S@)Mx&tq&I;z`ezDiN{Z8 zoYJWOLE{hbkn>XQ+6H2vh5TSVsj)XAzCTYO%T!c>%xJ>&N{4P~D8*LlF zHCfW@d_6c-_aB z*t-033tV(jE(Dd06Ef`xj}`*(y^VCAD}BlENL^$!EvMRsI##%?ZCcM zf8BX=)1;HDew0Y7lnRCNfE?F)lhed_3!-$QT^>1da}ZPng_9SifXg1JwST zzt2+h$HGJh>Y7s{1lV@T*$Gz~Yf1@ZfMlNdrBt;^*raWEl33VR0 z6c|8B8Mqu5Y+ZNP-97pc%&36Bx1tqM}%dj^sjwOyVnfIt|%cZdG%$+dIhj zzx*KTNgqrf1(3^x$tvR`)n3bbP5_Z$j>$8hJcp$3KWZ`QznF-{kiCF6;NwIA&t)|t zuGP6}9)lWzI|j~6H-T~B+gyiEPoR&I?zKK2sK-Q_g!}-`_wW?V;ti?+S%JM3JPpe6 zI?EK`p!3RDJ1if{D1IV7as8rjr!(NqJK;jJm?TiXi&4S9zXgD?lY(W;*$(PlTv;!& zHnVeP&f|^lE!cWIKv1umdECFx{l~Yob@KZmT%yTUX~aL|-MaaN zutmUZ!60G8-9$#i10x!6O7}u7T5WNBg1BEyH@y}}K~`rX%sj-ucayQQSP1U;iU1I^ z#h*AZf?N;NMCqACIqUYEsixN?qrnq%8BwZ8+a0zzFfEct-@RFGxIlp$lEpR&Zb8aQ zmIxF{CaakiF-Q*KGZ?RPr1E3D!%l1m6$V2$YJ1|EPNK4}CiqTawJ}>c-}`%CKkn3E z|E{??I^pg#U`KlqS&54?T&Pk}9Pp_3J;PuS^BInRl$>eI0Y46xIW)GIimG$q^d*Hw z6cL{J5|v_*$nIYXpCsJdKFX;5lB_12B<})5ZZUgE=bJJLOY0gm3-E`9p$9B7oP~*m z(}ULN5D%w3-ugw!dP;A%D08^2XCSu8#fWD-`h}?~&suVDtElXPVTIwuaAb3`g@bm9 zvBuGuYcdsx!I%5-ogT?sqe zu~PK2==%%X7W)cbc9#(hjxOgBwKc)ygzWuUR8q-cv|pX z1U6G0w?4j(F5P#&w`q_S@~8-d6#0&KSE)E+6T6=uD5c zmegY^(S#QD!UV!+S+Wy_apzo3%UH{uL)ZL6Q*wZ`1jcRA8Y)goe;p&}uBB`R&n`9$ zG$%zjA;&d%IcM`+lWE@sh;fMWrt2$YY&3=nGB_JfY5`(R2l=cuQx3Rej_Vx`YcMh3 z{)tEX#++DT8QaS-8Ln*c(%hvBu7=F>VPX0Y>wH)#F}3Q}PifSDhEq07!N1q5cpYPi zEj>7SO9-p^o&98n;g0|!q(96eq@!HG$1GtPNj}zWDMl#b|0DZ_Q zq$7Q{k=UN{MbD2z`Iq12nq%GWfmBAz4j*d>isl*dE+mUO4uj z&l~xRhE8FPJj{5(cZh!lj_x(TFqdrE{cwGGE^>Bm3`srk(c;JFy8Q96i^3eME!~LA zw2f*VJfS6M-($|h6jtqLH(j4p_;;G2RSq2LmI5#M?M)HEex6_M6EJDA+_=0p?l}DQ z0YS5j;HJ1Zso;x4Ao@J>9Tp}{^#?rYTknwYXV(~Ur%=%_bm*3 zb{jt#xAPz_S7P|j$)vA-tcuXlEaB1?|9_@=zx+Q>uaBNyT1Q&KZ1n1ewKhMgL2~A9 z){Lwxj6}DrkoJ_a9JJ|9@e;Ab=uyW=jg(_*MxQ>}|M_%;HD+B71($s;fq#jd%T(t$ zuewHfw1i;15Cju|8kI(>CG5&8g#SwSBv}H0E!b=?Y>(pXlYPYLbK~<|H?i4-9x0-$ zVA(K&b6|~9Qg=sJX#TX)RY>HWF3@zz1+YeJ8RqHiX9+KbW86G`H`(XR*D}0bc1?O; ztGwmMOY8qXcSZ9yZamxDk3h1|T;p+{jNs;{h{9l0$TpTTmPwR- z-?Aqf3AwXWwh*$XvdoMnlqFjz648)-YnicRtL%Ga-?Q)huHku}&*%H-`+fcX8QsWr z-RHc|`@GLN*W9B?=jFOW@R=S~s#t2Qhr&&)@$VjjCrs#_I*7o3*NbuE+Wq33ij0hv ze8_Kf>EcDIKiT(n2zUplWh%NRkEQ~0?yO(_I@x&+Lu?0j4X4aQFQIDlkJLgQ--pn2 zu?YtvJBOyAUIq7!55ox|3|#rM z5b04ISnYu4iGKpU{34L{T=Yc5PPyH~PB2@N3U8)eU?{OSX6b+DJ7hjk<}SQm*Ee*W z3nwN}8c8(klyy78r*TFwH0<2dMl31~6A#SNJ9o0kT^$;P;ruo%BGF?qd1#K0FtTB= z7lCAtZ#K#>f#-KiYS`6a2F-NDFQK$He{9QHF~~D!=`o64AXF4~;O8UxW`3+*qC4XS zqmdfe>`myocDuyH*1pR7_m9c}R(-n@1bsA6x@D6@Y}Rzk3W01w0{qxoO%S2flEbHhBQBbo5%;6U0xK>vX} zHzfzwkORwRtWg(pI;r@oHY|!2W+Hl_;jJ)_b7x~)w5>5O{_D(u8Y49lLUZAWbOD>| z50KI9nqlPrNU&y}-kWA|b!Z7=*q#)|jS0BPg`fG{s>wz0NO}p)cB1aBcpj90pSvt``2814VtU+F3ySu^%YK8P`?$B$o9Ia zjCVNYRIUogaUb_sA~Qw`N3tFv`%J}wIW}rgq#Hq)faAYyB`U+H)l}fr2(;phi)88$ z5&w3gRvj{o5FROyx&%!eOGTqlKVSbT?{UDvdnqY!(7IexMPARGpJ(t%6NAEc@w|aD zdEeXws28})g80Q@`aCQgIKzh(@1nSHOo7pdls*77*tRQ!7b9u0=eH4f`9sFYCpW14 zH^JV9o8M1xb72hG{JopyLJ{dn9N1wN+OR(gZixEpfnANBtTi);`!&T!%tqj?nuLLu!h0paod zAn*h`4qkn`$UuH^s)G4t0LpOJlQxQQfk)>?V|jp;8?oYmLw;p`8#;rbkgSFL$u%jp zbPs6Qc?ROy4r(9^gS9%6r2!DYyY5RbzPs=zwg^rHY@w4%id8GR$>OcFRdSSuh5J(( zyMpO!QEL`43n%oaU@Gs0pAb`zH7c_y!Q&}eq!fOTNXb_HP6e4pcKc7wX>a;S^hNr; zIE^B4MuKU!7Z-+jDz0w*SqvgQpBt+UKeYVtPZ`0L6Esc&_}#2|c+9(|i(`-|tZ6k_=u*+Ao~^cX;)u*qw>=*t#gEA>2RumYwfxL`FcSTl?9S&imtoZJ zpAgBYqUlZ~15#UUfCdg}AeSjaJF294P>Cn??WDKuj{T|qH=zp5L#D6wy&5OQwld#@ z@q5Q)@YQGeoooH+LUHi*y%Q()l|IIWEf1aG6!-pWEff`bv&xuHo)>T3cmRR7CK{xj zQG!vo?yX;|b3g+rl~rnoK^VoTTTR;ZN5(c``J+Q+O#ajx@h^n|zEEP6nn^J6sAE`p zV?{3|Rdf%Ec>hFZDvS!U)ih|2W<~KT~yZ3N*t zlWu6bmIm^x(rygiI1jw@Eo7pQA`;!lC0<^oq7F%Nr*__*@jm)T6X+b!M?T^&^z$A8 zjO;{vTvCWjuHh}RsjN)a3teB3-D-IfWI&oPYe^u;%##e+j~QoAP~P1xrIUwcX}-)` z0ttjAL(<+b00Cd!ivf{X;D#IGu3O|9**OuKv+nx941xU7O$h~mqJbA@KnMf|Lp?__ zW>o4q3SI7>(k>qz9m&qNtbZ0EgS7t|4Bfu+%RC4P_Y0{W=gjN8_`cH(4q{zQU}7%- z4PGJOxAy4pZX#IRmDkn$5sA5%m#fq@v}_a^Cx;vV*vI>N9&nIPYV5f{kSUymXA+7J z)g$1V2FqYLU!)Ba5b#2GchVETzizz=Kp^w$X+caaWUPVj%ieVWVb1|0LyHZW30}9> zP0Pyl?8X=G=4|uiq2=iNl|D!y@FR_6B&fW6<^<4O|0N6m`sWzm1AaG7 zY?r9O%9k08*X@j(w7I|N2uP1uQoizHp?2^oX=5+D)X$AId?-@{*l;-T9QF_=PKExk zBt1Rwd+@^urK^ixT5+aJ@>jG~3fcRvExsz+I=Y?7<{yi|AE4c(N$`ujP5#1iWuukr zzqW_J_IulORZL?->sQKOGy9)Ipy(ACvpULO6#dCrH?EV33S(vN7kh0-TG4uS0m@)T zowqV7uIOKW*a?}0s|?-_hUN&+_aqLS_9daFttZ4^A=DLTL1?1a8gSCwk?4qfSrTp` zk!TKoQbJ9zBC~|qnk|wn*^-Aq=_@hXw)a|`|I_L(l0P)ypyZ7K;l!+7OB(Q4KSyVD z4+2jiAbe6OJZFV77<$`gG81McnFgMN3j8%F{<;Ia-M{vouMs5)FW>j@!jKQ}!c8J< zn+UM4h14)0#$uz-U@)wy==>4dH2*)b)fTz68RN#(j%TK`ww4u-q1k-(9oAu<4L*&P z4G!#9)y_9(-AXHJ<7^h4Ml)_)8}bHsDGn)4M#p|3nk4xYaeI=_^$CH@)epN?I=huDRWzy;S)4n2j7%Hx0Bqzn7qCatDeXj% zKiNsZke~)4<2kd{E>@3fXpz$ctcL&ZST)MyZ+B=cDKok@pYtsC`nwKS+rHG;^)RGS z5l~FRd88;bdxSI^9s9Q81gsybB(QBCLHHH^tym^I4FRtXCb{449Khp<3bRJGG3xCl z7=wy3pjIB$Lw_Qxb?^*>X9%pIa6_3_w9FKEQ|_5~w5bUlQFg_yo-LWjP%45rajG%P zmm6n_zx7GL63?p~fuiAlfDjMS*^*U)WtlgqE)_RLqEE1p5>_5&_l^yB@A#627J_ug z$^XY4zvC|i&)yp5xxjLHT`(AO$tvo#smcpQw(o?#ne(#5pE@G^Y7)3W58XxyvUDir z;4lYFBYRMj@y@I3>!d2>Qs@_F^W%ZcN%9N`Jl!P{3ap|?&^+_J4h_uD)ba-4KpEg5 z;s3zF!PL(3S2SfH=1vlx+GlBiy{ydixqt$d11EN-SR8JNx29k;&NiO2Jm18L5wnGp zzz>`q1r`Mv1T4xxT9gx8aZ?^PIjq0pj#7u9V1A>OIOUx`$@>p?0Z&R`A>6cwdj~nb zjK#j=xnnd3JYBztKiw4w5G|MJTBs58O7$TU)HO}>w@5eRL$a(v+U-;XJoPcj+u5SP z+mdR+6|?Hl`1OfWakz%oCLM7?5;yiojZzCTH1dePoqgRG&F8FcE(O!bICPpx6@U6i zP)7(4fhDnL@_-VOz@;gGgxLC5Lad8K2g7$=90puGaiZX-k1C9%DZ?ST8iB#ErK0<+ z>9+n@qz;1VSiBx%6rI~mo-@KFh3Dl?FP(BsBJ26CM) zv`qnl$KP#(28|;Lit@WU#FgWT5aw-Z8?kGRHgSS3!)ZMjA%6VrYEh^@j6qBpzdCwc z=8sPdX#o$-sprQwYSQJ;N5Tm3Yx#V^0UF|3j4k58!&m-?hs8b4leko9Plh&-JiHsv zA#uPO4d3Fj9o3;IC!A|8ng=zf$cS@o|0?{aa^wjuC`ag1(YC%#ePRkzNmP)%mrR!Z z%2-21X@^t1i`VIo6<#ZcQ{~|-!RdwU9Jn^|dzox%a82T;FTnuADvTklr4JF&hX}RD zQT35%N(z$O|jpj53&WjQh0HNu4zH=_xJqx#6glS+R3Q|}Tto$|}|rSgQ+a-e@P zoN()dv7)y_f4RgBoON!}#?%4*YuyY~{1*SSvNBt;|9`j%M|4r~Zi1keK3zIToGxlTTcl3YLg z4(D0%bo^bat=P2?2*+D~U%{?odc<1PKUPXw!X+2I{0_85VuonTwhK?9dxOa>U^=_W3w@x(GU=XBsPY%-8TZCSv7pKQjrx z)^ovqX7Snz;5mnF%hw&AI`TfSv=N+!=YX!-}H4hpu7=qxYys z(kpJ^#mDMUz^}JMKh&W#JU1@XngAz){4Ee>Z>XjU%ffo2MwcQxx43uMM*~F5XGS7- zJ9hel^u*MoM-un%uQhUSf1jEE+8;Q4aG0lgA{70(-`KSdHy_F7?){wYBO*2SD<@9O zj;!nM6XJ6w1HkRXd>ue{SqAS_SC};H&Zm{y`y--dpjxQQ7;ohZ7;paO$@+*V1PG%n zyY)IAk*?2;9cJ(fBio0qSSvCGWgw3eDM((FXPgYxy*V(xqn0{K;I@x8P1Y?(n|{zLUzt> z_!$fWZfnUqd6kU^70Re@mv=v(`47_fD*40U$NX+YZTb;a=cG{IY{Jh(*O*ClaL%-Ii_LfZH=GxM~HhoenRB6 zsY&VrC4!ZWVT@qaW^dBQ1ocKtpU*;~)k-0sxX{2tsu}sjSq$b}D!O9iRK@;WKv3rh zRu6_MzF`71$Bv|;9lcL2IP9!8RFu2?atmv6KH2gX%NNF|6eU zJ(2ggjMN`e!(D~FT^Mk|g;LnTcIGm-q%cy#CWrAd48(amk6>sb+!F7%^`%g%8Ecj@SuMo#N$BSl@I^l9n*m7%{3h{shUz0kw7FIUasFmqL=M<5 z5KLbL5cqWEb0^jMfVar>vyELMzwIAgzu}-$MkwRW*8c&F{eXs!L_t8LxP_X^IG8P9`cCxb>2SnJ2BlS^0hSkv0C#~H3|F<&E z9Y&a$b?+63R+w6Sk5)h)s54<~af%?4^^G7Sh|d``L&-_%D;oky(oheSmH#xxh*4^A zfh4D>W}v~#$4DAHT|1J%cv2b2Ufn;)-q#QJSYo{96VKuy{p<^}`c+b*d|)6R=G8`y zXQ()XXX3!>;%=sXw!M!)vMs(<_Ny!OpPb8Zk(+kew5+!4_0uTbqc86}uNl$*q;B5ZfW5XYpUB~4HK^4u`t z$iAbdU$5_>E!62Bn)_1L6h{To>h^9?lH&_cLPhP@W|q4j+M4pvl~t$M>1(mc!^_}- z|0Dix@(z%qd+MLP_>uM!t{))-CMb~(Vl_ap1bXS)B=Trz(a;mI&ox+?_w7YQYBw*p zvn>3Bp|61W%=*V;Ioh3;^S&2a8h>`kE$C}(D~vBolApRZ@P>}qN>v9XZ%q_R6;9dS znOKQ(?8|Ggq289+X$=P*nJq{>yQUWUJa&FV6tFUpRQK=Zg9!#8Y}N^9o0GUO!y2Mt zH#spx`k-=8lx<{eF(-Cd%uFG{T8k%^>p7)>lHi=rpoXDT{|lTq!Mf3V~lIg38$! z6`x+C4z+y6xtJsH7YEPX&b0>BlYlZ_!-i(#58Yhwi3aqUXZ5ExH+$>sseEzkVm5J6 z4!y-c5o+VEOmFW9Su~LHx4-(q`WDeqGck#&wmt^;2)e8X`603|)7Jwr7VhmmpdF78 z^p2S6E4xrPV=(&&DK7SD?DzvD+*Cb%f-bM~%Icj0pw8cZ63fe1hq&q691bDy-2qd1 zpKGBQ{!?3^C-3o71>A|24d66JQa>Z+f8(?_ml!ubF<1VO-5S@;IKIW_H_zAC)4yEU z+Ke?b7K;(7B(a@5qUP0?OOR zAOaB-6#C;iqW!tcfW^u;jh&lG7NfuNodGRA`&^Qp-;jsktqz8)8qYWFZwuWL0)cDD zgY}s(NOm^g{Kaf=&~L@vugGg)qd#Wd_?tl12&|eJh)Q_y(7X3YH`zy>0=yVkOPpaV zBDn9GeVG(!Wwgh$UZY8k4FqaVNrYR9JP&PoFfLE(c3vlUJhg*MiOg_BSH&Gr1{kVw z3@iY0s}}{I{DA=@I&ad5j<%*V4A7v7a{6qutA-uurfRdau*Y&=QM}R4gHljq%(9$t zy1c)HN+V(BsQ%Qe7&B+~ozFcuu67pm*smTo|J34MyRE^5aI?Z}^lxC>~dsJrvtYYJZ@G$-x7 z`qkdS!K_mFmx}Rq)g>9AYU~&@Ig`N@{O%J{G+uNqL6<(_Cy_G@)sU^uMSc8 zzy19VTzwK=CH?G>(D-k4h{s=O4v4Z{Q4H$wC-lS|^zWnlxbFaH$pGi5>rcfGY^HB~ zmfKWvUzss20uNCU4Zdx9E7z-UvR8QXlW?(7*6>wVNw$@;s*SRkuGWJ2P)F~yoy{LV zK2*$q5tw1naWRXGzXkaO)S5AT}ma=RaHF*7{vrW@a2){z=&sQkI;sedRH@GlM z)u1YmIWoJy$pNNgAm3tP#IUfi-Wu6uR7MDNd%CA;tEW$Qt5tW&lBLCM-c)w2PPf!1 z*+<;|LQz4%aFXZ5nWECt=j&_l1#lyg1%4zBGsc+Ot^8J`lgiekvo$fU>oI~Z8xx#! zL?oW5hJcy~^t?qZ3${SEE=WG!%usKtIUc7SYsl6{JvWzf{rYBi8XaXP!@|Q89%nlGOq%${%x*T#E=G2} z+v3~Gz9nS5!34M=I-;}5B2r=M$IDV0$X-Ce!^Y=hUmgv#;9}&MM}TMX^O6I7t@Aii zCje;#8;jHdZEe^Xt1hUaxEm;sgVC1)o%0M?B)Uo7_U=W{2PV(V!l^^1BB$m-`QqJ$sNBE4sVM}93xW;kD?knP z@XmIPGORxNcAFY-oFv&=Qk9G6Oj4QO(DAZuN9o+Mk|FRkskNlnfoe*OSr%W%&h5+1 z9OrkB8Z!qkjvPv3R>cnWUoJkaoTlJFQ_rXiO`LzXwTCX-;|0q6)D*q88of=aU_KtyiC>_-7(% z?D7EAAIvRIYG9}=gm*4dg9*gApR@s5?UA#`C@3A8PU+iSWS8A9T;*?^2RR2+T|OhC z$xg{To1;E_?d|QoL2{cGfFPXT8ql)K-b>zI%;>vNId?2McpSr*baxhHq~7Z4>H_#-9L$ zo^c#71QrSBYEJ;uMHeVDf&)in(M90z-xSqoenQ|)fv?D_Lrygr4gH`F)R5XJ0jEat z-_%EgW;EtXp)VIEbmTF=_;Y%b{bfCeNCJr-=!o}5hA3@SJ^00b1 zIAUR!(D^4G|453hr~JJ1QyqR=9fA^dab8=K=Ci(Y%zpR`(Prs|kd5y#l|vme^R?VF zy~;0Q%zeA6c9Le-POmV?%`wRI?0~m`MthCFis}9I>ETALnyj-nVzTQ#_JWE_U3UQp zKoocSeB@ctlb7Ju0kIu!XCv;rS4PY5 z_~0IkkJtdMh`(8>Z7RH{D$uEsh)MG=WkI-ey6k3=t@;J{kh5TpMv4SmV5 z%Y?B_>lj_{Pd?zL-0SP>XD;{-UhtHh&#GF_a&UL=kXau~GvkWlU-qh6@iIFc#k!=p?rwD%Y}LoaFfBN93!j6n0TTEL%cC$tZh5v5$$-H{dP$ zdJcF#2fVlyw4SRXh>DBP^OfPYI!G}XC|d`?c?Tbw`6+NBBkBC$MX+sFr{EIiTBuF+ zhz95YsU(2r3+0~WE(b${bCB&mHFSVHhjc>`sh#YbAK8m@eWq<4Zr`r+SxjS} z9w+2(b>~W0v5_Q1cI%MAw&=4|@1Ft8-fMN?-a|27g*(54MJeLa<{ z3eR%-eYrFY8BO-;rQxnx7$>QPzePjNS|%uusW>9hV!b$N0n#kHUQV2J=!BkFZBu>v z4{ln7?YRjdVnuHNfA{`-RPuUjMlW;Ks>;BbI_L{OuZGt2w4+~{*UrYkta*^1?DkxE z*=Vv~KS}oqRLu)KO`^GRsx)Ea(~ie*`4rxm4CPMBPle~AMzC#f+6);PEG_ZZvH?-cO(*n0>v@W5Uy1)fUJ>+CRV*1d8f;_-Wp~z^HyG^g z?20pzeY*vPh0T?!K>8&WLuHP>3-J!VzWo*B+K%9DSM41gx%OpcWs|bY`0$d?pOtpH zqI6})eOH70R=eF=a`+?bFZxh}<~d7HcWlo@)sL!@Zf7u{zxXUZ+a;@vpLe+ya!v9d z_W@I@(aRkLwGdJ5xJLHdc4pMS=!%mNZu+`q?5*;zA@Eb=_v|buF}}%B}&9GND+HX&?jnhdPe*sqGAI<2{(_o<2Aj`lQN#(3s zc{WEDm~V7<^p47|NUTidHK-&~YTYHFXYt&=QQfO?ysgOr44ZtuEMJs~OS$ z=;p))@X;t>>ave}{p0w+To*K`4{p!LR84KKhVOJXHa3>joc3y{*qpD|;X>cim7aUN z(FKZ$`n;!+Uu5Snx~ub%7V$))3aQ*%*<5Jen3i4rNL$)bG2{Akus6o)Zr%5BlIljj zzg(z)yG~6U-rIgjlmdT9pzFnv?JkzAXD9p`b@Ymt}VB!Z9zMXfC?egKt8E;#PIK)``yQ3v4$j3eVLJ==c>6+TTJMc zk$3s|qAR3IM+S5YVyqCL?XgnoUb&DcO9SMN-}Lqz`_8wjXpSX+TJH}UyivEngYkz2 z9Q#U#<%B1a^NK?z{74Fo|FTcA@OwmXL0{;zLhXb~THEb*Sa8XqdJeZ%i8ArXbS!j= zKd-akZig=zuum&>iXO1UpJt8g2VMS8SK>oKm!IKH0~_e_hhUGna6LeZpH@fU=kB*z zfcuhEKI6q60i@_D8%<@r(=EKT*IqpM=0 zZ`N&LVL=BR(+hi>n_HHV;M$aF?jtMXI_tAyl(?QWkrd}!b#*CerCiMyK+HyAFInG43^I5>DUnBbjIPK_Ky^0}tCsq8!GhmLz zzuY>Sg6>YWqdiDYCx75SFaE!%V(HH0>Sbq`KVNxY^pBV!Unye2V!=LD@QYkDph`~sb*n(0$LEp}miY~0e_b^`RX;gu{5 z!v9=Dz*xlR)|j|Z$u^FQofv{@+3!2PWZXCLUd%2V$wQlnuoW#t^a{<(DnBm#qV_^B zR|Hyo{V~*a5lqSd#~u%oE5b!MFPo*rW)?7G3BSKKYup5 zx*Xp99pgcP|ClCpnLj-|5pDOI=J$>SK6c0DgbW#kH#o+HH^9-IW-BP0pPJ^r-M7YE zxLp#cX>+CAj8@o;)}rzk>!AoVD^Gt^&;fiWUNQoG+9tWr2ZrfdYV@2`hPp5eASX5^ z{cCS@l!hN3t3QUeM5nO0lSfiwpC{+73r(2{h?NvyrkcqZ3qOPpJ4&!d5@r&rRH%uI z77Vz)xXlMf1n8i4x+EinqUXZDYq||yMhw`GMWFAyoa&}$l7Vx>sHa2VoG@xz9Sk8a zQCAUfR{`FcLdo&qz<<4P9*w2L=w0=!BuF8=MUmLKVaxS7(n;~yeMZ%l`vv3fs0S^04;_<7EAC~;()On_?ekc zrVbLT`t$n2Mrz-*z%?i8z0hZ@!nrl6Y`KFrYn zH?jc6DCPGo6EzMjqh_Im50`e!@>;VqJ=OM(47KE*d6v#LO90*APJ!QUF?#trCDsj~ zSDIzhPX{@&!>StoA{c)n0=)?Qh>8Gyym+5n8G6Je?QZ(w7WhHzq>bhL96g5Tg3B6% zGPKrkJOwQ~GsW~DbnpJl{|UKPurhzUTowsDjN*(veSO+RFDH^m8r#w z#TykEB^X=62+-{$Z3jCXx*^m<-Ih@W3yknx(zdtrctkAA!jlnxaM@~UVtka52Y9G{ zKKqNcIl#t6n*wGoeD=$r6(xRgyH5iD@)9M-6Rka4IP4*KsX5%e`QEhK*QwX$rsHnY z1&%7T#6^q(a-`2OJo6AQJpR&98w-{WN00Gvy`;m2jOe_=HQO(b80mY8`frE6F z-k*y0$C(K9fv6J_d0~l!FBooIB6g5stjNs8Oo4D178Yo!Uz6;hv0AH zJ>%w&?S>$FXY=A#b{~D0%pFb50LWMvMCUI8v~`+Onj21S0oM)Ze2ETGpSowJbYB?( z|Abs-2a#WOr1=v)CCRJQqY3BL6cN^lPiO{AiD>v^Xr_UJTj5{#`Qhq81}=K+Qu7M| zA!}B!#KPc^sL#)LKCTq_&(UXz&gh9}k>^*6JiW}!&6_RA9cIAMz($Wan=1rpVXS1O ziw;6@p;nr*JXDnt;^9g0$viL@7qb(Fo@A9ZP#QO0#rVzD?@Ewn+)+A=)famo8)c{+ z!!Z3o4@uALsQRzn9Pp$YU_akOMAuw4@b82wkZsNy;^N|- zN*s)n$y5;DH=Hhs9v2{j%c%L`f>cGgNrrPubHRX(Hp2bYvj-m8{5-mPYS#%mBrpIc z%rpWgc)5HE-sc4Ezj$!;A15qmnagM$f!6N;A4t-e++jTeSr0shRH6IYNO8Mg8v+EV zviZi&s!s@rvh~rgE8rF@n}7UX#Sj9u@AKICXE?CeW#rB#vksLEKlfg)z+FE3OvlV+ z{9r_^W`0By8U&-$Ju3laD5pVr=OQI>UFLq^f3xFmB-HJ}(ea;s4Q*$4&3s8ZO6Zng zgqYZZGX;Xj1u(~i9s_dOYQ})SqIh2w32>2g`jIZceCK$1!0%vnLCA8KNOfek1aO?YEETagkwx|2%Rdm_b4#+GbYI!u&jLVQz1~3D?I>T7%^TXN ziqC~(ddSIF*@y7gj3?TD$~S}r=ryQ^m^0u)i{_R{R%+@UbJ`%WL1>bcxj7GP!F8lM zJYL#7nmcK;e8A^EK@ zp87tq$TtGPVsC~s%Xk^%FSU9d!lR}6lKWMVBQ18?>{<0BV=S&MjI$|2vW-UNco>H0 z^2_umb@3iXWhWW$9(`R|Ny*Yq!p4F8Q0%k`;u;9b_P@pWN2#B*lwHYNhR zHTU}}E_f+4ST8&L<}U^akC8U>jX*cl-)51z#Y|#8e9Q1Kb1-goFW_To^BMg@gouzbG&kB9F%;M&B%lIORb(u#ZO z{bdhrW+}FRCLV~0bvLMfAPT#?nksQnXkV(Fj)M>TU@|=-t-6Sq)5y*i$LrNip}S`im0j)+&8ZOAtxv~ zexyF2iU8QOsbOLkJr1(Q8h&}|MOTE+lE7q{mQm}g&(RZBnVEWaNdbCx;xZ7vtM~dg zBR;l1Kg!$AqZ$~eZnS<_mqrDeFfWT~FOH0@cM-R@Q-)d|uS$~Dka>{o_cPI78M0_R z6A|mTpus;fkbr{zt@*Aqh;YJE&^ryqzaXGM+vVgk?ag_x_sYeN? z+D-~{ii@BX@duxf>tcwo8y@pU5K5Q#?1;!hFklr(CydXL8@6DX3ZscqM;?Sbs>oAW zvh7k@_g`@JZ31vjUZ{S(53U*ju0o-8ukb@`U*py|8T0QMjS1P!z7|w_Yj+6cb5WAc zM9&|6yAq(ajt;xzgz!~FM#na+(bC~0JymZQSVJ%_xX+xB_%Cg@%U(H@v2=CM=8DCEFT8DDzAr1Yef8rz zOIuN#^ewkGdtUI!%j|?o&)6`+VjJPR86|OqnFk&}@U=jY4p$=1y%5TW159OCDd9Zb z0(i2qc5{#u7C-PrkkRCW+it|Hsr(f&GNg!UW=~W5SHytJbgI}V;R0Cgs7t?>tWEl? z=U;P6iq|@48ePh}o~zE2Sf&-mDw)!$9!(I2;dppmwhs}YCSCp1gWVy-KrVb4eCXAR z5YH)gM>n+x>J5n{>b3}F=(a{)Vk&hfNn(AH$_Fpc4Y*J{qfvSdI}$^b|IRP+2mCe2 z9z$o$^tH?{+Fc0h(V7ql63mp(HY>PGh4;@7(z82Y8J0nSTz*Kb>q96tK5Ovlvm^{y zC(x+LsUk+k2n{GC7hdA`DnA)F`FE1=Rps#7hDF4VGF%k#=E5JO(@ln9JtUoISpH3& z)8nK7C6QJ?LWPqAZHV#-TSdIcNJQGd1U%`o znu-qFcmg|)wA*>jy?zNY7Gg3C6l|-%oMm}ExKBb|5hazcjkK;2YkmvE4QD2ASB_$U zVZSGbOBbmlqBANJeHbuZPF7izG)EgqY2w(W^_>KD;NpLZZ;3l4LOddP59vT zN`3vZQ3PP)UiuXCse-k2|{EdVIDd+}0tpUi{eCx|^F6gphQOtFtqPW(g_ zo#Yq@&~j*gKS8#LGY9Km!qQ{!Gz6( zBBwN77z^Lv>?AL&MNOW6WLEl|^Zw;I!VaW#KGn;9E5u!x<{+BcVJuimZIXTUrM@d4 zooI~?zcpei2Sc1%AHlmqcNB)ajvVxdaWb?LJx zZD0QDoOjnTWw-lBV?hGF$#8D;ZZKhl{NC@uZ|YjK;d%`pH6Zod3<|M+FWg|Bq;`op)MUX*)ZUt2==J zUjI~b#-g=ql*0!kpSql7 z+<3^G+tqi*q==U;l$fNq;Ngk_n3JF?T|?!9Ux{316?!0KvS`NWR6XCt-I0{aQ>SdAYe zItIQg!s5zB2`lE;o)iSUxh~N`#|Fo*o)I|<^Qx24bY7@l>OV-2U6aTSIklf-V|nHI z;Z($XC(^mYtffBpXuXNOVQi|P<)=gl3eZDt7@rCunt880^zNr#JFK}Z8qt5+@r~od z?KPXi;onV6)VZd&Y6Ws~UKSlHe46fl(#vs6>EeVFQ?a$Kqb= zTCYp<(s8UFpa-j={b>JyIH-u1O6i=l=PgUEntMu2ReIQO&nvUR%aekJ+tmD)O~rRk znwOvLXf=eRHyOYBwfFYhVk*~LMT^2;WRcSwpra|ZGQm#*xHfcQ*auu+vc zff?^GPj#5{TJru}9#CLIwqDrC?%{k@yfwM3XJR1DaA|ak#oW!PNzjGDv^jm=vUt|# zo|f=VVjhy*cWj?Zf^Q-7xE-tfo5Be7!3CWo#Nd6K33m0rqvn zOEy-$Ve_4i;gRcWRDl1!0--;gMS2dHR0`$D#~X;k7uyX!t6zXrsu7hw zf$6KNzN+I2K@vdksnW##w3N|7s~2CO6JM$U)`!9Pc>qburkcyn<#cgSdV8VQBQNAM=N-D?0;h8#8cWZG)MhI5sX^~Qt zZ&IcW;TO9SySP$fMGZd6@0XSb1wallQo=_fTQB9kcBr{K+T?7+`>kEIh_-t^yHu>- zVsbIn_AylWzRTe?Sqm4+rI!GyJ`^MCI;w>uG~5a%hLGEm=P_}>>f5Do74#UZl!R&7 zU_#zfVMWpHh}bSU04hpHZGL!vg0u4esFuI-K(B!$E(MBS=5Sf(oM2RO-GR zw;FvXdj5(co)cy!ekVN=_vUTP+GKx8F;_y@@2&xpEXo_?;HC31z zanRJsECgS*dnY|Nq2|lzCkK1!fcaFcwqGg15K3}!c_4{L7Oo* zO4JNOann~n*F%PQ4DIV3g0jMiR8~BO+VX&&PA-bG%M_W4>(TC#z7;N|wrDT#m(5k|Qbq<035%|u7cbZ^zUA$S66R`m-zL?S zzsdb&{oiw;@)i%RqJINNq}1~jg57$SIFnqbzrSi5K!QB8|9}Wmd86BPKc22 z1~?Pn#n}gMvfFBq_4z$REKt#58b?QeWnc)`6P~SAgHC30vTd@C6il>64fti#lmWXY z(` z04BP@4KU-ILq*(7xS$%kU*Qv&I2bkQux(jZOO2sI}m|BC(PcjjTrfi>^00)EBfw$5ZMFGFI=oTW_JfZjb63yXr5Hu4 zEY%lf1&>b#_VwPbTbfR9@Sw+6x?n;7?GYaZyjBt>+PdfOD2lwWe}Kmew50a(+*}su z1~zpMp%_S4(^{8s{zrjWKh;CRZqbo{T_K z=6CwhC(S`5dWgh&ijI!2bG0$IzF@Hm+YU=ZMb7`$8n^8t&-OxuXqZ#-bb zGa%aj)Ds86_mWd@Rd1>*-o(_03FV4H<-)g=HZHUY^5!~TAkM20bgti1E9$>ECSz`H zF6WVWgz~Y_k2?tKUd---M~hEk;?_7wHo=`GS)l>+GX7dTD{ z_b`FbI;ibm9}ic8{T76YE|XZnb&Ra?A256cO6*g-1!!%8t|l0bEP>xzCxt>H2n8vZ zkLvq!;DLiIV9l@~sL}0e(&eJKUWv;w4D~lMUsDJ56Yky#tVmvRS%gxg#DE2ea z4NV}tzJZ>~wfSl{&7^_bm*C9wE&82ZudvszWof*UK1(kpOQ8*f_I9LnTFzZ-ILWl# z?gt0BQG+CAM+fo#@IqLRJR+I@xb_2eucQ!E%=tVjf z`)TJSImTv~Wn+lFalDEs_mPJY`Dagc5zkjBxLUpvYsi9eVOdLvPPEdJHPAJGxI#Z;d#D)%+%# zdx`q&lxi0lDB#$&w*AzaAl1#kwr9~8EMn<63!4nS;~i!?3&QiICmVz9_zw0IevGZV zH#c!JL9!J4RWY`Qeq)-x1Vx2AN@L&y&{9k!>n{6LaEJ;kL4%!S8BYsfy{1A&8=!Ex zp*^mdr<|QdiCnd}d6Q&RP=u?`mI`^Pz<m75;Fr?$+@Zc96=2lL;TPBllz%^U&oSsT=ucmjImk%l#B&$75v4aeuS8&WoCQ z)Yg`ceA?QKd8N8xfWSm8iu2JmGjlLRW5OsUPExsZSp;l9N&i0CkiTwkDWp;>Vy4ZN zDqLc{Rr#*#D+%4RGs#P8PrbHQfA`$PwcUuF`$OpQpODcqgGF(yiN@IGTloO#kf2y{(K!-b}~w!a1Cz|HxLc-~I%uh|P0_Ph9yHn3+2U-WA9y+*s- zt6Pbd3>f`fqfn2AmZ3Vd`P7^>6guKJP-5c3%vDOS?&2fwAkiY-n34DIo8p4pl!fEh zE?wdv4q0US{J#B7tn{@!S^>!|%<_5YY8zfipZX_I{>xrB3M#BQ3wG?na5a!sB0uYt zH>6&eK>X+PW{Hv{KeW3LVCDKb^d3NQDG%gDOlC6mvIcA`JL1lA)4*+JHq=cFo;Z#% zz7;xLu`YA>+}hjap^NYzwmbK)p%UPM*mqS7aC4iNorib&m1hxv)hfhC76wFK%G2&bd-;}S%&#@*JfbB^lrR2tIec{QEPZq+n7SI9Ln~ z-fK|bMtApcLat|wCVm#QD!s-_NaE?YcmDdWNcWzj zhOu(pC;9T#r*Y@$n%Q|6zH<&KAA==_ea4V^)_&-$A)kZ({dpTs#{->D9A(_7hleyRmR8f=s|upJ`QXMRE{s& z-KZRGPy0jU5aWWDyV9Np@R~V#gXS~w#A30Z}rQ_Y8_g46m6cn+orBc z8LE$JPC;8zmYRiKv-#YA~q$ z8j3LEOaIUns<`Wyh#fX|o_qzcW=D|?Ag6TpENeSL`1-f~|MUie;0Paeu(Z&Yya?pa3&!~BovZ{!k87ao$kWoTeGW5EXT9+^s6W9qQ&85Wzes&T|Jaz z3X4j5p>K0Z=8`mc9-G3?Z(ISdX!>exMFDtu{I@o-fbyOfA?Fq3xCl>QOLd>5iMAIE zAdk#3ub+?actz2;OHgTAdJsBvsYC*mXf z*ogYt;gwbph7uE$QG{Sl!{ZDaMj$``meoNB0a3wZ(;^w^D> zmvS65(X@XOLufIHBB#KiT^P>*{KT@FuuFo)2;s?sg4_k9K($`Gnzk$5t1sL5xeFXK z1y!7#C4>(U-b4F@w;X*;_3mvT0IImkV#rMkX4V|gHH_U4f4h6OC^h7C4ZlZAU0i9fz${i)5Kol)_=0YpU$-lU>;$@c8k zlU{3j)P`@_wqchFo$}YG?{~p*h{170R_4H|*S}`i5-CT|WIPY z;x}a*PP*pq(+oJixeFMu0Gf=>eif8hk@064WiCSfaj?rErvMKmuY@i56s`4>Hh_vo zez`YF-Nr@g5@u(mvwZS0uh=QN?+<|UYN2H}*-657IF_Iy8ru>xGkHeFn`~UoEx)_i zKm7jvdkW>8SIFdiW|xK7`jl6pruM)KH97*Q1uw zba-Ab5655(|0LdX5_hsSTyV@zySUhrbA4&NR!N1L2tP{~x`CG-TU?UF!Vg6{TF zU$i9F-LB}2xa&={IFwuI;X|cPN0YNS+BEaKd%EmZ(wsbRb{`|p-wGgmu5xn40q<#e z&~EV>lKzJP{=$4Ztp0m~DX~zyrgI{$*~T7OJmt*YZq3NJox6dJkVg0$ z?qbRZcvt4YlX{L~!O6sEvd0%8`L=YRyP*FpmFzxmLm00G9K-~u^y>7LX9gBrW6c@B#LYhWv>d! z#7e=k6^AgRrTL;THKyJ!V&pW2JYY?OCFc*s>=MsHAfCC(5=UF<=`-!&66u#{h9bp? zw(C+h+Y|6*u^k4GSj9m&cVn}LW#%{hiWbfvDf9abpyb)Z$)+b6q;kJ}Cc@QzXbG|7 zUll10ra%<_rZn-S>0KhZCp~mI@^VX>z1G`4{~4ZRqx!8N5!>UGBxtTE#F>

$d1cg{!w?tT~M-myN1!9z-mJVwf7Q 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

-~?Z{kN|P_x){luNdu|(JnZn>NHX} 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

tpZT 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 z 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

Sb=M|26w|;AzB7Qgba11vd0q7WD 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 with the RustFS server address: + +```text +http://:9001 +``` + +![RustFS Console login page with key, STS, and OIDC sign-in options](./images/rustfs-conosle-login.png) + +For a local deployment, use `http://localhost:9001`. Windows and macOS desktop launchers use port `7001` instead. + +If the login page cannot reach the target RustFS service, select **Server Configuration** or open `/config`. Enter the externally reachable RustFS service address and save it after the health check succeeds. **Reset** clears the saved address; **Skip** returns to login without changing it. + +## Log in + +The login methods shown depend on the deployment configuration: + +- **Key Login** uses the access key and secret key configured for the RustFS deployment. This is the standard login method for a local administrator. +- **STS Login** uses temporary Security Token Service (STS) credentials. Use it only when your identity workflow has issued a valid session token. +- **OIDC Login** appears when an OpenID Connect (OIDC) provider is configured. Select the provider and complete authentication with the identity provider. + +After login, the Console opens the first page your account can access. Menus and actions vary by account policy and enabled platform capabilities; a missing menu does not necessarily indicate a Console error. + +If login fails, verify the selected login method, credentials, target server address, and account status before retrying. + +:::warning[Do not expose default credentials] + +RustFS falls back to `rustfsadmin` / `rustfsadmin` when custom credentials are not configured. Use these defaults only for a throwaway local test. Configure a unique access key and a strong secret key before making the Console reachable by other users. + +::: + +## Operational notes + +- Use [TLS](/integration/tls-configured) before exposing the Console outside a trusted network. +- Restrict network access to the Console listener and configure [Console CORS](/administration/cors) only when cross-origin access is required. +- The Console session inherits the permissions of the signed-in identity. Use a least-privilege account for routine work. +- Signing out or an expired session returns you to the login page. Do not store administrator credentials in shared browsers. + +## Management workflows + +- [Create and manage buckets](/administration/data/bucket/creation) +- [Upload and manage objects](/administration/data/object/creation) +- [Manage access keys](/security-compliance/iam/access-token) +- [Configure identity and access management](/security-compliance/iam) + +## Next steps + +Review the [security checklist](/installation/requirement/checklists/security-checklists) before exposing the Console outside a trusted network. For OIDC-based login, continue with the [OIDC configuration guide](/security-compliance/oidc). \ No newline at end of file diff --git a/content/management/cors/index.md b/content/administration/cors/index.md similarity index 100% rename from content/management/cors/index.md rename to content/administration/cors/index.md diff --git a/content/management/data/bucket/creation.md b/content/administration/data/bucket/creation.md similarity index 68% rename from content/management/data/bucket/creation.md rename to content/administration/data/bucket/creation.md index fc7d6124..46804ffc 100644 --- a/content/management/data/bucket/creation.md +++ b/content/administration/data/bucket/creation.md @@ -1,15 +1,14 @@ --- title: "RustFS Bucket Creation" -description: "Create buckets using the RustFS UI, MinIO Client, or API." +description: "Create buckets using the RustFS UI, rc, or the S3 API." --- -This guide explains how to create buckets using the RustFS UI, `mc` (MinIO Client), or API. +This guide explains how to create buckets using the RustFS UI, `rc`, or the S3 API. -## Creating Buckets - -Prerequisites: +## Requirements - A running RustFS instance (see [Installation Guide](../../../installation/index.md)). +- [`rc`](/operations/rc) installed and configured with an alias for the command-line workflow. ## Using the RustFS UI @@ -19,22 +18,22 @@ Prerequisites: ![bucket creation](images/bucket-creation-by-ui.png) -### Using `mc` +## Using `rc` -> See the [`mc` Usage Guide](../../../developer/mc.md) for installation and configuration. +See the [`rc` guide](/operations/rc) for installation and alias configuration. Create a bucket: ```bash -# create rustfs bucket -mc mb rustfs/bucket-creation-by-mc -Bucket created successfully `rustfs/bucket-creation-by-mc`. +rc bucket create rustfs/my-bucket +rc bucket list rustfs/ +``` -# confirm bucket creation -mc ls rustfs/bucket-creation-by-mc +```text +✓ Bucket 'rustfs/my-bucket' created successfully. ``` -### Using the API +## Using the API Create a bucket via API: diff --git a/content/management/data/bucket/deletion.md b/content/administration/data/bucket/deletion.md similarity index 68% rename from content/management/data/bucket/deletion.md rename to content/administration/data/bucket/deletion.md index a81a397d..b2234a81 100644 --- a/content/management/data/bucket/deletion.md +++ b/content/administration/data/bucket/deletion.md @@ -1,9 +1,14 @@ --- title: "RustFS Bucket Deletion" -description: "Delete buckets using the RustFS UI, MinIO Client, or API." +description: "Delete buckets using the RustFS UI, rc, or the S3 API." --- -This guide explains how to delete buckets using the RustFS UI, `mc` (MinIO Client), or API. +This guide explains how to delete buckets using the RustFS UI, `rc`, or the S3 API. + +## Requirements + +- Install and configure [`rc`](/operations/rc) before using the command-line workflow. +- Empty the target bucket before deleting it, or use `--force` only after reviewing the objects that will be removed. **Warning**: Deleting a bucket is irreversible and may break applications relying on it. Ensure you have backed up any necessary data before proceeding. @@ -16,20 +21,18 @@ This guide explains how to delete buckets using the RustFS UI, `mc` (MinIO Clien ![bucket deletion](images/bucket-deletion-on-ui.png) -## Using `mc` +## Using `rc` -See the [`mc` Usage Guide](../../../developer/mc.md) for installation and configuration. +See the [`rc` guide](/operations/rc) for installation and alias configuration. Delete a bucket: ```bash -# delete bucket -mc rb rustfs/bucket-creation-by-mc -Removed `rustfs/bucket-creation-by-mc` successfully. +rc bucket remove rustfs/my-bucket +``` -# confirm bucket deletion -mc ls rustfs/bucket-creation-by-mc -mc: Unable to list folder. Bucket `bucket-creation-by-mc` does not exist. +```text +✓ Bucket 'rustfs/my-bucket' removed successfully. ``` ## Using the API diff --git a/content/management/data/bucket/images/bucket-creation-by-ui.png b/content/administration/data/bucket/images/bucket-creation-by-ui.png similarity index 100% rename from content/management/data/bucket/images/bucket-creation-by-ui.png rename to content/administration/data/bucket/images/bucket-creation-by-ui.png diff --git a/content/management/data/bucket/images/bucket-deletion-on-ui.png b/content/administration/data/bucket/images/bucket-deletion-on-ui.png similarity index 100% rename from content/management/data/bucket/images/bucket-deletion-on-ui.png rename to content/administration/data/bucket/images/bucket-deletion-on-ui.png diff --git a/content/management/data/bucket/meta.json b/content/administration/data/bucket/meta.json similarity index 100% rename from content/management/data/bucket/meta.json rename to content/administration/data/bucket/meta.json diff --git a/content/management/data/bucket/policy.md b/content/administration/data/bucket/policy.md similarity index 85% rename from content/management/data/bucket/policy.md rename to content/administration/data/bucket/policy.md index d2682230..f0e15a5a 100644 --- a/content/management/data/bucket/policy.md +++ b/content/administration/data/bucket/policy.md @@ -7,6 +7,6 @@ This page is reserved for the verified RustFS bucket policy workflow. :::note[Documentation status] -Bucket-specific examples will be added after validation. For the current policy document model and supported conditions, see [Users, Groups, and Policies](/administration/iam/policies). +Bucket-specific examples will be added after validation. For the current policy document model and supported conditions, see [Users, Groups, and Policies](/security-compliance/iam/policies). ::: \ No newline at end of file diff --git a/content/management/data/bucket/quota.md b/content/administration/data/bucket/quota.md similarity index 68% rename from content/management/data/bucket/quota.md rename to content/administration/data/bucket/quota.md index 81169dde..0931afaf 100644 --- a/content/management/data/bucket/quota.md +++ b/content/administration/data/bucket/quota.md @@ -7,6 +7,6 @@ This page is reserved for the verified RustFS bucket quota workflow. :::note[Documentation status] -Quota configuration and enforcement behavior will be added after validation against the current RustFS release. Review [Usage Limits](/concepts/limit) for global limits that are already documented. +Quota configuration and enforcement behavior will be added after validation against the current RustFS release. ::: \ No newline at end of file diff --git a/content/management/data/bucket/replication.md b/content/administration/data/bucket/replication.md similarity index 72% rename from content/management/data/bucket/replication.md rename to content/administration/data/bucket/replication.md index 50bd92f3..9d2d317a 100644 --- a/content/management/data/bucket/replication.md +++ b/content/administration/data/bucket/replication.md @@ -7,6 +7,6 @@ This page is reserved for the verified RustFS bucket replication workflow. :::note[Documentation status] -Configuration, monitoring, and recovery steps will be added after validation against the current RustFS release. See the [glossary](/concepts/glossary) for replication terminology. +Configuration, monitoring, and recovery steps will be added after validation against the current RustFS release. ::: \ No newline at end of file diff --git a/content/management/data/data-tiering.md b/content/administration/data/data-tiering.md similarity index 70% rename from content/management/data/data-tiering.md rename to content/administration/data/data-tiering.md index 78cdcc5b..d2572206 100644 --- a/content/management/data/data-tiering.md +++ b/content/administration/data/data-tiering.md @@ -7,6 +7,6 @@ This page is reserved for the verified RustFS data tiering workflow. :::note[Documentation status] -Configuration and operational guidance will be added after validation against the current RustFS release. See the [glossary](/concepts/glossary) for current storage-tier terminology. +Configuration and operational guidance will be added after validation against the current RustFS release. ::: \ No newline at end of file diff --git a/content/management/data/lifecycle-management.md b/content/administration/data/lifecycle-management.md similarity index 67% rename from content/management/data/lifecycle-management.md rename to content/administration/data/lifecycle-management.md index 3e830632..d3cac3f2 100644 --- a/content/management/data/lifecycle-management.md +++ b/content/administration/data/lifecycle-management.md @@ -7,6 +7,6 @@ This page is reserved for the verified RustFS lifecycle management workflow. :::note[Documentation status] -Rule configuration will be added after validation against the current RustFS release. The [Object Scanner](/management/data/object/scanner) page describes the background process that evaluates lifecycle work. +Rule configuration will be added after validation against the current RustFS release. The [Object Scanner](/administration/data/object/scanner) page describes the background process that evaluates lifecycle work. ::: \ No newline at end of file diff --git a/content/management/data/meta.json b/content/administration/data/meta.json similarity index 100% rename from content/management/data/meta.json rename to content/administration/data/meta.json diff --git a/content/management/data/object/creation.md b/content/administration/data/object/creation.md similarity index 72% rename from content/management/data/object/creation.md rename to content/administration/data/object/creation.md index f7a272d5..b0c5ca07 100644 --- a/content/management/data/object/creation.md +++ b/content/administration/data/object/creation.md @@ -1,19 +1,17 @@ --- title: "Object Creation" -description: "Create objects using the RustFS UI, MinIO Client, or API." +description: "Create objects using the RustFS UI, rc, or the S3 API." --- Objects are the fundamental storage units in RustFS, containing data, metadata, and a unique key. This guide covers object creation (upload). -> For concepts related to Objects, see [Core Concepts](../../../concepts/glossary.md). - -## Creating Objects - -Prerequisites: +## Requirements - A running RustFS instance (see [Installation Guide](../../../installation/index.md)). +- [`rc`](/operations/rc) installed and configured with an alias for the command-line workflow. +- A target bucket. Create one by following [Bucket Creation](../bucket/creation.md). -[Create a bucket](../bucket/creation.md), then upload files to it. +## Creating Objects ### Using the RustFS UI @@ -28,20 +26,15 @@ Click on an object to view its details. ![object details info](images/object_details_info.png) -### Using `mc` +### Using `rc` -> See the [`mc` Usage Guide](../../../developer/mc.md) for installation and configuration. +See the [`rc` guide](/operations/rc) for installation and alias configuration. Upload a file: ```bash -# upload file -mc cp 1.txt rustfs/bucket-creation-by-mc -/tmp/1.txt: 13 B / 13 B ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 61 B/s 0s% - -# confirm file uploaded -mc ls rustfs/bucket-creation-by-mc -[2025-08-01 10:01:08 CST] 13B 1.txt +rc object copy /path/to/hello.txt rustfs/my-bucket/hello.txt +rc object list rustfs/my-bucket ``` Verify the upload in the RustFS Console. diff --git a/content/management/data/object/deletion.md b/content/administration/data/object/deletion.md similarity index 69% rename from content/management/data/object/deletion.md rename to content/administration/data/object/deletion.md index fa50fb76..5e310623 100644 --- a/content/management/data/object/deletion.md +++ b/content/administration/data/object/deletion.md @@ -1,11 +1,14 @@ --- title: "Object Deletion" -description: "Delete objects using the RustFS UI, MinIO Client, or API." +description: "Delete objects using the RustFS UI, rc, or the S3 API." --- This guide covers object deletion. -> For concepts related to objects, see [Core Concepts](../../../concepts/glossary.md). +## Requirements + +- Install and configure [`rc`](/operations/rc) before using the command-line workflow. +- Confirm the alias, bucket, and object key before deleting an object. ## Using the RustFS UI @@ -16,22 +19,23 @@ This guide covers object deletion. ![object deletion from ui](images/delete_file_from_ui.png) -### Using `mc` +## Using `rc` Delete a file: ```bash -# delete file -mc rm rustfs/bucket-creation-by-mc/1.txt -Removed `rustfs/bucket-creation-by-mc/1.txt`. +rc object remove rustfs/my-bucket/hello.txt +rc object list rustfs/my-bucket +``` -# confirm deletion -mc ls rustfs/bucket-creation-by-mc/1.txt +```text +Removed: rustfs/my-bucket/hello.txt +✓ Removed 1 object(s). ``` Verify the deletion in the RustFS Console. -### Using the API +## Using the API Delete a file via API: diff --git a/content/management/data/object/images/delete_file_from_ui.png b/content/administration/data/object/images/delete_file_from_ui.png similarity index 100% rename from content/management/data/object/images/delete_file_from_ui.png rename to content/administration/data/object/images/delete_file_from_ui.png diff --git a/content/management/data/object/images/object_details_info.png b/content/administration/data/object/images/object_details_info.png similarity index 100% rename from content/management/data/object/images/object_details_info.png rename to content/administration/data/object/images/object_details_info.png diff --git a/content/management/data/object/images/upload_file_from_ui.png b/content/administration/data/object/images/upload_file_from_ui.png similarity index 100% rename from content/management/data/object/images/upload_file_from_ui.png rename to content/administration/data/object/images/upload_file_from_ui.png diff --git a/content/management/data/object/meta.json b/content/administration/data/object/meta.json similarity index 100% rename from content/management/data/object/meta.json rename to content/administration/data/object/meta.json diff --git a/content/management/data/object/multipart-upload.md b/content/administration/data/object/multipart-upload.md similarity index 77% rename from content/management/data/object/multipart-upload.md rename to content/administration/data/object/multipart-upload.md index a0b798a5..187c8584 100644 --- a/content/management/data/object/multipart-upload.md +++ b/content/administration/data/object/multipart-upload.md @@ -7,6 +7,6 @@ This page is reserved for a client-neutral multipart upload workflow. :::note[Documentation status] -Detailed steps will be added after the examples are validated across supported clients. For current request limits, see [Usage Limits](/concepts/limit). +Detailed steps will be added after the examples are validated across supported clients. ::: \ No newline at end of file diff --git a/content/management/data/object/object-lock.md b/content/administration/data/object/object-lock.md similarity index 100% rename from content/management/data/object/object-lock.md rename to content/administration/data/object/object-lock.md diff --git a/content/management/data/object/scanner.md b/content/administration/data/object/scanner.md similarity index 100% rename from content/management/data/object/scanner.md rename to content/administration/data/object/scanner.md diff --git a/content/management/data/object/versioning.md b/content/administration/data/object/versioning.md similarity index 70% rename from content/management/data/object/versioning.md rename to content/administration/data/object/versioning.md index cd497c47..aaa75072 100644 --- a/content/management/data/object/versioning.md +++ b/content/administration/data/object/versioning.md @@ -7,6 +7,6 @@ This page is reserved for the verified RustFS object versioning workflow. :::note[Documentation status] -Configuration, recovery, and deletion behavior will be documented after validation against the current RustFS release. See the [glossary](/concepts/glossary) for the current terminology. +Configuration, recovery, and deletion behavior will be documented after validation against the current RustFS release. ::: \ No newline at end of file diff --git a/content/administration/iam/meta.json b/content/administration/iam/meta.json deleted file mode 100644 index 5c55fb55..00000000 --- a/content/administration/iam/meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "title": "IAM", - "pages": [ - "[Users, Groups, and Policies](/administration/iam/policies)", - "[Service Accounts and STS](/administration/iam/sts)", - "[External Identity (OIDC)](/administration/iam/oidc)", - "[Access Keys](/administration/iam/access-token)" - ] -} diff --git a/content/administration/iam/oidc.md b/content/administration/iam/oidc.md deleted file mode 100644 index ea95c027..00000000 --- a/content/administration/iam/oidc.md +++ /dev/null @@ -1,183 +0,0 @@ ---- -title: "External Identity (OIDC)" -description: "Connect the RustFS Console to Keycloak, Authing, or any standard OpenID Connect provider for single sign-on." ---- - -RustFS supports standard OpenID Connect (OIDC) for Console login. Any standards-compliant provider works; this guide uses Keycloak and Authing as worked examples. The examples use the default RustFS provider id, `default`. - -## Integration Model - -RustFS expects a provider that offers issuer metadata (`.well-known/openid-configuration`), an authorization endpoint, a token endpoint, a JWKS (or another verifiable ID token signature path), and an authorization-code flow that returns an `id_token`. - -The browser login flow is: - -1. The user opens the RustFS OIDC authorize endpoint (`/rustfs/admin/v3/oidc/authorize/`). -2. RustFS creates `state`, `nonce`, and a PKCE S256 challenge, then redirects the browser to the provider. -3. The provider redirects back to `/rustfs/admin/v3/oidc/callback/` with `code` and `state`. -4. RustFS exchanges the code with `client_id`, `client_secret`, and the PKCE verifier. -5. RustFS validates the ID token signature, issuer, audience, expiry, and nonce. -6. RustFS maps ID token claim values to RustFS policy names and issues one-hour STS credentials for the Console. - -RustFS does not call the provider's authorization APIs for object or admin authorization. Authorization is handled entirely by RustFS policies after claims are mapped. - -## Claim Mapping - -RustFS reads `groups` or `roles` claims from the ID token and maps each value to a RustFS policy name. Keep claim values equal to policy names: - -| Claim value | RustFS policy | Purpose | -| --- | --- | --- | -| `consoleAdmin` | `consoleAdmin` | Full Console, admin, KMS, and S3 access. | -| `readwrite` | `readwrite` | S3 read/write access. | -| `readonly` | `readonly` | S3 read-only access. | -| `writeonly` | `writeonly` | S3 write-only access. | -| `diagnostics` | `diagnostics` | Diagnostic admin access. | - -If a login succeeds but no claim value matches a RustFS policy (and no group is mapped), the STS exchange is rejected. - -## RustFS Configuration - -Configure the provider and the public browser origin through environment variables, then restart RustFS: - -```bash -export RUSTFS_BROWSER_REDIRECT_URL="https://rustfs.example.com" - -export RUSTFS_IDENTITY_OPENID_ENABLE=on -export RUSTFS_IDENTITY_OPENID_CONFIG_URL="" -export RUSTFS_IDENTITY_OPENID_CLIENT_ID="" -export RUSTFS_IDENTITY_OPENID_CLIENT_SECRET="" -export RUSTFS_IDENTITY_OPENID_SCOPES="openid,profile,email" -export RUSTFS_IDENTITY_OPENID_REDIRECT_URI="https://rustfs.example.com/rustfs/admin/v3/oidc/callback/default" -export RUSTFS_IDENTITY_OPENID_REDIRECT_URI_DYNAMIC=off -export RUSTFS_IDENTITY_OPENID_DISPLAY_NAME="My IdP" -export RUSTFS_IDENTITY_OPENID_GROUPS_CLAIM="groups" -export RUSTFS_IDENTITY_OPENID_ROLES_CLAIM="roles" -export RUSTFS_IDENTITY_OPENID_EMAIL_CLAIM="email" -export RUSTFS_IDENTITY_OPENID_USERNAME_CLAIM="preferred_username" -``` - -For short-lived connectivity testing only, you may temporarily add: - -```bash -export RUSTFS_IDENTITY_OPENID_ROLE_POLICY="consoleAdmin" -``` - -:::warning - -Do not keep `role_policy=consoleAdmin` in production unless every user of this client should receive full Console access. Claim-to-policy mapping is the production authorization model. - -::: - -If the deployment manages configuration through compatible admin commands, the same keys can be set with `mc admin config set identity_openid enable=on config_url=... client_id=... client_secret=... scopes=... redirect_uri=... redirect_uri_dynamic=off display_name=... groups_claim=... roles_claim=... email_claim=... username_claim=...` followed by `mc admin service restart `. - -:::note - -`RUSTFS_BROWSER_REDIRECT_URL` is a process environment variable, not an `identity_openid` provider key. Configure it in the RustFS service environment even when the provider is stored through admin config. - -::: - -### Named Providers - -To register more than one provider (or use a provider id other than `default`), suffix the provider-specific environment variables with the provider id and register the matching callback URL at the IdP: - -```bash -export RUSTFS_IDENTITY_OPENID_ENABLE_keycloak=on -export RUSTFS_IDENTITY_OPENID_CONFIG_URL_keycloak="https://keycloak.example.com/realms/rustfs" -export RUSTFS_IDENTITY_OPENID_CLIENT_ID_keycloak="rustfs-console" -export RUSTFS_IDENTITY_OPENID_CLIENT_SECRET_keycloak="" -export RUSTFS_IDENTITY_OPENID_SCOPES_keycloak="openid,profile,email" -export RUSTFS_IDENTITY_OPENID_REDIRECT_URI_keycloak="https://rustfs.example.com/rustfs/admin/v3/oidc/callback/keycloak" -export RUSTFS_IDENTITY_OPENID_REDIRECT_URI_DYNAMIC_keycloak=off -export RUSTFS_IDENTITY_OPENID_DISPLAY_NAME_keycloak="Keycloak" -export RUSTFS_IDENTITY_OPENID_GROUPS_CLAIM_keycloak="groups" -``` - -`RUSTFS_BROWSER_REDIRECT_URL` remains global and is not suffixed per provider. - -### Redirect URL Priority - -RustFS builds browser-facing URLs with this priority: - -1. Provider `redirect_uri`, when configured, is used for the OIDC callback URL sent to the IdP. -2. `RUSTFS_BROWSER_REDIRECT_URL`, when configured, is used as the public origin for OIDC callback generation when no provider `redirect_uri` exists, and for Console success redirects and logout fallback redirects. -3. Request headers are used only when provider dynamic redirects are enabled and no browser redirect URL is configured. - -For reverse-proxy or load-balancer deployments, set `RUSTFS_BROWSER_REDIRECT_URL` to avoid depending on `Host` and `X-Forwarded-Proto`. OIDC authorize and callback requests must reach the same RustFS node, because in-flight OIDC `state` is local to the node — configure session affinity on the load balancer. - -## Example: Keycloak - -Example values: realm `rustfs`, issuer `https://keycloak.example.com/realms/rustfs`, client id `rustfs-console`, callback `https://rustfs.example.com/rustfs/admin/v3/oidc/callback/default`. - -1. Create or select the `rustfs` realm and verify discovery: - - ```bash - curl -fsS "https://keycloak.example.com/realms/rustfs/.well-known/openid-configuration" \ - | jq '.issuer,.authorization_endpoint,.token_endpoint,.jwks_uri' - ``` - -2. Create a client: `Client type` = `OpenID Connect`, `Client ID` = `rustfs-console`, enable `Client authentication` and `Standard flow`, disable `Implicit flow`, `Direct access grants`, and `Service accounts roles`. Set `Valid redirect URIs` to the exact RustFS callback URL, `Web origins` to `https://rustfs.example.com`, and PKCE Code Challenge Method to `S256`. Copy the client secret from `Credentials`. -3. Map groups to policies: create Keycloak groups named after RustFS policies (e.g. `consoleAdmin`, `readonly`), add users, and add a `Group Membership` mapper to the client scope with `Token Claim Name` = `groups`, `Full group path` = `Off`, `Add to ID token` = `On`, `Multivalued` = `On`. -4. Configure RustFS with `RUSTFS_IDENTITY_OPENID_CONFIG_URL="https://keycloak.example.com/realms/rustfs"` and the client id/secret as shown above, then restart. - -:::note - -Keep `Full group path` disabled. RustFS policy names cannot contain `/`, so `/consoleAdmin` will not map to the `consoleAdmin` policy. If you use Keycloak roles instead of groups, emit a flat top-level `roles` claim (via a `User Realm Role` or `User Client Role` mapper) and set `RUSTFS_IDENTITY_OPENID_ROLES_CLAIM=roles` — RustFS does not parse Keycloak's nested `realm_access.roles` claim. RustFS submits the client secret in the token request body, so do not disable `client_secret_post`. - -::: - -## Example: Authing - -Example values: application domain `https://example.authing.cn`, issuer `https://example.authing.cn/oidc`, App ID as `client_id`, App Secret as `client_secret`. - -1. Create a self-hosted application named `RustFS Console`; record the App ID, App Secret, issuer, and discovery URL. Authing deployments use different issuer paths (`/oidc` or `/oauth/oidc`) — always copy the issuer from the Authing console. -2. Protocol settings: Protocol = OpenID Connect, Grant type = Authorization Code, Response type = `code`, token endpoint authentication = `client_secret_post`, PKCE = allow or require `S256`, ID token signing algorithm = `RS256` recommended. -3. Register the exact callback URL `https://rustfs.example.com/rustfs/admin/v3/oidc/callback/default`. -4. Assign users roles named after RustFS policies and confirm the ID token contains, for example: - - ```json - { - "roles": ["consoleAdmin"] - } - ``` - -5. Configure RustFS with `RUSTFS_IDENTITY_OPENID_SCOPES="openid,profile,email,roles"` and `RUSTFS_IDENTITY_OPENID_ROLES_CLAIM="roles"`, then restart. - -## Validation - -Verify provider discovery, then check that the provider is visible to RustFS: - -```bash -curl -fsS "https://rustfs.example.com/rustfs/admin/v3/oidc/providers" | jq -``` - -Test the browser flow by opening: - -```text -https://rustfs.example.com/rustfs/admin/v3/oidc/authorize/default -``` - -Expected result: redirect to the IdP, sign in, redirect back to `/rustfs/admin/v3/oidc/callback/default?code=...&state=...`, RustFS validates the ID token, issues STS credentials, and the browser lands on the Console with the mapped permissions. - -## Troubleshooting - -| Symptom | Common cause | Fix | -| --- | --- | --- | -| `/oidc/providers` does not show the provider | Provider did not load, or RustFS was not restarted | Check environment variables and restart RustFS. | -| IdP reports a redirect mismatch | Registered redirect URI differs from the RustFS callback URL | Use the exact callback URL including the provider id. | -| Callback reports missing `code` or `state` | Proxy dropped the query string | Preserve the full callback URL and query string. | -| Token exchange fails | Wrong client secret or token auth method | Confirm the client is confidential and accepts `client_secret_post`. | -| RustFS reports no `id_token` | Missing `openid` scope or non-OIDC flow | Include `openid` and use the authorization code flow. | -| ID token verification fails | Issuer, audience, signing algorithm, or JWKS mismatch | Compare discovery metadata with the RustFS config; prefer `RS256`. | -| Login succeeds but access is denied | No claim value matched a RustFS policy | Emit `groups` or `roles` as a flat ID token claim matching policy names. | -| Console redirects to an internal host | Missing `RUSTFS_BROWSER_REDIRECT_URL` | Set it to the public browser origin. | -| Invalid or expired OIDC state | Callback reached a different RustFS node | Configure load-balancer session affinity for authorize and callback. | - -## Production Checklist - -- The IdP and RustFS use HTTPS. -- The registered redirect URI is the exact callback URL, not a wildcard. -- `RUSTFS_BROWSER_REDIRECT_URL` is set to the public RustFS browser origin. -- `RUSTFS_IDENTITY_OPENID_REDIRECT_URI` matches the registered callback URL. -- PKCE S256 is enabled or required. -- Users receive `groups` or `roles` claims that match RustFS policy names. -- `role_policy=consoleAdmin` is not used as a permanent production shortcut. -- The load balancer preserves query strings and pins authorize/callback requests to one node. diff --git a/content/administration/index.md b/content/administration/index.md index 589b709e..508c2552 100644 --- a/content/administration/index.md +++ b/content/administration/index.md @@ -1,16 +1,16 @@ --- -title: "Security & Compliance" -description: "Configure identity, encryption, transport security, audit logging, and credentials for RustFS." +title: "Administration" +description: "Administer RustFS data, access protocols, and browser-based management." --- -Use this section to secure access to RustFS and configure the controls required by your environment. +Use this section to manage RustFS through the Console, administer buckets and objects, and configure access protocols. -## Security areas +## Administration areas -- [IAM Management](./iam/index.md) covers users, groups, policies, and access keys. -- [Data Encryption](./security/encryption/index.md) covers server-side encryption options and key management. -- [STS Token Service](./security/sts-token-service/index.md) covers temporary credentials. -- [OIDC](./iam/oidc.md) covers external identity providers. -- [TLS Configuration](/integration/tls-configured) covers encrypted client and node connections. -- [Audit Logs](./security/audit-logs.md) covers audit target configuration. -- [Credential Management](/operations/credentials) covers root credentials and secret injection. \ No newline at end of file +- [Console](./console/index.md) covers browser-based administration and sign-in methods. +- [Data Management](./data/object/object-lock.md) covers buckets, objects, lifecycle behavior, and data protection features. +- [Protocol Support](./protocols/s3.md) covers S3, WebDAV, and FTPS access. +- [CORS Configuration](./cors/index.md) covers cross-origin access to RustFS services. +- [Virtual-Host Access](/integration/virtual) covers domain-based S3 addressing. + +For identity, encryption, transport security, and auditing, continue with [Security & Compliance](/security-compliance). \ No newline at end of file diff --git a/content/administration/meta.json b/content/administration/meta.json index cd2b7b76..9c086574 100644 --- a/content/administration/meta.json +++ b/content/administration/meta.json @@ -1,12 +1,10 @@ { - "title": "Security & Compliance", + "title": "Administration", "pages": [ - "[IAM Management](/administration/iam)", - "security/encryption", - "[STS Token Service](/administration/security/sts-token-service)", - "security/oidc", - "[TLS Configuration](/integration/tls-configured)", - "[Audit Logs](/administration/security/audit-logs)", - "[Credential Management](/operations/credentials)" + "console", + "data", + "protocols", + "cors", + "[Virtual-Host Access](/integration/virtual)" ] } \ No newline at end of file diff --git a/content/management/protocols/ftps.md b/content/administration/protocols/ftps.md similarity index 100% rename from content/management/protocols/ftps.md rename to content/administration/protocols/ftps.md diff --git a/content/management/protocols/meta.json b/content/administration/protocols/meta.json similarity index 100% rename from content/management/protocols/meta.json rename to content/administration/protocols/meta.json diff --git a/content/management/protocols/s3.md b/content/administration/protocols/s3.md similarity index 80% rename from content/management/protocols/s3.md rename to content/administration/protocols/s3.md index f7c37332..ae1e7b0e 100644 --- a/content/management/protocols/s3.md +++ b/content/administration/protocols/s3.md @@ -4,5 +4,3 @@ description: "Find verified client examples for accessing RustFS through its S3- --- RustFS exposes an S3-compatible API for object storage clients. Use the verified [client examples](/developer/examples) to configure an endpoint, credentials, region, and path-style addressing. - -For API boundaries and request limits, see [Usage Limits](/concepts/limit). \ No newline at end of file diff --git a/content/management/protocols/webdav.md b/content/administration/protocols/webdav.md similarity index 100% rename from content/management/protocols/webdav.md rename to content/administration/protocols/webdav.md diff --git a/content/administration/security/encryption/kms.md b/content/administration/security/encryption/kms.md deleted file mode 100644 index 851739ac..00000000 --- a/content/administration/security/encryption/kms.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: "KMS" -description: "Find verified references for Key Management Service integration in RustFS." ---- - -RustFS exposes KMS-backed server-side encryption settings through its command-line interface and environment variables. - -Use the [CLI reference](/reference/cli) and [environment variable reference](/reference/environment-variables) for the currently verified configuration surface. A complete key-management workflow will be added after runtime validation. - -## Next steps - -Compare [SSE-S](./sse-s.md) and [SSE-C](./sse-c.md) before selecting an encryption model. \ No newline at end of file diff --git a/content/administration/security/encryption/sse-c.md b/content/administration/security/encryption/sse-c.md deleted file mode 100644 index 1923bacc..00000000 --- a/content/administration/security/encryption/sse-c.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: "SSE-C" -description: "Learn about server-side encryption with customer-provided keys in RustFS." ---- - -SSE-C is the section for server-side encryption where you provide the encryption key with the object request. - -Detailed configuration guidance will be added after the workflow has been validated against a current RustFS release. - -## Next steps - -Compare [SSE-S](./sse-s.md) and [KMS](./kms.md) before selecting an encryption model. \ No newline at end of file diff --git a/content/administration/security/encryption/sse-s.md b/content/administration/security/encryption/sse-s.md deleted file mode 100644 index 8a8ba1a1..00000000 --- a/content/administration/security/encryption/sse-s.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: "SSE-S" -description: "Learn about server-managed server-side encryption in RustFS." ---- - -SSE-S is the section for server-side encryption where RustFS manages the encryption keys used for stored objects. - -Detailed configuration guidance will be added after the workflow has been validated against a current RustFS release. - -## Next steps - -Compare [SSE-C](./sse-c.md) and [KMS](./kms.md) before selecting an encryption model. \ No newline at end of file diff --git a/content/administration/security/oidc/meta.json b/content/administration/security/oidc/meta.json deleted file mode 100644 index 414b32c6..00000000 --- a/content/administration/security/oidc/meta.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "title": "OIDC", - "pages": [ - "[Keycloak](/administration/iam/oidc#example-keycloak)", - "[Authing](/administration/iam/oidc#example-authing)" - ] -} \ No newline at end of file diff --git a/content/administration/security/sts-token-service/index.md b/content/administration/security/sts-token-service/index.md deleted file mode 100644 index 54c0db57..00000000 --- a/content/administration/security/sts-token-service/index.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: "STS Token Service" -description: "Learn how RustFS issues temporary credentials through the Security Token Service." ---- - -The RustFS Security Token Service (STS) issues temporary credentials for existing IAM identities and external OpenID Connect (OIDC) identities. - -## STS workflows - -RustFS supports temporary credential workflows through `AssumeRole` and `AssumeRoleWithWebIdentity`. See [Service Accounts and STS](/administration/iam/sts) for the verified request parameters, credential behavior, and usage guidance. - -For identity-provider configuration, see [External Identity (OIDC)](/administration/security/oidc). - -## Next steps - -Review [IAM Management](/administration/iam) to understand how RustFS evaluates policies for temporary credentials. \ No newline at end of file diff --git a/content/concepts/architecture.md b/content/concepts/architecture.md deleted file mode 100644 index 848a3261..00000000 --- a/content/concepts/architecture.md +++ /dev/null @@ -1,100 +0,0 @@ ---- -title: "RustFS Architecture" -description: "Introduction to RustFS Architecture" ---- - -RustFS is a high-performance object storage system compatible with the AWS S3 API. It features a concise, lightweight, scalable, and decentralized architecture. - -Objects can be documents, videos, PDF files, or any other unstructured data. RustFS provides a scalable, flexible, and efficient solution for storing, accessing, and managing this data. Its compatibility with the AWS S3 API enables seamless integration with existing S3-based applications. - -The following diagram illustrates the architecture: - -```mermaid -flowchart LR - APP[Applications] --> S3API(["S3 API"]) - - subgraph DIST["Distributed RustFS"] - direction TB - subgraph N1["Node 1"] - direction LR - S3a[S3] - subgraph OL1["Object Layer"] - direction TB - C1[Cache] - K1[Compression] - E1[Encryption] - B1["Erasure Code · Bitrot"] - end - SL1["Storage Layer"] - J1[("JBOD / FS disks")] - S3a -->|Object API| OL1 - OL1 -->|Storage API| SL1 - SL1 <--> J1 - end - subgraph N2["Node 2"] - direction LR - S3b[S3] - subgraph OL2["Object Layer"] - direction TB - C2[Cache] - K2[Compression] - E2[Encryption] - B2["Erasure Code · Bitrot"] - end - SL2["Storage Layer"] - J2[("JBOD / FS disks")] - S3b -->|Object API| OL2 - OL2 -->|Storage API| SL2 - SL2 <--> J2 - end - NN["Node n ..."] - N1 <-->|Internal RESTful API| N2 - N2 <-->|Internal RESTful API| NN - end - - S3API --> N1 - S3API --> N2 - S3API --> NN - - classDef server fill:#dbeafe,stroke:#3b82f6,stroke-width:2px,color:#1e293b; - classDef store fill:#dcfce7,stroke:#22c55e,stroke-width:2px,color:#1e293b; - classDef svc fill:#eef2ff,stroke:#6366f1,stroke-width:2px,color:#1e293b; - classDef muted fill:#f3f4f6,stroke:#9ca3af,stroke-width:2px,color:#1e293b; - classDef accent fill:#fae8ff,stroke:#c026d3,stroke-width:2px,color:#1e293b; - class APP,NN muted - class S3API accent - class S3a,S3b,SL1,SL2 server - class C1,K1,E1,B1,C2,K2,E2,B2 svc - class J1,J2 store -``` - -This diagram represents the basic architecture of RustFS. A distributed grid uses multiple nodes to execute a single task, connected via a network to enable communication. - -## Consistency Design - -In both distributed and single-machine modes, all read and write operations strictly follow the **read-after-write** consistency model. - -## Key Concepts - -**Object**: The fundamental unit of storage in RustFS, representing files, byte streams, or any unstructured data. - -**Bucket**: A logical container for storing objects. Data is isolated between buckets. For clients, it functions similarly to a top-level directory. - -**Drive**: The physical disk that stores data, passed as a parameter when RustFS starts. All object data in RustFS is stored on these drives. - -**Set**: A group of drives. Distributed deployment automatically divides the cluster into one or more sets based on scale. Drives in each set are distributed across different locations. An object is stored within a single set. (Sometimes referred to as **Stripes**). - -Consider the following when designing the architecture and deploying devices: - -- One object is stored on one set. -- One cluster is divided into multiple sets. -- The number of drives in a set is fixed, defaulting to automatic calculation by the system based on cluster scale. -- Drives in a set should be distributed across different nodes as much as possible. - -## Architectural Design - -Traditional distributed storage architectures often rely on distinct Master nodes, Metadata nodes, and Data nodes. This complexity can make deployment challenging and introduces single points of failure—if metadata is lost, data integrity is at risk. - -RustFS adopts a decentralized, peer-to-peer architecture where all nodes are equal. This design greatly simplifies deployment and eliminates metadata bottlenecks. A single command is sufficient to start the system. - -RustFS draws inspiration from the elegant and scalable architecture of MinIO, adopting a similar design philosophy that prioritizes simplicity and reliability without compromising on features. We acknowledge MinIO's contribution to promoting the S3 protocol and setting a high standard for object storage architecture. diff --git a/content/concepts/comparison.md b/content/concepts/comparison.md deleted file mode 100644 index 9ef28a66..00000000 --- a/content/concepts/comparison.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: "RustFS vs Other Storage Products" -description: "Comparison of RustFS with mainstream object storage products" ---- - -| Parameter | Ceph | MinIO | RustFS | -| - | - | - | - | -| Development Language | C++ | Go | Rust | -| Open Source License | GPL-2.0, LGPL-2.1, LGPL-3.0 | AGPL-3.0 | Apache-2.0 | -| Metadata Center | √ | x | x | -| Block Storage | √ | x | x | -| File Storage | √ | x | x | -| Architecture | Heavy architecture design | Lightweight architecture design | Lightweight architecture design | -| Community Activity | √ | √ | √ | -| License Friendliness | Medium | Restrictive (AGPL) | Excellent (Permissive) | -| Performance | Hardware dependent | High performance, low latency | High performance, low latency | -| File Protocol | S3, RBD, CephFS, etc. | S3 | S3 | -| Ease of Use | Low | High | High | -| Scalability | EB level | EB level | EB level | -| Hardware Requirements | High | Medium | Low | -| Memory Management | Manual | GC-based | Ownership-based (No GC) | -| Scaling | High difficulty | Low difficulty | Low difficulty | -| Rebalancing | High resource consumption | Low resource consumption | Low resource consumption | -| Commercial Support | √ | √ | √ | - - - -## Global Object Storage Architectural Approaches - -Currently, distributed object storage products worldwide are mainly divided into two architectural approaches: - -1. **Centralized Metadata**: Represented by Ceph. - -2. **Decentralized Metadata**: Represented by RustFS and MinIO. - -Comparison of advantages and disadvantages: - -| Feature | Centralized Metadata | Decentralized Metadata | -| - | - | - | -| Architecture Characteristics | Dedicated metadata server or center for unified metadata management | Metadata distributed across storage nodes, no dedicated metadata server | -| Metadata Management | Efficient centralized management, fast query and update | Distributed metadata storage, avoiding single-point bottlenecks | -| Single Point of Failure | Metadata server may become a single point of failure, requiring additional high-availability design | No single node failure risk | -| Deployment Complexity | Complex deployment and maintenance, requires professional operations skills | Relatively simple deployment and maintenance, suitable for cloud-native and containerized scenarios | -| Performance Issues | Under high concurrency, metadata server may become a performance bottleneck | Small file support will consume more IOPS | -| Typical Scenarios | File systems (such as Lustre, CephFS) and scenarios requiring complex metadata | Object storage (RustFS, MinIO) and large-scale distributed systems | - - -## About Storage Speed - -RustFS and MinIO share similar design principles, with overall speed largely dependent on network bandwidth and disk I/O. Benchmarks indicate that RustFS can achieve read speeds of up to 323 GB/s and write speeds of 183 GB/s. - -RustFS and MinIO stand out as leaders in high-performance distributed object storage. Under comparable configurations, their throughput significantly exceeds that of traditional architectures like Ceph. diff --git a/content/concepts/glossary.md b/content/concepts/glossary.md deleted file mode 100644 index a0a2ef78..00000000 --- a/content/concepts/glossary.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -title: "Glossary" -description: "This article introduces commonly used vocabulary in object storage to help users quickly understand object storage" ---- - -| Term | Description | -|--------------------------|--------------------------------------------------------------------------| -| Object Storage | An architecture where data is stored as objects, replacing traditional file hierarchy structures | -| Bucket | A container for storing objects with globally unique namespace | -| Object | Basic storage unit containing data, metadata, and unique identifier (Object Key) | -| Metadata | Key-value pair information describing object attributes (such as file type, creation time) | -| S3-Compatible | Storage services compatible with Amazon S3 API standards | -| Data Durability | The probability that data remains intact and accessible over a period of time (e.g., 99.999999999%) | -| Replication | Redundancy technology that ensures data safety through multiple copies | -| Erasure Coding | A method of data protection in which data is broken into fragments, expanded and encoded with redundant data pieces | -| Cold Storage | Low-cost storage type for infrequently accessed data (such as archived data) | -| Lifecycle Management | Policies for automatically transitioning/deleting objects (e.g., move to cold storage after 30 days) | -| Versioning | Retaining historical versions of objects to prevent overwriting | -| Storage Class | Different performance/cost storage tiers (Standard, Infrequent Access, Archive) | -| Access Key | Authentication keys for API requests (Access Key ID + Secret Access Key) | -| Region | Geographic location of storage infrastructure (e.g., East China 1, US West) | -| Availability Zone (AZ) | Isolated data centers with independent power/network within the same region | -| Endpoint | Domain address for accessing storage service (e.g., us-east1.rustfs.com) | -| RESTful API | API design specification based on HTTP protocol | -| Multipart Upload | Mechanism for splitting large files for upload and merging | -| Pre-Signed URL | Temporary access links with time validity | -| Server-Side Encryption (SSE) | Automatic data encryption on server side (SSE-S3/SSE-KMS/SSE-C) | -| Client-Side Encryption (CSE) | Local encryption on client side before upload | -| Cross-Region Replication | Automatic object replication across geographic regions | -| Access Control List (ACL) | Rule list controlling access permissions for buckets/objects | -| Bucket Policy | JSON-based fine-grained permission control policies | -| IAM | Identity and Access Management system for centralized user/role permission management | -| Event Notification | Sending notifications to message queues/function computing when events trigger | -| Data Lake | Repository for centralized storage of structured/unstructured data | -| Compliance | Meeting data storage regulatory requirements like GDPR, HIPAA | -| Logging & Audit | Recording all API operation logs for auditing | -| Monitoring & Alerting | Real-time monitoring of storage usage/requests with alerting | -| CORS | Rules controlling browser cross-origin resource access | -| Transfer Acceleration | Optimizing upload/download speed through edge nodes | -| CDN Integration | Combining with Content Delivery Network for caching acceleration | -| Data Export | Process of migrating data to other storage systems | -| Data Import | Batch data migration from external systems to object storage | -| Static Website Hosting | Directly hosting HTML/CSS/JS static files through buckets | -| Hotlink Protection | Technology preventing external websites from stealing resource links | -| Request Rate Limiting | Controlling API request frequency per user/IP | -| Tagging | Adding classification tags to buckets/objects for management | -| Inventory Report | Periodically generated CSV/ORC files listing storage objects | -| Data Restoration | Restoring data from archive storage to accessible state | -| Storage Gateway | Access layer mapping object storage as a local file system; also a hybrid cloud solution connecting local systems with cloud storage | -| Data Compression | Compressing data before upload to save storage space | -| Data Deduplication | Eliminating duplicate data to reduce storage usage | -| Direct Read Archive | Technology for directly reading archived data without restoration | -| Bandwidth Control | Limiting download bandwidth to avoid network congestion | -| Concurrent Connections | Number of simultaneous data transfer connections | -| Data Migration Service | Automated migration tools (e.g., AWS Snowball) | -| Client SDK | Developer toolkits for integrating storage services (e.g., Python/Java SDK) | -| CLI | Command line management tools (e.g., aws s3 cp) | -| Web Console | Web-based management interface | -| Data Integrity Check | Verifying transmission integrity through MD5/SHA | -| Resumable Upload/Download | Continuing transfer from breakpoint after network interruption | -| Mirror Back to Source | Pulling and saving from specified origin when requested object doesn't exist | -| Canary Release | Release strategy gradually opening new features to some users | -| Soft Delete | Marking objects for deletion while maintaining recovery period | -| Object Lock | Compliance protection mechanism preventing object deletion or overwriting | -| Watermarking | Adding identification information to images/videos | -| Thumbnail Generation | Automatically creating thumbnail versions of images | -| Image Processing | Online cropping/scaling/rotation processing functions | -| Video Transcoding | Converting video formats/resolutions for different devices | -| Content Moderation | Automatically detecting inappropriate images/videos/text | -| Cost Analysis | Calculating costs by storage type/request count dimensions | -| Usage Monitoring | Real-time dashboard viewing storage/traffic/request counts | -| Storage Analytics | Tools analyzing storage patterns to optimize costs | -| Requester Pays | Billing model where data downloader bears the cost | -| Tiered Storage | Automatically moving data to lower-cost storage tiers | -| Intelligent Tiering | Automatically selecting optimal storage type based on access patterns | -| PrivateLink | Accessing object storage through internal network avoiding public exposure | -| VPC Endpoint | Entry point for securely accessing storage services within Virtual Private Cloud | -| SSL/TLS | Encrypting data transmission through HTTPS protocol | -| Client-Side Encryption | Users encrypting data themselves before upload | -| KMS | Key Management Service for centralized encryption key management | -| Permission Boundary | Limiting maximum permission scope of IAM roles/users | -| Temporary Credentials | Short-term valid access tokens (e.g., STS Token) | -| MFA Delete | Requiring multi-factor authentication to delete data | -| Immutability | Property preventing data tampering (combined with WORM model) | -| Legal Hold | Mandatory protection prohibiting data deletion/modification in compliance scenarios | -| Cross-Account Sharing | Allowing other cloud accounts to access specified storage resources | -| Prefetch Policy | Loading data into cache in advance to accelerate subsequent access | -| Cache-Control | Specifying browser/CDN caching behavior through HTTP headers | -| Delayed Deletion | Delaying deletion operations to prevent accidental actions | -| Batch Operations | Performing unified operations on multiple objects (delete/copy/restore) | -| Data Lineage | Metadata records tracking data sources and change history | -| Data Catalog | Retrieval system storing metadata information | -| Hybrid Cloud Storage | Architecture using both local storage and cloud storage | -| Edge Storage | Providing storage services at edge nodes close to data sources | -| Multi-Cloud Storage | Storage solutions across different cloud service providers | -| Storage Federation | Abstraction layer for unified management of multiple storage systems | -| Object Tag | Adding custom classification tags to objects | -| Bucket Tag | Adding management/billing related tags to buckets | -| Storage Quota | Limiting maximum capacity of buckets | -| Request Throttling | Limiting API requests per unit time | -| SLA | Service Level Agreement commitments for availability/durability (e.g., 99.9% availability) | -| Disaster Recovery | Ensuring business continuity through cross-region backups | -| Storage Topology | Distribution structure of data at physical/logical levels | -| Proximity Access | Routing user requests to nearest storage nodes | -| Global Namespace | Unified view management of cross-region buckets | -| Zero-Copy Migration | Fast data migration through metadata operations | -| SNSD (Single-Node Single-Disk) | Deployment topology with one server and one data disk, suited for testing and development; see [Single Node Single Disk Mode](../installation/linux/single-node-single-disk.md) | -| SNMD (Single-Node Multiple-Disk) | Deployment topology with one server and multiple data disks, providing disk-level redundancy via erasure coding; see [Single Node Multiple Disk Mode](../installation/linux/single-node-multiple-disk.md) | -| MNMD (Multiple-Node Multiple-Disk) | Distributed deployment topology across multiple servers and disks, recommended for production; see [Multiple Node Multiple Disk Mode](../installation/linux/multiple-node-multiple-disk.md) | diff --git a/content/concepts/introduction.md b/content/concepts/introduction.md deleted file mode 100644 index 14ecf77a..00000000 --- a/content/concepts/introduction.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: "What is RustFS?" -description: "RustFS is an open-source, distributed object storage solution released under the Apache 2.0 license." ---- - -RustFS is a distributed object storage system designed for simplicity and efficiency. It is fully compatible with the S3 protocol and released as open-source software under the Apache 2.0 license, making it friendly for commercial integration. Built with Rust, RustFS benefits from the language's memory safety and zero-cost abstractions, and is developed by a community of engineers worldwide. Its mission is to make high-performance storage, data security, and cost-efficiency accessible to everyone — including as the storage foundation for AI-era workloads. - -## Operating System and CPU Support - -RustFS runs on almost any CPU and operating system, including Linux, Unix, Windows, macOS, FreeBSD, and Docker, as well as edge gateways. -Supported CPU architectures: x86, ARM, and others. - -## RustFS Features - -- **S3 Compatible**: Fully compatible with the S3 protocol, integrating with big data ecosystems, data lakes, backup solutions, and media processing workflows. -- **Distributed Architecture**: Scales horizontally to meet storage requirements of various sizes. -- **Commercially Friendly**: Released under the Apache 2.0 license, safe and flexible for commercial integration. -- **High Performance**: Built with Rust, offering performance comparable to C/C++ with modern safety guarantees. -- **Secure by Design**: Rust's memory safety guarantees reduce common classes of security vulnerabilities. -- **Cross-Platform**: Runs natively on Windows, macOS, and Linux. -- **Extensible and Customizable**: Supports a plugin architecture, and the open-source codebase allows deep customization. -- **Cloud-Native**: Suited to containerized environments such as Docker and Kubernetes for rapid deployment. - -## Next Steps - -- [Quick Start](../installation/linux/quick-start.md) — deploy RustFS on Linux with one command. -- [Architecture](./architecture.md) — how RustFS is designed. -- [Comparison](./comparison.md) — how RustFS compares with other object storage products. diff --git a/content/concepts/limit.md b/content/concepts/limit.md deleted file mode 100644 index 735b2c0e..00000000 --- a/content/concepts/limit.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: "Usage Limits" -description: "RustFS is a simple, efficient, distributed object storage. It is fully S3 compatible, open source software released under the Apache2 license." ---- - -## S3 API Limits - -> The following standards strictly follow S3 protocol standards for specification. - -| Item | Specification | -| --------------------- | ---------------------------------- | -| Maximum object size | 5 TiB | -| Minimum object size | 0 B | -| Maximum object size for single PUT operation | Non-multipart upload: 5 GiB; Multipart upload: 5 TiB | -| Maximum number of parts per upload | 10,000 | -| Part size range | 5 MiB to 5 GiB; last part can be 0 B to 5 GiB | -| Maximum number of parts returned per list parts request | 10,000 | -| Maximum number of objects returned per list objects request | 1,000 | -| Maximum number of multipart uploads returned per list multipart uploads request | 1,000 | -| Maximum length of bucket name | 63 characters | -| Maximum length of object name | 1024 characters | -| Maximum length of each `/` separated object name segment | 255 characters | -| Maximum number of versions per single object | 10,000 | - ---- - -## Erasure Coding Limits - -> EC parameters are configured based on Reed-Solomon matrix EC algorithm. Actual limits depend on the specific Erasure Coding (EC) configuration. - -| Item | Specification | -| ---------------------------- | ------------------------------ | -| Maximum number of servers per cluster | No hard limit | -| Minimum number of servers | 1 | -| When server count is 1, minimum number of drives per server | 1 (for single-node single-drive deployment, cannot provide additional reliability or availability) | -| When server count is 2 or more, minimum number of drives per server | 1 | -| Maximum number of drives per server | No hard limit | -| Read quorum count | N − M (the number of data shards, where M is the parity shard count) | -| Write quorum count | N − M; when data and parity counts are equal, N − M + 1 | - ---- - -## Object Naming Limits - -### File System and Operating System Limits - -Object names in RustFS are primarily limited by the underlying operating system and file system. For example, Windows and some other operating systems restrict the use of certain special characters such as `^`, `*`, `|`, `\`, `/`, `&`, `"`, or `;`. - -Refer to relevant documentation for a complete list of restrictions based on your specific operating system and file system. - -RustFS recommends using Linux operating systems based on XFS file systems in production environments for better performance and compatibility. - -### Naming Conflict Handling - -In RustFS, applications must assign unique and non-conflicting keys to all objects. This includes avoiding creating objects whose names might conflict with parent object or sibling object names. RustFS will return an empty set when performing LIST operations at locations where conflicts occur. - -For example, the following operations would cause namespace conflicts: - -```bash -PUT data/hello/2025/first/a.csv -PUT data/hello/2025/first # Conflicts with existing object prefix - -PUT data/hello/2025/first/ -PUT data/hello/2025/first/vendors.csv # Conflicts with existing object -``` - -Although you can perform GET or HEAD operations on these objects, naming conflicts will cause LIST operations executed at the `hello/2025/first/` path to return empty result sets. diff --git a/content/concepts/principle/erasure-coding.md b/content/concepts/principle/erasure-coding.md deleted file mode 100644 index 25f0efcd..00000000 --- a/content/concepts/principle/erasure-coding.md +++ /dev/null @@ -1,142 +0,0 @@ ---- -title: "Erasure Coding Principles" -description: "RustFS, as a new-generation distributed object storage system, demonstrates unique advantages in cloud storage through innovative architectural design and memory safety features. One of its core innovations is the deep application of Reed-Solomon Erasure Coding." ---- - -## Core Algorithm and Application Scope - -Reed-Solomon Code (RS Code) is an erasure code based on finite field algebraic structures. Due to its **efficient data recovery capability** and **flexible redundancy configuration**, it is widely used in multiple fields. Below, we detail its core application scenarios from two dimensions: technical fields and practical applications: - -### 1.1. Distributed Storage Systems (such as RustFS) -- **Data Sharding and Redundancy** - Divide original data into `k` shards, generate `m` parity shards (total `n=k+m`). Any loss of ≤ `m` shards can recover data. - **Example**: RS(10,4) strategy allows simultaneous loss of 4 nodes (storage utilization 71%), saving 50% storage space compared to triple replication (33%). - -- **Fault Recovery Mechanism** - Through **Gaussian elimination** or **Fast Fourier Transform (FFT)** algorithms, use surviving shards to reconstruct lost data, with recovery time inversely proportional to network bandwidth. - -- **Dynamic Adjustment Capability** - Supports runtime adjustment of `(k,m)` parameters to adapt to different storage tiers' (hot/warm/cold data) reliability requirements. - -### 1.2. Communication Transmission -- **Satellite Communication** - Handle long delay, high bit error rate issues in deep space channels (e.g., NASA Mars rover uses RS(255,223) code with error correction capability up to 16 bytes/codeword). - -- **5G NR Standards** - Use RS codes combined with CRC checks in control channels to ensure reliable transmission of critical signaling. - -- **Wireless Sensor Networks** - Solve cumulative packet loss problems in multi-hop transmission, typical configuration RS(6,2) can tolerate 33% data loss. - -### 1.3. Digital Media Storage -- **QR Codes** - Use RS codes to implement fault tolerance level adjustment (L7%, M15%, Q25%, H30%), ensuring correct decoding even with partially damaged areas. - -- **Blu-ray Discs** - Use RS(248,216) code combined with cross-interleaving to correct continuous burst errors caused by scratches. - -- **DNA Data Storage** - Add RS checksums during synthetic biomolecular chain synthesis to resist base synthesis/sequencing errors (e.g., Microsoft experimental project uses RS(4,2)). - -## Basic Concepts of Erasure Coding - -### 2.1 Evolution of Storage Redundancy -```rust -// Traditional triple replication storage -let data = "object_content"; -let replicas = vec![data.clone(), data.clone(), data.clone()]; -``` -Traditional multi-replication schemes have low storage efficiency issues (storage utilization 33%). Erasure coding technology divides data into blocks and calculates checksum information, achieving a balance between storage efficiency and reliability. - -### 2.2 Core Parameter Definitions -- **k**: Number of original data shards -- **m**: Number of parity shards -- **n**: Total number of shards (n = k + m) -- **Recovery threshold**: Any k shards can recover original data - -| Scheme Type | Redundancy | Fault Tolerance | -|------------|----------|------------| -| 3 Replicas | 200% | 2 nodes | -| RS(10,4) | 40% | 4 nodes | - -## Mathematical Principles of Reed-Solomon Codes - -### 3.1 Finite Field (Galois Field) Construction -Use GF(2^8) field (256 elements), satisfying: -$$ -α^8 + α^4 + α^3 + α^2 + 1 = 0 -$$ -Generator polynomial is `0x11D`, corresponding to binary `100011101` - -### 3.2 Encoding Matrix Construction -Vandermonde matrix example (k=2, m=2): -$$ -G = \begin{bmatrix} -1 & 0 \\ -0 & 1 \\ -1 & 1 \\ -1 & 2 -\end{bmatrix} -$$ - -### 3.3 Encoding Process -Data vector D = [d₁, d₂,..., dk] -Encoding result C = D × G - -**Generator polynomial interpolation method**: -Construct polynomial passing through k data points: -$$ -p(x) = d_1 + d_2x + ... + d_kx^{k-1} -$$ -Parity value calculation: -$$ -c_i = p(i), \quad i = k+1,...,n -$$ - -## Engineering Implementation in RustFS - -### 4.1 Data Sharding Strategy -```rust -struct Shard { - index: u8, - data: Vec, - hash: [u8; 32], -} - -fn split_data(data: &[u8], k: usize) -> Vec { - // Sharding logic implementation -} -``` -- Dynamic shard size adjustment (64 KB-4 MB) -- Streaming shard checksums using the HighwayHash256 algorithm for bitrot detection - -### 4.2 Parallel Encoding Optimization -```rust -use rayon::prelude::*; - -fn rs_encode(data: &[Shard], m: usize) -> Vec { - data.par_chunks(k).map(|chunk| { - // SIMD-accelerated matrix operations - unsafe { gf256_simd::rs_matrix_mul(chunk, &gen_matrix) } - }).collect() -} -``` -- Parallel computing framework based on Rayon -- Use AVX2 instruction set to optimize finite field operations - -### 4.3 Decoding Recovery Process -```mermaid -sequenceDiagram - Client->>Coordinator: Data read request - Coordinator->>Nodes: Query shard status - alt Sufficient available shards - Nodes->>Coordinator: Return k shards - Coordinator->>Decoder: Start decoding - Decoder->>Client: Return original data - else Insufficient shards - Coordinator->>Repairer: Trigger repair process - Repairer->>Nodes: Collect surviving shards - Repairer->>Decoder: Data reconstruction - Decoder->>Nodes: Write new shards - end -``` diff --git a/content/concepts/principle/meta.json b/content/concepts/principle/meta.json deleted file mode 100644 index 6cbea485..00000000 --- a/content/concepts/principle/meta.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "title": "Core Concepts", - "pages": [ - "[Erasure Coding](/concepts/principle/erasure-coding)" - ] -} diff --git a/content/developer/examples/aws-cli.md b/content/developer/examples/aws-cli.md index 055c67f0..bc5f6448 100644 --- a/content/developer/examples/aws-cli.md +++ b/content/developer/examples/aws-cli.md @@ -15,7 +15,7 @@ brew install awscli ## Configure -Set your [access keys](../../administration/iam/access-token.md) and region: +Set your [access keys](../../security-compliance/iam/access-token.md) and region: ```bash aws configure @@ -68,4 +68,4 @@ aws s3 ls s3://my-bucket --endpoint-url http://localhost:9000 ## Next steps -Build applications against RustFS with an [S3 SDK](../sdk/index.md), or manage objects with [mc](../mc.md). +Build applications against RustFS with an [S3 SDK](../sdk/index.md), or manage objects with [`rc`](/operations/rc). diff --git a/content/developer/examples/aws-sdk-go.md b/content/developer/examples/aws-sdk-go.md index b9ff138a..70958b90 100644 --- a/content/developer/examples/aws-sdk-go.md +++ b/content/developer/examples/aws-sdk-go.md @@ -15,7 +15,7 @@ go get github.com/aws/aws-sdk-go-v2/service/s3 ## Configure -Replace `http://localhost:9000` with your server address and use your own [access keys](../../administration/iam/access-token.md). RustFS requires path-style addressing (`UsePathStyle: true`): +Replace `http://localhost:9000` with your server address and use your own [access keys](../../security-compliance/iam/access-token.md). RustFS requires path-style addressing (`UsePathStyle: true`): ```go {9,11} import ( @@ -52,4 +52,4 @@ my-bucket ## Next steps -See the full [Go SDK guide](../sdk/go.md), or manage objects with [mc](../mc.md). +See the full [Go SDK guide](../sdk/go.md), or manage objects with [`rc`](/operations/rc). diff --git a/content/developer/examples/aws-sdk-js.md b/content/developer/examples/aws-sdk-js.md index 20a74216..408757a1 100644 --- a/content/developer/examples/aws-sdk-js.md +++ b/content/developer/examples/aws-sdk-js.md @@ -13,7 +13,7 @@ npm install @aws-sdk/client-s3 ## Configure -Replace `http://localhost:9000` with your server address and use your own [access keys](../../administration/iam/access-token.md). RustFS requires path-style addressing (`forcePathStyle: true`): +Replace `http://localhost:9000` with your server address and use your own [access keys](../../security-compliance/iam/access-token.md). RustFS requires path-style addressing (`forcePathStyle: true`): ```javascript title="index.mjs" {8} import { S3Client, CreateBucketCommand, PutObjectCommand, ListObjectsV2Command } from "@aws-sdk/client-s3"; @@ -43,4 +43,4 @@ hello.txt 12 ## Next steps -See the full [JavaScript SDK guide](../sdk/javascript.md), or manage objects with [mc](../mc.md). +See the full [JavaScript SDK guide](../sdk/javascript.md), or manage objects with [`rc`](/operations/rc). diff --git a/content/developer/examples/boto3.md b/content/developer/examples/boto3.md index 6b1e84e0..1f598b68 100644 --- a/content/developer/examples/boto3.md +++ b/content/developer/examples/boto3.md @@ -13,7 +13,7 @@ pip install boto3 ## Configure -Point the client at your RustFS endpoint. Replace `http://localhost:9000` with your server address, and use your own [access keys](../../administration/iam/access-token.md). RustFS requires path-style addressing, set via botocore `Config`: +Point the client at your RustFS endpoint. Replace `http://localhost:9000` with your server address, and use your own [access keys](../../security-compliance/iam/access-token.md). RustFS requires path-style addressing, set via botocore `Config`: ```python import boto3 @@ -50,4 +50,4 @@ hello.txt 12 ## Next steps -See the [S3 SDK overview](../sdk/index.md) for more languages, or manage objects with [mc](../mc.md). +See the [S3 SDK overview](../sdk/index.md) for more languages, or manage objects with [`rc`](/operations/rc). diff --git a/content/developer/examples/mc.md b/content/developer/examples/mc.md deleted file mode 100644 index 9a0d353d..00000000 --- a/content/developer/examples/mc.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -title: "mc (MinIO Client)" -description: "Connect the MinIO Client (mc) to RustFS and perform basic object operations." ---- - -[mc](https://min.io/docs/minio/linux/reference/minio-mc.html) is the MinIO command-line client for S3-compatible object storage, and it works with RustFS out of the box. - -## Install - -```bash -brew install minio/stable/mc -``` - -Or download a binary from the [official install guide](https://min.io/docs/minio/linux/reference/minio-mc.html#install-mc). - -## Configure - -Create an alias pointing at your RustFS endpoint. Replace `http://localhost:9000` with your server address, and use your own [access keys](../../administration/iam/access-token.md): - -```bash -mc alias set rustfs http://localhost:9000 -``` - -```text -Added `rustfs` successfully. -``` - -`mc` uses path-style requests by default, which is what RustFS expects. The default region is `us-east-1`. - -## Verify - -Create a bucket: - -```bash -mc mb rustfs/my-bucket -``` - -```text -Bucket created successfully `rustfs/my-bucket`. -``` - -Upload a file: - -```bash -mc cp /path/to/hello.txt rustfs/my-bucket/ -``` - -```text -/path/to/hello.txt: 12 B / 12 B 100.00% 1.2 KiB/s 0s -``` - -List the bucket: - -```bash -mc ls rustfs/my-bucket -``` - -```text -[2026-07-15 10:30:00 UTC] 12B STANDARD hello.txt -``` - -## Next steps - -See the [full mc guide](../mc.md) for bucket policies, mirroring, and more operations. diff --git a/content/developer/examples/meta.json b/content/developer/examples/meta.json index 062e98db..4742275a 100644 --- a/content/developer/examples/meta.json +++ b/content/developer/examples/meta.json @@ -1,7 +1,7 @@ { "title": "Examples", "pages": [ - "[mc (MinIO Client)](/developer/examples/mc)", + "[rc](/operations/rc)", "[AWS CLI](/developer/examples/aws-cli)", "[s3cmd](/developer/examples/s3cmd)", "[rclone](/developer/examples/rclone)", diff --git a/content/developer/examples/rclone.md b/content/developer/examples/rclone.md index 299e56dd..b93bedbf 100644 --- a/content/developer/examples/rclone.md +++ b/content/developer/examples/rclone.md @@ -15,7 +15,7 @@ Or see the [official install guide](https://rclone.org/install/). ## Configure -Add a remote to `~/.config/rclone/rclone.conf`. Replace `http://localhost:9000` with your server address, and use your own [access keys](../../administration/iam/access-token.md). `force_path_style = true` is required because RustFS uses path-style addressing: +Add a remote to `~/.config/rclone/rclone.conf`. Replace `http://localhost:9000` with your server address, and use your own [access keys](../../security-compliance/iam/access-token.md). `force_path_style = true` is required because RustFS uses path-style addressing: ```ini title="~/.config/rclone/rclone.conf" [rustfs] @@ -56,4 +56,4 @@ rclone ls rustfs:my-bucket ## Next steps -Build applications against RustFS with an [S3 SDK](../sdk/index.md), or manage objects with [mc](../mc.md). +Build applications against RustFS with an [S3 SDK](../sdk/index.md), or manage objects with [`rc`](/operations/rc). diff --git a/content/developer/examples/s3cmd.md b/content/developer/examples/s3cmd.md index 3e6e5e72..16fbf31c 100644 --- a/content/developer/examples/s3cmd.md +++ b/content/developer/examples/s3cmd.md @@ -18,7 +18,7 @@ pip install s3cmd ## Configure -Create `~/.s3cfg`. Replace `localhost:9000` with your server address and use your own [access keys](../../administration/iam/access-token.md). RustFS uses path-style addressing, so set the bucket host to the same endpoint: +Create `~/.s3cfg`. Replace `localhost:9000` with your server address and use your own [access keys](../../security-compliance/iam/access-token.md). RustFS uses path-style addressing, so set the bucket host to the same endpoint: ```ini title="~/.s3cfg" [default] @@ -53,4 +53,4 @@ upload: '/path/to/hello.txt' -> 's3://my-bucket/hello.txt' [1 of 1] ## Next steps -See the [SDK overview](../sdk/index.md) to connect an application, or manage objects with [mc](../mc.md). +See the [SDK overview](../sdk/index.md) to connect an application, or manage objects with [`rc`](/operations/rc). diff --git a/content/developer/examples/terraform.md b/content/developer/examples/terraform.md index c2557d6c..08b7ea2d 100644 --- a/content/developer/examples/terraform.md +++ b/content/developer/examples/terraform.md @@ -7,7 +7,7 @@ The [Terraform AWS provider](https://registry.terraform.io/providers/hashicorp/a ## Configure -Replace `http://localhost:9000` with your server address and use your own [access keys](../../administration/iam/access-token.md). The skip flags stop the provider from calling AWS-only metadata and STS endpoints: +Replace `http://localhost:9000` with your server address and use your own [access keys](../../security-compliance/iam/access-token.md). The skip flags stop the provider from calling AWS-only metadata and STS endpoints: ```hcl title="main.tf" provider "aws" { diff --git a/content/developer/index.md b/content/developer/index.md index 3bb863f5..6304a1f7 100644 --- a/content/developer/index.md +++ b/content/developer/index.md @@ -1,13 +1,12 @@ --- title: Developer -description: Guide to using STS, MCP, MinIO Client, SDKs, and APIs with RustFS. +description: Guide to using STS, MCP, SDKs, and APIs with RustFS. --- -This guide provides instructions for integrating and interacting with RustFS using STS, MCP (Model Context Protocol), MinIO Client (`mc`), SDKs, and APIs. It is designed to enable developers to build efficient and scalable applications on top of RustFS. This document covers the following topics: +This guide provides instructions for integrating and interacting with RustFS using STS, MCP (Model Context Protocol), SDKs, and APIs. It is designed to enable developers to build applications on top of RustFS. This document covers the following topics: - STS - [MCP Usage Guide](./mcp.md) -- [MinIO Client Usage Guide](./mc.md) - [SDK Usage Guide](./sdk/index.md) - [Integration Guide](./integration/index.md) - API Usage Guide \ No newline at end of file diff --git a/content/developer/integration/ai-workflow/dify.md b/content/developer/integration/ai-workflow/dify.md index e11826ab..e1610705 100644 --- a/content/developer/integration/ai-workflow/dify.md +++ b/content/developer/integration/ai-workflow/dify.md @@ -19,4 +19,4 @@ The exact setting names depend on the Dify version and deployment method. Match ## Next steps -See [Access Key Management](/administration/iam/access-token) to create dedicated credentials. \ No newline at end of file +See [Access Key Management](/security-compliance/iam/access-token) to create dedicated credentials. \ No newline at end of file diff --git a/content/developer/integration/ai-workflow/n8n.md b/content/developer/integration/ai-workflow/n8n.md index 7bc4cf00..b87a9a4a 100644 --- a/content/developer/integration/ai-workflow/n8n.md +++ b/content/developer/integration/ai-workflow/n8n.md @@ -19,4 +19,4 @@ The available fields depend on the n8n version and S3 integration in use. Confir ## Next steps -See [Access Key Management](/administration/iam/access-token) to create dedicated credentials. \ No newline at end of file +See [Access Key Management](/security-compliance/iam/access-token) to create dedicated credentials. \ No newline at end of file diff --git a/content/developer/integration/big-data/iceberg.md b/content/developer/integration/big-data/iceberg.md index 58966c19..7d4de6dc 100644 --- a/content/developer/integration/big-data/iceberg.md +++ b/content/developer/integration/big-data/iceberg.md @@ -19,4 +19,4 @@ Iceberg does not define one universal object-store configuration. Apply these va ## Next steps -See [Access Key Management](/administration/iam/access-token) to create dedicated credentials. \ No newline at end of file +See [Access Key Management](/security-compliance/iam/access-token) to create dedicated credentials. \ No newline at end of file diff --git a/content/developer/mc.md b/content/developer/mc.md deleted file mode 100644 index 62a9d4c5..00000000 --- a/content/developer/mc.md +++ /dev/null @@ -1,114 +0,0 @@ ---- -title: "Manage RustFS Objects with MinIO Client" -description: "Manage RustFS objects using the MinIO Client." ---- - -The MinIO Client (`mc`) is a command-line tool for managing object storage services. It supports MinIO, Amazon S3, and other S3-compatible services. As RustFS is S3-compatible, you can use `mc` to manage RustFS objects. - -Prerequisites: - -- An available RustFS instance. Refer to the [Installation Guide](../installation/index.md). -- The `mc` tool is installed. -- Available [access keys](../administration/iam/access-token.md). - -## Using `mc` with RustFS - -First, configure an alias for RustFS using the `mc alias` command: - -```bash -mc alias set rustfs http://192.168.1.100:9000 -``` - -Output: - -```bash -Added `rustfs` successfully. -``` - -You can now use `mc` with the `rustfs` alias to create/delete buckets, upload/download files, etc. - -### List Buckets - -List all buckets: - -```bash -mc ls rustfs -``` - -Output: - -```bash -[2025-08-01 10:46:24 CST] 0B bucket-creation-by-api/ -[2025-07-29 09:15:35 CST] 0B rustfs-demo/ -[2025-08-03 09:44:45 CST] 0B bucket-creation-by-ui/ -``` - -### Create Bucket - -Create a bucket: - -```bash -mc mb rustfs/bucket-creation-by-mc -``` - -Output: - -```bash -Bucket created successfully `rustfs/bucket-creation-by-mc`. -``` - -### Delete Bucket - -Delete a bucket: - -```bash -mc rb rustfs/bucket-creation-by-mc -``` - -Output: - -```bash -Removed `rustfs/bucket-creation-by-mc` successfully. -``` - -### Upload File - -Upload a file to a bucket: - -```bash -mc cp file_name rustfs/bucket-creation-by-mc -``` - -Output: - -```bash -...path/to/file_name: 4 B / 4 B ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 12 B/s 0s% -``` - -### Delete File - -Delete a file from a bucket: - -```bash -mc rm rustfs/bucket-creation-by-mc/file_name -``` - -Output: - -```bash -Removed `rustfs/bucket-creation-by-mc/1.txt`. -``` - -### Download File from Storage - -Use `mc get` command to download a file from a bucket: - -```bash -mc get rustfs/bucket-creation-by-mc/file_name ./file_name -``` - -Response: - -```bash -...eation-by-mc/file_name: 4 B / 4 B ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 18 B/s 0s% -``` diff --git a/content/developer/mcp.md b/content/developer/mcp.md index 3333db3e..fd997b01 100644 --- a/content/developer/mcp.md +++ b/content/developer/mcp.md @@ -174,7 +174,7 @@ Currently, mainstream AI IDEs such as Cursor, Windsurf, Trae, etc. all support M } ``` -> `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` are RustFS access keys. You can refer to the [Access Key Management chapter](../administration/iam/access-token.md) for creation. +> `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` are RustFS access keys. You can refer to the [Access Key Management chapter](../security-compliance/iam/access-token.md) for creation. If added successfully, you can list the [available tools](#️-available-tools) on the MCP configuration page. diff --git a/content/developer/meta.json b/content/developer/meta.json index 7fa47c38..9f7fdbe5 100644 --- a/content/developer/meta.json +++ b/content/developer/meta.json @@ -1,7 +1,6 @@ { "title": "Developer", "pages": [ - "mc", "sdk", "integration", "license" diff --git a/content/developer/sdk/go.md b/content/developer/sdk/go.md index 16bd0542..b40cab27 100644 --- a/content/developer/sdk/go.md +++ b/content/developer/sdk/go.md @@ -9,7 +9,7 @@ RustFS ships no first-party Go SDK — it is fully S3-compatible, so you use the - Go 1.21 or later - A working RustFS instance (refer to [Installation Guide](../../installation/index.md)) — the S3 API listens on port `9000`, the Console on port `9001` -- Access keys, set at install time via the `RUSTFS_ACCESS_KEY` / `RUSTFS_SECRET_KEY` environment variables (refer to [Access Key Management](../../administration/iam/access-token.md)) +- Access keys, set at install time via the `RUSTFS_ACCESS_KEY` / `RUSTFS_SECRET_KEY` environment variables (refer to [Access Key Management](../../security-compliance/iam/access-token.md)) :::tip[Local test] diff --git a/content/developer/sdk/java.md b/content/developer/sdk/java.md index a0f307b9..938a7f16 100644 --- a/content/developer/sdk/java.md +++ b/content/developer/sdk/java.md @@ -9,7 +9,7 @@ RustFS ships no first-party Java SDK — it is S3-compatible, so you use the off * Java 8 or later and Maven (or Gradle) * A running RustFS instance (see the [Installation Guide](../../installation/index.md)) — the S3 API listens on port `9000`, the Console on port `9001` -* Access keys, set at install time via the `RUSTFS_ACCESS_KEY` / `RUSTFS_SECRET_KEY` environment variables (see [Access Key Management](../../administration/iam/access-token.md)) +* Access keys, set at install time via the `RUSTFS_ACCESS_KEY` / `RUSTFS_SECRET_KEY` environment variables (see [Access Key Management](../../security-compliance/iam/access-token.md)) :::tip[Local test] diff --git a/content/developer/sdk/javascript.md b/content/developer/sdk/javascript.md index b9ab2914..f010b46f 100644 --- a/content/developer/sdk/javascript.md +++ b/content/developer/sdk/javascript.md @@ -11,7 +11,7 @@ RustFS ships no first-party JavaScript SDK — it is S3-compatible, so you use t * Node.js 18 or later * A running RustFS instance (see the [Installation Guide](../../installation/index.md)) — the S3 API listens on port `9000`, the Console on port `9001` -* Access keys, set at install time via the `RUSTFS_ACCESS_KEY` / `RUSTFS_SECRET_KEY` environment variables (see [Access Key Management](../../administration/iam/access-token.md)) +* Access keys, set at install time via the `RUSTFS_ACCESS_KEY` / `RUSTFS_SECRET_KEY` environment variables (see [Access Key Management](../../security-compliance/iam/access-token.md)) :::tip[Local test] diff --git a/content/developer/sdk/python.md b/content/developer/sdk/python.md index c6c63133..922ec994 100644 --- a/content/developer/sdk/python.md +++ b/content/developer/sdk/python.md @@ -21,7 +21,7 @@ This guide covers: * Python 3.8 or later * A running RustFS instance (see the [Installation Guide](../../installation/index.md)) — the S3 API listens on port `9000`, the Console on port `9001` -* Access keys, set at install time via the `RUSTFS_ACCESS_KEY` / `RUSTFS_SECRET_KEY` environment variables (see [Access Key Management](../../administration/iam/access-token.md)) +* Access keys, set at install time via the `RUSTFS_ACCESS_KEY` / `RUSTFS_SECRET_KEY` environment variables (see [Access Key Management](../../security-compliance/iam/access-token.md)) :::tip[Local test] diff --git a/content/developer/sdk/rust.md b/content/developer/sdk/rust.md index 746f7df2..9d818134 100644 --- a/content/developer/sdk/rust.md +++ b/content/developer/sdk/rust.md @@ -9,7 +9,7 @@ RustFS ships no first-party Rust client crate — it is fully S3-compatible, so - Rust 1.78 or later (install via [rustup](https://rustup.rs/)) - An available RustFS instance (refer to [Installation Guide](../../installation/index.md)) — the S3 API listens on port `9000`, the Console on port `9001` -- Access keys, set at install time via the `RUSTFS_ACCESS_KEY` / `RUSTFS_SECRET_KEY` environment variables (refer to [Access Key Management](../../administration/iam/access-token.md)) +- Access keys, set at install time via the `RUSTFS_ACCESS_KEY` / `RUSTFS_SECRET_KEY` environment variables (refer to [Access Key Management](../../security-compliance/iam/access-token.md)) :::tip[Local test] diff --git a/content/developer/sdk/typescript.md b/content/developer/sdk/typescript.md index 5218f01c..d26b7a93 100644 --- a/content/developer/sdk/typescript.md +++ b/content/developer/sdk/typescript.md @@ -9,7 +9,7 @@ RustFS ships no first-party TypeScript SDK — it is fully S3-compatible, so you - Node.js 18 or later (the examples use ES modules — set `"type": "module"` in your `package.json`) - An available RustFS instance (refer to [Installation Guide](../../installation/index.md) for installation) — the S3 API listens on port `9000`, the Console on port `9001` -- Access keys, set at install time via the `RUSTFS_ACCESS_KEY` / `RUSTFS_SECRET_KEY` environment variables (refer to [Access Key Management](../../administration/iam/access-token.md) for creation) +- Access keys, set at install time via the `RUSTFS_ACCESS_KEY` / `RUSTFS_SECRET_KEY` environment variables (refer to [Access Key Management](../../security-compliance/iam/access-token.md) for creation) :::tip[Local test] diff --git a/content/index.mdx b/content/index.mdx index 5de0e36c..d9b0e2a3 100644 --- a/content/index.mdx +++ b/content/index.mdx @@ -1,69 +1,83 @@ --- title: "RustFS Documentation" -description: "RustFS is a high-performance, distributed, S3-compatible object storage system written in Rust." +description: "Install RustFS on Linux, containers, Kubernetes, macOS, or Windows." --- -RustFS is a high-performance, distributed object storage system written in Rust, fully compatible with the S3 protocol and released under the Apache 2.0 license. It suits private cloud, data lake, AI/ML, and cloud-native workloads. +

+ -## Try RustFS in 10 minutes +
+ + + + Install on Linux + Start on a Linux server, then choose a single-node or distributed topology. + + - - } title="Quick Start" href="/installation/linux/quick-start"> - One command to install, log in to the Console, and store your first object. - - } title="Run with Docker" href="/installation/container"> - A single `docker run` for local evaluation — no server required. - - + + + + Install with Docker + Run RustFS as a container for local evaluation or managed deployments. + + -## Find your path + + + + Install with Podman + Deploy a rootless or daemonless RustFS container with Podman. + + -### Evaluating RustFS? + + + + Install with Helm + Deploy one RustFS cluster on Kubernetes with the RustFS Helm chart. + + - - } title="What is RustFS?" href="/concepts/introduction"> - Positioning, features, and license. - - } title="Architecture & Comparison" href="/concepts/architecture"> - How it works and how it measures up against alternatives. - - } title="Usage Limits" href="/concepts/limit"> - The boundaries you should know before committing. - - + + + + Install with the Operator + Manage Kubernetes-native RustFS tenants, pools, and lifecycle operations. + + -### Deploying and operating? + + + + Install on macOS + Run RustFS locally on Apple silicon or Intel-based Mac computers. + + - - } title="Installation Guide" href="/installation/"> - Linux (single-node to multi-node), Docker, Kubernetes, Windows, macOS. - - } title="Checklist" href="/installation/requirement/checklists/hardware-checklists"> - Hardware, network, software, and security prep. - - } title="Operations" href="/operations/upgrade"> - Rolling upgrades, scaling, troubleshooting, and data healing. - - - -### Building an application? - - - } title="SDKs" href="/developer/sdk"> - Java, Python, Go, Rust, JavaScript/TypeScript — any S3 client works. - - } title="Tool Examples" href="/developer/examples/mc"> - Minimal recipes for mc, AWS CLI, boto3, and rclone. - - } title="Reference" href="/reference/environment-variables"> - Environment variables, CLI, ports, limits, and glossary. - - - -## Why RustFS - -- **S3 Compatible** — drop-in for the S3 ecosystem: SDKs, tools, and data pipelines. -- **Distributed** — erasure-coded storage pools scale across disks and nodes. -- **Fast and memory-safe** — Rust performance without garbage-collection pauses. -- **Open source, commercially friendly** — Apache 2.0 licensed. -- **Cloud-native** — first-class Docker and Kubernetes deployment. + + + + Install on Windows + Install and run RustFS on a supported Windows environment. + + +
+
diff --git a/content/installation/cloud-native/helm-chart.md b/content/installation/cloud-native/helm-chart.md deleted file mode 100644 index c7a96aba..00000000 --- a/content/installation/cloud-native/helm-chart.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: "Helm Chart Installation" -description: "Deploy a standalone or distributed RustFS cluster on Kubernetes with the official Helm chart." ---- - -Use the official RustFS Helm chart when you want Helm to manage one RustFS deployment directly. You need a Kubernetes cluster, `kubectl`, Helm 3, and a StorageClass that can provision the required persistent volumes. - -## 1. Get the chart - -The chart is stored in the RustFS source repository under `helm/rustfs`: - -```bash -git clone https://github.com/rustfs/rustfs.git -cd rustfs/helm/rustfs -``` - -## 2. Install RustFS - -Install the chart in a dedicated namespace and replace the credential placeholders before running the command: - -```bash -helm install rustfs . \ - --namespace rustfs \ - --create-namespace \ - --set secret.rustfs.access_key= \ - --set secret.rustfs.secret_key= \ - --set storageclass.dataStorageSize=100Gi \ - --set storageclass.logStorageSize=1Gi -``` - -The chart deploys distributed mode by default. For a development-only standalone deployment, add these values: - -```bash ---set mode.standalone.enabled=true \ ---set mode.distributed.enabled=false -``` - -## 3. Verify the deployment - -Wait for the RustFS pods to become ready: - -```bash -kubectl -n rustfs get pods -w -``` - -Without an Ingress, forward the S3 API and Console services to your workstation: - -```bash -kubectl -n rustfs port-forward svc/rustfs 9000:9000 9001:9001 -``` - -The S3 API is available at `http://localhost:9000`, and the Console is available at `http://localhost:9001`. - -For storage sizing, probes, ingress, TLS, server pools, and uninstall steps, see the [complete Kubernetes Helm guide](/installation/cloud-native). - -## Next steps - -- [Install with the RustFS Operator](/installation/cloud-native/operator) -- [Configure TLS](/integration/tls-configured) -- [Observe RustFS](/operations/observability) \ No newline at end of file diff --git a/content/installation/cloud-native/helm-chart/cert-manager.md b/content/installation/cloud-native/helm-chart/cert-manager.md new file mode 100644 index 00000000..5386eaf3 --- /dev/null +++ b/content/installation/cloud-native/helm-chart/cert-manager.md @@ -0,0 +1,69 @@ +--- +title: cert-manager +description: Use cert-manager to issue and renew TLS certificates for a RustFS Helm deployment. +--- + +Use **cert-manager** to issue the certificate referenced by the RustFS Ingress. This provides HTTPS for the public S3 API and Console endpoint and allows cert-manager to renew the certificate. + +## Requirements + +- cert-manager is installed and its controller Pods are ready. +- An `Issuer` or `ClusterIssuer` is ready. +- The RustFS hostname resolves to the Ingress controller. + +Verify the issuer before installing RustFS: + +```bash +kubectl get clusterissuer letsencrypt-prod +kubectl -n cert-manager get pods +``` + +## 1. Configure Ingress TLS + +Add the Ingress and certificate settings to your standalone or distributed values file: + +```yaml title="values.yaml" +ingress: + enabled: true + className: nginx + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + hosts: + - host: s3.example.com + paths: + - path: / + pathType: Prefix + tls: + enabled: true + certManager: + enabled: true + existingSecret: + enabled: false + name: "" +``` + +Replace the Ingress class, issuer, and hostname for your cluster. For a namespace-scoped Issuer, use the `cert-manager.io/issuer` annotation instead. + +## 2. Apply the configuration + +```bash +helm upgrade rustfs ./helm/rustfs \ + --namespace rustfs \ + -f values.yaml +``` + +The Ingress references the `rustfs-tls` Secret for a release named `rustfs`. cert-manager ingress-shim reads the issuer annotation and creates a Certificate that writes to this Secret. + +## 3. Verify the certificate + +```bash +kubectl -n rustfs get ingress,certificate,certificaterequest +kubectl -n rustfs describe certificate rustfs-tls +kubectl -n rustfs get secret rustfs-tls +``` + +Wait for the Certificate to report `Ready=True`, then open `https://s3.example.com`. + +## Use cert-manager with mTLS + +The chart also uses cert-manager to issue server and client certificates when `mtls.enabled=true`. See [mTLS](./mtls.md) to use the chart-managed CA or reference an existing issuer. \ No newline at end of file diff --git a/content/installation/cloud-native/helm-chart/index.md b/content/installation/cloud-native/helm-chart/index.md new file mode 100644 index 00000000..bc28bffe --- /dev/null +++ b/content/installation/cloud-native/helm-chart/index.md @@ -0,0 +1,21 @@ +--- +title: Overview +description: Understand how the RustFS Helm chart deploys standalone and distributed clusters on Kubernetes. +--- + +The official **RustFS Helm chart** deploys one RustFS cluster directly into Kubernetes. Helm renders the workload, Services, credentials, configuration, PersistentVolumeClaims (PVCs), Ingress, and optional certificate resources from a single values file. + +The chart supports two deployment modes: + +- **Standalone** creates one Pod with one data PVC. Use it for evaluation and development. +- **Distributed** creates a StatefulSet with multiple Pods and data PVCs. `replicaCount` controls the number of Pods and `drivesPerNode` controls the number of data PVCs mounted by each Pod. + +Distributed mode is enabled by default. The chart also supports multiple append-only server pools, but a single explicit topology is easier to operate for an initial deployment. Credentials must be supplied through chart values or an existing Secret; the chart rejects empty and well-known default credentials unless insecure development defaults are explicitly enabled. + +Use the Helm chart when you want Helm to manage one RustFS cluster. Use the [RustFS Operator](../operator/index.md) when you need Kubernetes custom resources, multiple Tenants, or Operator-driven pool management. + +## Helm chart workflows + +- [Install](./installation.md) covers requirements and standalone or distributed deployment. +- [mTLS](./mtls.md) encrypts and authenticates traffic between RustFS Pods. +- [cert-manager](./cert-manager.md) issues and renews certificates for RustFS Ingress and mTLS. \ No newline at end of file diff --git a/content/installation/cloud-native/helm-chart/installation.mdx b/content/installation/cloud-native/helm-chart/installation.mdx new file mode 100644 index 00000000..5523fffb --- /dev/null +++ b/content/installation/cloud-native/helm-chart/installation.mdx @@ -0,0 +1,168 @@ +--- +title: Install +description: Install standalone or distributed RustFS on Kubernetes with the official Helm chart. +--- + +This guide gets the official RustFS chart from source or the Helm repository, installs it, and verifies access to the S3 API and Console. + +## Requirements + +| Component | Requirement | +|-----------|-------------| +| Helm | Version 3 | +| Kubernetes | A cluster reachable with `kubectl` | +| StorageClass | Dynamic PVC provisioning for RustFS data | +| RustFS | `1.0.0-alpha.69` or later | + +An Ingress controller is optional. If you enable Ingress, set `ingress.className` to the controller in your cluster, such as `nginx` or `traefik`. + +Check the active cluster and available StorageClasses: + +```bash +kubectl config current-context +kubectl get storageclass +helm version --short +``` + +## 1. Get the chart + +Choose how Helm should access the chart. Both options set `RUSTFS_CHART`, which the installation commands below use. + + + + +Clone the RustFS source repository to use the chart at `helm/rustfs`: + +```bash +git clone https://github.com/rustfs/rustfs.git +cd rustfs +export RUSTFS_CHART=./helm/rustfs +``` + + + + +Add the RustFS repository listed on [Artifact Hub](https://artifacthub.io/packages/helm/rustfs/rustfs), then update the local repository index: + +```bash +helm repo add rustfs https://charts.rustfs.com +helm repo update +export RUSTFS_CHART=rustfs/rustfs +``` + + + + +## 2. Install standalone mode + +Create a values file for a one-Pod development deployment: + +```yaml title="standalone-values.yaml" +mode: + standalone: + enabled: true + distributed: + enabled: false + +secret: + rustfs: + access_key: "" + secret_key: "" + +storageclass: + name: standard + dataStorageSize: 10Gi + logStorageSize: 1Gi + +ingress: + enabled: false +``` + +Replace `standard` with a StorageClass in your cluster, then install: + +```bash +helm upgrade --install rustfs "$RUSTFS_CHART" \ + --namespace rustfs \ + --create-namespace \ + -f standalone-values.yaml +``` + +## 3. Install distributed mode + +For a distributed cluster, set the Pod and drive counts explicitly. Total data drives equal `replicaCount * drivesPerNode`. + +```yaml title="distributed-values.yaml" +mode: + standalone: + enabled: false + distributed: + enabled: true + +replicaCount: 4 +drivesPerNode: 2 + +secret: + rustfs: + access_key: "" + secret_key: "" + +storageclass: + name: standard + dataStorageSize: 100Gi + logStorageSize: 1Gi + +ingress: + enabled: false +``` + +The example creates four Pods and eight data PVCs. Ensure the cluster can schedule all Pods and provision all PVCs, then install: + +```bash +helm upgrade --install rustfs "$RUSTFS_CHART" \ + --namespace rustfs \ + --create-namespace \ + -f distributed-values.yaml +``` + +:::warning[Choose the topology before installation] + +Kubernetes does not allow updates to StatefulSet `volumeClaimTemplates`. Changing `drivesPerNode` later requires StatefulSet recreation or a new installation. + +::: + +## 4. Verify and access RustFS + +```bash +kubectl -n rustfs get pods,pvc,services +kubectl -n rustfs rollout status statefulset/rustfs +``` + +Standalone mode creates a Deployment instead of a StatefulSet. Check it with: + +```bash +kubectl -n rustfs rollout status deployment/rustfs +``` + +Forward the S3 API and Console to your workstation: + +```bash +kubectl -n rustfs port-forward svc/rustfs 9000:9000 9001:9001 +``` + +Use `http://localhost:9000` as the S3 endpoint and open `http://localhost:9001` for the Console. + +## Key values + +| Value | Purpose | Chart default | +|-------|---------|---------------| +| `mode.standalone.enabled` | Enable one-Pod standalone mode | `false` | +| `mode.distributed.enabled` | Enable distributed mode | `true` | +| `replicaCount` | Distributed Pod count | `4` | +| `drivesPerNode` | Data PVCs per Pod | Inferred from `replicaCount` | +| `storageclass.name` | StorageClass for PVCs | `local-path` | +| `storageclass.dataStorageSize` | Size of each data PVC | `256Mi` | +| `storageclass.logStorageSize` | Size of each log PVC | `256Mi` | +| `service.endpoint.port` | S3 API port | `9000` | +| `service.console.port` | Console port | `9001` | + +We recommend setting storage sizes explicitly; the chart defaults are intended only for basic evaluation. \ No newline at end of file diff --git a/content/installation/cloud-native/helm-chart/meta.json b/content/installation/cloud-native/helm-chart/meta.json new file mode 100644 index 00000000..c439f551 --- /dev/null +++ b/content/installation/cloud-native/helm-chart/meta.json @@ -0,0 +1,8 @@ +{ + "title": "Helm Chart", + "pages": [ + "installation", + "mtls", + "cert-manager" + ] +} \ No newline at end of file diff --git a/content/installation/cloud-native/helm-chart/mtls.md b/content/installation/cloud-native/helm-chart/mtls.md new file mode 100644 index 00000000..5144b472 --- /dev/null +++ b/content/installation/cloud-native/helm-chart/mtls.md @@ -0,0 +1,66 @@ +--- +title: mTLS +description: Configure mutual TLS between RustFS Pods deployed by the Helm chart. +--- + +The chart can enable mutual Transport Layer Security (mTLS) for RustFS Pod communication. When enabled, RustFS requires client certificates, uses HTTPS for generated peer URLs, and mounts server, client, and CA material into every Pod. + +## Requirements + +mTLS uses cert-manager `Issuer` and `Certificate` resources. Install cert-manager before enabling it and confirm that its CRDs are available: + +```bash +kubectl get crd certificates.cert-manager.io issuers.cert-manager.io +``` + +## 1. Use the chart-managed CA + +Add the following setting to your existing values file: + +```yaml title="values.yaml" +mtls: + enabled: true +``` + +Upgrade the release: + +```bash +helm upgrade rustfs ./helm/rustfs \ + --namespace rustfs \ + -f values.yaml +``` + +The chart creates a self-signed root CA, a namespace Issuer, and server and client Certificates. It mounts the resulting Secrets and configures RustFS with `RUSTFS_SERVER_MTLS_ENABLE=1` and `RUSTFS_TLS_PATH=/opt/tls`. Health probes also use the generated client certificate. + +## 2. Use an existing Issuer + +To use an Issuer or ClusterIssuer already managed by your platform, configure its reference: + +```yaml title="values.yaml" +mtls: + enabled: true + existingIssuerRef: + enabled: true + name: internal-ca + kind: ClusterIssuer + group: cert-manager.io +``` + +The issuer must be ready and able to issue both server and client certificates in the `rustfs` namespace. Use `kind: Issuer` for a namespace-scoped issuer. + +## 3. Verify mTLS + +```bash +kubectl -n rustfs get issuer,certificate,secret +kubectl -n rustfs describe certificate rustfs-server-tls +kubectl -n rustfs describe certificate rustfs-client-tls +kubectl -n rustfs get pods +``` + +For a release named `rustfs`, the generated certificate Secrets are `rustfs-server-tls` and `rustfs-client-tls`. + +:::warning[Plan external access] + +mTLS requires clients to present a trusted certificate. Validate how your Ingress controller or other external client presents that certificate before enabling mTLS on an existing deployment. + +::: \ No newline at end of file diff --git a/content/installation/cloud-native/index.md b/content/installation/cloud-native/index.md index c756353e..ef338b58 100644 --- a/content/installation/cloud-native/index.md +++ b/content/installation/cloud-native/index.md @@ -11,6 +11,7 @@ RustFS ships an official Helm chart that deploys either a single-node instance ( - Helm 3 - RustFS image version `>= 1.0.0-alpha.69` (the chart requirement) - A StorageClass with a working provisioner — the chart defaults to [`local-path`](https://github.com/rancher/local-path-provisioner); set `storageclass.name` to use your own +- [`rc`](/operations/rc) installed on the administration host before using the server-pool commands in this guide The chart lives in the RustFS source repository under `helm/rustfs`: @@ -177,7 +178,7 @@ What to expect during the rollout, per the chart's documentation: :::note -`rc` is the RustFS admin command-line client referenced by the chart documentation (`rc admin pool ls` / `expand` / `rebalance` / `decommission`). Confirm its availability and packaging with your RustFS distribution before relying on it in runbooks. +`rc` is the RustFS command-line client. Use `rc admin pool list`, `expand`, `rebalance`, and `decommission` for the server-pool workflows described by the chart. ::: diff --git a/content/installation/cloud-native/meta.json b/content/installation/cloud-native/meta.json index 8e024ab7..520b603a 100644 --- a/content/installation/cloud-native/meta.json +++ b/content/installation/cloud-native/meta.json @@ -1,7 +1,7 @@ { "title": "Cloud Native", "pages": [ - "[Helm Chart](/installation/cloud-native/helm-chart)", - "[Operator](/installation/cloud-native/operator)" + "helm-chart", + "operator" ] } diff --git a/content/installation/cloud-native/operator.md b/content/installation/cloud-native/operator.md deleted file mode 100644 index b4f25404..00000000 --- a/content/installation/cloud-native/operator.md +++ /dev/null @@ -1,99 +0,0 @@ ---- -title: "Operator Installation" -description: "Install the RustFS Kubernetes Operator and create a development Tenant custom resource." ---- - -Use the RustFS Kubernetes Operator when you want to manage RustFS clusters as namespaced `Tenant` custom resources. The Operator reconciles the RBAC, Services, StatefulSets, and persistent volume claims required by each Tenant. - -:::warning[Pre-release software] - -The RustFS Operator is currently `v0.1.0` pre-release and under active development. Evaluate it in a non-production cluster and review the [upstream Operator repository](https://github.com/rustfs/operator) before adopting it. - -::: - -You need Kubernetes 1.30 or newer, Helm 3, `kubectl` access to the target cluster, and a StorageClass that can satisfy Tenant persistent volume claims. - -## 1. Get the Operator chart - -Clone the official Operator repository. Its Helm chart is stored under `deploy/rustfs-operator`: - -```bash -git clone https://github.com/rustfs/operator.git -cd operator -``` - -## 2. Install the Operator - -Install the Operator and its custom resource definitions in the `rustfs-system` namespace: - -```bash -helm install rustfs-operator deploy/rustfs-operator/ \ - --namespace rustfs-system \ - --create-namespace -``` - -Verify the Operator and Console pods: - -```bash -kubectl get pods -n rustfs-system -kubectl logs -n rustfs-system \ - -l app.kubernetes.io/name=rustfs-operator,app.kubernetes.io/component=operator -``` - -## 3. Create a development Tenant - -Create a minimal single-node Tenant for evaluation: - -```yaml title="tenant.yaml" -apiVersion: rustfs.com/v1alpha1 -kind: Tenant -metadata: - name: dev-minimal - namespace: default -spec: - image: rustfs/rustfs:latest - pools: - - name: dev-pool - servers: 1 - persistence: - volumesPerServer: 1 -``` - -:::warning[Development credentials] - -This minimal manifest does not configure a credential Secret and is suitable only for local evaluation. For production-style testing, create a Kubernetes Secret containing `accesskey` and `secretkey`, then reference it with `spec.credsSecret.name`. - -::: - -Apply the Tenant and wait for its pod to become ready: - -```bash -kubectl apply -f tenant.yaml -kubectl get tenant dev-minimal -kubectl get pods,pvc,svc -l rustfs.tenant=dev-minimal -kubectl wait --for=condition=ready pod \ - -l rustfs.tenant=dev-minimal \ - --timeout=300s -``` - -## 4. Access the Tenant - -Forward the Tenant S3 API: - -```bash -kubectl port-forward svc/dev-minimal-io 9000:9000 -``` - -In another terminal, forward the Tenant Console: - -```bash -kubectl port-forward svc/dev-minimal-console 9001:9001 -``` - -The S3 API is available at `http://localhost:9000`, and the Tenant Console is available at `http://localhost:9001`. - -## Next steps - -- Review the [Operator user guide](https://github.com/rustfs/operator/blob/main/docs/operator-user-guide.md) -- Review the [Tenant examples](https://github.com/rustfs/operator/tree/main/examples) -- [Configure TLS](/integration/tls-configured) \ No newline at end of file diff --git a/content/installation/cloud-native/operator/index.md b/content/installation/cloud-native/operator/index.md new file mode 100644 index 00000000..68777f69 --- /dev/null +++ b/content/installation/cloud-native/operator/index.md @@ -0,0 +1,28 @@ +--- +title: Overview +description: Understand how RustFS Operator manages RustFS clusters on Kubernetes. +--- + +**RustFS Operator** applies the Kubernetes Operator pattern to RustFS clusters. Instead of creating StatefulSets, Services, PersistentVolumeClaims (PVCs), and configuration by hand, you declare the required storage cluster as a Kubernetes custom resource. The controller watches that resource and continuously reconciles the running cluster with the declared state. + +The Operator installs two Custom Resource Definitions (CRDs): + +- `Tenant` (`rustfs.com/v1alpha1`) represents one RustFS cluster. It defines storage pools, credentials, scheduling, Transport Layer Security (TLS), and Key Management Service (KMS) settings. +- `PolicyBinding` (`sts.rustfs.com/v1alpha1`) maps a Kubernetes ServiceAccount to RustFS policies when workloads request temporary credentials from the Operator Security Token Service (STS). + +One Operator can manage multiple Tenants across namespaces. Each Tenant has independent storage, credentials, S3 and Console services, and lifecycle. The Operator creates one StatefulSet for each pool, so you can add capacity by appending a pool without rebuilding the cluster. It also reports `Ready`, `Progressing`, or `Degraded` conditions and Kubernetes Events, and exposes health and metrics endpoints for cluster monitoring. + +The same API covers small test clusters and distributed deployments. Sensitive credentials and KMS material stay in Kubernetes Secrets, while version-controlled Tenant manifests hold only Secret references. This makes deployments repeatable, supports GitOps workflows, and keeps routine operations such as multi-tenant management, pool expansion, TLS, and encryption within Kubernetes-native tools. + +## Operator workflows + +- [Install](./installation.md) covers requirements, Helm installation, Console access, and TLS configuration. +- [Multi-Tenant](./tenant.md) creates isolated RustFS clusters for different teams or workloads. +- [Pool Expansion](./pool-expansion.md) adds storage capacity by appending a pool to an existing Tenant. +- [KMS Integration](./kms.md) configures local or HashiCorp Vault key management for encrypted data. + +:::warning[Pre-release software] + +RustFS Operator is currently `v0.1.0` pre-release software under active development. Validate upgrades and Tenant changes in a non-production cluster first. + +::: \ No newline at end of file diff --git a/content/installation/cloud-native/operator/installation.md b/content/installation/cloud-native/operator/installation.md new file mode 100644 index 00000000..3e8cb0c2 --- /dev/null +++ b/content/installation/cloud-native/operator/installation.md @@ -0,0 +1,117 @@ +--- +title: Install +description: Prepare Kubernetes, install RustFS Operator, and access its Console securely. +--- + +This guide installs the Operator with Helm, verifies the deployment, and exposes the Operator Console locally or through HTTPS. + +## Requirements + +| Component | Requirement | +|-----------|-------------| +| Kubernetes | `v1.30` or later | +| Helm | `v3.0` or later | +| kubectl | Compatible with the Kubernetes cluster | +| StorageClass | Dynamic PVC provisioning for Tenant storage | + +Your account must be able to create CRDs, cluster RBAC, Deployments, and Services. Confirm the target cluster before installation: + +```bash +kubectl config current-context +kubectl get storageclass +``` + +## 1. Install the Operator + +The Helm chart is stored in the Operator repository: + +```bash +git clone https://github.com/rustfs/operator.git +cd operator + +helm upgrade --install rustfs-operator deploy/rustfs-operator/ \ + --namespace rustfs-system \ + --create-namespace +``` + +Common settings belong in a values file: + +```yaml title="values.yaml" +operator: + replicas: 1 + metrics: + enabled: true + tenantMonitor: + enabled: true + intervalSeconds: 300 +console: + enabled: true + service: + type: ClusterIP +``` + +Apply the file with `-f values.yaml`. The chart generates `OPERATOR_*` variables from these values; do not duplicate them under `operator.env`. + +## 2. Verify the installation + +```bash +kubectl -n rustfs-system get pods,services +kubectl get crd tenants.rustfs.com +kubectl -n rustfs-system rollout status deployment/rustfs-operator +kubectl -n rustfs-system rollout status deployment/rustfs-operator-console +``` + +## 3. Access the Operator Console + +The Console listens on port `9090`. Generate a short-lived login token: + +```bash +kubectl -n rustfs-system create token rustfs-operator-console --duration=24h +``` + +Forward the Console service to your workstation: + +```bash +kubectl -n rustfs-system port-forward \ + svc/rustfs-operator-console 19090:9090 +``` + +Open `http://127.0.0.1:19090` and paste the token into the login form. The Helm installation notes print the exact ServiceAccount and Service names when release names or namespaces differ. + +:::note[Local HTTP access] +If your browser does not retain the login over HTTP, set `CONSOLE_COOKIE_SECURE=false` under `console.env` for local testing only. Keep secure cookies enabled for HTTPS. +::: + +## 4. Configure Console TLS + +Use one HTTPS hostname for both the Console UI and `/api/v1`. Create a TLS Secret, or let cert-manager create it, then enable Ingress: + +```yaml title="values.yaml" +console: + ingress: + enabled: true + className: nginx + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + hosts: + - host: console.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: console-tls + hosts: + - console.example.com +``` + +Upgrade the release with the values file: + +```bash +helm upgrade rustfs-operator deploy/rustfs-operator/ \ + --namespace rustfs-system \ + -f values.yaml +``` + +Replace the Ingress class, issuer, and hostname for your environment. If cert-manager is not installed, create the `console-tls` Secret with your certificate and private key before the upgrade. + +Next, [create a Tenant](./tenant.md). \ No newline at end of file diff --git a/content/installation/cloud-native/operator/kms.md b/content/installation/cloud-native/operator/kms.md new file mode 100644 index 00000000..60c89950 --- /dev/null +++ b/content/installation/cloud-native/operator/kms.md @@ -0,0 +1,89 @@ +--- +title: KMS Integration +description: Configure local or HashiCorp Vault key management for a RustFS Tenant through the Operator encryption API. +--- + +Configure Key Management Service (KMS) integration through `spec.encryption`. Do not add `RUSTFS_KMS_*` variables to `spec.env`; the Operator generates them from the structured Tenant configuration and Secret references. + +## Choose a backend + +Use `local` only for a single-server Tenant. Use `vault` for distributed deployments where every Tenant Pod can reach HashiCorp Vault. + +## Local KMS + +Create a master key Secret: + +```yaml title="local-kms-secret.yaml" +apiVersion: v1 +kind: Secret +metadata: + name: rustfs-local-kms + namespace: storage-a +type: Opaque +stringData: + local-master-key: "replace-with-a-random-master-key" +``` + +Add the encryption block to the existing Tenant manifest: + +```yaml title="tenant.yaml" +spec: + encryption: + enabled: true + backend: local + local: + keyDirectory: /data/rustfs0/.kms-keys + masterKeySecretRef: + name: rustfs-local-kms + key: local-master-key + defaultKeyId: tenant-default +``` + +The key directory must be inside a mounted data path so it survives Pod replacement. + +## HashiCorp Vault KMS + +Create a Secret containing a Vault token: + +```yaml title="vault-kms-secret.yaml" +apiVersion: v1 +kind: Secret +metadata: + name: rustfs-kms + namespace: storage-a +type: Opaque +stringData: + vault-token: "replace-with-vault-token" +``` + +Add the Vault configuration to the existing Tenant manifest: + +```yaml title="tenant.yaml" +spec: + encryption: + enabled: true + backend: vault + vault: + endpoint: https://vault.example.com:8200 + kmsSecret: + name: rustfs-kms + defaultKeyId: tenant-default +``` + +Every Tenant Pod must be able to resolve and connect to the Vault endpoint and trust its certificate. + +## Apply the configuration + +```bash +kubectl apply -f local-kms-secret.yaml +kubectl apply -f tenant.yaml +kubectl -n storage-a describe tenant tenant-a +``` + +For Vault, apply `vault-kms-secret.yaml` before `tenant.yaml`. Changing encryption settings rolls the affected StatefulSets. + +:::warning[Protect encryption keys] + +Back up key material and test recovery before storing production data. Losing the local master key or Vault keys can make encrypted objects unrecoverable. + +::: \ No newline at end of file diff --git a/content/installation/cloud-native/operator/meta.json b/content/installation/cloud-native/operator/meta.json new file mode 100644 index 00000000..843952a8 --- /dev/null +++ b/content/installation/cloud-native/operator/meta.json @@ -0,0 +1,9 @@ +{ + "title": "Operator", + "pages": [ + "installation", + "tenant", + "pool-expansion", + "kms" + ] +} \ No newline at end of file diff --git a/content/installation/cloud-native/operator/pool-expansion.md b/content/installation/cloud-native/operator/pool-expansion.md new file mode 100644 index 00000000..482c99b5 --- /dev/null +++ b/content/installation/cloud-native/operator/pool-expansion.md @@ -0,0 +1,55 @@ +--- +title: Pool Expansion +description: Add storage capacity to a RustFS Tenant by appending a storage pool. +--- + +All pools in a Tenant form one RustFS cluster. Add capacity by appending a new pool to `spec.pools`; do not change the shape of an existing pool. + +:::warning[Existing pools are immutable] + +Do not change `servers` or `persistence.volumesPerServer` on an existing pool. The Operator creates an immutable StatefulSet for each pool. + +::: + +## 1. Check the Tenant + +```bash +kubectl -n storage-a get tenant tenant-a +kubectl -n storage-a get pods,pvc -l rustfs.tenant=tenant-a +``` + +Confirm that the Tenant is `Ready` and the cluster has enough compute and storage capacity. + +## 2. Add a pool + +Append the following entry to the existing `spec.pools` list in `tenant.yaml`. Keep all existing entries unchanged. + +```yaml title="tenant.yaml" +- name: pool-1 + servers: 2 + persistence: + volumesPerServer: 2 + volumeClaimTemplate: + storageClassName: standard + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 100Gi +``` + +This pool creates four PVCs: two servers multiplied by two volumes per server. Apply the complete Tenant manifest: + +```bash +kubectl apply -f tenant.yaml +``` + +## 3. Watch the expansion + +```bash +kubectl -n storage-a get tenant tenant-a -w +kubectl -n storage-a get pods,pvc \ + -l rustfs.pool=pool-1 +``` + +Wait for the Tenant to return to `Ready` before making another topology change. Increasing an existing PVC size is a separate Kubernetes storage operation and depends on the StorageClass. \ No newline at end of file diff --git a/content/installation/cloud-native/operator/tenant.md b/content/installation/cloud-native/operator/tenant.md new file mode 100644 index 00000000..08bec042 --- /dev/null +++ b/content/installation/cloud-native/operator/tenant.md @@ -0,0 +1,74 @@ +--- +title: Multi-Tenant +description: Create isolated RustFS Tenants and access their S3 API and Console services. +--- + +A `Tenant` represents one independent RustFS cluster. Use a separate namespace, credentials Secret, and Tenant resource for each team or workload. + +## 1. Create a namespace and credentials + +Create the Secret directly so credentials are not stored in a manifest: + +```bash +kubectl create namespace storage-a +kubectl -n storage-a create secret generic rustfs-tenant-creds \ + --from-literal=accesskey='' \ + --from-literal=secretkey='' +``` + +## 2. Define the Tenant + +This development example creates one RustFS Pod and one `10Gi` PVC. Replace `standard` with a StorageClass in your cluster. + +```yaml title="tenant.yaml" +apiVersion: rustfs.com/v1alpha1 +kind: Tenant +metadata: + name: tenant-a + namespace: storage-a +spec: + image: rustfs/rustfs:1.0.0-beta.10 + credsSecret: + name: rustfs-tenant-creds + pools: + - name: pool-0 + servers: 1 + persistence: + volumesPerServer: 1 + volumeClaimTemplate: + storageClassName: standard + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi +``` + +## 3. Apply and verify + +```bash +kubectl apply -f tenant.yaml +kubectl -n storage-a get tenant,pods,pvc,svc +kubectl -n storage-a describe tenant tenant-a +``` + +## 4. Access RustFS + +```bash +kubectl -n storage-a port-forward svc/tenant-a-io 9000:9000 +kubectl -n storage-a port-forward svc/tenant-a-console 9001:9001 +``` + +Run the commands in separate terminals. Use `http://localhost:9000` as the S3 endpoint and open `http://localhost:9001` for the Tenant Console. + +To add another Tenant, repeat the process with a different namespace, Secret, and Tenant name. List all managed Tenants with: + +```bash +kubectl get tenants --all-namespaces +``` + +:::warning[Production topology] + +The one-server example is for evaluation. Production Tenants need a distributed pool layout, resource requests, scheduling constraints, and an immutable image reference. + +::: \ No newline at end of file diff --git a/content/installation/console-first-steps.md b/content/installation/console-first-steps.md index 687510e2..590845d3 100644 --- a/content/installation/console-first-steps.md +++ b/content/installation/console-first-steps.md @@ -37,9 +37,9 @@ After signing in you land on the console home page, which shows: 1. On the Buckets page, in the top right corner, select **Create Bucket**. 2. Enter a bucket name (for example `my-first-bucket`) and click **Create**. -![bucket creation](../management/data/bucket/images/bucket-creation-by-ui.png) +![bucket creation](../administration/data/bucket/images/bucket-creation-by-ui.png) -Full details, including `mc` and API alternatives, are in [Bucket Creation](../management/data/bucket/creation.md). +Full details, including `rc` and API alternatives, are in [Bucket Creation](../administration/data/bucket/creation.md). ## 5. Upload Your First Object @@ -47,20 +47,20 @@ Full details, including `mc` and API alternatives, are in [Bucket Creation](../m 2. In the top right corner, select **Upload File/Folder**. 3. Choose one or more local files and click **Start Upload**. -![object creation from ui](../management/data/object/images/upload_file_from_ui.png) +![object creation from ui](../administration/data/object/images/upload_file_from_ui.png) Click the uploaded object to view its details — size, ETag, content type, and a shareable link. -![object details info](../management/data/object/images/object_details_info.png) +![object details info](../administration/data/object/images/object_details_info.png) -More options are covered in [Object Creation](../management/data/object/creation.md). +More options are covered in [Object Creation](../administration/data/object/creation.md). ## 6. Create Access Keys for Applications -The console sign-in credentials are administrator credentials — applications should use their own keys instead. Go to **Access Keys** in the console to create a scoped access key / secret key pair for your S3 clients and SDKs. See [Access Key Management](../administration/iam/access-token.md) for the full walkthrough. +The console sign-in credentials are administrator credentials — applications should use their own keys instead. Go to **Access Keys** in the console to create a scoped access key / secret key pair for your S3 clients and SDKs. See [Access Key Management](../security-compliance/iam/access-token.md) for the full walkthrough. ## Next Steps - Point any S3-compatible client at `http://:9000` (the S3 API port) with your new access keys. -- Explore [IAM management](../administration/iam/index.md) to add users and policies. +- Explore [IAM management](../security-compliance/iam/index.md) to add users and policies. - Review the [production checklists](./requirement/checklists/index.md) before going live. diff --git a/content/installation/container/index.mdx b/content/installation/container/index.mdx index e6e32759..be39d29c 100644 --- a/content/installation/container/index.mdx +++ b/content/installation/container/index.mdx @@ -14,6 +14,7 @@ This article is based on RustFS official Linux binary packages, packaging RustFS * Docker installed (≥ 20.10) and able to pull images and run containers normally * Local path `/mnt/rustfs/data` (or custom path) for mounting object data + * [`rc`](/operations/rc) installed on the administration host for the verification workflow 2. **Network and Firewall** * Ensure host ports 9000 (S3 API) and 9001 (Console) are open to external access (or consistent with custom ports) @@ -237,13 +238,13 @@ Whether you start only the `rustfs-server` or together with observability servic 2. **Test S3 API:** - Use `mc` or other S3 clients: + Use `rc` to verify the S3 API: ```bash # Use a unique access key and a strong, random secret (e.g. openssl rand -base64 24) - mc alias set rustfs http://localhost:9000 "" "" - mc mb rustfs/mybucket - mc ls rustfs + rc alias set rustfs http://localhost:9000 + rc bucket create rustfs/my-bucket + rc bucket list rustfs/ ``` If buckets can be successfully created and listed, deployment is effective. diff --git a/content/installation/index.md b/content/installation/index.md index 4e36b849..52d7124d 100644 --- a/content/installation/index.md +++ b/content/installation/index.md @@ -3,7 +3,7 @@ title: "Installation" description: "Choose the right RustFS deployment path: local trial, single-node or multi-node production, containers, and Kubernetes." --- -RustFS is a distributed object storage system written in Rust, fully compatible with the S3 protocol and released under the Apache 2.0 license. It runs on Linux, Windows, macOS, FreeBSD, and containers, across x86, ARM, RISC-V, and other CPU architectures. To learn more about the project itself, see [What is RustFS?](../concepts/introduction.md). +RustFS is a distributed object storage system written in Rust and released under the Apache 2.0 license. It runs on Linux, Windows, macOS, FreeBSD, and containers, across x86, ARM, RISC-V, and other CPU architectures. ## Choose Your Path diff --git a/content/installation/linux/quick-start.md b/content/installation/linux/quick-start.md index 3163f0e6..f68a6678 100644 --- a/content/installation/linux/quick-start.md +++ b/content/installation/linux/quick-start.md @@ -10,6 +10,7 @@ This guide takes you from an empty Linux server to a working RustFS instance: in - A Linux server (x86_64 or aarch64) with `systemd`, and root or sudo access - `unzip` installed, and outbound network access to download the package - Ports `9000` (S3 API) and `9001` (Console) reachable from your machine +- [`rc`](/operations/rc) installed on your workstation for the command-line verification workflow ## 1. Install and start RustFS @@ -61,18 +62,18 @@ Open `http://:9001` in your browser and sign in with the access key a 2. Open the bucket and use the upload action to add any local file. 3. Click the uploaded object to view its details — you have a working object store. -Prefer the command line? The same two operations with the [MinIO Client (`mc`)](../../developer/mc.md): +Prefer the command line? Configure [`rc`](/operations/rc), then perform the same operations: ```bash -mc alias set rustfs http://:9000 -mc mb rustfs/my-bucket -mc cp ./hello.txt rustfs/my-bucket -mc ls rustfs/my-bucket +rc alias set rustfs http://:9000 +rc bucket create rustfs/my-bucket +rc object copy /path/to/hello.txt rustfs/my-bucket/hello.txt +rc object list rustfs/my-bucket ``` ```text -Bucket created successfully `rustfs/my-bucket`. -[2026-07-15 10:00:00 UTC] 12B hello.txt +✓ Bucket 'rustfs/my-bucket' created successfully. +/path/to/hello.txt -> rustfs/my-bucket/hello.txt ``` diff --git a/content/installation/meta.json b/content/installation/meta.json index e617373b..2f9d8099 100644 --- a/content/installation/meta.json +++ b/content/installation/meta.json @@ -6,6 +6,7 @@ "container", "cloud-native", "[Windows](/installation/windows)", - "[macOS](/installation/macos)" + "[macOS](/installation/macos)", + "console-first-steps" ] } \ No newline at end of file diff --git a/content/installation/requirement/disk-preparation.md b/content/installation/requirement/disk-preparation.md index da847fac..cdcdbed6 100644 --- a/content/installation/requirement/disk-preparation.md +++ b/content/installation/requirement/disk-preparation.md @@ -68,5 +68,4 @@ Use the verified mount paths when you configure `RUSTFS_VOLUMES`. Keep disk labe ## Next steps - [Choose a deployment topology](/installation#deployment-mode-comparison) -- [Review erasure coding](/concepts/principle/erasure-coding) - [Continue with Linux prerequisites and service setup](/installation/linux/prerequisites-and-service) \ No newline at end of file diff --git a/content/installation/requirement/meta.json b/content/installation/requirement/meta.json index d212099e..acee121f 100644 --- a/content/installation/requirement/meta.json +++ b/content/installation/requirement/meta.json @@ -2,7 +2,6 @@ "title": "Requirement", "pages": [ "[Topology Selection](/installation#deployment-mode-comparison)", - "[EC Configuration](/concepts/principle/erasure-coding)", "disk-preparation", "checklists" ] diff --git a/content/management/console/index.md b/content/management/console/index.md deleted file mode 100644 index fc964df8..00000000 --- a/content/management/console/index.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: "Console" -description: "Use the RustFS Console to administer storage resources and access settings from a web browser." ---- - -The **RustFS Console** is the web interface for routine administration. Use this section to open the Console and find the management workflows available in the documentation. - -## Open the Console - -The Console listens on port `9001` by default. Open the following address in a browser, replacing `` with the RustFS server address: - -```text -http://:9001 -``` - -Sign in with the credentials configured for your RustFS deployment. For a guided first session, see [Console: First Steps](/installation/console-first-steps). - -## Management workflows - -- [Create and manage buckets](/management/data/bucket/creation) -- [Upload and manage objects](/management/data/object/creation) -- [Manage access keys](/administration/iam/access-token) -- [Configure identity and access management](/administration/iam) - -## Next steps - -Review the [security checklist](/installation/requirement/checklists/security-checklists) before exposing the Console outside a trusted network. \ No newline at end of file diff --git a/content/management/index.md b/content/management/index.md deleted file mode 100644 index 509d73f2..00000000 --- a/content/management/index.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "Administration" -description: "Guide to administering RustFS data, protocols, browser access, and permissions." ---- - -This section covers data, protocol, browser access, and identity administration: - -- [Data Management](./data/object/object-lock.md) -- [Protocol Support](./protocols/s3.md) -- [CORS Configuration](./cors/index.md) -- [IAM Management](../administration/iam/index.md) \ No newline at end of file diff --git a/content/management/meta.json b/content/management/meta.json deleted file mode 100644 index adcc6da9..00000000 --- a/content/management/meta.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "title": "Administration", - "pages": [ - "[Console](/management/console)", - "data", - "protocols", - "cors", - "[Virtual-Host Access](/integration/virtual)" - ] -} \ No newline at end of file diff --git a/content/meta.json b/content/meta.json index f21a1b99..f7ff8975 100644 --- a/content/meta.json +++ b/content/meta.json @@ -1,18 +1,11 @@ { "root": true, "pages": [ - "!concepts/introduction", - "!installation/linux/quick-start", - "!installation/console-first-steps", - "!concepts/architecture", - "!concepts/principle/erasure-coding", - "!concepts/comparison", - "!developer/examples", - "installation", - "management", "administration", + "security-compliance", "operations", + "troubleshooting", "developer", "reference" ] diff --git a/content/operations/cluster-health-check.md b/content/operations/cluster-health-check.md deleted file mode 100644 index 60bb3e64..00000000 --- a/content/operations/cluster-health-check.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: "Cluster Health Check" -description: "Check RustFS node readiness and cluster read and write health endpoints." ---- - -RustFS serves health endpoints from the S3 listener on port `9000`. - -| Endpoint | Purpose | -| --- | --- | -| `GET /health/live` | Confirms that the process is running. | -| `GET /health/ready` | Confirms storage, IAM, and peer readiness. | -| `GET /minio/health/cluster` | Checks cluster write health and lock quorum. | -| `GET /minio/health/cluster/read` | Checks cluster read health and lock quorum. | - -Check every node before maintenance: - -```bash -curl -fsS http://:9000/health/ready -``` - -A ready node returns HTTP `200`. For telemetry configuration and alerting guidance, see [Observability](./observability.md). \ No newline at end of file diff --git a/content/operations/index.md b/content/operations/index.md index c3ab2610..43994ccd 100644 --- a/content/operations/index.md +++ b/content/operations/index.md @@ -11,6 +11,5 @@ Use this section to keep RustFS deployments healthy through upgrades, scaling, a - [Scaling](./scaling/index.md) covers storage expansion, rebalancing, and decommissioning. - [High Availability](./high-availability/index.md) covers node healing and site replication. - [Observability](./observability.md) covers metrics, logs, traces, and health signals. -- [Cluster Health Check](./cluster-health-check.md) covers deployment health validation. -- [Event Notifications](./event-notifications.md) covers bucket event targets. -- [Troubleshooting](/troubleshooting) provides diagnostic guidance. \ No newline at end of file +- [Status Check](./status-check.md) covers cluster health and storage capacity checks. +- [Event Notifications](./event-notifications.md) covers bucket event targets. \ No newline at end of file diff --git a/content/operations/meta.json b/content/operations/meta.json index 980f07d3..c3ea86b2 100644 --- a/content/operations/meta.json +++ b/content/operations/meta.json @@ -1,12 +1,12 @@ { "title": "Operations & Observability", "pages": [ + "rc", "upgrade", "scaling", "high-availability", "observability", - "cluster-health-check", - "event-notifications", - "[Troubleshooting](/troubleshooting)" + "status-check", + "event-notifications" ] } \ No newline at end of file diff --git a/content/operations/rc.mdx b/content/operations/rc.mdx new file mode 100644 index 00000000..de5f5464 --- /dev/null +++ b/content/operations/rc.mdx @@ -0,0 +1,155 @@ +--- +title: "rc" +description: "Install the RustFS command-line client and use it to connect to and inspect a RustFS cluster." +--- + +`rc` is the RustFS command-line client for S3 object operations and RustFS administration. This guide installs `rc`, configures a cluster alias, and introduces the main inspection commands. The download examples use `v0.1.29`; check the [RustFS CLI releases](https://github.com/rustfs/cli/releases) for a newer version before installing. + +## Installation + +### Linux + +The default Linux archives are statically linked with musl for broad compatibility. Choose the tab that matches `uname -m`. + + + + +For an `x86_64` host: + +```bash +curl -fLO https://github.com/rustfs/cli/releases/download/v0.1.29/rustfs-cli-linux-amd64-v0.1.29.tar.gz +tar -xzf rustfs-cli-linux-amd64-v0.1.29.tar.gz +sudo install -m 0755 rc /usr/local/bin/rc +``` + + + + +For an `aarch64` host: + +```bash +curl -fLO https://github.com/rustfs/cli/releases/download/v0.1.29/rustfs-cli-linux-arm64-v0.1.29.tar.gz +tar -xzf rustfs-cli-linux-arm64-v0.1.29.tar.gz +sudo install -m 0755 rc /usr/local/bin/rc +``` + + + + +If your environment specifically requires a glibc-linked binary, select the `linux-amd64-gnu` or `linux-arm64-gnu` archive from the release page. + +### macOS + +Choose the tab that matches the value returned by `uname -m`. + + + + +For an Intel (`x86_64`) Mac: + +```bash +curl -fLO https://github.com/rustfs/cli/releases/download/v0.1.29/rustfs-cli-macos-amd64-v0.1.29.tar.gz +tar -xzf rustfs-cli-macos-amd64-v0.1.29.tar.gz +sudo install -m 0755 rc /usr/local/bin/rc +``` + + + + +For an Apple silicon (`arm64`) Mac: + +```bash +curl -fLO https://github.com/rustfs/cli/releases/download/v0.1.29/rustfs-cli-macos-arm64-v0.1.29.tar.gz +tar -xzf rustfs-cli-macos-arm64-v0.1.29.tar.gz +sudo install -m 0755 rc /usr/local/bin/rc +``` + + + + +You can alternatively install the package through Homebrew: + +```bash +brew install rustfs/tap/rc +``` + +### Windows + +The current Windows release provides an AMD64 binary. Install it through the official Scoop bucket: + +```powershell +scoop bucket add rustfs https://github.com/rustfs/scoop-bucket +scoop install rustfs/rc +``` + +### Verify the installation + +Open a new terminal and verify that `rc` is available: + +```bash +rc --version +rc --help +``` + +## Usage + +### Configure an alias + +An alias stores the RustFS endpoint and credentials so you do not need to repeat them for every command. Replace the credentials before running: + +```bash +rc alias set local http://localhost:9000 +rc alias list +``` + +`alias set` replaces an existing alias with the same name. `alias list` does not print secret keys. + +### Check service health + +Check whether the service is live and whether its required dependencies are ready: + +```bash +rc ping local +rc ready local +``` + +### Inspect the cluster + +Use the administrative cluster information command to inspect cluster status, servers, disks, and storage capacity: + +```bash +rc admin info cluster local +``` + +List server pools when the deployment uses multiple pools: + +```bash +rc admin pool list local +``` + +Administrative commands require credentials with the corresponding RustFS Admin API permissions. + +### Inspect buckets and objects + +Prefer the noun-first command groups for new scripts: + +```bash +rc bucket list local/ +rc object list local/my-bucket +rc object stat local/my-bucket/hello.txt +``` + +### Discover more commands + +Use `--help` at any command level to see the commands and options supported by your installed version: + +```bash +rc --help +rc alias --help +rc admin --help +rc admin info --help +rc bucket --help +rc object --help +``` + +For the complete source and command reference, see the [RustFS CLI repository](https://github.com/rustfs/cli). diff --git a/content/operations/scaling/data-rebalancing.md b/content/operations/scaling/data-rebalancing.md index fedec2df..85ae5b55 100644 --- a/content/operations/scaling/data-rebalancing.md +++ b/content/operations/scaling/data-rebalancing.md @@ -1,8 +1,92 @@ --- title: "Rebalancing" -description: "Redistribute existing objects across RustFS storage pools after expanding a cluster." +description: "Redistribute existing objects across active RustFS storage pools through the Console or rc." --- -After you add a storage pool, new writes prefer pools with more free space, but existing objects remain in their current pools. Rebalancing redistributes existing objects across all active pools. +## Overview -See [Rebalance After Expansion](./storage-pool-decommission.md#rebalance-after-expansion) for the verified admin API endpoints, progress fields, and stop behavior. \ No newline at end of file +### Requirements + +- Install [`rc`](/operations/rc) on the administration host before using the `rc` workflow in this guide. +- Configure credentials with rebalance administration permission. + +After [Pool Expansion](./storage-pool-expansion.md), new writes can use the added capacity, but existing objects remain in their original pools. **Rebalancing** moves existing objects across all active pools so their used-capacity ratios converge. + +Rebalancing requires at least two active pools. RustFS rejects a new rebalance while another rebalance or a pool decommission is running. Stopping a rebalance does not move already relocated objects back to their original pools. + +Before starting, verify that all nodes and disks are healthy, no decommission is active, and the cluster has enough free capacity for normal writes during the operation. Schedule the operation during a lower-traffic period because object movement consumes disk, network, and CPU resources. + +## Operation + +### Console + +1. Sign in to the RustFS Console with an account that has rebalance administration permission. +2. Open **Rebalance**. +3. Review the active pools and their used-capacity ratios. +4. Select **Start Rebalance** and confirm the operation. +5. Keep the page open or return to it periodically to review per-pool progress. +6. Use **Stop Rebalance** only when you need to halt the operation. Data already moved remains in its new pool. + +:::note[Image placeholder] + +Add screenshots of the Rebalance page before starting, while the operation is running, and after it reports `Completed`. + +::: + +### rc + +Configure the cluster alias if it does not already exist: + +```bash +rc alias set rustfs http://:9000 +``` + +Start the rebalance: + +```bash +rc admin rebalance start rustfs +``` + +Check progress: + +```bash +rc admin rebalance status rustfs +``` + +The status includes the operation ID and per-pool usage, moved bytes, object and version counts, remaining buckets, elapsed time, and estimated completion time when available. + +To stop a running rebalance: + +```bash +rc admin rebalance stop rustfs +``` + +`rc admin expand start|status|stop` and the `scale` alias expose the same post-expansion rebalance workflow. + +## Verification + +### Console + +Wait until the Rebalance page reports `Completed`. Confirm that: + +- No pool reports a failed or stopped state. +- Used-capacity ratios are closer across the active pools. +- All nodes and disks remain online. +- Normal object reads and writes succeed. + +:::note[Image placeholder] + +Add a screenshot of the completed rebalance with the final per-pool usage values. + +::: + +### rc + +Run: + +```bash +rc admin rebalance status rustfs +rc admin pool list rustfs +``` + +Verify that the rebalance status is `Completed`, remaining bucket counts are zero, and no failure is reported. Compare the per-pool usage ratios with the values recorded before the operation, then read an existing object and write a new test object through the S3 endpoint. \ No newline at end of file diff --git a/content/operations/scaling/storage-pool-decommission.md b/content/operations/scaling/storage-pool-decommission.md index c946f734..91e3f9f6 100644 --- a/content/operations/scaling/storage-pool-decommission.md +++ b/content/operations/scaling/storage-pool-decommission.md @@ -1,130 +1,119 @@ --- title: "Pool Decommission" -description: "This article describes how to retire a server pool with the decommission workflow and how to spread existing data onto new pools with rebalance, including the admin API endpoints, progress monitoring, and abort semantics." +description: "Drain and retire a RustFS storage pool through the Console or rc, then verify that its data was relocated." --- -## Concepts +## Overview -A RustFS cluster grows by adding **server pools** — additional groups of nodes/drives listed in `RUSTFS_VOLUMES` (space-separated expansion expressions). Two data-movement operations manage pools over their lifecycle: +### Requirements -- **Decommission** drains all objects off a pool onto the remaining active pools, so the pool can be removed from the deployment. Movement is one-directional and the pool is retired afterwards. -- **Rebalance** redistributes existing objects across **all** pools after an expansion, so a newly added (empty) pool takes its fair share. No pool is removed. +- Install [`rc`](/operations/rc) on the administration host before using the `rc` workflow in this guide. +- Configure credentials with decommission administration permission. -The two are mutually exclusive: rebalance refuses to start while a decommission is in progress, and both require a multi-pool deployment (single-pool clusters reject either operation because there is nowhere to move data). +**Pool decommission** moves objects from a selected pool to the remaining active pools so the target pool can be removed from the deployment. Use it when retiring hardware, replacing a pool, or consolidating capacity. -## When to Decommission +Decommission is different from [Rebalancing](./data-rebalancing.md): rebalance keeps every pool active, while decommission drains and retires the selected pool. The two operations cannot run at the same time. -- Retiring old hardware after adding a replacement pool. -- Shrinking a cluster that was previously expanded. -- Consolidating small pools into a larger one. +Before starting: -## Prerequisites +- Keep at least one active pool after the operation. +- Verify that the remaining pools have enough free capacity. RustFS requires their free space to cover the used bytes being drained plus a 30% overhead. +- Confirm that every node and disk is healthy and no rebalance is running. +- Back up critical data and schedule the operation during a lower-traffic period. +- Record the exact pool ID and volume expression before selecting the target. -1. **At least one active pool must remain.** You cannot decommission every pool; the request is rejected if no active pool would be left. -2. **Remaining pools need capacity.** The server verifies before starting that the free space on the remaining active pools is at least the used bytes of the pool(s) being drained **plus a 30% overhead**. Otherwise the start request fails with `insufficient target pool capacity`. -3. **Completed pools cannot be re-decommissioned.** Completion means the pool should now be removed from the deployment configuration (`RUSTFS_VOLUMES` / Helm pool list). Failed or canceled pools may be retried. -4. Healthy cluster: run decommission with all nodes up; the operation persists its state and resumes after restarts, but starting it on a degraded cluster adds risk. +Canceling a decommission does not roll back completed moves. Objects already relocated remain in the destination pools. -:::warning -Decommission moves data. Take a fresh backup or verify your replication targets before draining a pool, and schedule it in a low-traffic window. -::: +## Operation + +### Console + +1. Sign in to the RustFS Console with an account that has decommission administration permission. +2. Open **Pool Decommission**. +3. Locate the pool to retire and verify its ID, volume expression, used capacity, and status. +4. Select **Start Decommission** for that pool. +5. Review the confirmation dialog carefully, then select **Confirm**. +6. Use **Sync** to refresh the pool state and movement counters until the operation completes. -## Admin API Endpoints +:::note[Image placeholder] + +Add screenshots of the pool selection, confirmation dialog, running progress, and completed decommission state. + +::: -The admin API is served on port 9000 under the `/rustfs/admin/v3` prefix (a MinIO-compatible `/minio/admin` prefix also exists). All requests must be signed (AWS Signature V4) with credentials that hold the decommission admin permission — the root credential works. The `pool` query parameter takes the pool's command-line expression exactly as configured, or a zero-based pool index with `by-id=true`. +After completion, remove the drained pool expression from `RUSTFS_VOLUMES` on every remaining node and restart RustFS with the same ordered topology. For Helm, decommission the pool before removing its entry from `pools.list`; never remove or reorder a live pool entry. -| Method | Path | Purpose | -| --- | --- | --- | -| `GET` | `/rustfs/admin/v3/pools/list` | List pools and their status | -| `GET` | `/rustfs/admin/v3/pools/status?pool=` | Status of one pool | -| `GET` | `/rustfs/admin/v3/decommission/status[?pool=]` | Decommission progress (all pools or one) | -| `POST` | `/rustfs/admin/v3/pools/decommission?pool=` | Start draining a pool (comma-separated multi-pool targets are queued) | -| `POST` | `/rustfs/admin/v3/pools/cancel?pool=` | Cancel a running decommission | -| `POST` | `/rustfs/admin/v3/pools/clear?pool=` | Clear failed/canceled decommission metadata | +### rc -Example with `curl` (SigV4 signing via `--aws-sigv4`): +Configure the cluster alias if needed, then list the pools: ```bash -# Start decommissioning pool 0 (by index) -curl -X POST \ - --aws-sigv4 "aws:amz:us-east-1:s3" \ - --user ":" \ - "http://:9000/rustfs/admin/v3/pools/decommission?pool=0&by-id=true" - -# Or address the pool by its volumes expression -curl -X POST \ - --aws-sigv4 "aws:amz:us-east-1:s3" \ - --user ":" \ - "http://:9000/rustfs/admin/v3/pools/decommission?pool=http://server{1...4}/disk{1...4}" +rc alias set rustfs http://:9000 +rc admin pool list rustfs ``` -The request can be sent to any node; if the target pool's leader is a different node, RustFS forwards the operation over the authenticated internode RPC channel. +Start decommissioning pool `0` by its zero-based ID: -The upstream Helm chart documents the same workflow through an admin CLI as `rc admin pool ls` / `rc admin decommission` / `rc admin rebalance start `. +```bash +rc admin decommission start rustfs 0 --by-id +``` -:::note -The `rc` admin CLI is referenced by the upstream Helm README but may not be generally available in your distribution yet. The HTTP admin API above and the RustFS console are the verified interfaces; treat `rc admin ...` commands as equivalent shorthand where the tool is available. -::: +You can instead pass the exact pool volume expression without `--by-id`: -## Monitoring Progress +```bash +rc admin decommission start rustfs 'http://rustfs-node1:9000/data/rustfs{1...4}/mnmd' +``` -Poll the decommission status endpoint: +Monitor all pools or only the target pool: ```bash -curl -s \ - --aws-sigv4 "aws:amz:us-east-1:s3" \ - --user ":" \ - "http://:9000/rustfs/admin/v3/decommission/status" | jq +rc admin decommission status rustfs +rc admin decommission status rustfs 0 --by-id ``` -Each pool entry reports a `decommissionInfo` object with (field names as serialized): +To cancel a running operation, use: -- `startTime`, `startSize`, `totalSize`, `currentSize` -- `complete`, `failed`, `canceled` -- `objectsDecommissioned`, `objectsDecommissionedFailed` -- `bytesDecommissioned`, `bytesDecommissionedFailed` +```bash +rc admin decommission cancel rustfs 0 --by-id +``` -The operation is finished when `complete` is `true` and the failed counters are zero. Only one pool actively moves data at a time; additional targets in a multi-pool request wait in a queue. +If a decommission is failed or canceled, clear its metadata before retrying: -After completion, remove the drained pool from `RUSTFS_VOLUMES` (or the Helm `pools` list — entries there are append-only, so decommission **before** removing an entry) and restart the cluster with the new topology. +```bash +rc admin decommission clear rustfs 0 --by-id +``` -## Cancel, Clear, and Rollback Semantics +After the target reports `complete`, remove its expression from `RUSTFS_VOLUMES` on every remaining node and restart RustFS with the reduced topology. -- **Cancel** (`POST /rustfs/admin/v3/pools/cancel?pool=`) stops the running drain. The pool is marked `canceled` and stops receiving decommission traffic; a canceled (or failed) pool can be decommissioned again later. -- **Clear** (`POST /rustfs/admin/v3/pools/clear?pool=`) removes failed/canceled decommission metadata only, so status output is clean again. -- **There is no data rollback.** Objects already moved to other pools stay where they are; cancel/clear never move data back. This is by design — a partially drained pool is still fully functional, just emptier. +## Verification -## Rebalance After Expansion +### Console -After adding a pool, existing objects stay where they were written; only new writes prefer the pool with more free space. To actively spread existing data: +Confirm that the target pool reports `Completed`, with zero failed objects and zero failed bytes. After removing the pool from the startup topology and restarting RustFS, verify that: -| Method | Path | Purpose | -| --- | --- | --- | -| `POST` | `/rustfs/admin/v3/rebalance/start` | Start a cluster-wide rebalance (no query parameters; returns `{"id": ...}`) | -| `GET` | `/rustfs/admin/v3/rebalance/status` | Per-pool progress: `objects`, `versions`, `bytes`, `remainingBuckets`, current `bucket`/`object`, `elapsed`, `eta` | -| `POST` | `/rustfs/admin/v3/rebalance/stop` | Stop the rebalance | +- The retired pool no longer appears as an active pool. +- Every remaining node and disk is online. +- Remaining pools show the relocated data and have adequate free capacity. +- Existing objects can still be listed, read, and downloaded. -```bash -curl -X POST \ - --aws-sigv4 "aws:amz:us-east-1:s3" \ - --user ":" \ - "http://:9000/rustfs/admin/v3/rebalance/start" -``` +:::note[Image placeholder] -Rules enforced by the server: +Add screenshots of the completed decommission and the final topology after the retired pool is removed. -- rejected on single-pool deployments; -- rejected while a decommission is in progress (`cannot start rebalance while decommission is in progress`); -- rejected when a rebalance is already running (`rebalance is already in progress`). +::: -Rebalance runs until pools converge toward equal usage ratios; you can stop it at any time — like decommission, stopping never undoes moves already made. +### rc -## Local Test Rig +Before removing the pool from the topology, run: -The upstream repository ships `docker-compose.decommission.yml`, a single-container two-pool layout useful for rehearsing the workflow before touching production: +```bash +rc admin decommission status rustfs 0 --by-id +``` + +Confirm that the status is `complete` and the failed object and byte counters are zero. After updating `RUSTFS_VOLUMES` and restarting RustFS, run: ```bash -# Two pools inside one container: /data/pool0/disk{1...4} and /data/pool1/disk{1...4} -# S3 on host port 9100, console on 9101 -docker compose -f docker-compose.decommission.yml up -d +rc admin pool list rustfs ``` + +Verify that only the intended active pools remain. Read objects that previously resided on the retired pool and write a new test object through the S3 endpoint. diff --git a/content/operations/scaling/storage-pool-expansion.md b/content/operations/scaling/storage-pool-expansion.md index 092991cb..27699ece 100644 --- a/content/operations/scaling/storage-pool-expansion.md +++ b/content/operations/scaling/storage-pool-expansion.md @@ -1,266 +1,114 @@ --- title: "Pool Expansion" -description: "Learn how to expand a RustFS cluster by adding a storage pool and verifying the new topology." +description: "Add a storage pool to a RustFS cluster and verify the expanded topology through the Console or rc." --- -> Note: This document is based on the latest RustFS version. Please perform full data backup before scaling operations. For production environments, it's recommended to contact RustFS technical support engineers for solution review. - -## Scaling Solution Overview - -RustFS supports horizontal scaling by adding new storage pools (Server Pool). Each new storage pool must meet: - -1. Nodes within the storage pool must use **consecutive hostnames** (e.g., node5-node8) -2. Single storage pool must use **same specifications** of disks (type/capacity/quantity) -3. New storage pools must maintain **time synchronization** and **network connectivity** with existing clusters - -```mermaid -flowchart LR - APP[Applications] --> S3API(["S3 API"]) - - subgraph DIST["Distributed RustFS"] - direction TB - subgraph N1["Node 1"] - direction LR - S3a[S3] - subgraph OL1["Object Layer"] - direction TB - C1[Cache] - K1[Compression] - E1[Encryption] - B1["Erasure Code · Bitrot"] - end - SL1["Storage Layer"] - J1[("JBOD / FS disks")] - S3a -->|Object API| OL1 - OL1 -->|Storage API| SL1 - SL1 <--> J1 - end - subgraph N2["Node 2"] - direction LR - S3b[S3] - subgraph OL2["Object Layer"] - direction TB - C2[Cache] - K2[Compression] - E2[Encryption] - B2["Erasure Code · Bitrot"] - end - SL2["Storage Layer"] - J2[("JBOD / FS disks")] - S3b -->|Object API| OL2 - OL2 -->|Storage API| SL2 - SL2 <--> J2 - end - NN["Node n ..."] - N1 <-->|Internal RESTful API| N2 - N2 <-->|Internal RESTful API| NN - end - - S3API --> N1 - S3API --> N2 - S3API --> NN - - classDef server fill:#dbeafe,stroke:#3b82f6,stroke-width:2px,color:#1e293b; - classDef store fill:#dcfce7,stroke:#22c55e,stroke-width:2px,color:#1e293b; - classDef svc fill:#eef2ff,stroke:#6366f1,stroke-width:2px,color:#1e293b; - classDef muted fill:#f3f4f6,stroke:#9ca3af,stroke-width:2px,color:#1e293b; - classDef accent fill:#fae8ff,stroke:#c026d3,stroke-width:2px,color:#1e293b; - class APP,NN muted - class S3API accent - class S3a,S3b,SL1,SL2 server - class C1,K1,E1,B1,C2,K2,E2,B2 svc - class J1,J2 store +## Overview + +### Requirements + +- Install [`rc`](/operations/rc) on the administration host before using the `rc` workflow in this guide. +- Configure credentials with permission to read RustFS cluster and pool status. + +RustFS expands capacity by appending a **server pool** to the cluster topology. Each pool is one space-separated volume expression in `RUSTFS_VOLUMES`. After the expanded topology is applied, new writes can use the added capacity; existing objects remain in their current pools until you run [Rebalancing](./data-rebalancing.md). + +This guide uses the following two-pool example: + +```ini title="/etc/default/rustfs" +RUSTFS_VOLUMES="http://rustfs-node1:9000/data/rustfs{1...4}/mnmd http://rustfs-node2:9000/data/rustfs{1...4}/mnmd" ``` ---- +Before expanding: -## Pre-Scaling Preparation +- Back up critical data and run the workflow during a maintenance window. +- Use the same RustFS version, credentials, and complete `RUSTFS_VOLUMES` value on every node. +- Verify name resolution, time synchronization, and port `9000` connectivity between all old and new nodes. +- Prepare the new pool with the intended disk count and storage specification. -### 1.1 Hardware Planning Requirements +:::warning[Append the pool; do not replace the topology] -| Item | Minimum Requirements | Recommended Production Configuration | -|---------------|---------------------------|---------------------------| -| Node Count | 4 nodes/storage pool | 4 - 8 nodes/storage pool | -| Single Node Memory | 128 GB | 128 GB | -| Disk Type | SSD | NVMe SSD | -| Single Disk Capacity | ≥1 TB | ≥4 TB | -| Network Bandwidth | 10 Gbps | 25 Gbps | +Every node must start with the complete ordered pool list. Omitting the existing pool or using a different expression on one node creates an inconsistent topology. -### 1.2 System Environment Check +::: -```bash -# Check hostname continuity (new node example) -cat /etc/hosts -192.168.10.5 node5 -192.168.10.6 node6 -192.168.10.7 node7 -192.168.10.8 node8 - -# Verify time synchronization status -timedatectl status | grep synchronized - -# Check firewall rules (all nodes need to open ports 9000/9001) -firewall-cmd --list-ports | grep 9000 -firewall-cmd --list-ports | grep 9001 -``` +## Operation ---- +### Console -## Scaling Implementation Steps +The Console displays storage pools but does not add a pool to the server startup topology. Use this Console-assisted workflow: -### 2.1 New Node Basic Configuration +1. Record the existing pools and their usage from **Rebalance** or **Pool Decommission**. +2. Install the same RustFS version and service configuration on the new pool node or nodes. +3. Append the new pool expression to `RUSTFS_VOLUMES` on every existing and new node. Keep the existing expressions unchanged and in the same order. +4. Restart RustFS across all nodes so every process starts with the same expanded topology. +5. Wait for all nodes to become ready, then refresh the pool list in the Console. -```bash -# Create dedicated user (execute on all new nodes) -groupadd rustfs-user -useradd -M -r -g rustfs-user rustfs-user +:::note[Image placeholder] -# Create storage directories (example with 8 disks) -mkdir -p /data/rustfs{0..7} -chown -R rustfs-user:rustfs-user /data/rustfs* -``` +Add a screenshot of the Console pool list showing the existing and newly added pools. -### 2.2 Install RustFS Binary on all new nodes +::: -```bash -# Check rustfs version on existing node -/usr/local/bin/rustfs --version - -# Download the binary that matches the existing cluster version from -# https://github.com/rustfs/rustfs/releases (asset name: rustfs-linux-x86_64-musl-v.zip) -wget https://github.com/rustfs/rustfs/releases/download//rustfs-linux-x86_64-musl-v.zip -unzip rustfs-linux-x86_64-musl-v.zip -chmod +x rustfs -mv rustfs /usr/local/bin/ -``` +For a Helm deployment, append the new entry to `pools.list` and apply `helm upgrade`. Do not remove or reorder existing entries. + +### rc -### 2.3 Create RustFS configuration file on all new nodes (/etc/default/rustfs) +Configure an alias with credentials that can read cluster and pool status: ```bash -# Create configuration file (/etc/default/rustfs) -# Please replace and with yours values! -cat < /etc/default/rustfs -RUSTFS_ACCESS_KEY= # e.g. admin -RUSTFS_SECRET_KEY= # e.g. output of: openssl rand -base64 24 -RUSTFS_VOLUMES="http://node{1...4}:9000/data/rustfs{0...3} http://node{5...8}:9000/data/rustfs{0...7}" # add new storage pool to the existing; must match the hostname pattern used by the existing nodes byte for byte -RUSTFS_ADDRESS=":9000" -RUSTFS_CONSOLE_ADDRESS=":9001" -EOF +rc alias set rustfs http://:9000 ``` -### 2.4 Configure System Service on all new nodes +Record the current topology: ```bash -# Create systemd service file - -sudo tee /etc/systemd/system/rustfs.service <> /etc/sysctl.conf - echo "net.core.somaxconn=32768" >> /etc/sysctl.conf - sysctl -p - ``` +### rc ---- +Run: -## Troubleshooting Guide +```bash +rc admin pool list rustfs +rc admin pool status rustfs 1 --by-id +``` -| Symptom | Check Point | Fix Command | -|---------------------------|---------------------------------|-------------------------------| -| New nodes cannot join cluster | Check port 9000 connectivity | `telnet node5 9000` | -| Uneven data distribution | Check storage pool capacity configuration | Start a rebalance from the RustFS Console | -| Console shows abnormal node status | Verify time synchronization status | `chronyc sources` | +Verify that the new pool has the expected command-line expression and an active state. Write and read a test object through the normal S3 endpoint before starting a rebalance. diff --git a/content/operations/status-check.md b/content/operations/status-check.md new file mode 100644 index 00000000..3c6b56d2 --- /dev/null +++ b/content/operations/status-check.md @@ -0,0 +1,62 @@ +--- +title: "Status Check" +description: "Check RustFS cluster health and storage capacity from the Console or rc." +--- + +Use the **Status** page in the RustFS Console or the `rc` command line to review cluster availability and storage consumption. Before using the command-line workflow, [install `rc`](/operations/rc) and configure an alias for the target cluster. + +## Cluster status + +### Console + +1. Sign in to the RustFS Console. +2. Open **Status**. +3. Confirm that the cluster is online and review the server, network, and drive status. +4. Investigate any offline server, unavailable drive, or failed network connection before maintenance or capacity changes. + +### rc + +Run the cluster information command with your configured alias: + +```bash +rc admin info cluster rustfs +``` + +The overview reports the cluster state, RustFS version, server and disk counts, backend type, and erasure-coding parity. The node list shows uptime, network connectivity, drive availability, and pool membership. The disk list shows each drive's state and pool, set, and disk location. + +For machine-readable output, request JSON: + +```bash +rc admin info cluster rustfs --json +``` + +## Storage capacity + +### Console + +1. Sign in to the RustFS Console and open **Status**. +2. Review the cluster's used and total storage capacity. +3. Review the capacity and available space for each disk. +4. Compare usage over time and plan expansion before available capacity becomes insufficient for normal writes and maintenance. + +### rc + +Use the same cluster information command: + +```bash +rc admin info cluster rustfs +``` + +The **Storage** summary reports used capacity, total capacity, and the used percentage for the cluster. Each entry under **Disks** reports used, total, and available capacity for that drive. + +Use JSON output when collecting the values in a script or monitoring integration: + +```bash +rc admin info cluster rustfs --json +``` + +Administrative information commands require credentials with the corresponding RustFS Admin API permissions. + +## Next steps + +For continuous telemetry and alerting, continue with [Observability](./observability.md). To add storage, review [Pool Expansion](./scaling/storage-pool-expansion.md). \ No newline at end of file diff --git a/content/operations/upgrade/binary/index.md b/content/operations/upgrade/binary/index.md index a8fc13c0..594d2abe 100644 --- a/content/operations/upgrade/binary/index.md +++ b/content/operations/upgrade/binary/index.md @@ -47,4 +47,4 @@ curl -fsS http://:9000/health/ready ## Next steps -Review [Cluster Health Check](/operations/cluster-health-check) for additional post-upgrade validation. \ No newline at end of file +Review [Status Check](/operations/status-check) for additional post-upgrade validation. \ No newline at end of file diff --git a/content/operations/upgrade/container/index.md b/content/operations/upgrade/container/index.md index 4eac0a6c..23ac9d74 100644 --- a/content/operations/upgrade/container/index.md +++ b/content/operations/upgrade/container/index.md @@ -27,4 +27,4 @@ Recreate the container with the previous image tag and the unchanged persistent ## Next steps -Review [Cluster Health Check](/operations/cluster-health-check) for additional post-upgrade validation. \ No newline at end of file +Review [Status Check](/operations/status-check) for additional post-upgrade validation. \ No newline at end of file diff --git a/content/reference/environment-variables.md b/content/reference/environment-variables.md index a35f3da8..21c77f7a 100644 --- a/content/reference/environment-variables.md +++ b/content/reference/environment-variables.md @@ -39,7 +39,7 @@ Boolean variables accept `true`/`false`. Values shown as "unset" have no default | `RUSTFS_CORS_ALLOWED_ORIGINS` | empty | Comma-separated origins allowed to access the S3 API from a browser. Set `*` for wildcard origins without credentials. | | `RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS` | `*` | Comma-separated origins allowed to access the Console from a browser, or `*` for all origins. | -See [CORS Configuration](/management/cors) for configuration and verification steps. +See [CORS Configuration](/administration/cors) for configuration and verification steps. ## TLS & KMS diff --git a/content/reference/index.md b/content/reference/index.md index 8ab167ae..f52a23b9 100644 --- a/content/reference/index.md +++ b/content/reference/index.md @@ -1,6 +1,6 @@ --- title: "Reference" -description: "Look up RustFS configuration variables, commands, ports, metrics, limits, and terminology." +description: "Look up RustFS configuration variables, commands, ports, and metrics." --- Use this section for concise technical references while configuring, operating, or integrating RustFS. @@ -10,6 +10,4 @@ Use this section for concise technical references while configuring, operating, - [Environment Variables](./environment-variables.md) - [CLI](./cli.md) - [Ports & Health Endpoints](./ports.md) -- [Metrics](./metrics.md) -- [Usage Limits](/concepts/limit) -- [Glossary](/concepts/glossary) \ No newline at end of file +- [Metrics](./metrics.md) \ No newline at end of file diff --git a/content/reference/meta.json b/content/reference/meta.json index b407f39b..5cbc42ca 100644 --- a/content/reference/meta.json +++ b/content/reference/meta.json @@ -4,8 +4,6 @@ "environment-variables", "cli", "ports", - "metrics", - "[Usage Limits](/concepts/limit)", - "[Glossary](/concepts/glossary)" + "metrics" ] } \ No newline at end of file diff --git a/content/administration/security/audit-logs.md b/content/security-compliance/audit-logs.md similarity index 100% rename from content/administration/security/audit-logs.md rename to content/security-compliance/audit-logs.md diff --git a/content/administration/security/encryption/index.md b/content/security-compliance/encryption/index.md similarity index 76% rename from content/administration/security/encryption/index.md rename to content/security-compliance/encryption/index.md index fa390c58..0737b6cf 100644 --- a/content/administration/security/encryption/index.md +++ b/content/security-compliance/encryption/index.md @@ -7,8 +7,8 @@ RustFS provides server-side encryption options for data stored in the object sto ## Encryption options -- [SSE-S](./sse-s.md) covers server-managed server-side encryption. +- [SSE-S3](./sse-s.md) covers server-managed server-side encryption with the S3 `AES256` mode. - [SSE-C](./sse-c.md) covers server-side encryption with customer-provided keys. -- [KMS](./kms.md) covers server-side encryption backed by a Key Management Service (KMS). +- [SSE-KMS](./kms.md) covers Local, Vault KV2, and Vault Transit key-management backends. For the currently verified KMS configuration surface, see the [CLI reference](/reference/cli) and [environment variable reference](/reference/environment-variables). \ No newline at end of file diff --git a/content/security-compliance/encryption/kms.md b/content/security-compliance/encryption/kms.md new file mode 100644 index 00000000..23b3315f --- /dev/null +++ b/content/security-compliance/encryption/kms.md @@ -0,0 +1,157 @@ +--- +title: "SSE-KMS" +description: "Configure SSE-KMS with a local or HashiCorp Vault KMS backend for RustFS." +--- + +RustFS Key Management Service (KMS) generates and wraps per-object data encryption keys for [SSE-S3](./sse-s.md) and SSE-KMS. This guide configures SSE-KMS at server startup with a local key store, Vault KV v2 plus Transit, or Vault Transit. + +## Requirements + +- Install and configure [`rc`](/operations/rc) for the encryption verification workflow. +- Back up all existing data and KMS key material before changing encryption configuration. +- Apply the same KMS backend and default key ID to every RustFS node. +- Use a process manager or secret manager that injects secrets without committing them to source control. +- For Vault, enable the required secrets engines, create a least-privilege token, and make Vault reachable from every RustFS node. +- Provision a KMS master key before configuring its ID as the RustFS default key. + +:::warning[Key loss makes encrypted objects unreadable] + +RustFS does not store a recoverable copy of your KMS master key outside the configured backend. Back up local key files and their master key, or protect Vault data and recovery credentials, before writing encrypted objects. + +::: + +## Choose a backend + +| Backend | `RUSTFS_KMS_BACKEND` | Key storage and wrapping | Intended use | +| --- | --- | --- | --- | +| Local | `local` | Key files on the RustFS host | Development, testing, or a carefully backed-up single-host deployment | +| Vault KV2 | `vault` or `vault-kv2` | Metadata in Vault KV v2; wrapping through Vault Transit | Centralized production key management | +| Vault Transit | `vault-transit` | Cryptographic operations through Vault Transit | Centralized production key management without the KV2 backend mode | + +SSE-S3 and SSE-KMS both require the KMS service to be running. Configuring a bucket default alone does not make encrypted writes succeed when KMS is unavailable. + +## Configure the local backend + +Create an absolute key directory owned by the RustFS service account: + +```bash +sudo install -d -m 0700 -o rustfs -g rustfs /var/lib/rustfs/kms +``` + +Add the KMS settings to the RustFS environment file on every node: + +```ini title="/etc/default/rustfs" +RUSTFS_KMS_ENABLE=true +RUSTFS_KMS_BACKEND=local +RUSTFS_KMS_KEY_DIR=/var/lib/rustfs/kms +RUSTFS_KMS_LOCAL_MASTER_KEY= +``` + +`RUSTFS_KMS_KEY_DIR` is required by the RustFS server startup path. The local backend writes key files with owner-only permissions. RustFS rejects development defaults such as a temporary key directory or missing local master key unless you explicitly set `RUSTFS_KMS_ALLOW_INSECURE_DEV_DEFAULTS=true`; never enable that override in production. + +Restart RustFS after updating the environment. This starts the KMS backend but does not create a master key: + +```bash +sudo systemctl restart rustfs +sudo systemctl status rustfs --no-pager +``` + +## Configure Vault KV2 + +Enable a KV v2 engine for key metadata and a Transit engine for key wrapping. Then configure every RustFS node: + +```ini title="/etc/default/rustfs" +RUSTFS_KMS_ENABLE=true +RUSTFS_KMS_BACKEND=vault-kv2 +RUSTFS_KMS_VAULT_ADDRESS=https://vault.example.com:8200 +RUSTFS_KMS_VAULT_TOKEN= +RUSTFS_KMS_VAULT_MOUNT_PATH=transit +``` + +The server startup interface uses `secret` as the KV mount and `rustfs/kms/keys` as the key prefix. Use a Vault token that can read and write that KV path and perform the required Transit operations. + +RustFS validates the Vault URL and rejects insecure development defaults unless `RUSTFS_KMS_ALLOW_INSECURE_DEV_DEFAULTS=true` is set. Use HTTPS and a certificate trusted by the RustFS hosts in production. + +## Configure Vault Transit + +Enable the Transit engine and configure every RustFS node: + +```ini title="/etc/default/rustfs" +RUSTFS_KMS_ENABLE=true +RUSTFS_KMS_BACKEND=vault-transit +RUSTFS_KMS_VAULT_ADDRESS=https://vault.example.com:8200 +RUSTFS_KMS_VAULT_TOKEN= +RUSTFS_KMS_VAULT_MOUNT_PATH=transit +``` + +Vault Transit retains historical key versions, so rotating a Transit key does not by itself make objects wrapped with an older version unreadable. Keep old versions enabled for as long as objects depend on them. + +## Create and select the default key + +`RUSTFS_KMS_DEFAULT_KEY_ID` selects an existing key; it does not create one. Start the KMS backend without this variable, create the key, and only then add the variable and restart RustFS. + +When your installed `rc` exposes the KMS lifecycle commands, create and inspect a key with: + +```bash +rc admin kms status rustfs +rc admin kms key create rustfs --name rustfs-default-key +rc admin kms key status rustfs rustfs-default-key +``` + +Then add the same default key ID on every RustFS node: + +```ini title="/etc/default/rustfs" +RUSTFS_KMS_DEFAULT_KEY_ID=rustfs-default-key +``` + +Restart every node consistently after changing the default. + +For Vault Transit, you can alternatively create the named Transit key directly in Vault before configuring it as the default: + +```bash +vault write -f transit/keys/rustfs-default-key +``` + +:::note[rc version boundary] + +The tested `rc 0.1.29` on the validation host does not expose `rc admin kms`; it supports only the bucket and object encryption commands used below. Run `rc admin --help` before following the key lifecycle commands. If the KMS command family is absent, use a newer `rc` release that includes it or provision the key through the native RustFS KMS Admin API. + +::: + +## Verify KMS-backed encryption + +Create a test bucket, configure SSE-KMS with the default key, upload an object, and read it back: + +```bash +rc bucket create rustfs/my-bucket +rc bucket encryption set rustfs/my-bucket --mode sse-kms +rc bucket encryption info rustfs/my-bucket +rc object copy /path/to/hello.txt rustfs/my-bucket/hello.txt +rc object show rustfs/my-bucket/hello.txt +``` + +To select a specific KMS key, set it on the bucket default or the individual write: + +```bash +rc bucket encryption set rustfs/my-bucket \ + --mode sse-kms \ + --key-id rustfs-default-key + +rc object copy /path/to/hello.txt rustfs/my-bucket/hello.txt \ + --enc-kms rustfs/my-bucket/hello.txt=rustfs-default-key +``` + +The selector before `=` must exactly match the destination passed to `rc object copy`. + +## Rotate and recover keys + +- **Vault token:** issue a replacement token, update every RustFS node, restart consistently, verify encrypted reads and writes, and then revoke the old token. +- **Vault Transit key:** rotate the Transit key in Vault. New wrapping operations use the new version while Vault retains older versions for decryption. +- **Local or Vault KV2 key material:** back up the current material before rotation and verify that objects written before and after rotation remain readable. +- **Existing objects:** rotating a master key does not rewrite object data. Each object keeps the wrapped data key stored with its encryption metadata. + +Do not delete or disable an old master-key version until you have established that no retained object depends on it. + +## Next steps + +Configure [SSE-S3](./sse-s.md), or use [SSE-C](./sse-c.md) when the client must retain control of the encryption key. \ No newline at end of file diff --git a/content/administration/security/encryption/meta.json b/content/security-compliance/encryption/meta.json similarity index 100% rename from content/administration/security/encryption/meta.json rename to content/security-compliance/encryption/meta.json diff --git a/content/security-compliance/encryption/sse-c.md b/content/security-compliance/encryption/sse-c.md new file mode 100644 index 00000000..f397d1b6 --- /dev/null +++ b/content/security-compliance/encryption/sse-c.md @@ -0,0 +1,84 @@ +--- +title: "SSE-C" +description: "Use customer-provided AES-256 keys for RustFS object encryption and decryption." +--- + +SSE-C encrypts an object with a 256-bit key supplied by the client on every write and read request. RustFS validates and uses the key for the request but does not persist the plaintext customer key. SSE-C does not use the RustFS KMS backend. + +## Requirements + +- Install and configure [`rc`](/operations/rc). +- Install OpenSSL for the key-generation example. +- Use HTTPS so customer keys are encrypted in transit. +- Store the customer key in a secret manager and maintain a mapping between each object and its key. + +:::warning[The same key is required for every read] + +If you lose an SSE-C key, RustFS cannot recover it and the object becomes unreadable. Never log, commit, or send the key over an unencrypted connection. + +::: + +## Prepare a customer key + +Generate 32 random bytes through a hexadecimal intermediate, then derive the Base64 key and its Base64-encoded MD5 checksum: + +```bash +SSE_C_KEY_HEX=$(openssl rand -hex 32) +SSE_C_KEY_B64=$(printf '%s' "$SSE_C_KEY_HEX" | xxd -r -p | openssl base64 -A) +SSE_C_KEY_MD5=$(printf '%s' "$SSE_C_KEY_HEX" | xxd -r -p \ + | openssl dgst -md5 -binary | openssl base64 -A) +``` + +Move these values into your approved secret-management workflow. Do not print them to the terminal or shell history. + +## Upload an encrypted object + +`rc 0.1.29` has no dedicated SSE-C option. Its signed custom-header option can send the three S3 SSE-C headers: + +```bash +rc object copy /path/to/hello.txt rustfs/my-bucket/hello.txt \ + -H "x-amz-server-side-encryption-customer-algorithm:AES256" \ + -H "x-amz-server-side-encryption-customer-key:$SSE_C_KEY_B64" \ + -H "x-amz-server-side-encryption-customer-key-md5:$SSE_C_KEY_MD5" +``` + +The required request headers are: + +| Header | Value | +| --- | --- | +| `x-amz-server-side-encryption-customer-algorithm` | `AES256` | +| `x-amz-server-side-encryption-customer-key` | Base64-encoded 32-byte key | +| `x-amz-server-side-encryption-customer-key-md5` | Base64-encoded MD5 digest of the raw key | + +## Read the encrypted object + +Supply the same three headers on reads and metadata requests: + +```bash +rc object show rustfs/my-bucket/hello.txt \ + -H "x-amz-server-side-encryption-customer-algorithm:AES256" \ + -H "x-amz-server-side-encryption-customer-key:$SSE_C_KEY_B64" \ + -H "x-amz-server-side-encryption-customer-key-md5:$SSE_C_KEY_MD5" +``` + +The same workflow was validated with `rc 0.1.29` against RustFS by uploading an object, reading it with the original key, and comparing the returned bytes with the source. + +:::warning[Protect command arguments] + +The generic `-H` method places expanded header values in the `rc` process arguments, which may be visible to other processes on the host. For production automation, prefer an S3 SDK that accepts SSE-C key material through protected memory or a credential provider instead of command-line arguments. + +::: + +## Clear shell variables + +After the operation, remove key material from the shell environment: + +```bash +unset SSE_C_KEY_HEX SSE_C_KEY_B64 SSE_C_KEY_MD5 +``` + +Changing a bucket's SSE-S3 or SSE-KMS default does not alter SSE-C objects. SSE-C is selected by the customer-key headers on each individual request and takes precedence for that request. + +## Next steps + +Compare [SSE-S3](./sse-s.md) for server-managed encryption and [KMS](./kms.md) for explicit KMS-backed key management. \ No newline at end of file diff --git a/content/security-compliance/encryption/sse-s.md b/content/security-compliance/encryption/sse-s.md new file mode 100644 index 00000000..7a4a9929 --- /dev/null +++ b/content/security-compliance/encryption/sse-s.md @@ -0,0 +1,77 @@ +--- +title: "SSE-S3" +description: "Configure server-managed SSE-S3 encryption for RustFS buckets and object writes." +--- + +SSE-S3 uses the S3 `AES256` encryption mode. RustFS generates a unique data encryption key for each encrypted object and uses the configured KMS service to wrap that key. Clients do not provide or retain the encryption key. + +## Requirements + +- Configure and start [KMS](./kms.md), create its default key, and verify the key is available before enabling SSE-S3. +- Install and configure [`rc`](/operations/rc). +- Create the target bucket and verify that normal object writes succeed. + +:::warning[KMS availability is required] + +RustFS accepts an SSE-S3 bucket default even when KMS is unavailable, but encrypted object writes then fail. Verify an encrypted write and read before using the bucket for production data. + +::: + +## Set bucket default encryption + +Apply SSE-S3 to new writes that do not specify an object-level encryption mode: + +```bash +rc bucket encryption set rustfs/my-bucket --mode sse-s3 +rc bucket encryption info rustfs/my-bucket +``` + +The reported mode should be `SSE-S3`. + +Upload and read a test object: + +```bash +rc object copy /path/to/hello.txt rustfs/my-bucket/hello.txt +rc object show rustfs/my-bucket/hello.txt +``` + +## Encrypt one object explicitly + +Apply SSE-S3 to a single destination without changing the bucket default: + +```bash +rc object copy /path/to/hello.txt rustfs/my-bucket/hello.txt \ + --enc-s3 rustfs/my-bucket/hello.txt +``` + +For recursive writes, the encryption selector must exactly match the destination prefix: + +```bash +rc object copy ./reports/ rustfs/my-bucket/reports/ --recursive \ + --enc-s3 rustfs/my-bucket/reports/ +``` + +## Clear the bucket default + +Remove the default rule: + +```bash +rc bucket encryption clear rustfs/my-bucket +rc bucket encryption info rustfs/my-bucket +``` + +Clearing or changing the bucket default affects only later writes. It does not decrypt or rewrite existing objects. + +## Request contract + +S3 clients request SSE-S3 with: + +```http +x-amz-server-side-encryption: AES256 +``` + +An object-level request overrides the bucket default for that write. If no object-level mode is supplied, RustFS resolves the effective mode from the bucket encryption configuration. + +## Next steps + +Use [SSE-KMS](./kms.md#verify-kms-backed-encryption) when you need an explicit KMS key ID, or compare [SSE-C](./sse-c.md) for client-held keys. \ No newline at end of file diff --git a/content/administration/iam/access-token.md b/content/security-compliance/iam/access-token.md similarity index 100% rename from content/administration/iam/access-token.md rename to content/security-compliance/iam/access-token.md diff --git a/content/administration/iam/images/access_token_creation.png b/content/security-compliance/iam/images/access_token_creation.png similarity index 100% rename from content/administration/iam/images/access_token_creation.png rename to content/security-compliance/iam/images/access_token_creation.png diff --git a/content/administration/iam/images/access_token_deletion.png b/content/security-compliance/iam/images/access_token_deletion.png similarity index 100% rename from content/administration/iam/images/access_token_deletion.png rename to content/security-compliance/iam/images/access_token_deletion.png diff --git a/content/administration/iam/index.md b/content/security-compliance/iam/index.md similarity index 97% rename from content/administration/iam/index.md rename to content/security-compliance/iam/index.md index e375079f..53b0ccf3 100644 --- a/content/administration/iam/index.md +++ b/content/security-compliance/iam/index.md @@ -51,5 +51,4 @@ For service accounts and STS sessions carrying a session policy, the effective p - [Users, Groups, and Policies](./policies.md) — managing users and groups, the policy document format, and built-in policies. - [Service Accounts and STS](./sts.md) — derived access keys and temporary credentials via `AssumeRole`. -- [External Identity (OIDC)](./oidc.md) — Console SSO with Keycloak, Authing, or any standard OpenID Connect provider. - [Access Keys](./access-token.md) — creating and deleting access keys from the Console. diff --git a/content/security-compliance/iam/meta.json b/content/security-compliance/iam/meta.json new file mode 100644 index 00000000..451db04e --- /dev/null +++ b/content/security-compliance/iam/meta.json @@ -0,0 +1,8 @@ +{ + "title": "IAM", + "pages": [ + "[Users, Groups, and Policies](/security-compliance/iam/policies)", + "[Service Accounts and STS](/security-compliance/iam/sts)", + "[Access Keys](/security-compliance/iam/access-token)" + ] +} diff --git a/content/administration/iam/policies.md b/content/security-compliance/iam/policies.md similarity index 94% rename from content/administration/iam/policies.md rename to content/security-compliance/iam/policies.md index b6c024a6..a0827f64 100644 --- a/content/administration/iam/policies.md +++ b/content/security-compliance/iam/policies.md @@ -5,9 +5,14 @@ description: "Managing RustFS IAM users and groups, the policy document JSON for This page covers day-to-day IAM administration: creating users and groups, attaching policies, and writing custom policy documents. +## Requirements + +- Install [`rc`](/operations/rc) before using RustFS administrative commands. +- Configure an alias with credentials that permit the corresponding `admin:*` actions. + ## Managing Users -Users can be managed from the Console (**Identity** section in the left navigation) or through the admin REST API. All admin endpoints live under the `/rustfs/admin/v3` prefix; a MinIO-compatible prefix (`/minio/admin`) is also served for `mc admin` / madmin-style clients. Requests must be signed (AWS Signature V4) by a credential whose policies allow the corresponding `admin:*` action. +Users can be managed from the Console (**Identity** section in the left navigation), with `rc admin`, or through the admin REST API. The native admin endpoints live under the `/rustfs/admin/v3` prefix. Requests must be signed (AWS Signature V4) by a credential whose policies allow the corresponding `admin:*` action. | Operation | Method and path | Notes | | --- | --- | --- | diff --git a/content/administration/iam/sts.md b/content/security-compliance/iam/sts.md similarity index 98% rename from content/administration/iam/sts.md rename to content/security-compliance/iam/sts.md index c88d88b3..8f9d2c09 100644 --- a/content/administration/iam/sts.md +++ b/content/security-compliance/iam/sts.md @@ -53,7 +53,7 @@ Omit `accessKey`/`secretKey` to have RustFS generate a random pair. Omit `policy RustFS implements an AWS-compatible STS endpoint at the server root (`POST /` with form-encoded parameters). Two actions are supported: - `AssumeRole` — for existing IAM identities, signed with SigV4. -- `AssumeRoleWithWebIdentity` — for OIDC identities, authenticated by the JWT itself (see [External Identity (OIDC)](./oidc.md)). +- `AssumeRoleWithWebIdentity` — for OIDC identities, authenticated by the JWT itself (see [External Identity (OIDC)](../oidc/index.md)). ### AssumeRole diff --git a/content/security-compliance/index.md b/content/security-compliance/index.md new file mode 100644 index 00000000..68960b85 --- /dev/null +++ b/content/security-compliance/index.md @@ -0,0 +1,15 @@ +--- +title: "Security & Compliance" +description: "Configure identity, encryption, transport security, audit logging, and credentials for RustFS." +--- + +Use this section to secure access to RustFS and configure the controls required by your environment. + +## Security areas + +- [IAM Management](./iam/index.md) covers users, groups, policies, and access keys. +- [Data Encryption](./encryption/index.md) covers server-side encryption options and key management. +- [OIDC](./oidc/index.md) covers external identity providers. +- [TLS Configuration](/integration/tls-configured) covers encrypted client and node connections. +- [Audit Logs](./audit-logs.md) covers audit target configuration. +- [Credential Management](/operations/credentials) covers root credentials and secret injection. diff --git a/content/security-compliance/meta.json b/content/security-compliance/meta.json new file mode 100644 index 00000000..19c24174 --- /dev/null +++ b/content/security-compliance/meta.json @@ -0,0 +1,11 @@ +{ + "title": "Security & Compliance", + "pages": [ + "iam", + "encryption", + "oidc", + "[TLS Configuration](/integration/tls-configured)", + "audit-logs", + "[Credential Management](/operations/credentials)" + ] +} diff --git a/content/security-compliance/oidc/index.md b/content/security-compliance/oidc/index.md new file mode 100644 index 00000000..edfdd5f0 --- /dev/null +++ b/content/security-compliance/oidc/index.md @@ -0,0 +1,181 @@ +--- +title: "OIDC" +description: "Configure OpenID Connect single sign-on for the RustFS Console by using Keycloak as an example provider." +--- + +## Overview + +RustFS integrates with **Keycloak** through the OpenID Connect (OIDC) Authorization Code Flow. The examples on this page use the default RustFS provider ID, `default`. + +The browser login flow is: + +1. RustFS sends an authorization-code request with a Proof Key for Code Exchange (PKCE) S256 challenge. +2. Keycloak authenticates the user and redirects the browser to RustFS with `code` and `state`. +3. RustFS exchanges the code at the Keycloak token endpoint. +4. RustFS verifies the ID token signature, issuer, audience, expiry, and nonce. +5. RustFS maps ID token claim values to local RustFS Identity and Access Management (IAM) policies and issues temporary credentials for the Console session. + +Keycloak authenticates the user, while RustFS policies authorize S3 and administration operations. RustFS does not use Keycloak Authorization Services for object or Console authorization. + +The examples use the following values. Replace the hostnames and client secret for your environment: + +| Setting | Example | +| --- | --- | +| Keycloak realm | `rustfs` | +| Keycloak issuer | `https://keycloak.example.com/realms/rustfs` | +| Keycloak client ID | `rustfs-console` | +| Public RustFS origin | `https://rustfs.example.com` | +| RustFS callback URL | `https://rustfs.example.com/rustfs/admin/v3/oidc/callback/default` | + +## Configuration + +### Keycloak configuration + +#### Create the realm and client + +1. Open the Keycloak Admin Console and create or select the `rustfs` realm. +2. Open **Clients** and create an OpenID Connect client with the client ID `rustfs-console`. +3. Enable **Client authentication** and **Standard flow**. +4. Disable unused flows, including **Implicit flow**, **Direct access grants**, and **Service accounts roles**. +5. Set **Valid redirect URIs** to the exact RustFS callback URL: + + ```text + https://rustfs.example.com/rustfs/admin/v3/oidc/callback/default + ``` + +6. Set **Web origins** to the public RustFS origin: + + ```text + https://rustfs.example.com + ``` + +7. Set **Proof Key for Code Exchange Code Challenge Method** to `S256`. +8. Save the client and copy its secret from **Credentials**. + +RustFS submits the client secret in the token request body. Do not apply a Keycloak client policy that disables `client_secret_post`. + +#### Map Keycloak groups to RustFS policies + +RustFS maps values in the ID token `groups` claim to RustFS policy names. Create Keycloak groups that match the policies required by your users, such as `consoleAdmin`, `readwrite`, or `readonly`, then assign users to those groups. + +Add a **Group Membership** mapper to a dedicated client scope and attach it to `rustfs-console`: + +| Mapper field | Value | +| --- | --- | +| Name | `rustfs-groups` | +| Token Claim Name | `groups` | +| Full group path | Off | +| Add to ID token | On | +| Add to access token | On | +| Add to userinfo | On | +| Multivalued | On | + +Keep **Full group path** disabled. A value such as `/consoleAdmin` does not match the RustFS policy named `consoleAdmin`. + +:::note[Using Keycloak roles] + +If you use realm or client roles instead of groups, add a role mapper that emits a flat top-level `roles` array. RustFS does not parse Keycloak's nested `realm_access.roles` claim. + +::: + +### RustFS configuration + +You can configure the Keycloak provider from the RustFS Console or through environment variables. + +#### Configure with the Console + +:::note[Console configuration placeholder] + +The Console workflow and screenshots for configuring an OIDC provider will be added here after the current interface is finalized. Until then, use the environment-variable configuration below. + +::: + +#### Configure with environment variables + +Add the Keycloak provider and public browser origin to the RustFS service environment: + +```ini title="/etc/default/rustfs" +RUSTFS_BROWSER_REDIRECT_URL="https://rustfs.example.com" + +RUSTFS_IDENTITY_OPENID_ENABLE=on +RUSTFS_IDENTITY_OPENID_CONFIG_URL="https://keycloak.example.com/realms/rustfs" +RUSTFS_IDENTITY_OPENID_CLIENT_ID="rustfs-console" +RUSTFS_IDENTITY_OPENID_CLIENT_SECRET="" +RUSTFS_IDENTITY_OPENID_SCOPES="openid,profile,email" +RUSTFS_IDENTITY_OPENID_REDIRECT_URI="https://rustfs.example.com/rustfs/admin/v3/oidc/callback/default" +RUSTFS_IDENTITY_OPENID_REDIRECT_URI_DYNAMIC=off +RUSTFS_IDENTITY_OPENID_DISPLAY_NAME="Keycloak" +RUSTFS_IDENTITY_OPENID_GROUPS_CLAIM="groups" +RUSTFS_IDENTITY_OPENID_ROLES_CLAIM="roles" +RUSTFS_IDENTITY_OPENID_EMAIL_CLAIM="email" +RUSTFS_IDENTITY_OPENID_USERNAME_CLAIM="preferred_username" +``` + +Restart RustFS after applying the configuration. + +`RUSTFS_BROWSER_REDIRECT_URL` must contain the public scheme and authority without a path. It controls the Console success redirect and logout fallback URL. The provider callback URL must exactly match the URL registered in Keycloak. + +:::warning[Map claims in production] + +Do not use `RUSTFS_IDENTITY_OPENID_ROLE_POLICY=consoleAdmin` as a permanent shortcut. Map Keycloak groups or roles to RustFS policies so each user receives only the required permissions. + +::: + +For a reverse proxy or load balancer, preserve the callback query string and route the authorize and callback requests to the same RustFS node. The in-flight OIDC `state` is local to that node. + +## Verification + +### Verify Keycloak discovery + +Query the realm discovery document: + +```bash +curl -fsS "https://keycloak.example.com/realms/rustfs/.well-known/openid-configuration" | jq '{ + issuer, + authorization_endpoint, + token_endpoint, + jwks_uri, + code_challenge_methods_supported, + token_endpoint_auth_methods_supported +}' +``` + +Confirm that: + +- `issuer` is `https://keycloak.example.com/realms/rustfs`. +- `authorization_endpoint`, `token_endpoint`, and `jwks_uri` are present. +- `code_challenge_methods_supported` includes `S256`. +- The token endpoint supports client-secret authentication in the request body. + +### Verify the RustFS provider + +After restarting RustFS, check that the provider is available: + +```bash +curl -fsS "https://rustfs.example.com/rustfs/admin/v3/oidc/providers" | jq +``` + +The response should include the `default` provider with the display name `Keycloak`. + +### Test Console login + +Open the RustFS Console and select **Keycloak**, or open the authorization endpoint directly: + +```text +https://rustfs.example.com/rustfs/admin/v3/oidc/authorize/default +``` + +Verify the complete flow: + +1. The browser redirects to Keycloak. +2. The user signs in. +3. Keycloak redirects to the RustFS callback URL with `code` and `state`. +4. RustFS validates the ID token and creates the Console session. +5. The Console opens with the permissions mapped from the user's `groups` or `roles` claim. + +If authentication succeeds but access is denied, confirm that the ID token contains a flat `groups` or `roles` claim and that each value exactly matches an existing RustFS policy name. + +## Next steps + +- Review [users, groups, and policies](../iam/policies.md) before assigning Keycloak groups. +- Review the [Console security notes](/administration/console) before exposing the login endpoint publicly. diff --git a/content/security-compliance/oidc/meta.json b/content/security-compliance/oidc/meta.json new file mode 100644 index 00000000..6d7b10f9 --- /dev/null +++ b/content/security-compliance/oidc/meta.json @@ -0,0 +1,4 @@ +{ + "title": "OIDC", + "pages": [] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d08d3bad..a4bab488 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "mermaid": "^11.16.0", "react": "^19.2.7", "react-dom": "^19.2.7", + "react-icons": "^5.7.0", "react-server-dom-webpack": "^19.2.7", "rehype-katex": "^7.0.1", "remark-math": "^6.0.0", @@ -6210,6 +6211,15 @@ "react": "^19.2.7" } }, + "node_modules/react-icons": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.7.0.tgz", + "integrity": "sha512-LBLy340Rzqy6+/yVhZKT3B/QpP1BZaesGqasf09HPOBzRarcDIFH0WwXlXQfE7q7ipxK4MSiC5DIBWURCny6fw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-remove-scroll": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", diff --git a/package.json b/package.json index 8cfef2cd..7d3ac823 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "mermaid": "^11.16.0", "react": "^19.2.7", "react-dom": "^19.2.7", + "react-icons": "^5.7.0", "react-server-dom-webpack": "^19.2.7", "rehype-katex": "^7.0.1", "remark-math": "^6.0.0", diff --git a/press.config.tsx b/press.config.tsx index aa93b381..cab5283e 100644 --- a/press.config.tsx +++ b/press.config.tsx @@ -7,22 +7,10 @@ import { takumiPlugin } from "fumapress/plugins/takumi"; import { createNotebookLayoutPage } from "fumapress/layouts/notebook"; import defaultMdxComponents, { createRelativeLink } from "fumadocs-ui/mdx"; import { docs } from "./.source/server"; +import { BrandLogo } from "./src/components/brand-logo"; import { Mermaid } from "./src/components/mermaid"; import { Tab, Tabs } from "./src/components/tabs"; -// Lucide icons registered for use in MDX (e.g. } />). -import { - Rocket, - Container, - BookOpen, - Network, - Gauge, - Server, - ClipboardCheck, - Activity, - Code, - Terminal, - Library, -} from "lucide-react"; +import { ArrowRight } from "lucide-react"; const isDev = import.meta.env.DEV; @@ -225,18 +213,8 @@ gtag('config', 'G-TWW7WMTWL9');`, Mermaid, Tab, Tabs, - // Lucide icons for } /> on landing pages. - Rocket, - Container, - BookOpen, - Network, - Gauge, - Server, - ClipboardCheck, - Activity, - Code, - Terminal, - Library, + BrandLogo, + ArrowRight, }; }, }), diff --git a/public/_redirects b/public/_redirects new file mode 100644 index 00000000..ddd5d572 --- /dev/null +++ b/public/_redirects @@ -0,0 +1,13 @@ +/management /administration 301 +/management/* /administration/:splat 301 +/administration/iam /security-compliance/iam 301 +/administration/iam/oidc /security-compliance/oidc 301 +/administration/iam/* /security-compliance/iam/:splat 301 +/administration/security/encryption /security-compliance/encryption 301 +/administration/security/encryption/* /security-compliance/encryption/:splat 301 +/administration/security/sts-token-service /security-compliance/iam/sts 301 +/administration/security/oidc /security-compliance/oidc 301 +/administration/security/audit-logs /security-compliance/audit-logs 301 +/security-compliance/iam/oidc /security-compliance/oidc 301 +/security-compliance/sts-token-service /security-compliance/iam/sts 301 +/operations/cluster-health-check /operations/status-check 301 diff --git a/src/app.css b/src/app.css index ef7b5f4b..f647fb65 100644 --- a/src/app.css +++ b/src/app.css @@ -49,3 +49,242 @@ border-radius: 0; background: var(--color-fd-background); } + +.rustfs-install-home { + --rustfs-install-accent: #d61f45; + margin-top: 1.5rem; +} + +article:has(.rustfs-install-home) > h1 { + max-width: 48rem; + font-size: 3.75rem; + line-height: 1.08; +} + +article:has(.rustfs-install-home) > h1 + p { + max-width: 48rem; + font-size: 1.25rem; + line-height: 1.65; +} + +article:has(.rustfs-install-home) > h1 + p + div { + display: none; +} + +.rustfs-install-intro { + max-width: 52rem; + margin-bottom: 2.75rem; +} + +.rustfs-install-lede { + margin: 0; + color: var(--color-fd-muted-foreground); + font-size: 1.125rem; + line-height: 1.75; +} + +.rustfs-install-overview { + display: inline-flex; + align-items: center; + gap: 0.5rem; + margin-top: 1.25rem; + color: var(--rustfs-install-accent); + font-weight: 650; + text-decoration: none; +} + +.rustfs-install-overview > svg { + width: 1rem; + height: 1rem; + transition: transform 160ms ease; +} + +.rustfs-install-overview:hover > svg { + transform: translateX(0.2rem); +} + +.rustfs-install-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1rem; +} + +.rustfs-install-card { + position: relative; + display: grid; + grid-template-columns: 3rem minmax(0, 1fr) 1.25rem; + gap: 1rem; + align-items: start; + min-height: 10.5rem; + padding: 1.5rem; + overflow: hidden; + border: 1px solid var(--color-fd-border); + border-radius: 0.5rem; + background: + linear-gradient(135deg, color-mix(in srgb, var(--rustfs-install-accent) 4%, transparent), transparent 42%), + var(--color-fd-card); + color: var(--color-fd-foreground); + text-decoration: none; + box-shadow: 0 1px 2px color-mix(in srgb, var(--color-fd-foreground) 5%, transparent); + transition: border-color 160ms ease, box-shadow 160ms ease, transform 160ms ease; +} + +.rustfs-install-card:hover { + border-color: color-mix(in srgb, var(--rustfs-install-accent) 58%, var(--color-fd-border)); + box-shadow: 0 10px 24px color-mix(in srgb, var(--color-fd-foreground) 9%, transparent); + transform: translateY(-2px); +} + +.rustfs-install-card:focus-visible, +.rustfs-install-overview:focus-visible { + outline: 2px solid var(--color-fd-ring); + outline-offset: 3px; +} + +.rustfs-install-icon { + --rustfs-brand-color: var(--rustfs-install-accent); + display: inline-flex; + align-items: center; + justify-content: center; + width: 3rem; + height: 3rem; + border: 1px solid color-mix(in srgb, var(--rustfs-brand-color) 28%, var(--color-fd-border)); + border-radius: 0.5rem; + background: color-mix(in srgb, var(--rustfs-brand-color) 9%, var(--color-fd-background)); + color: var(--rustfs-brand-color); +} + +.rustfs-install-icon > svg { + width: 1.5rem; + height: 1.5rem; +} + +.rustfs-install-icon--linux { + --rustfs-brand-color: #171717; +} + +.rustfs-install-icon--docker { + --rustfs-brand-color: #2496ed; +} + +.rustfs-install-icon--podman { + --rustfs-brand-color: #892ca0; +} + +.rustfs-install-icon--helm { + --rustfs-brand-color: #0f1689; +} + +.rustfs-install-icon--kubernetes { + --rustfs-brand-color: #326ce5; +} + +.rustfs-install-icon--apple { + --rustfs-brand-color: #171717; +} + +.rustfs-install-icon--windows { + --rustfs-brand-color: #0078d4; +} + +.rustfs-install-copy { + display: flex; + min-width: 0; + flex-direction: column; + gap: 0.625rem; +} + +.rustfs-install-copy > strong { + font-size: 1.0625rem; + line-height: 1.35; +} + +.rustfs-install-copy > span { + color: var(--color-fd-muted-foreground); + font-size: 0.9375rem; + line-height: 1.6; +} + +.rustfs-install-arrow { + width: 1.125rem; + height: 1.125rem; + margin-top: 0.1875rem; + color: var(--color-fd-muted-foreground); + transition: color 160ms ease, transform 160ms ease; +} + +.rustfs-install-card:hover .rustfs-install-arrow { + color: var(--rustfs-install-accent); + transform: translateX(0.2rem); +} + +@media (max-width: 48rem) { + article:has(.rustfs-install-home) > h1 { + font-size: 2.5rem; + } + + article:has(.rustfs-install-home) > h1 + p { + font-size: 1.0625rem; + } + + .rustfs-install-intro { + margin-bottom: 2rem; + } + + .rustfs-install-grid { + grid-template-columns: minmax(0, 1fr); + } + + .rustfs-install-card { + min-height: 9.5rem; + } +} + +@media (max-width: 30rem) { + .rustfs-install-lede { + font-size: 1rem; + } + + .rustfs-install-card { + grid-template-columns: 2.75rem minmax(0, 1fr); + padding: 1.25rem; + } + + .rustfs-install-icon { + width: 2.75rem; + height: 2.75rem; + } + + .rustfs-install-arrow { + display: none; + } +} + +.dark .rustfs-install-home { + --rustfs-install-accent: #ff5574; +} + +.dark .rustfs-install-icon--linux, +.dark .rustfs-install-icon--apple { + --rustfs-brand-color: #f5f5f5; +} + +.dark .rustfs-install-icon--docker { + --rustfs-brand-color: #59b9ff; +} + +.dark .rustfs-install-icon--podman { + --rustfs-brand-color: #c77dda; +} + +.dark .rustfs-install-icon--helm { + --rustfs-brand-color: #929bff; +} + +.dark .rustfs-install-icon--kubernetes { + --rustfs-brand-color: #78a4ff; +} + +.dark .rustfs-install-icon--windows { + --rustfs-brand-color: #4bb4ff; +} diff --git a/src/components/brand-logo.tsx b/src/components/brand-logo.tsx new file mode 100644 index 00000000..6b9a6d84 --- /dev/null +++ b/src/components/brand-logo.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { FaApple, FaLinux, FaWindows } from "react-icons/fa6"; +import { SiDocker, SiHelm, SiKubernetes, SiPodman } from "react-icons/si"; + +const logos = { + apple: FaApple, + docker: SiDocker, + helm: SiHelm, + kubernetes: SiKubernetes, + linux: FaLinux, + podman: SiPodman, + windows: FaWindows, +} as const; + +export function BrandLogo({ name }: { name: keyof typeof logos }) { + const Logo = logos[name]; + + return