Files
GearBox/scripts/migrate-images-to-s3.ts
Jean-Luc Makiola d519a83cc4
Some checks failed
CI / ci (push) Failing after 19s
CI / deploy (push) Has been skipped
CI / e2e (push) Has been skipped
infra: migrate deployment to Coolify with Garage S3
- Remove docker-compose files (Coolify manages services individually)
- Replace MinIO with Garage (S3-compatible, actively maintained)
- Add CI deploy job: build+push :develop image on every green Develop push
- Add Coolify webhook trigger for automatic redeployment
- Update README, .env.example, and storage references
- Rename migrate script to provider-agnostic name

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:28:43 +02:00

92 lines
2.3 KiB
TypeScript

/**
* One-time migration script: uploads/ -> S3-compatible object storage
*
* Reads all image files from the local uploads/ directory and uploads each
* to the S3 bucket via the storage service. Preserves original filenames
* as object keys.
*
* Prerequisites:
* - S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY env vars must be set
* - The S3 bucket must exist
*
* Usage:
* bun run scripts/migrate-images-to-s3.ts
*/
import { readdir } from "node:fs/promises";
import { extname, join } from "node:path";
import { uploadImage } from "../src/server/services/storage.service";
const UPLOADS_DIR = "uploads";
const CONTENT_TYPE_MAP: Record<string, string> = {
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".png": "image/png",
".webp": "image/webp",
};
const IMAGE_EXTENSIONS = new Set(Object.keys(CONTENT_TYPE_MAP));
async function main() {
// Check if uploads/ directory exists
try {
await readdir(UPLOADS_DIR);
} catch {
console.log("No uploads directory found. Nothing to migrate.");
process.exit(0);
}
// Read all files
const allFiles = await readdir(UPLOADS_DIR);
const imageFiles = allFiles.filter((f) =>
IMAGE_EXTENSIONS.has(extname(f).toLowerCase()),
);
if (imageFiles.length === 0) {
console.log("No image files found in uploads/. Nothing to migrate.");
process.exit(0);
}
console.log(`Found ${imageFiles.length} images to migrate\n`);
let success = 0;
let failed = 0;
for (const filename of imageFiles) {
const filePath = join(UPLOADS_DIR, filename);
const ext = extname(filename).toLowerCase();
const contentType = CONTENT_TYPE_MAP[ext] ?? "application/octet-stream";
try {
const file = Bun.file(filePath);
const buffer = await file.arrayBuffer();
await uploadImage(Buffer.from(buffer), filename, contentType);
success++;
console.log(` Migrated: ${filename}`);
} catch (err) {
failed++;
console.error(
` FAILED: ${filename} - ${err instanceof Error ? err.message : String(err)}`,
);
}
}
console.log(
`\nMigration complete: ${success}/${imageFiles.length} files migrated, ${failed} failures`,
);
if (failed > 0) {
console.log("Re-run this script to retry failed uploads.");
}
console.log(
"\nOriginal files preserved in uploads/. Delete manually after verifying: rm -rf uploads/",
);
}
main().catch((err) => {
console.error("Migration failed:", err);
process.exit(1);
});