Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support MinIO for file storage #365

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
19 changes: 13 additions & 6 deletions backend/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,18 @@ LANGCHAIN_API_KEY=your_langsmith_api_key_here
# To create a LangSmith project, visit LangSmith: https://www.langchain.com/langsmith
LANGCHAIN_PROJECT=your_langsmith_project_name_here


# FILE_UPLOAD: Whether to enable file upload to storage
# Set to true if file upload is required.
# Available options: false, s3, minio
# If set to false, AWS_S3_BUCKET_NAME is not required.
FILE_UPLOAD=false
# AWS_S3_BUCKET_NAME: S3 Bucket name
# This is the name of the S3 Bucket
AWS_S3_BUCKET_NAME="your_s3_bucket_name"
# Set to true if file upload is required.
FILE_UPLOAD=true
# Storage configuration for either AWS S3 or MinIO
# This is the name of the S3 Bucket or MinIO Bucket
BUCKET_NAME="default-storage"
Comment on lines +75 to +77
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add explicit storage type configuration

The configuration doesn't provide a clear way to select between S3 and MinIO storage backends. This could lead to confusion about which storage system is being used.

Add a STORAGE_TYPE variable to explicitly control the storage backend:

 # Storage configuration for either AWS S3 or MinIO
+# STORAGE_TYPE: The storage backend to use
+# Available options: s3, minio
+STORAGE_TYPE=minio
+
 # This is the name of the S3 Bucket or MinIO Bucket
 BUCKET_NAME="default-storage"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Storage configuration for either AWS S3 or MinIO
# This is the name of the S3 Bucket or MinIO Bucket
BUCKET_NAME="default-storage"
# Storage configuration for either AWS S3 or MinIO
# STORAGE_TYPE: The storage backend to use
# Available options: s3, minio
STORAGE_TYPE=minio
# This is the name of the S3 Bucket or MinIO Bucket
BUCKET_NAME="default-storage"

# MinIO endpoint, only required for MinIO
MINIO_ENDPOINT="http://localhost:9000"
# Common access keys for both S3 and MinIO
MINIO_ACCESS_KEY="minioadmin"
MINIO_SECRET_KEY="minioadmin"
Comment on lines +78 to +82
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance security and documentation for MinIO configuration

The MinIO configuration needs better documentation and security warnings for sensitive values.

Apply this diff to improve the configuration:

-# MinIO endpoint, only required for MinIO
+# MINIO_ENDPOINT: The endpoint URL for the MinIO server
+# Format: http(s)://<host>:<port>
+# Example: http://localhost:9000 (For development mode)
 MINIO_ENDPOINT="http://localhost:9000"

-# Common access keys for both S3 and MinIO
+# MINIO_ACCESS_KEY: Access key for authentication
+# Warning: Use a strong, unique value in production
+# Default: minioadmin (for development only)
 MINIO_ACCESS_KEY="minioadmin"

+# MINIO_SECRET_KEY: Secret key for authentication
+# Warning: Keep this value secret and never commit to version control
+# Default: minioadmin (for development only)
 MINIO_SECRET_KEY="minioadmin"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# MinIO endpoint, only required for MinIO
MINIO_ENDPOINT="http://localhost:9000"
# Common access keys for both S3 and MinIO
MINIO_ACCESS_KEY="minioadmin"
MINIO_SECRET_KEY="minioadmin"
# MINIO_ENDPOINT: The endpoint URL for the MinIO server
# Format: http(s)://<host>:<port>
# Example: http://localhost:9000 (For development mode)
MINIO_ENDPOINT="http://localhost:9000"
# MINIO_ACCESS_KEY: Access key for authentication
# Warning: Use a strong, unique value in production
# Default: minioadmin (for development only)
MINIO_ACCESS_KEY="minioadmin"
# MINIO_SECRET_KEY: Secret key for authentication
# Warning: Keep this value secret and never commit to version control
# Default: minioadmin (for development only)
MINIO_SECRET_KEY="minioadmin"

# AWS Region, only required for AWS S3
AWS_REGION="your_aws_region"
36 changes: 35 additions & 1 deletion backend/docker/docker-compose-full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,22 @@ services:
LANGCHAIN_API_KEY: "your_langsmith_api_key_here"
LANGCHAIN_PROJECT: "your_langsmith_project_name_here"
FILE_UPLOAD: false
AWS_S3_BUCKET_NAME: "your_s3_bucket_name"
AWS_REGION: "your_aws_region_here"
# Default configuration values for using Minio
BUCKET_NAME: "default-storage"
MINIO_ENDPOINT: "http://localhost:9000"
STORAGE_ACCESS_KEY: "minioadmin"
STORAGE_SECRET_KEY: "minioadmin"
Comment on lines +30 to +35
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Enhance security and usability of storage configuration.

The current configuration has several issues:

  1. Sensitive credentials are hardcoded in plain text
  2. Some values use placeholder text instead of defaults
  3. No environment variable substitution is used

Apply this diff to improve the configuration:

