fix: Resolve bcrypt architecture and duplicate resource creation issues

Backend fixes:
- Fix bcrypt exec format error for ARM64 architecture
- Add architecture detection for MinIO client download
- Install build tools and rebuild bcrypt from source
- Add postinstall script to automatically rebuild bcrypt
- Rebuild bcrypt after copying files to ensure correct binary

Frontend fixes:
- Handle duplicate resource creation in QuickStartWizard
- Add error handling for "already exists" scenarios
- Prevent multiple executions with isCreating flag
- Allow wizard to complete even if resources already exist

These changes fix:
1. Backend crash on ARM64 due to bcrypt binary mismatch
2. 500 errors when resources already exist in QuickStartWizard
3. Race conditions when clicking Complete button multiple times

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-07-24 09:17:22 +02:00
parent b4fc144cd3
commit 9cdd7b0050
3 changed files with 95 additions and 39 deletions
+2 -1
View File
@@ -11,7 +11,8 @@
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"hash-password": "node scripts/hash-password.js",
"generate-password": "node generate-password.js"
"generate-password": "node generate-password.js",
"postinstall": "npm rebuild bcrypt --build-from-source"
},
"keywords": ["minio", "api", "backend"],
"author": "",
+11 -3
View File
@@ -1,7 +1,8 @@
FROM node:20-alpine
# Install MinIO client
RUN wget https://dl.min.io/client/mc/release/linux-amd64/mc && \
# Install MinIO client (detect architecture)
RUN ARCH=$(uname -m | sed 's/x86_64/amd64/g' | sed 's/aarch64/arm64/g') && \
wget https://dl.min.io/client/mc/release/linux-${ARCH}/mc && \
chmod +x mc && \
mv mc /usr/local/bin/
@@ -11,12 +12,19 @@ WORKDIR /app
# Copy package files
COPY package*.json ./
# Install production dependencies
# Install build tools for native dependencies (keep them)
RUN apk add --no-cache python3 make g++ \
&& rm -rf /app/node_modules
# Install production dependencies (will trigger postinstall)
RUN npm ci --only=production
# Copy application files
COPY . .
# Rebuild bcrypt after copying files
RUN npm rebuild bcrypt --build-from-source
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
@@ -73,6 +73,7 @@ const QuickStartWizard: React.FC<QuickStartWizardProps> = ({
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [completedSetup, setCompletedSetup] = useState<any>(null);
const [isCreating, setIsCreating] = useState(false);
const steps = [
t('quickWizard:steps.createBucket'),
@@ -125,57 +126,102 @@ const QuickStartWizard: React.FC<QuickStartWizardProps> = ({
};
const handleComplete = async () => {
// Prevent multiple executions
if (isCreating) {
return;
}
setIsCreating(true);
setLoading(true);
setError('');
const results = {
bucket: setupData.bucketName,
user: setupData.userName,
password: setupData.userPassword,
policy: `${setupData.bucketName}-${setupData.policyType}-policy`,
};
let bucketCreated = false;
let userCreated = false;
let policyCreated = false;
try {
// Step 1: Create bucket
await api.post('/buckets', { bucketName: setupData.bucketName }, {
timeout: 30000, // 30 seconds timeout
});
// Step 1: Create bucket (skip if already exists)
try {
await api.post('/buckets', { bucketName: setupData.bucketName }, {
timeout: 30000, // 30 seconds timeout
});
bucketCreated = true;
} catch (err: any) {
if (err.response?.data?.error?.includes('already own it') ||
err.response?.data?.error?.includes('BucketAlreadyOwnedByYou')) {
// Bucket already exists, that's ok
bucketCreated = true;
} else {
throw err;
}
}
// Step 2: Create user
await userService.createUser({
accessKey: setupData.userName,
secretKey: setupData.userPassword,
});
// Step 2: Create user (skip if already exists)
try {
await userService.createUser({
accessKey: setupData.userName,
secretKey: setupData.userPassword,
});
userCreated = true;
} catch (err: any) {
if (err.response?.data?.error?.includes('already exists') ||
err.response?.data?.error?.includes('UserAlreadyExists')) {
// User already exists, that's ok
userCreated = true;
} else {
throw err;
}
}
// Step 3: Create policy
const policyName = `${setupData.bucketName}-${setupData.policyType}-policy`;
// Step 3: Create policy (skip if already exists)
const policyName = results.policy;
// Map policy types to template keys
const templateKey = setupData.policyType === 'readwrite'
? 'bucketFullAccess'
: setupData.policyType === 'readonly'
? 'bucketReadOnly'
: 'bucketWriteOnly';
const policyJson = policyService.generatePolicyFromTemplate(
templateKey,
setupData.bucketName
);
await policyService.createPolicy({
name: policyName,
policy: policyJson,
});
try {
// Map policy types to template keys
const templateKey = setupData.policyType === 'readwrite'
? 'bucketFullAccess'
: setupData.policyType === 'readonly'
? 'bucketReadOnly'
: 'bucketWriteOnly';
const policyJson = policyService.generatePolicyFromTemplate(
templateKey,
setupData.bucketName
);
await policyService.createPolicy({
name: policyName,
policy: policyJson,
});
policyCreated = true;
} catch (err: any) {
if (err.response?.data?.error?.includes('already exists') ||
err.response?.data?.error?.includes('PolicyAlreadyExists')) {
// Policy already exists, that's ok
policyCreated = true;
} else {
throw err;
}
}
// Step 4: Attach policy to user
// Step 4: Attach policy to user (always try this)
await policyService.attachPolicy({
policyName: policyName,
userName: setupData.userName,
});
setCompletedSetup({
bucket: setupData.bucketName,
user: setupData.userName,
password: setupData.userPassword,
policy: policyName,
});
setCompletedSetup(results);
setActiveStep(steps.length);
} catch (err) {
setError(err instanceof Error ? err.message : t('quickWizard:errors.creationFailed'));
setIsCreating(false);
} finally {
setLoading(false);
}
@@ -199,6 +245,7 @@ const QuickStartWizard: React.FC<QuickStartWizardProps> = ({
});
setError('');
setCompletedSetup(null);
setIsCreating(false);
}
};