Files
cf-multisite/scripts/upload.js
kappa 8850031c45
Some checks failed
Deploy to CF Multisite / deploy (push) Failing after 1m53s
Initial commit: CF Multisite 멀티테넌트 정적 호스팅
- Cloudflare Workers + R2 기반
- Edge 캐싱으로 비용 절감
- 티어별 Rate Limiting (free/basic/pro)
- KV 기반 사용량 추적
- Admin API (usage, customers, tiers, stats)
- Gitea Actions 배포 워크플로우

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 09:20:46 +09:00

146 lines
3.7 KiB
JavaScript

#!/usr/bin/env node
/**
* R2 업로드 스크립트
*
* 사용법:
* node scripts/upload.js <customer-id> <source-dir>
*
* 예시:
* node scripts/upload.js demo-site ./sample-site
*
* 환경변수:
* R2_ENDPOINT - R2 엔드포인트 URL
* R2_ACCESS_KEY - R2 접근 키
* R2_SECRET_KEY - R2 시크릿 키
* R2_BUCKET - 버킷 이름 (기본: multisite-bucket)
*/
const { S3Client, PutObjectCommand, ListObjectsV2Command, DeleteObjectsCommand } = require('@aws-sdk/client-s3');
const fs = require('fs');
const path = require('path');
// MIME 타입 매핑
const MIME_TYPES = {
'.html': 'text/html; charset=utf-8',
'.css': 'text/css',
'.js': 'application/javascript',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.ttf': 'font/ttf',
'.pdf': 'application/pdf',
'.xml': 'application/xml',
'.txt': 'text/plain',
'.md': 'text/markdown',
};
// 재귀적으로 모든 파일 찾기
function getAllFiles(dir, files = []) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
getAllFiles(fullPath, files);
} else {
files.push(fullPath);
}
}
return files;
}
async function main() {
const [,, customerId, sourceDir] = process.argv;
if (!customerId || !sourceDir) {
console.error('Usage: node scripts/upload.js <customer-id> <source-dir>');
console.error('Example: node scripts/upload.js demo-site ./sample-site');
process.exit(1);
}
// 환경변수 체크
const endpoint = process.env.R2_ENDPOINT;
const accessKeyId = process.env.R2_ACCESS_KEY;
const secretAccessKey = process.env.R2_SECRET_KEY;
const bucket = process.env.R2_BUCKET || 'multisite-bucket';
if (!endpoint || !accessKeyId || !secretAccessKey) {
console.error('Missing environment variables:');
console.error(' R2_ENDPOINT, R2_ACCESS_KEY, R2_SECRET_KEY');
console.error('');
console.error('R2 API 토큰 생성: Cloudflare Dashboard > R2 > Manage R2 API Tokens');
process.exit(1);
}
// S3 클라이언트 생성 (R2는 S3 호환)
const s3 = new S3Client({
region: 'auto',
endpoint,
credentials: { accessKeyId, secretAccessKey },
});
const sourcePath = path.resolve(sourceDir);
if (!fs.existsSync(sourcePath)) {
console.error(`Source directory not found: ${sourcePath}`);
process.exit(1);
}
console.log(`Uploading to R2...`);
console.log(` Customer: ${customerId}`);
console.log(` Source: ${sourcePath}`);
console.log(` Bucket: ${bucket}`);
console.log('');
// 모든 파일 수집
const files = getAllFiles(sourcePath);
const prefix = `sites/${customerId}`;
// 업로드
let uploaded = 0;
let failed = 0;
for (const file of files) {
const relativePath = path.relative(sourcePath, file);
const key = `${prefix}/${relativePath}`.replace(/\\/g, '/');
const ext = path.extname(file).toLowerCase();
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
try {
const body = fs.readFileSync(file);
await s3.send(new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: body,
ContentType: contentType,
}));
console.log(`${key}`);
uploaded++;
} catch (error) {
console.error(`${key}: ${error.message}`);
failed++;
}
}
console.log('');
console.log(`Done! Uploaded: ${uploaded}, Failed: ${failed}`);
if (failed > 0) {
process.exit(1);
}
}
main().catch(error => {
console.error('Fatal error:', error);
process.exit(1);
});