-            AWS_REGION: "your_aws_region_here"
-            # Default configuration values for using Minio
-            BUCKET_NAME: "default-storage"
-            MINIO_ENDPOINT: "http://localhost:9000"
-            STORAGE_ACCESS_KEY: "minioadmin"
-            STORAGE_SECRET_KEY: "minioadmin"
+            AWS_REGION: ${AWS_REGION:-us-east-1}
+            # Storage configuration (supports both S3 and MinIO)
+            BUCKET_NAME: ${BUCKET_NAME:-default-storage}
+            MINIO_ENDPOINT: ${MINIO_ENDPOINT:-http://minio:9000}
+            STORAGE_ACCESS_KEY: ${STORAGE_ACCESS_KEY}
+            STORAGE_SECRET_KEY: ${STORAGE_SECRET_KEY}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
AWS_REGION: "your_aws_region_here"
# Default configuration values for using Minio
BUCKET_NAME: "default-storage"
MINIO_ENDPOINT: "http://localhost:9000"
STORAGE_ACCESS_KEY: "minioadmin"
STORAGE_SECRET_KEY: "minioadmin"
AWS_REGION: ${AWS_REGION:-us-east-1}
# Storage configuration (supports both S3 and MinIO)
BUCKET_NAME: ${BUCKET_NAME:-default-storage}
MINIO_ENDPOINT: ${MINIO_ENDPOINT:-http://minio:9000}
STORAGE_ACCESS_KEY: ${STORAGE_ACCESS_KEY}
STORAGE_SECRET_KEY: ${STORAGE_SECRET_KEY}

ports:
- "3000:3000"
depends_on:
- mongo
- minio
restart: unless-stopped
links:
- "mongo:mongo"
- "yorkie:yorkie"
- "minio:minio"

yorkie:
image: "yorkieteam/yorkie:0.5.0"
Expand Down Expand Up @@ -70,3 +77,30 @@ services:
interval: 5s
timeout: 2s
retries: 20

# You can remove the following content if you're using S3 or not using Minio.
minio:
image: minio/minio
ports:
- "9000:9000"
- "9001:9001"
volumes:
- minio_data:/data
environment:
MINIO_ROOT_USER: "minioadmin"
MINIO_ROOT_PASSWORD: "minioadmin"
entrypoint: /bin/sh -c "
/usr/bin/docker-entrypoint.sh server --console-address ':9001' --address ':9000' /data &
sleep 1 &&
mc alias set myminio http://localhost:9000 minioadmin minioadmin &&
if ! mc ls myminio/default-storage; then mc mb myminio/default-storage; fi &&
wait"
command: server --console-address ":9001" --address ":9000" /data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
Comment on lines +83 to +103
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance MinIO service security and configuration.

Several improvements can be made to the MinIO service configuration:

  1. Use environment variables for credentials
  2. Simplify the entrypoint script
  3. Optimize health check frequency

Apply these changes:

     minio:
         image: minio/minio
         ports:
             - "9000:9000"
             - "9001:9001"
         volumes:
             - minio_data:/data
         environment:
-            MINIO_ROOT_USER: "minioadmin"
-            MINIO_ROOT_PASSWORD: "minioadmin"
+            MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
+            MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
         entrypoint: /bin/sh -c "
             /usr/bin/docker-entrypoint.sh server --console-address ':9001' --address ':9000' /data &
-            sleep 1 &&
+            sleep 3 &&
             mc alias set myminio http://localhost:9000 minioadmin minioadmin &&
             if ! mc ls myminio/default-storage; then mc mb myminio/default-storage; fi &&
             wait"
         command: server --console-address ":9001" --address ":9000" /data
         healthcheck:
             test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
-            interval: 30s
+            interval: 10s
             timeout: 20s
             retries: 3
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
image: minio/minio
ports:
- "9000:9000"
- "9001:9001"
volumes:
- minio_data:/data
environment:
MINIO_ROOT_USER: "minioadmin"
MINIO_ROOT_PASSWORD: "minioadmin"
entrypoint: /bin/sh -c "
/usr/bin/docker-entrypoint.sh server --console-address ':9001' --address ':9000' /data &
sleep 1 &&
mc alias set myminio http://localhost:9000 minioadmin minioadmin &&
if ! mc ls myminio/default-storage; then mc mb myminio/default-storage; fi &&
wait"
command: server --console-address ":9001" --address ":9000" /data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
image: minio/minio
ports:
- "9000:9000"
- "9001:9001"
volumes:
- minio_data:/data
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
entrypoint: /bin/sh -c "
/usr/bin/docker-entrypoint.sh server --console-address ':9001' --address ':9000' /data &
sleep 3 &&
mc alias set myminio http://localhost:9000 minioadmin minioadmin &&
if ! mc ls myminio/default-storage; then mc mb myminio/default-storage; fi &&
wait"
command: server --console-address ":9001" --address ":9000" /data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 10s
timeout: 20s
retries: 3


volumes:
minio_data:
18 changes: 10 additions & 8 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { Module } from "@nestjs/common";
import { PrismaService } from "./db/prisma.service";
import { UsersModule } from "./users/users.module";
import { AuthModule } from "./auth/auth.module";
import { ConfigModule } from "@nestjs/config";
import { APP_GUARD } from "@nestjs/core/constants";
import { AuthModule } from "./auth/auth.module";
import { JwtAuthGuard } from "./auth/jwt.guard";
import { WorkspacesModule } from "./workspaces/workspaces.module";
import { WorkspaceUsersModule } from "./workspace-users/workspace-users.module";
import { WorkspaceDocumentsModule } from "./workspace-documents/workspace-documents.module";
import { DocumentsModule } from "./documents/documents.module";
import { CheckModule } from "./check/check.module";
import { PrismaService } from "./db/prisma.service";
import { DocumentsModule } from "./documents/documents.module";
import { FilesModule } from "./files/files.module";
import { IntelligenceModule } from "./intelligence/intelligence.module";
import { LangchainModule } from "./langchain/langchain.module";
import { FilesModule } from "./files/files.module";
import { SettingsModule } from "./settings/settings.module";
import { StorageModule } from "./storage/storage.module";
import { UsersModule } from "./users/users.module";
import { WorkspaceDocumentsModule } from "./workspace-documents/workspace-documents.module";
import { WorkspaceUsersModule } from "./workspace-users/workspace-users.module";
import { WorkspacesModule } from "./workspaces/workspaces.module";

@Module({
imports: [
Expand All @@ -34,6 +35,7 @@ import { SettingsModule } from "./settings/settings.module";
FilesModule,
ConfigModule,
SettingsModule,
StorageModule,
minai621 marked this conversation as resolved.
Show resolved Hide resolved
],
controllers: [],
providers: [
Expand Down
4 changes: 3 additions & 1 deletion backend/src/files/files.module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Module } from "@nestjs/common";
import { PrismaService } from "src/db/prisma.service";
import { StorageModule } from "src/storage/storage.module";
import { FilesController } from "./files.controller";
import { FilesService } from "./files.service";
import { PrismaService } from "src/db/prisma.service";

@Module({
imports: [StorageModule],
controllers: [FilesController],
providers: [FilesService, PrismaService],
})
Expand Down
8 changes: 4 additions & 4 deletions backend/src/files/files.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import {
BadRequestException,
Inject,
Injectable,
NotFoundException,
UnauthorizedException,
Expand All @@ -18,14 +19,13 @@ import { ExportFileRequestBody, ExportFileResponse } from "./types/export-file.t

@Injectable()
export class FilesService {
private s3Client: S3Client;
private readonly markdown: MarkdownIt;

constructor(
@Inject("STORAGE_CLIENT") private s3Client: S3Client,
private configService: ConfigService,
private prismaService: PrismaService
) {
this.s3Client = new S3Client();
this.markdown = new MarkdownIt({
html: true,
breaks: true,
Expand Down Expand Up @@ -55,7 +55,7 @@ export class FilesService {

const fileKey = `${workspace.slug}-${generateRandomKey()}.${contentType.split("/")[1]}`;
const command = new PutObjectCommand({
Bucket: this.configService.get("AWS_S3_BUCKET_NAME"),
Bucket: this.configService.get("BUCKET_NAME"),
minai621 marked this conversation as resolved.
Show resolved Hide resolved
Key: fileKey,
StorageClass: "INTELLIGENT_TIERING",
ContentType: contentType,
Expand All @@ -70,7 +70,7 @@ export class FilesService {
async createDownloadPresignedUrl(fileKey: string) {
try {
const command = new GetObjectCommand({
Bucket: this.configService.get("AWS_S3_BUCKET_NAME"),
Bucket: this.configService.get("BUCKET_NAME"),
Key: fileKey,
});
return getSignedUrl(this.s3Client, command, { expiresIn: 3600 });
Expand Down
38 changes: 38 additions & 0 deletions backend/src/storage/storage.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { S3Client, S3ClientConfig } from "@aws-sdk/client-s3";
import { Module } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";

const s3ClientFactory = {
provide: "STORAGE_CLIENT",
useFactory: (configService: ConfigService): S3Client | null => {
const fileUpload = configService.get<boolean | "s3" | "minio">("FILE_UPLOAD");
if (!fileUpload) {
return null;
}
const region = configService.get<string>("AWS_REGION");
const endpoint = configService.get<string>("MINIO_ENDPOINT");
const accessKeyId = configService.get<string>("STORAGE_ACCESS_KEY");
const secretAccessKey = configService.get<string>("STORAGE_SECRET_KEY");

const config: S3ClientConfig = {
region,
...(fileUpload === "minio" && {
endpoint,
forcePathStyle: true,
credentials: {
accessKeyId,
secretAccessKey,
},
}),
};

return new S3Client(config);
},
inject: [ConfigService],
};

@Module({
providers: [s3ClientFactory],
exports: [s3ClientFactory],
})
export class StorageModule {}