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:
@@ -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": "",
|
||||
|
||||
@@ -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';
|
||||
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
|
||||
);
|
||||
const policyJson = policyService.generatePolicyFromTemplate(
|
||||
templateKey,
|
||||
setupData.bucketName
|
||||
);
|
||||
|
||||
await policyService.createPolicy({
|
||||
name: policyName,
|
||||
policy: policyJson,
|
||||
});
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user