diff --git a/.gitignore b/.gitignore index f9a87baa..c38ad72e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,10 +8,6 @@ guppy # Environment Files .env* -# Config Files -*.yaml -*.toml - # IDE's *.idea/ diff --git a/Dockerfile b/Dockerfile index 7c278eab..0af30c05 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # ============================================ -# Build stage (shared) +# Build base stage # ============================================ -FROM golang:1.25.3-trixie AS build +FROM golang:1.26.1-trixie AS build-base # Docker sets TARGETARCH automatically during multi-platform builds ARG TARGETARCH @@ -10,15 +10,32 @@ WORKDIR /go/src/guppy COPY go.* . RUN go mod download -COPY . . +COPY --parents cmd internal pkg Makefile main.go version.json ./ + +# ============================================ +# Production build stage +# ============================================ +FROM build-base AS build-prod + +# Allow the Makefile to look up the git commit, at the expense of busting the +# cache whenever `.git` changes. +COPY --parents .git ./ # Production build - with symbol stripping -RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} make guppy-prod +RUN --mount=type=cache,target=/root/.cache/go-build CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} make guppy-prod # ============================================ # Debug build stage # ============================================ -FROM build AS build-debug +FROM build-base AS build-debug + +# Debug build - no optimizations, no inlining +RUN --mount=type=cache,target=/root/.cache/go-build CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} make guppy-debug + +# ============================================ +# Debug tools +# ============================================ +FROM golang:1.26.1-trixie AS build-debug-tools ARG TARGETARCH @@ -26,9 +43,6 @@ ARG TARGETARCH RUN GOARCH=${TARGETARCH} go install github.com/go-delve/delve/cmd/dlv@latest && \ GOARCH=${TARGETARCH} go install github.com/storacha/randdir@latest -# Debug build - no optimizations, no inlining -RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} make guppy-debug - # ============================================ # Production image # ============================================ @@ -39,7 +53,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ curl \ && rm -rf /var/lib/apt/lists/* -COPY --from=build /go/src/guppy/guppy /usr/bin/guppy +COPY --from=build-prod /go/src/guppy/guppy /usr/bin/guppy ENTRYPOINT ["/usr/bin/guppy"] @@ -69,8 +83,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && rm -rf /var/lib/apt/lists/* # Delve debugger and randdir tool -COPY --from=build-debug /go/bin/dlv /usr/bin/dlv -COPY --from=build-debug /go/bin/randdir /usr/bin/randdir +COPY --from=build-debug-tools /go/bin/dlv /usr/bin/dlv +COPY --from=build-debug-tools /go/bin/randdir /usr/bin/randdir # Debug binary (with symbols, no optimizations) COPY --from=build-debug /go/src/guppy/guppy /usr/bin/guppy @@ -87,3 +101,19 @@ RUN echo 'alias ll="ls -la"' >> /etc/bash.bashrc && \ SHELL ["/bin/bash", "-c"] ENTRYPOINT ["/usr/bin/guppy"] + +# ============================================ +# Test image (without interactive tools) +# ============================================ +FROM debian:bookworm-slim AS test + +# Debug binary (with symbols, no optimizations) +COPY --from=build-debug /go/src/guppy/guppy /usr/bin/guppy + +# Create data directories +RUN mkdir -p /root/.storacha/guppy /root/.config/guppy + +WORKDIR /root + +SHELL ["/bin/bash", "-c"] +ENTRYPOINT ["/usr/bin/guppy"] diff --git a/Makefile b/Makefile index 3e7b835d..27fe823c 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ DB_PATH ?= ~/.storacha/guppy/preparation.db GOOSE := go tool goose VERSION=$(shell awk -F'"' '/"version":/ {print $$4}' version.json) -COMMIT=$(shell git rev-parse --short HEAD) +COMMIT=$(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown") DATE=$(shell date -u -Iseconds) GOFLAGS=-ldflags="-X github.com/storacha/guppy/pkg/build.version=$(VERSION) -X github.com/storacha/guppy/pkg/build.Commit=$(COMMIT) -X github.com/storacha/guppy/pkg/build.Date=$(DATE) -X github.com/storacha/guppy/pkg/build.BuiltBy=make" DOCKER?=$(shell which docker) @@ -59,3 +59,7 @@ docker-prod: docker-setup docker-dev: docker-setup $(DOCKER) buildx build --platform linux/amd64,linux/arm64 --target dev -t guppy:dev . + +test-upload: + @echo "Running upload test..." + ./test/doupload diff --git a/test/.gitignore b/test/.gitignore index 9f623a4b..f7aa4061 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,4 +1,5 @@ doupload-dir baduploads-dir ipfs -mprocs.log \ No newline at end of file +mprocs.log +smelt \ No newline at end of file diff --git a/test/compose.smelt-override.yml b/test/compose.smelt-override.yml new file mode 100644 index 00000000..bc3c6b4d --- /dev/null +++ b/test/compose.smelt-override.yml @@ -0,0 +1,14 @@ +# This file overrides the Smelt Compose configuration. Paths are relative to +# that file, not this one. + +services: + upload: + environment: + - SPRUE_LOG_LEVEL=debug + volumes: + - ../upload-config:/etc/sprue:ro + +networks: + storacha-network: + # Use an isolated network, not the normal Smelt network. + external: false diff --git a/test/compose.yml b/test/compose.yml new file mode 100644 index 00000000..8906e0f4 --- /dev/null +++ b/test/compose.yml @@ -0,0 +1,44 @@ +name: guppy-test + +include: + - path: + - ./smelt/compose.yml + - ./compose.smelt-override.yml + +services: + guppy-doupload: + extends: + service: guppy + file: ./smelt/systems/guppy/compose.yml + build: + context: .. + dockerfile: Dockerfile + target: dev + image: guppy:dev + environment: + - IN_CONTAINER=true + entrypoint: ["/usr/local/bin/doupload"] + volumes: + - ./doupload:/usr/local/bin/doupload + depends_on: + email-clicker: + condition: service_started + piri-0: + condition: service_healthy + piri-1: + condition: service_healthy + piri-2: + condition: service_healthy + + email-clicker: + image: docker:cli + environment: + - COMPOSE_PROJECT=guppy-test + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - ./email-clicker/click.sh:/usr/local/bin/click.sh:ro + entrypoint: ["/bin/sh", "/usr/local/bin/click.sh"] + depends_on: + - upload + networks: + - storacha-network diff --git a/test/doupload b/test/doupload index 302767f9..b3c59b8a 100755 --- a/test/doupload +++ b/test/doupload @@ -1,4 +1,4 @@ -#!/bin/zsh +#!/bin/bash # Performs an upload and retrieval test using the guppy client CLI. # @@ -12,7 +12,7 @@ # To use a specific schema, add ?search_path= to the URL. # # The test will: -# 1. Create a temporary email inbox using maildrop.cc +# 1. Check out Smelt, initialize it, and move into a container. # 2. Create a new space # 3. Upload some random data to the space # 4. Retrieve the full data @@ -21,308 +21,170 @@ # 7. Retrieve the full data again # 8. Verify that all retrieved data matches the original +# TODO: Postgres support is not yet ported to Smelt/Sprue + set -e set -o pipefail -# Parse arguments -database_url="" -while [[ $# -gt 0 ]]; do - case $1 in - --database-url) - database_url="$2" - shift 2 - ;; - *) - echo "Unknown option: $1" - echo "Usage: test/doupload [--database-url ]" - exit 1 - ;; - esac -done - -# If a database URL was provided, set up the database. -created_db_name="" -if [[ -n "$database_url" ]]; then - # Check for psql - if ! command -v psql &> /dev/null; then - echo "psql could not be found, please install it to use --database-url." - exit 1 - fi - - # Parse the URL to check if a database name is included. - # Split off query string first, then check the path component. - # postgres://user:pass@host:port/dbname?sslmode=disable -> has db name - # postgres://user:pass@host:port?sslmode=disable -> no db name - # postgres://user:pass@host:port/?sslmode=disable -> no db name - # postgres://user:pass@host:port -> no db name - url_base="${database_url%%\?*}" # everything before ? - url_query="" - if [[ "$database_url" == *"?"* ]]; then - url_query="?${database_url#*\?}" # ?sslmode=disable&... - fi - - # After the scheme (postgres://), find the path after the host - after_scheme="${url_base#*://}" # user:pass@host:port/dbname - db_path="${after_scheme#*/}" # dbname (or same as after_scheme if no /) - - # No db name if: no / found (db_path == after_scheme), or path is empty - if [[ "$db_path" == "$after_scheme" ]] || [[ -z "$db_path" ]]; then - # No database name provided; create a temporary one - created_db_name="guppy_test_$(head -c 8 < /dev/urandom | xxd -p)" - # Strip trailing slash from base - url_base="${url_base%/}" - - # We need to connect to the "postgres" database to create a new database. We - # also need to strip out parameters that Go's adapter understands but psql - # doesn't, namely `search_path`. Conveniently, we also don't need to set a - # search_path for this operation. - psql_query=$(echo "$url_query" | sed 's/[?&]search_path=[^&]*//' | sed 's/^&/?/') - postgres_db_url="${url_base}/postgres${psql_query}" - - psql "$postgres_db_url" -c "CREATE DATABASE ${created_db_name};" >/dev/null - database_url="${url_base}/${created_db_name}${url_query}" - echo "Created temporary database: ${created_db_name}" - fi -fi -# Check for dependencies -if ! command -v jq &> /dev/null; then - echo "jq could not be found, please install it to run this script." - exit 1 -fi -if ! command -v htmlq &> /dev/null; then - echo "htmlq could not be found, please install it to run this script." - exit 1 -fi - -# Change to the directory of this script -cd "$(dirname "$0")" - -# Teeing to /dev/fd/3 will show output on stdout while still capturing it. -exec 3>&1 - - -sandbox="doupload-dir" - -go build -gcflags="all=-N -l" .. || { echo "Failed to build guppy"; exit 1; } - -export STORACHA_SERVICE_URL="https://staging.up.warm.storacha.network" -export STORACHA_SERVICE_DID="did:web:staging.up.warm.storacha.network" -export STORACHA_RECEIPTS_URL="https://staging.up.warm.storacha.network/receipt/" -export STORACHA_INDEXING_SERVICE_URL="https://staging.indexer.warm.storacha.network" -export STORACHA_INDEXING_SERVICE_DID="did:web:staging.indexer.warm.storacha.network" -export GUPPY_REPO_DATA_DIR="./$sandbox/storacha" -export GUPPY_REPO_DATABASE_URL="$database_url" - -dataDir="$sandbox/data" -outDir1="$sandbox/out1" -outDir2="$sandbox/out2" -outDir3="$sandbox/out3" - -# Track background processes to kill on exit -cleanup_pids=() - -# Cleanup function to kill background processes and their children -cleanup() { - local pid= - for pid in "${cleanup_pids[@]}"; do - if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then - echo "Cleaning up background process $pid and its children" - # Kill all child processes first - pkill -P "$pid" 2>/dev/null || true - # Then kill the parent - kill "$pid" 2>/dev/null || true - # Wait a moment, then force kill if still alive - sleep 0.1 - if kill -0 "$pid" 2>/dev/null; then - pkill -9 -P "$pid" 2>/dev/null || true - kill -9 "$pid" 2>/dev/null || true - fi - fi - done - - # Drop temporary database if we created one - if [[ -n "$created_db_name" ]]; then - echo "Dropping temporary database: ${created_db_name}" - psql "$postgres_db_url" -c "DROP DATABASE IF EXISTS ${created_db_name};" >/dev/null 2>&1 || true - fi +runGuppyContainer () { + case $1 in + down) + echo "Taking down containers..." + docker compose down + echo "Removing volumes..." + docker volume prune --force --all --filter "label=com.docker.compose.project=guppy-test" + exit $? + ;; + shell) + service="${2:-guppy}" + echo "Starting a shell in the container..." + docker compose run --rm --entrypoint="/bin/sh" "$service" + exit $? + ;; + *) + # Start up the `piri` services first to watch them. + upServices $(docker compose config --services | grep piri- | sort) + + echo "๐Ÿงน Resetting data-dir" + docker volume prune --force --all --filter "label=com.docker.compose.project=guppy-test" --filter "label=com.docker.compose.volume=guppy-data" >/dev/null + + [ -d smelt/.git ] || git clone https://github.com/storacha/smelt.git smelt + + echo "๐Ÿ Initializing smelt" + export SMELT_MANIFEST="$PWD/smelt-manifest.yml" + (cd smelt && make init >/dev/null) + + # docker compose run --rm --entrypoint="true" guppy-doupload + + + + echo "๐Ÿณ Moving into the container..." + echo + docker compose run --rm --build --quiet-build guppy-doupload + exit $? + ;; + esac } -# Set up trap to cleanup on EXIT, INT, TERM -trap cleanup EXIT INT TERM - -# Track the last command for error reporting -last_command="" -trap 'last_command=$ZSH_DEBUG_CMD' DEBUG - -# Error handler -handle_error() { - local exit_code=$1 - local failed_command=$2 - echo - echo "โŒ Command failed with exit code $exit_code: $failed_command" -} +runTests () { + # Teeing to /dev/fd/3 will show output on stdout while still capturing it. + exec 3>&1 -# Handle errors with some printed output -trap 'handle_error $? "$last_command"' ERR + # HTTPS is not available in Smelt, so allow insecure DID resolution. + export GUPPY_NETWORK_INSECURE_DID_RESOLUTION=true -main () { - rm -rf "$sandbox" - mkdir -p "$sandbox" + account="racha@hen.house" - go run github.com/storacha/randdir@latest --output "$dataDir/large-files" --size 50MB --min-file-size 10MB - go run github.com/storacha/randdir@latest --output "$dataDir/small-files" --size 5MB --min-file-size 100KB --max-file-size 1MB + echo + echo "๐Ÿญ Generating random data" + randdir --output "data/large-files" --size 50MB --min-file-size 10MB + randdir --output "data/small-files" --size 5MB --min-file-size 100KB --max-file-size 1MB - # Generate a random maildrop email address - local random_id=$(head -c 10 < /dev/urandom | base32 | tr "[:upper:]" "[:lower:]") - local account="${random_id}@maildrop.cc" + echo echo "๐Ÿ” Logging in as $account" # Log in - log_in "$account" + guppy login "$account" echo echo "๐ŸŽ Generating new space" - space=$(./guppy space generate | tee /dev/fd/3) + space=$(guppy space generate | tee /dev/fd/3) - echo - echo "๐Ÿ“œ Listing space info" - ./guppy space info "$space" + # (Currently not supported in Smelt/Sprue) + # echo + # echo "๐Ÿ“œ Listing space info" + # guppy space info "$space" echo - echo "๐Ÿ“ค Uploading data from $dataDir to space $space" - ./guppy upload source add "$space" "$dataDir" - rootCID=$(./guppy upload "$space" | tee /dev/fd/3 | grep 'Upload completed successfully:' | awk '{print $4}') + echo "๐Ÿ“ค Uploading data from data/ to space $space" + guppy upload source add "$space" "data" + rootCID=$(guppy upload "$space" | tee /dev/fd/3 | grep 'Upload completed successfully:' | awk '{print $4}') echo echo "๐Ÿง Checking local upload state" - ./guppy upload check "$space" + guppy upload check "$space" echo "โœ… Upload state passed checks!" echo echo "๐Ÿ•ต Verifying uploaded data is accessible and consistent" - ./guppy verify "$rootCID" + guppy verify "$rootCID" echo "โœ… Uploaded data verified!" echo - echo "๐Ÿ“ฅ Retrieving data from space $space with root CID $rootCID to $outDir1" - ./guppy retrieve "$space" "$rootCID" "$outDir1" + echo "๐Ÿ“ฅ Retrieving data from space $space with root CID $rootCID to out1" + guppy retrieve "$space" "$rootCID" "out1" echo - echo "๐Ÿ“ฅ Retrieving data from only subdir with root CID $rootCID to $outDir2" - ./guppy retrieve "$space" "$rootCID/small-files" "$outDir2" + echo "๐Ÿ“ฅ Retrieving data from only subdir with root CID $rootCID to out2" + guppy retrieve "$space" "$rootCID/small-files" "out2" + # TODO: Re-login not working yet. - echo - echo "๐Ÿ”„ Resetting client" - ./guppy reset - echo "๐Ÿ” Logging in as $account again" + # echo + # echo "๐Ÿ”„ Resetting client" + # guppy reset + # echo "๐Ÿ” Logging in as $account again" - # Log in again - log_in "$account" + # # Log in again + # guppy login "$account" - # Remove from cleanup list once completed - cleanup_pids=("${cleanup_pids[@]/$login_pid}") - - echo - echo "๐Ÿ“ฅ Retrieving data from space $space with root CID $rootCID to $outDir3" - ./guppy retrieve "$space" "$rootCID" "$outDir3" + # echo + # echo "๐Ÿ“ฅ Retrieving data from space $space with root CID $rootCID to out3" + # guppy retrieve "$space" "$rootCID" "out3" echo "โ†”๏ธ Verifying retrieved data matches original" - diff -r "$dataDir" "$outDir1" - diff -r "$dataDir/small-files" "$outDir2" - diff -r "$dataDir" "$outDir3" + diff -r "data" "out1" + diff -r "data/small-files" "out2" + # diff -r "data" "out3" echo "โœ… Retrieval verified!" - jq -n \ - --arg account "$account" \ - --arg space "$space" \ - --arg rootCID "$rootCID" \ - --arg dataDir "$dataDir" \ - --arg subdir "subdir" \ - '{$account, $space, $rootCID, $dataDir, $subdir}' > "$sandbox/test-params.json" + # TODO: + # jq -n \ + # --arg account "$account" \ + # --arg space "$space" \ + # --arg rootCID "$rootCID" \ + # --arg dataDir "$dataDir" \ + # --arg subdir "subdir" \ + # '{$account, $space, $rootCID, $dataDir, $subdir}' > "$sandbox/test-params.json" } -log_in() { - local account="$1" - # Start login in background - ./guppy login "$account" >/dev/null & - login_pid=$! - cleanup_pids+=("$login_pid") +upServices () { + if ! command -v mprocs &> /dev/null; then + echo "To see logs while waiting, install \`mprocs\`." + docker compose up -d --wait "$@" + return + fi - # Verify email - verify_email "$account" + local mprocsPort + local config + + mprocsPort=$((5000 + RANDOM % 1000)) + config=$(mktemp) && mv "$config" "$config.json" && config="$config.json" + jq \ + '{ + server: "\($mprocsServer)", + procs: { + "Starting...": "docker compose up -d --wait \($ARGS.positional | join(" ")) && mprocs --server \"\($mprocsServer)\" --ctl \"{c: quit}\"" + } + ($ARGS.positional | INDEX(.[]; " " + .) | map_values({shell: "docker compose logs -f --no-log-prefix \(.)", stop: {"send-keys": [""]}})) + }' \ + -n \ + --arg mprocsServer "127.0.0.1:$mprocsPort" \ + --args "$@" \ + >"$config" + + mprocs --config "$config" + rm "$config" +} - # Wait for login to complete - wait $login_pid - # Remove from cleanup list once completed - cleanup_pids=("${cleanup_pids[@]/$login_pid}") -} +# Bootstrap this script into a `guppy` service container. +if [[ -z "$IN_CONTAINER" ]]; then + # Change to the directory of this script + cd "$(dirname "$0")" -seen_message_ids=() - -# Function to check maildrop inbox and click verification link -verify_email() { - local email="$1" - local inbox_name="${email%%@*}" - - echo "โณ Waiting for verification email..." - local max_attempts=30 - local attempt=0 - - while [ $attempt -lt $max_attempts ]; do - # Fetch inbox from Maildrop using GraphQL API - local inbox_query='{"query":"query { inbox(mailbox:\"'${inbox_name}'\") { id subject } }"}' - local inbox_response=$(curl -s -X POST \ - -H 'content-type: application/json' \ - --url https://api.maildrop.cc/graphql \ - --data "$inbox_query") - - # Find the first message that we haven't seen yet - local message_id="" - local all_message_ids=($(jq -r '.data.inbox[].id // empty' <<< "$inbox_response")) - - for id in "${all_message_ids[@]}"; do - # Check if this ID is in the seen list - if [[ ! " ${seen_message_ids[@]} " =~ " ${id} " ]]; then - message_id="$id" - seen_message_ids+=("$message_id") - break - fi - done - - if [ -n "$message_id" ]; then - echo "๐Ÿ“ฌ Found email (ID: $message_id), retrieving verification link..." - - # Fetch the message content (HTML) using GraphQL - local message_query='{"query":"query { message(mailbox:\"'${inbox_name}'\", id:\"'${message_id}'\") { html } }"}' - local message_response=$(curl -s -X POST \ - -H 'content-type: application/json' \ - --url https://api.maildrop.cc/graphql \ - --data "$message_query") - - # Extract the HTML content from the GraphQL response and decode it - local message_html=$(jq -r '.data.message.html' <<< "$message_response") - - # Extract all https links from the HTML - local verify_url=$(htmlq --attribute href a.button <<< "$message_html") - - echo "โœ… Submitting approval form to: $verify_url" - curl -sL -X POST "$verify_url" > /dev/null - - echo "โœ“ Email verified!" - return 0 - else - echo "โš ๏ธ No verification link found in email, retrying..." - fi - - attempt=$((attempt + 1)) - sleep 2 - done - - echo "โŒ Failed to receive verification email after ${max_attempts} attempts" - return 1 -} + runGuppyContainer "$@" +else + runTests +fi -main \ No newline at end of file diff --git a/test/email-clicker/click.sh b/test/email-clicker/click.sh new file mode 100755 index 00000000..47c6c89b --- /dev/null +++ b/test/email-clicker/click.sh @@ -0,0 +1,21 @@ +#!/bin/sh +set -eu + +: "${COMPOSE_PROJECT:?COMPOSE_PROJECT must be set}" + +until id=$(docker ps -q \ + --filter "label=com.docker.compose.project=$COMPOSE_PROJECT" \ + --filter "label=com.docker.compose.service=upload") && [ -n "$id" ]; do + sleep 0.2 +done + +echo "Watching upload container $id for login URLs..." + +docker logs -f --since 0s "$id" 2>&1 | while IFS= read -r line; do + url=$(printf '%s' "$line" | grep -oE '"url"[[:space:]]*:[[:space:]]*"[^"]+"' | sed -E 's/.*"url"[^"]*"([^"]+)".*/\1/' || true) + [ -n "$url" ] || continue + echo "clicking $url" + if ! wget -O /dev/null --post-data='' "$url"; then + echo "verification fetch failed: $url" >&2 + fi +done diff --git a/test/smelt-manifest.yml b/test/smelt-manifest.yml new file mode 100644 index 00000000..a5035d93 --- /dev/null +++ b/test/smelt-manifest.yml @@ -0,0 +1,12 @@ +version: 1 +piri: + nodes: + - storage: + db: sqlite + blob: filesystem + - storage: + db: sqlite + blob: filesystem + - storage: + db: sqlite + blob: filesystem diff --git a/test/upload-config/config.yaml b/test/upload-config/config.yaml new file mode 100644 index 00000000..4b957f04 --- /dev/null +++ b/test/upload-config/config.yaml @@ -0,0 +1,49 @@ +# Upload service configuration for Docker Compose environment + +deployment: + environment: "development" + allow_provision_without_payment_plan: true # allows provision without customer + max_replicas: 3 + +server: + host: "0.0.0.0" + port: 80 # Use port 80 for did:web resolution + public_url: http://upload:80 + +identity: + key_file: "/keys/upload.pem" + service_did: "did:web:upload" + +indexer: + endpoint: "http://indexer:80" + did: "did:web:indexer" + +mailer: + type: "nop" + +dynamodb: + endpoint: "http://dynamodb-local:8000" + region: "us-west-1" + agent_index_table: "agent-index" + blob_registry_table: "blob-registry" + consumer_table: "consumer" + customer_table: "customer" + delegation_table: "delegation" + space_metrics_table: "space-metrics" + admin_metrics_table: "admin-metrics" + replica_table: "replica" + revocation_table: "revocation" + storage_provider_table: "storage-provider" + subscription_table: "subscription" + space_diff_table: "space-diff" + upload_table: "upload" + +s3: + endpoint: "http://minio:9000" + region: "us-west-1" + agent_message_bucket: "agent-message" + delegation_bucket: "delegation" + upload_shards_bucket: "upload-shards" + +log: + level: "info"