From ba1e0cad1a3900d5a8f0f4eb653e33e140d4ecb1 Mon Sep 17 00:00:00 2001 From: abuckit Date: Thu, 4 Jun 2026 08:50:05 -0400 Subject: [PATCH 1/2] test: allow body in tracking response writer test --- cmd/api-response_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/api-response_test.go b/cmd/api-response_test.go index 4f7891947..970f62a4c 100644 --- a/cmd/api-response_test.go +++ b/cmd/api-response_test.go @@ -130,7 +130,7 @@ func TestGetURLScheme(t *testing.T) { func TestTrackingResponseWriter(t *testing.T) { rw := httptest.NewRecorder() trw := &trackingResponseWriter{ResponseWriter: rw} - trw.WriteHeader(123) + trw.WriteHeader(299) if !trw.headerWritten { t.Fatal("headerWritten was not set by WriteHeader call") } @@ -142,7 +142,7 @@ func TestTrackingResponseWriter(t *testing.T) { // Check that WriteHeader and Write were called on the underlying response writer resp := rw.Result() - if resp.StatusCode != 123 { + if resp.StatusCode != 299 { t.Fatalf("unexpected status: %v", resp.StatusCode) } body, err := io.ReadAll(resp.Body) From a4831d473b30768dade72a68946837e89271db1d Mon Sep 17 00:00:00 2001 From: abuckit Date: Sat, 13 Jun 2026 21:24:44 -0400 Subject: [PATCH 2/2] Remove testing directory --- testing/cluster/Dockerfile | 13 - testing/cluster/Dockerfile.minio | 61 --- testing/cluster/README.md | 502 ----------------------- testing/cluster/cluster.sh | 607 ---------------------------- testing/cluster/docker-compose.yml | 132 ------ testing/cluster/dockerfile-gen.sh | 48 --- testing/cluster/entrypoint.sh | 64 --- testing/cluster/minio-entrypoint.sh | 121 ------ 8 files changed, 1548 deletions(-) delete mode 100644 testing/cluster/Dockerfile delete mode 100644 testing/cluster/Dockerfile.minio delete mode 100644 testing/cluster/README.md delete mode 100755 testing/cluster/cluster.sh delete mode 100644 testing/cluster/docker-compose.yml delete mode 100644 testing/cluster/dockerfile-gen.sh delete mode 100755 testing/cluster/entrypoint.sh delete mode 100644 testing/cluster/minio-entrypoint.sh diff --git a/testing/cluster/Dockerfile b/testing/cluster/Dockerfile deleted file mode 100644 index e59e52cab..000000000 --- a/testing/cluster/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM ubuntu:24.04 - -RUN sed -i 's|http://ports.ubuntu.com/ubuntu-ports/|http://mirrors.mit.edu/ubuntu-ports/|g' /etc/apt/sources.list.d/*.sources 2>/dev/null; sed -i 's|http://ports.ubuntu.com|http://mirrors.mit.edu/ubuntu-ports|g' /etc/apt/sources.list 2>/dev/null; apt-get update && apt-get install -y --no-install-recommends systemd systemd-sysv xfsprogs openssh-server curl ca-certificates iputils-ping && apt-get clean && rm -rf /var/lib/apt/lists/* - -RUN mkdir -p /var/run/sshd /etc/systemd/system/multi-user.target.wants && if [ -f /lib/systemd/system/ssh.service ]; then ln -sf /lib/systemd/system/ssh.service /etc/systemd/system/multi-user.target.wants/ssh.service; fi && if [ -f /usr/lib/systemd/system/sshd.service ]; then ln -sf /usr/lib/systemd/system/sshd.service /etc/systemd/system/multi-user.target.wants/sshd.service; fi && if [ -f /etc/ssh/sshd_config ]; then sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config; fi && if [ -f /etc/ssh/sshd_config ]; then sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config; fi && if [ -f /etc/ssh/sshd_config ]; then sed -i 's/^UsePAM.*/UsePAM no/' /etc/ssh/sshd_config; fi && if [ -f /etc/ssh/sshd_config ] && ! grep -q '^UsePAM ' /etc/ssh/sshd_config; then echo 'UsePAM no' >> /etc/ssh/sshd_config; fi - -RUN mkdir -p /var/lib/buckit-drives /data - -COPY entrypoint.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh - -STOPSIGNAL SIGRTMIN+3 -ENTRYPOINT ["/entrypoint.sh"] diff --git a/testing/cluster/Dockerfile.minio b/testing/cluster/Dockerfile.minio deleted file mode 100644 index 630f2acbb..000000000 --- a/testing/cluster/Dockerfile.minio +++ /dev/null @@ -1,61 +0,0 @@ -# Dockerfile for MinIO migration test container. -# -# Follows the MinIO RHEL deployment guide at: -# https://buckit-io.github.io/docs/operations/deployments/baremetal-deploy-minio-on-redhat-linux.html -# -# The MinIO RPM is downloaded at build time from the official download server. -# The correct architecture variant is selected automatically via Docker -# BuildKit's TARGETARCH build argument (amd64 on GitHub Actions standard -# runners; arm64 on Apple Silicon / self-hosted ARM runners). No manual -# flag is required — `docker build` and `docker compose build` set it. -# -# Usage (via cluster.sh): -# ./cluster.sh minio start -# ./cluster.sh minio stop - -FROM rockylinux:9 - -# Install system dependencies: systemd (init), xfsprogs (mkfs.xfs), wget, openssh-server -RUN dnf install -y systemd xfsprogs wget openssh-server && dnf clean all - -# Download and install the MinIO RPM for the target architecture. -# TARGETARCH is injected by BuildKit (e.g. "amd64", "arm64"); it matches -# MinIO's URL path segment exactly, so no extra mapping is needed. -ARG TARGETARCH -RUN wget "https://dl.min.io/server/minio/release/linux-${TARGETARCH}/minio.rpm" \ - -O /tmp/minio.rpm \ - && dnf install -y /tmp/minio.rpm \ - && rm -f /tmp/minio.rpm - -# Create the minio service user and group (per MinIO deployment guide) -RUN groupadd -r minio-user \ - && useradd -M -r -g minio-user minio-user - -# Directories: persistent backing images live in /var/lib/minio-drives -# (mounted as a named Docker volume); XFS filesystems are mounted under -# /mnt/data/drive{N} at container start time. -RUN mkdir -p /var/lib/minio-drives /mnt/data - -# Configure sshd: allow root login with a password, disable PAM (not needed -# in a container), create the run directory required by sshd. -RUN mkdir -p /var/run/sshd \ - && sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config \ - && sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config \ - && sed -i 's/^UsePAM.*/UsePAM no/' /etc/ssh/sshd_config \ - && grep -q '^UsePAM ' /etc/ssh/sshd_config || echo 'UsePAM no' >> /etc/ssh/sshd_config - -# Enable minio and sshd systemd services via wants symlinks. -# (systemctl enable does not work during docker build — no running systemd.) -RUN mkdir -p /etc/systemd/system/multi-user.target.wants \ - && ln -sf /usr/lib/systemd/system/minio.service \ - /etc/systemd/system/multi-user.target.wants/minio.service \ - && ln -sf /usr/lib/systemd/system/sshd.service \ - /etc/systemd/system/multi-user.target.wants/sshd.service - -COPY minio-entrypoint.sh /minio-entrypoint.sh -RUN chmod +x /minio-entrypoint.sh - -EXPOSE 9000 9001 22 - -STOPSIGNAL SIGRTMIN+3 -ENTRYPOINT ["/minio-entrypoint.sh"] diff --git a/testing/cluster/README.md b/testing/cluster/README.md deleted file mode 100644 index 1858f574d..000000000 --- a/testing/cluster/README.md +++ /dev/null @@ -1,502 +0,0 @@ -# Buckit / MinIO Test Cluster Manager - -`cluster.sh` is a single-script tool that builds and manages Docker-based test -clusters for both **Buckit** (the target server under development) and **MinIO** -(used as a migration source). Every node runs inside a privileged Docker -container with: - -- **systemd** as PID 1 — the same init model as a bare-metal deployment -- **XFS loopback drives** — real formatted volumes, not bind mounts -- **SSH** — full shell access to any node for debugging or data loading - -Both cluster types are managed via `docker compose` and can co-exist on the -same host simultaneously, enabling end-to-end migration testing in a single -terminal. - ---- - -## Prerequisites - -| Requirement | Notes | -|---|---| -| Docker Desktop ≥ 4.x (or Docker Engine) | Compose v2 must be available (`docker compose`) | -| `bash` ≥ 4 | Ships with macOS via Homebrew; native on Linux | -| ~1 GB free disk per node-drive | Default: 4 nodes × 4 drives × 1 GB = 16 GB per cluster | -| ARM64 host (Apple Silicon / linux/arm64) | The MinIO image is built from the ARM64 RPM; the Buckit image adapts to any architecture | - -Docker Desktop on Mac requires **privileged containers** to be allowed (enabled -by default; no extra setting needed). - ---- - -## Quick Start - -```bash -# ── Buckit cluster (4 nodes, 4 drives each) ────────────────────────────────── -./cluster.sh create - -# ── MinIO single-node (migration source) ───────────────────────────────────── -./cluster.sh minio start - -# ── MinIO 4-node distributed cluster ───────────────────────────────────────── -./cluster.sh minio start --nodes 4 - -# ── Tear everything down ───────────────────────────────────────────────────── -./cluster.sh destroy -./cluster.sh minio stop -``` - ---- - -## File Layout - -``` -testing/cluster/ -├── cluster.sh # Main CLI — all commands live here -├── dockerfile-gen.sh # Generates Dockerfile for the Buckit cluster -├── entrypoint.sh # Entrypoint for Buckit cluster nodes -├── Dockerfile.minio # Static Dockerfile for MinIO nodes (Rocky Linux 9) -├── minio-entrypoint.sh # Entrypoint for MinIO nodes -└── README.md # This file - -# Generated at runtime (not committed): -├── Dockerfile # Generated by dockerfile-gen.sh for Buckit -├── docker-compose.yml # Generated compose for Buckit cluster -├── docker-compose.minio.yml # Generated compose for MinIO cluster -├── .state/ # Saved Buckit cluster config -└── .state-minio/ # Saved MinIO cluster config -``` - ---- - -## How It Works - -### Containers - -Each node runs as a **privileged Docker container** using the host's Linux -kernel. Privilege is required for two reasons: - -1. `modprobe loop` — loads the loop block-device module so loopback images can - be attached. -2. `mount` — mounts the formatted loopback devices inside the container. - -The container's init process is **systemd** (`/sbin/init`), started by the -entrypoint script after drives are set up. Systemd then brings up the target -service (Buckit or MinIO) exactly as it would on a real server. - -### Loopback XFS Drives - -Each node's drives are **sparse image files** stored in a named Docker volume -(so data survives container restarts until `destroy` / `minio stop` is called). - -At container start the entrypoint: - -1. Calls `fallocate` to create each image file the first time. -2. Formats it with `mkfs.xfs -f`. -3. Attaches it to a loop device via `losetup`. -4. Mounts the loop device at the expected path. - -On subsequent starts (e.g. after `docker compose restart`) the image files -already exist, so only the `losetup` + `mount` steps are repeated. - -Docker Desktop on Mac runs containers inside a Linux VM. Loop device -attachments made by one container run can leak into the VM kernel across -restarts. The entrypoint detects and detaches stale loop devices -(`losetup -d`) before creating new ones. - -### Dockerfile Generation (Buckit) - -Because the Buckit cluster supports multiple base images (Ubuntu, Debian, Rocky -Linux, Fedora, …), the `Dockerfile` is **generated at `create`/`expand` time** -by `dockerfile-gen.sh`. The generator detects the package manager from the -image name and emits the correct `apt-get` or `dnf` install command. - -The MinIO image is always Rocky Linux 9, so it uses a **static -`Dockerfile.minio`** instead. - -### Docker Compose Generation - -`cluster.sh` generates `docker-compose.yml` / `docker-compose.minio.yml` at -runtime. This allows the same script to handle different node counts, drive -counts, port bases, and cluster names without a templating engine. - -### State Files - -After `create` / `minio start`, the chosen configuration is written to -`.state/config` or `.state-minio/config`. Subsequent commands (`expand`, -`destroy`, `minio stop`) source that file so you don't have to repeat flags. - ---- - -## Buckit Cluster - -### `create` - -Builds the cluster image and starts N nodes. - -```bash -./cluster.sh create [options] -``` - -**What happens:** - -1. `dockerfile-gen.sh` generates a `Dockerfile` tailored to the chosen base - image. -2. `docker-compose.yml` is generated with one service per node. -3. `docker compose up -d --build` builds the image and starts all containers. -4. Each container's entrypoint creates loopback XFS drives, sets the root SSH - password, then hands off to systemd. - -**Example:** - -```bash -# Defaults: 4 nodes, 4 drives × 1 G, ubuntu:24.04 -./cluster.sh create - -# 8 nodes, Rocky Linux 9, 2 G drives, 512 M RAM each -./cluster.sh create --nodes 8 --image rockylinux:9 --drive-size 2G --memory 512M - -# Named cluster (useful when running multiple clusters in parallel) -./cluster.sh create --name my-test --nodes 4 -``` - -### `expand` - -Adds more nodes to a running cluster without restarting the existing ones. - -```bash -./cluster.sh expand --nodes NUM -``` - -`--nodes` here means *how many to add*, not the new total. - -```bash -# Add 2 more nodes to the current cluster (total becomes 6 if 4 were created) -./cluster.sh expand --nodes 2 -``` - -**What happens:** - -1. Loads the saved state to recover the original node count and settings. -2. Regenerates `docker-compose.yml` for the new total. -3. Runs `docker compose up -d --build` — Docker Compose only starts the new - services. - -### `destroy` - -Stops all containers and removes volumes (drive data is lost). - -```bash -./cluster.sh destroy - -# Destroy a named cluster -./cluster.sh destroy --name my-test -``` - -Removes `Dockerfile`, `docker-compose.yml`, and `.state/`. - -### Options Reference - -| Flag | Default | Description | -|---|---|---| -| `-n, --nodes NUM` | `4` | Number of cluster nodes | -| `-d, --drives NUM` | `4` | Drives per node | -| `-s, --drive-size SIZE` | `1G` | Size per drive (e.g. `500M`, `2G`) | -| `-m, --memory SIZE` | `256M` | RAM limit per container | -| `-i, --image IMAGE` | `ubuntu:24.04` | Base Docker image | -| `--ssh-base-port PORT` | `2201` | First host port mapped to node SSH | -| `--ssh-password PASS` | `buckitadmin` | Root password for SSH | -| `-N, --name NAME` | `buckit-test` | Compose project / cluster name | - -### Port Allocation - -Ports are allocated sequentially from the base. With defaults and 4 nodes: - -| Node | API (S3) | Console | SSH | -|---|---|---|---| -| node1 | `9000` | `9001` | `2201` | -| node2 | `9002` | `9003` | `2202` | -| node3 | `9004` | `9005` | `2203` | -| node4 | `9006` | `9007` | `2204` | - -Formula: -``` -API port = --base-port (9000) + (i−1) × 2 -Console port = API port + 1 -SSH port = --ssh-base-port (2201) + (i−1) -``` - -### SSH Access - -```bash -# Connect to node 1 (password: buckitadmin) -ssh root@localhost -p 2201 - -# Connect to node 3 -ssh root@localhost -p 2203 -``` - -### S3 Credentials - -The default S3 access key / secret set on all Buckit nodes: - -| Key | Value | -|---|---| -| Access key | `buckitadmin` | -| Secret key | `buckitadmin` | - -### Supported Base Images - -The image must include `systemd`. Alpine is not supported. - -| Family | Tested images | -|---|---| -| Debian / Ubuntu | `ubuntu:24.04`, `ubuntu:22.04`, `debian:12`, `debian:11` | -| RHEL-family | `rockylinux:9`, `almalinux:9`, `fedora:40`, `amazonlinux:2023`, `quay.io/centos/centos:stream9` | - ---- - -## MinIO Cluster - -The MinIO cluster uses a **static `Dockerfile.minio`** based on Rocky Linux 9. -It installs MinIO from the official ARM64 RPM following the -[MinIO RHEL deployment guide](https://buckit-io.github.io/docs/operations/deployments/baremetal-deploy-minio-on-redhat-linux.html). - -### `minio start` — Single Node - -```bash -./cluster.sh minio start [options] -``` - -Starts one standalone MinIO container. This is the simplest setup for -migration testing when you don't need erasure coding on the source. - -```bash -# Defaults: 1 node, 4 drives × 1 G -./cluster.sh minio start - -# Custom credentials and drive size -./cluster.sh minio start \ - --root-user admin --root-password secret \ - --drives 2 --drive-size 2G -``` - -**Output:** - -``` -MinIO container 'minio-migration' is starting. - - API: http://localhost:19000 - Console: http://localhost:19001 - SSH: ssh root@localhost -p 2299 (password: minioadmin) - User: minioadmin / minioadmin -``` - -### `minio start --nodes N` — Distributed Cluster - -```bash -./cluster.sh minio start --nodes N [options] -``` - -Starts an N-node distributed MinIO cluster. Each node: - -- Gets its own named Docker volume for drive backing images. -- Uses hostname `minio1`…`minioN` on the shared Docker bridge network. -- Has `MINIO_VOLUMES` set to - `http://minio{1...N}:9000/mnt/data/drive{0...D-1}` — MinIO's brace- - expansion URL that points every node at the full topology. - -MinIO recommends **at least 4 nodes** for distributed erasure coding. - -```bash -# 4-node cluster, 4 drives each (MinIO's recommended minimum) -./cluster.sh minio start --nodes 4 - -# 4-node cluster with larger drives and a custom name -./cluster.sh minio start --nodes 4 --drives 4 --drive-size 2G \ - --name staging-minio --root-user myadmin --root-password mypw -``` - -**Output (4 nodes):** - -``` -MinIO cluster 'minio-migration' is starting (4 nodes). - - User: minioadmin Pass: minioadmin SSH pass: minioadmin - - Node API Console SSH - ---- --- ------- --- - node1 http://localhost:19000 http://localhost:19001 ssh root@localhost -p 2299 - node2 http://localhost:19002 http://localhost:19003 ssh root@localhost -p 2300 - node3 http://localhost:19004 http://localhost:19005 ssh root@localhost -p 2301 - node4 http://localhost:19006 http://localhost:19007 ssh root@localhost -p 2302 -``` - -### `minio stop` - -Stops and removes all MinIO containers and their drive volumes. - -```bash -./cluster.sh minio stop - -# Stop a named cluster -./cluster.sh minio stop --name staging-minio -``` - -### Options Reference - -| Flag | Default | Description | -|---|---|---| -| `-n, --nodes NUM` | `1` | Number of MinIO nodes | -| `--drives NUM` | `4` | Drives per node | -| `--drive-size SIZE` | `1G` | Size per drive | -| `--api-port PORT` | `19000` | First host API port | -| `--console-port PORT` | `19001` | First host console port (single-node only) | -| `--ssh-port PORT` | `2299` | First host SSH port | -| `--root-user USER` | `minioadmin` | `MINIO_ROOT_USER` | -| `--root-password PASS` | `minioadmin` | `MINIO_ROOT_PASSWORD` | -| `--ssh-password PASS` | `minioadmin` | Root SSH password | -| `--name NAME` | `minio-migration` | Compose project / cluster name | - -### Port Allocation - -Ports are allocated sequentially from the base. With defaults and 4 nodes: - -| Node | API (S3) | Console | SSH | -|---|---|---|---| -| minio1 | `19000` | `19001` | `2299` | -| minio2 | `19002` | `19003` | `2300` | -| minio3 | `19004` | `19005` | `2301` | -| minio4 | `19006` | `19007` | `2302` | - -Formula: -``` -API port = --api-port (19000) + (i−1) × 2 -Console port = API port + 1 -SSH port = --ssh-port (2299) + (i−1) -``` - -The defaults are chosen to avoid overlap with the Buckit cluster's range -(9000–9xxx / 2201–22xx), so both clusters can run at the same time. - -### SSH Access - -```bash -# Connect to minio1 (password: minioadmin) -ssh root@localhost -p 2299 - -# Connect to minio3 in a 4-node cluster -ssh root@localhost -p 2301 -``` - ---- - -## Migration Testing Workflow - -The typical workflow is to start a MinIO cluster (source), populate it with -test data, start a Buckit cluster (target), connect the two networks so they -can reach each other, then run `mc mirror` to migrate objects and verify -integrity. - -### 1. Start the MinIO source - -```bash -# Single-node MinIO (simple cases) -./cluster.sh minio start - -# Or a 4-node distributed MinIO (closer to production) -./cluster.sh minio start --nodes 4 -``` - -### 2. Load test data into MinIO - -```bash -# Configure the MinIO Client (mc) -mc alias set source http://localhost:19000 minioadmin minioadmin - -# Create a bucket and upload data -mc mb source/my-bucket -mc cp --recursive /path/to/data source/my-bucket/ -``` - -### 3. Start the Buckit target cluster - -```bash -./cluster.sh create -``` - -```bash -# Configure mc to talk to Buckit node 1 -mc alias set target http://localhost:9000 buckitadmin buckitadmin -``` - -### 4. Connect the Docker networks - -By default the two clusters sit on separate bridge networks. To let `mc -mirror` run container-to-container (bypassing host port forwarding), connect -the MinIO containers to the Buckit network: - -```bash -# Single-node MinIO -docker network connect buckit-test-net minio-migration - -# 4-node MinIO cluster -docker network connect buckit-test-net minio-migration-node1 -docker network connect buckit-test-net minio-migration-node2 -docker network connect buckit-test-net minio-migration-node3 -docker network connect buckit-test-net minio-migration-node4 -``` - -Now containers on either network can resolve `minio` / `minio1`…`minioN` and -`node1`…`nodeN` by hostname. - -### 5. Mirror data to Buckit - -```bash -mc mirror source/my-bucket target/my-bucket -``` - -### 6. Verify - -```bash -mc diff source/my-bucket target/my-bucket -``` - -### 7. Tear down - -```bash -./cluster.sh minio stop -./cluster.sh destroy -``` - ---- - -## Defaults Reference - -### Buckit cluster - -| Setting | Default | -|---|---| -| Cluster name | `buckit-test` | -| Nodes | `4` | -| Drives per node | `4` | -| Drive size | `1G` | -| Memory per node | `256M` | -| Base image | `ubuntu:24.04` | -| S3 API base port | `9000` | -| SSH base port | `2201` | -| Root / S3 credentials | `buckitadmin` / `buckitadmin` | - -### MinIO cluster - -| Setting | Default | -|---|---| -| Cluster name | `minio-migration` | -| Nodes | `1` | -| Drives per node | `4` | -| Drive size | `1G` | -| Base image | `rockylinux:9` (fixed) | -| S3 API base port | `19000` | -| SSH base port | `2299` | -| Root credentials | `minioadmin` / `minioadmin` | -| SSH password | `minioadmin` | diff --git a/testing/cluster/cluster.sh b/testing/cluster/cluster.sh deleted file mode 100755 index c9b7774c2..000000000 --- a/testing/cluster/cluster.sh +++ /dev/null @@ -1,607 +0,0 @@ -#!/bin/bash -# Buckit/MinIO test cluster manager using Docker Compose. -# Creates containers with systemd, loopback XFS drives, and networking. -set -e - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "$SCRIPT_DIR/dockerfile-gen.sh" - -# Defaults -CLUSTER_NAME="buckit-test" -NODES=4 -DRIVES=4 -DRIVE_SIZE="1G" -MEMORY="256M" -IMAGE="ubuntu:24.04" -BASE_PORT=9000 -SSH_BASE_PORT=2201 -SSH_PASSWORD="buckitadmin" -STATE_DIR="$SCRIPT_DIR/.state" - -usage() { - cat < [options] - -Commands: - create Create a new Buckit test cluster - expand Add nodes to an existing cluster - destroy Tear down the cluster - minio start Start a standalone MinIO container (migration source) - minio stop Stop and remove the MinIO migration container - -Options (create / expand): - -n, --nodes NUM Number of nodes (default: $NODES) - -d, --drives NUM Drives per node (default: $DRIVES) - -s, --drive-size SIZE Size per drive, e.g. 1G, 500M (default: $DRIVE_SIZE) - -m, --memory SIZE RAM limit per container, e.g. 2G, 512M (default: $MEMORY) - -i, --image IMAGE Base Docker image (default: $IMAGE) - --ssh-base-port PORT First host port mapped to container SSH (default: $SSH_BASE_PORT) - --ssh-password PASS Root password for SSH in test containers (default: $SSH_PASSWORD) - -N, --name NAME Cluster name (default: $CLUSTER_NAME) - -h, --help Show this help - -Supported images (must have systemd): - Debian/Ubuntu: ubuntu:24.04, ubuntu:22.04, debian:12, debian:11 - RHEL-family: rockylinux:9, almalinux:9, fedora:40, amazonlinux:2023 - quay.io/centos/centos:stream9 - Not supported: Alpine (no systemd) -EOF - exit 0 -} - -parse_args() { - while [[ $# -gt 0 ]]; do - case "$1" in - -n | --nodes) - NODES="$2" - shift 2 - ;; - -d | --drives) - DRIVES="$2" - shift 2 - ;; - -s | --drive-size) - DRIVE_SIZE="$2" - shift 2 - ;; - -m | --memory) - MEMORY="$2" - shift 2 - ;; - -i | --image) - IMAGE="$2" - shift 2 - ;; - --ssh-base-port) - SSH_BASE_PORT="$2" - shift 2 - ;; - --ssh-password) - SSH_PASSWORD="$2" - shift 2 - ;; - -N | --name) - CLUSTER_NAME="$2" - shift 2 - ;; - -h | --help) usage ;; - *) shift ;; - esac - done -} - -generate_compose() { - local start_node="$1" - local num_nodes="$2" - local total_nodes="$3" - local compose_file="$4" - - # Build the server endpoint string for all nodes - local endpoints="" - for i in $(seq 1 "$total_nodes"); do - endpoints+="http://node${i}:9000/data/drive{0...$((DRIVES - 1))} " - done - endpoints="${endpoints% }" - - # Start or append compose file - if [ "$start_node" -eq 1 ]; then - cat >"$compose_file" <>"$compose_file" <>"$compose_file" <>"$compose_file" - done - - cat >>"$compose_file" <"$STATE_DIR/config" </dev/null || true - fi - rm -f "$SCRIPT_DIR/Dockerfile" "$SCRIPT_DIR/docker-compose.yml" - rm -rf "$STATE_DIR" - echo "Buckit cluster destroyed." - - teardown_minio -} - -# --------------------------------------------------------------------------- -# MinIO migration-test container -# --------------------------------------------------------------------------- - -# Defaults for the standalone MinIO container (ports chosen above 19000 to -# avoid clashing with a simultaneously-running Buckit cluster). -MINIO_NODES=1 -MINIO_DRIVES=4 -MINIO_DRIVE_SIZE="1G" -MINIO_API_PORT=19000 -MINIO_CONSOLE_PORT=19001 -MINIO_SSH_PORT=2299 -MINIO_ROOT_USER="minioadmin" -MINIO_ROOT_PASSWORD="minioadmin" -MINIO_SSH_PASSWORD="minioadmin" -MINIO_CONTAINER_NAME="minio-migration" -MINIO_STATE_DIR="$SCRIPT_DIR/.state-minio" - -minio_usage() { - cat < [options] - -Subcommands: - start Build and start MinIO (single node or distributed cluster) - stop Stop and remove all MinIO containers (destroys drive data) - -Options (start): - -n, --nodes NUM Number of MinIO nodes (default: $MINIO_NODES) - --drives NUM Drives per node (default: $MINIO_DRIVES) - --drive-size SIZE Size per drive (default: $MINIO_DRIVE_SIZE) - --api-port PORT First host API port (default: $MINIO_API_PORT) - --console-port PORT First host console port (default: $MINIO_CONSOLE_PORT) - --ssh-port PORT First host SSH port (default: $MINIO_SSH_PORT) - --root-user USER MINIO_ROOT_USER (default: $MINIO_ROOT_USER) - --root-password PASS MINIO_ROOT_PASSWORD (default: $MINIO_ROOT_PASSWORD) - --ssh-password PASS Root SSH password (default: $MINIO_SSH_PASSWORD) - --name NAME Cluster name prefix (default: $MINIO_CONTAINER_NAME) - -h, --help Show this help - -With --nodes 1 (default) a single standalone MinIO container is started. -With --nodes N>1 a distributed MinIO cluster is created: each node gets its -own loopback XFS drives and they all share MINIO_VOLUMES set to - http://minio{1...N}:9000/mnt/data/drive{0...D-1} -MinIO recommends a minimum of 4 nodes for erasure-coded distributed mode. - -Ports are allocated sequentially from the base values: - Node i API = --api-port + (i-1)*2 - Console = --api-port + (i-1)*2 + 1 - SSH = --ssh-port + (i-1) - -The container(s) run Rocky Linux 9 with MinIO installed from the official -ARM64 RPM. Drive images live in named Docker volumes (one per node) and are -removed by 'minio stop'. - -To connect the cluster to a running Buckit network for migration testing: - docker network connect ${CLUSTER_NAME}-net ${MINIO_CONTAINER_NAME}-node1 - docker network connect ${CLUSTER_NAME}-net ${MINIO_CONTAINER_NAME}-node2 - ... (single-node: docker network connect ${CLUSTER_NAME}-net ${MINIO_CONTAINER_NAME}) -EOF - exit 0 -} - -parse_minio_args() { - while [[ $# -gt 0 ]]; do - case "$1" in - -n | --nodes) - MINIO_NODES="$2" - shift 2 - ;; - --drives) - MINIO_DRIVES="$2" - shift 2 - ;; - --drive-size) - MINIO_DRIVE_SIZE="$2" - shift 2 - ;; - --api-port) - MINIO_API_PORT="$2" - shift 2 - ;; - --console-port) - MINIO_CONSOLE_PORT="$2" - shift 2 - ;; - --root-user) - MINIO_ROOT_USER="$2" - shift 2 - ;; - --root-password) - MINIO_ROOT_PASSWORD="$2" - shift 2 - ;; - --ssh-port) - MINIO_SSH_PORT="$2" - shift 2 - ;; - --ssh-password) - MINIO_SSH_PASSWORD="$2" - shift 2 - ;; - --name) - MINIO_CONTAINER_NAME="$2" - shift 2 - ;; - -h | --help) minio_usage ;; - *) shift ;; - esac - done -} - -generate_minio_compose() { - local compose_file="$1" - - # Pre-compute MINIO_VOLUMES so it can be injected into every node's - # environment. Single-node uses local paths; distributed uses HTTP URLs - # with MinIO's brace-expansion syntax so all nodes agree on the topology. - local minio_volumes - if [ "$MINIO_NODES" -eq 1 ]; then - if [ "$MINIO_DRIVES" -eq 1 ]; then - minio_volumes="/mnt/data/drive0" - else - minio_volumes="/mnt/data/drive{0...$((MINIO_DRIVES - 1))}" - fi - else - if [ "$MINIO_DRIVES" -eq 1 ]; then - minio_volumes="http://minio{1...${MINIO_NODES}}:9000/mnt/data/drive0" - else - minio_volumes="http://minio{1...${MINIO_NODES}}:9000/mnt/data/drive{0...$((MINIO_DRIVES - 1))}" - fi - fi - - # Write compose header - cat >"$compose_file" <>"$compose_file" <>"$compose_file" <>"$compose_file" - done - - # Shared network — all nodes must be able to resolve each other by hostname - cat >>"$compose_file" <"$MINIO_STATE_DIR/config" </dev/null || true - echo "MinIO cluster destroyed." - fi - rm -f "$SCRIPT_DIR/docker-compose.minio.yml" - rm -rf "$MINIO_STATE_DIR" -} - -cmd_minio() { - local subcmd="${1:-}" - [ -n "$subcmd" ] && shift - - case "$subcmd" in - start) - parse_minio_args "$@" - - local label - [ "$MINIO_NODES" -eq 1 ] && label="container" || label="cluster (${MINIO_NODES} nodes)" - echo "Starting MinIO ${label} '${MINIO_CONTAINER_NAME}'..." - echo " Nodes: ${MINIO_NODES}" - echo " Drives: ${MINIO_DRIVES} x ${MINIO_DRIVE_SIZE} per node" - echo " API base: ${MINIO_API_PORT} (+2 per node)" - echo " SSH base: ${MINIO_SSH_PORT} (+1 per node)" - - generate_minio_compose "$SCRIPT_DIR/docker-compose.minio.yml" - save_minio_state - - # Build the image once, then start nodes one by one. - # Sequential startup prevents concurrent losetup calls from racing over - # the same free loop device (TOCTOU: "Device or resource busy"). - docker compose -p "$MINIO_CONTAINER_NAME" -f "$SCRIPT_DIR/docker-compose.minio.yml" build - for i in $(seq 1 "$MINIO_NODES"); do - local svc - [ "$MINIO_NODES" -eq 1 ] && svc="minio" || svc="minio${i}" - echo " Starting ${svc}..." - docker compose -p "$MINIO_CONTAINER_NAME" -f "$SCRIPT_DIR/docker-compose.minio.yml" up -d "$svc" - done - - echo "" - if [ "$MINIO_NODES" -eq 1 ]; then - echo "MinIO container '${MINIO_CONTAINER_NAME}' is starting." - echo "" - echo " API: http://localhost:${MINIO_API_PORT}" - echo " Console: http://localhost:${MINIO_CONSOLE_PORT}" - echo " SSH: ssh root@localhost -p ${MINIO_SSH_PORT} (password: ${MINIO_SSH_PASSWORD})" - echo " User: ${MINIO_ROOT_USER} / ${MINIO_ROOT_PASSWORD}" - echo "" - echo "Tip: to attach to a running Buckit cluster network:" - echo " docker network connect ${CLUSTER_NAME}-net ${MINIO_CONTAINER_NAME}" - else - echo "MinIO cluster '${MINIO_CONTAINER_NAME}' is starting (${MINIO_NODES} nodes)." - echo "" - echo " User: ${MINIO_ROOT_USER} Pass: ${MINIO_ROOT_PASSWORD} SSH pass: ${MINIO_SSH_PASSWORD}" - echo "" - printf " %-8s %-28s %-30s %s\n" "Node" "API" "Console" "SSH" - printf " %-8s %-28s %-30s %s\n" "----" "---" "-------" "---" - for i in $(seq 1 "$MINIO_NODES"); do - local api_port=$((MINIO_API_PORT + (i - 1) * 2)) - local console_port=$((api_port + 1)) - local ssh_port=$((MINIO_SSH_PORT + i - 1)) - printf " node%-4d http://localhost:%-10d http://localhost:%-12d ssh root@localhost -p %d\n" \ - "$i" "$api_port" "$console_port" "$ssh_port" - done - echo "" - echo "Tip: to attach each node to a running Buckit cluster network:" - for i in $(seq 1 "$MINIO_NODES"); do - echo " docker network connect ${CLUSTER_NAME}-net ${MINIO_CONTAINER_NAME}-node${i}" - done - fi - ;; - - stop) - parse_minio_args "$@" - teardown_minio - ;; - - *) minio_usage ;; - esac -} - -# --------------------------------------------------------------------------- -# Main -# --------------------------------------------------------------------------- -case "${1:-}" in -create) - shift - cmd_create "$@" - ;; -expand) - shift - cmd_expand "$@" - ;; -destroy) - shift - cmd_destroy "$@" - ;; -minio) - shift - cmd_minio "$@" - ;; -*) usage ;; -esac diff --git a/testing/cluster/docker-compose.yml b/testing/cluster/docker-compose.yml deleted file mode 100644 index 8725d5cec..000000000 --- a/testing/cluster/docker-compose.yml +++ /dev/null @@ -1,132 +0,0 @@ -name: buckit-test - -services: - node1: - build: - context: . - dockerfile: Dockerfile - container_name: buckit-test-node1 - hostname: node1 - privileged: true - mem_limit: 256M - dns: - - 8.8.8.8 - - 1.1.1.1 - environment: - - DRIVES=4 - - DRIVE_SIZE=1G - - MINIO_ROOT_USER=buckitadmin - - MINIO_ROOT_PASSWORD=buckitadmin - - BUCKIT_ENDPOINTS=http://node1:9000/data/drive{0...3} http://node2:9000/data/drive{0...3} http://node3:9000/data/drive{0...3} http://node4:9000/data/drive{0...3} - - SSH_ROOT_PASSWORD=buckitadmin - volumes: - - node1-drives:/var/lib/buckit-drives - tmpfs: - - /run - - /run/lock - ports: - - "9000:9000" - - "9001:9001" - - "2201:22" - networks: - - buckit-test-net - - node2: - build: - context: . - dockerfile: Dockerfile - container_name: buckit-test-node2 - hostname: node2 - privileged: true - mem_limit: 256M - dns: - - 8.8.8.8 - - 1.1.1.1 - environment: - - DRIVES=4 - - DRIVE_SIZE=1G - - MINIO_ROOT_USER=buckitadmin - - MINIO_ROOT_PASSWORD=buckitadmin - - BUCKIT_ENDPOINTS=http://node1:9000/data/drive{0...3} http://node2:9000/data/drive{0...3} http://node3:9000/data/drive{0...3} http://node4:9000/data/drive{0...3} - - SSH_ROOT_PASSWORD=buckitadmin - volumes: - - node2-drives:/var/lib/buckit-drives - tmpfs: - - /run - - /run/lock - ports: - - "9002:9000" - - "9003:9001" - - "2202:22" - networks: - - buckit-test-net - - node3: - build: - context: . - dockerfile: Dockerfile - container_name: buckit-test-node3 - hostname: node3 - privileged: true - mem_limit: 256M - dns: - - 8.8.8.8 - - 1.1.1.1 - environment: - - DRIVES=4 - - DRIVE_SIZE=1G - - MINIO_ROOT_USER=buckitadmin - - MINIO_ROOT_PASSWORD=buckitadmin - - BUCKIT_ENDPOINTS=http://node1:9000/data/drive{0...3} http://node2:9000/data/drive{0...3} http://node3:9000/data/drive{0...3} http://node4:9000/data/drive{0...3} - - SSH_ROOT_PASSWORD=buckitadmin - volumes: - - node3-drives:/var/lib/buckit-drives - tmpfs: - - /run - - /run/lock - ports: - - "9004:9000" - - "9005:9001" - - "2203:22" - networks: - - buckit-test-net - - node4: - build: - context: . - dockerfile: Dockerfile - container_name: buckit-test-node4 - hostname: node4 - privileged: true - mem_limit: 256M - dns: - - 8.8.8.8 - - 1.1.1.1 - environment: - - DRIVES=4 - - DRIVE_SIZE=1G - - MINIO_ROOT_USER=buckitadmin - - MINIO_ROOT_PASSWORD=buckitadmin - - BUCKIT_ENDPOINTS=http://node1:9000/data/drive{0...3} http://node2:9000/data/drive{0...3} http://node3:9000/data/drive{0...3} http://node4:9000/data/drive{0...3} - - SSH_ROOT_PASSWORD=buckitadmin - volumes: - - node4-drives:/var/lib/buckit-drives - tmpfs: - - /run - - /run/lock - ports: - - "9006:9000" - - "9007:9001" - - "2204:22" - networks: - - buckit-test-net - -volumes: - node1-drives: - node2-drives: - node3-drives: - node4-drives: - -networks: - buckit-test-net: - driver: bridge diff --git a/testing/cluster/dockerfile-gen.sh b/testing/cluster/dockerfile-gen.sh deleted file mode 100644 index b9940bdd3..000000000 --- a/testing/cluster/dockerfile-gen.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash -# Generate a Dockerfile for the given base image. -# Usage: source this or call generate_dockerfile - -generate_dockerfile() { - local base_image="$1" - local dockerfile="$2" - - # Detect package manager from image name - local install_cmd - case "$base_image" in - *ubuntu* | *debian*) - install_cmd="sed -i 's|http://ports.ubuntu.com/ubuntu-ports/|http://mirrors.mit.edu/ubuntu-ports/|g' /etc/apt/sources.list.d/*.sources 2>/dev/null; sed -i 's|http://ports.ubuntu.com|http://mirrors.mit.edu/ubuntu-ports|g' /etc/apt/sources.list 2>/dev/null; apt-get update && apt-get install -y --no-install-recommends systemd systemd-sysv xfsprogs openssh-server curl ca-certificates iputils-ping && apt-get clean && rm -rf /var/lib/apt/lists/*" - ;; - *rocky* | *centos* | *alma* | *fedora* | *rhel* | *amazonlinux*) - install_cmd="dnf install -y systemd xfsprogs openssh-server curl ca-certificates iputils && dnf clean all" - ;; - *alpine*) - echo "Error: Alpine does not support systemd. Use Ubuntu, Debian, Rocky, or Fedora." - exit 1 - ;; - *) - install_cmd="apt-get update && apt-get install -y systemd systemd-sysv xfsprogs openssh-server curl ca-certificates iputils-ping && apt-get clean && rm -rf /var/lib/apt/lists/*" - ;; - esac - - cat >"$dockerfile" <> /etc/ssh/sshd_config; fi - -RUN mkdir -p /var/lib/buckit-drives /data - -COPY entrypoint.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh - -STOPSIGNAL SIGRTMIN+3 -ENTRYPOINT ["/entrypoint.sh"] -EOF -} diff --git a/testing/cluster/entrypoint.sh b/testing/cluster/entrypoint.sh deleted file mode 100755 index f35e3abb5..000000000 --- a/testing/cluster/entrypoint.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/bash -# Container entrypoint: create loopback XFS drives, then exec systemd. -set -e - -DRIVES=${DRIVES:-4} -DRIVE_SIZE=${DRIVE_SIZE:-1G} -DATA_DIR=${DATA_DIR:-/data} -SSH_ROOT_PASSWORD=${SSH_ROOT_PASSWORD:-buckitadmin} - -if command -v chpasswd >/dev/null 2>&1; then - echo "root:${SSH_ROOT_PASSWORD}" | chpasswd -fi - -# Ensure loop device support -modprobe loop 2>/dev/null || true - -# Pre-populate /dev/loop0.../dev/loop63 so that losetup --find --show can open -# the device node immediately after the kernel atomically allocates its index. -# Without this a freshly-started container may be missing higher-numbered nodes. -for _n in $(seq 0 63); do - [ -e "/dev/loop${_n}" ] || mknod "/dev/loop${_n}" b 7 "${_n}" 2>/dev/null || true -done -unset _n - -# Detach any orphaned loop devices pointing to deleted backing files (e.g. from -# prior container runs on Docker Desktop, whose loop attachments leak into the -# host VM kernel even after the container's volume is removed). -if command -v losetup >/dev/null 2>&1; then - losetup -a 2>/dev/null | awk -F: '/\(deleted\)/{print $1}' | while read -r stale; do - losetup -d "$stale" 2>/dev/null || true - done -fi - -attach_loop() { - # losetup --find --show atomically selects the next free loop device and - # attaches the image in one kernel operation (LOOP_CTL_GET_FREE + - # LOOP_SET_FD). The old two-step approach — losetup -f to read the next - # free device, then a separate losetup to attach — has a TOCTOU - # race: when N nodes start concurrently they all observe the same "next - # free" number before any of them has attached, so N-1 of them hit - # "Device or resource busy". The atomic form closes that window entirely. - local img="$1" - local loop num - loop=$(losetup --find --show "$img") || return 1 - # Materialise the /dev node if the kernel assigned an index above the - # pre-populated range inside this container's /dev namespace. - num=${loop##/dev/loop} - [ -e "$loop" ] || mknod "$loop" b 7 "$num" 2>/dev/null || true - echo "$loop" -} - -for i in $(seq 0 $((DRIVES - 1))); do - img="/var/lib/buckit-drives/drive${i}.img" - mnt="${DATA_DIR}/drive${i}" - mkdir -p "$mnt" - if [ ! -f "$img" ]; then - fallocate -l "$DRIVE_SIZE" "$img" - mkfs.xfs -f "$img" - fi - loop=$(attach_loop "$img") - mount "$loop" "$mnt" -done - -exec /sbin/init diff --git a/testing/cluster/minio-entrypoint.sh b/testing/cluster/minio-entrypoint.sh deleted file mode 100644 index cec485105..000000000 --- a/testing/cluster/minio-entrypoint.sh +++ /dev/null @@ -1,121 +0,0 @@ -#!/bin/bash -# Container entrypoint for the MinIO migration test container. -# -# Steps performed at startup: -# 1. Set up loopback-backed XFS drives (same technique as the Buckit cluster). -# 2. Set ownership of all drive mount points to minio-user. -# 3. Write /etc/default/minio from environment variables (per MinIO RHEL -# deployment guide). -# 4. Hand off to /sbin/init (systemd), which starts minio.service. -# -# Environment variables (set via docker compose): -# DRIVES Number of drives to create (default: 4) -# DRIVE_SIZE Size of each drive image (default: 1G) -# MINIO_ROOT_USER MinIO root / admin username (default: minioadmin) -# MINIO_ROOT_PASSWORD MinIO root / admin password (default: minioadmin) -# SSH_ROOT_PASSWORD Root password for SSH access (default: minioadmin) -set -e - -DRIVES=${DRIVES:-4} -DRIVE_SIZE=${DRIVE_SIZE:-1G} -MINIO_ROOT_USER=${MINIO_ROOT_USER:-minioadmin} -MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD:-minioadmin} -SSH_ROOT_PASSWORD=${SSH_ROOT_PASSWORD:-minioadmin} - -# --------------------------------------------------------------------------- -# Set the root password so SSH password authentication works. -# --------------------------------------------------------------------------- -if command -v chpasswd >/dev/null 2>&1; then - echo "root:${SSH_ROOT_PASSWORD}" | chpasswd -fi - -# --------------------------------------------------------------------------- -# Loop device helpers (identical to the Buckit cluster entrypoint) -# --------------------------------------------------------------------------- - -# Ensure loop device support is loaded in the host kernel. -modprobe loop 2>/dev/null || true - -# Pre-populate /dev/loop0.../dev/loop63 so that losetup --find --show can open -# the device node immediately after the kernel atomically allocates its index. -# Without this a freshly-started container may be missing higher-numbered nodes. -for _n in $(seq 0 63); do - [ -e "/dev/loop${_n}" ] || mknod "/dev/loop${_n}" b 7 "${_n}" 2>/dev/null || true -done -unset _n - -# Detach any orphaned loop devices pointing to deleted backing files (e.g. -# from prior container runs on Docker Desktop, whose loop attachments leak -# into the host VM kernel after the container volume is removed). -if command -v losetup >/dev/null 2>&1; then - losetup -a 2>/dev/null | awk -F: '/\(deleted\)/{print $1}' | while read -r stale; do - losetup -d "$stale" 2>/dev/null || true - done -fi - -attach_loop() { - # losetup --find --show atomically selects the next free loop device and - # attaches the image in one kernel operation (LOOP_CTL_GET_FREE + - # LOOP_SET_FD). The old two-step approach — losetup -f to read the next - # free device, then a separate losetup to attach — has a TOCTOU - # race: when N nodes start concurrently they all observe the same "next - # free" number before any of them has attached, so N-1 of them hit - # "Device or resource busy". The atomic form closes that window entirely. - local img="$1" - local loop num - loop=$(losetup --find --show "$img") || return 1 - # Materialise the /dev node if the kernel assigned an index above the - # pre-populated range inside this container's /dev namespace. - num=${loop##/dev/loop} - [ -e "$loop" ] || mknod "$loop" b 7 "$num" 2>/dev/null || true - echo "$loop" -} - -# --------------------------------------------------------------------------- -# Create / mount XFS loopback drives -# --------------------------------------------------------------------------- - -for i in $(seq 0 $((DRIVES - 1))); do - img="/var/lib/minio-drives/drive${i}.img" - mnt="/mnt/data/drive${i}" - mkdir -p "$mnt" - if [ ! -f "$img" ]; then - fallocate -l "$DRIVE_SIZE" "$img" - mkfs.xfs -f "$img" - fi - loop=$(attach_loop "$img") - mount "$loop" "$mnt" -done - -# Set ownership so the minio service (running as minio-user) can write data. -chown -R minio-user:minio-user /mnt/data - -# --------------------------------------------------------------------------- -# Write /etc/default/minio (per MinIO RHEL deployment guide) -# --------------------------------------------------------------------------- -# MINIO_VOLUMES is normally pre-computed by cluster.sh and injected via the -# compose environment — covering both single-node local paths and distributed -# http://minio{1...N}:9000/... URLs. Fall back to local-path computation -# only when the variable is absent (e.g. a manual `docker run`). -if [ -z "${MINIO_VOLUMES:-}" ]; then - if [ "$DRIVES" -eq 1 ]; then - MINIO_VOLUMES="/mnt/data/drive0" - else - MINIO_VOLUMES="/mnt/data/drive{0...$((DRIVES - 1))}" - fi -fi - -cat >/etc/default/minio <