diff --git a/backend/package.json b/backend/package.json index a919406..42c42c9 100644 --- a/backend/package.json +++ b/backend/package.json @@ -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": "", diff --git a/docker/Dockerfile.backend b/docker/Dockerfile.backend index e3c983b..f75ab0d 100644 --- a/docker/Dockerfile.backend +++ b/docker/Dockerfile.backend @@ -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 diff --git a/frontend/src/components/Dashboard/QuickStartWizard.tsx b/frontend/src/components/Dashboard/QuickStartWizard.tsx index cb0c044..db2c4e7 100644 --- a/frontend/src/components/Dashboard/QuickStartWizard.tsx +++ b/frontend/src/components/Dashboard/QuickStartWizard.tsx @@ -73,6 +73,7 @@ const QuickStartWizard: React.FC = ({ const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [completedSetup, setCompletedSetup] = useState(null); + const [isCreating, setIsCreating] = useState(false); const steps = [ t('quickWizard:steps.createBucket'), @@ -125,57 +126,102 @@ const QuickStartWizard: React.FC = ({ }; 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 = ({ }); setError(''); setCompletedSetup(null); + setIsCreating(false); } };