remove_backed_up_blobs.mjs 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. // @ts-check
  2. import { readFileSync } from 'node:fs'
  3. import commandLineArgs from 'command-line-args'
  4. import { client } from '../lib/mongodb.js'
  5. import {
  6. getBackedUpBlobHashes,
  7. unsetBackedUpBlobHashes,
  8. } from '../lib/backup_store/index.js'
  9. let gracefulShutdownInitiated = false
  10. // Parse command line arguments
  11. const args = commandLineArgs([
  12. { name: 'input', type: String, alias: 'i', defaultOption: true },
  13. { name: 'commit', type: Boolean, default: false },
  14. ])
  15. if (!args.input) {
  16. console.error(
  17. 'Usage: node remove_backed_up_blobs.mjs --input <csv-file> [--commit]'
  18. )
  19. process.exit(1)
  20. }
  21. if (!args.commit) {
  22. console.log('Running in dry-run mode. Use --commit to apply changes.')
  23. }
  24. // Signal handling
  25. process.on('SIGINT', handleSignal)
  26. process.on('SIGTERM', handleSignal)
  27. function handleSignal() {
  28. console.warn('Graceful shutdown initiated')
  29. gracefulShutdownInitiated = true
  30. }
  31. // Process CSV and remove blobs
  32. async function main() {
  33. const projectBlobs = new Map()
  34. const lines = readFileSync(args.input, 'utf8').split('\n')
  35. const SHA1_HEX_REGEX = /^[a-f0-9]{40}$/
  36. // Skip header
  37. for (const line of lines.slice(1)) {
  38. if (!line.trim() || gracefulShutdownInitiated) break
  39. const [projectId, path] = line.split(',')
  40. const pathParts = path.split('/')
  41. const hash = pathParts[3] + pathParts[4]
  42. if (!SHA1_HEX_REGEX.test(hash)) {
  43. console.warn(`Invalid SHA1 hash for project ${projectId}: ${hash}`)
  44. continue
  45. }
  46. if (!projectBlobs.has(projectId)) {
  47. projectBlobs.set(projectId, new Set())
  48. }
  49. projectBlobs.get(projectId).add(hash)
  50. }
  51. // Process each project
  52. for (const [projectId, hashes] of projectBlobs) {
  53. if (gracefulShutdownInitiated) break
  54. if (!args.commit) {
  55. console.log(
  56. `DRY-RUN: would remove ${hashes.size} blobs from project ${projectId}`
  57. )
  58. continue
  59. }
  60. try {
  61. const originalHashes = await getBackedUpBlobHashes(projectId)
  62. if (originalHashes.size === 0) {
  63. continue
  64. }
  65. const result = await unsetBackedUpBlobHashes(
  66. projectId,
  67. Array.from(hashes)
  68. )
  69. if (result) {
  70. console.log(
  71. `Project ${projectId}: want to remove ${hashes.size}, removed ${originalHashes.size - result.blobs.length}, ${result.blobs.length} remaining`
  72. )
  73. }
  74. } catch (err) {
  75. console.error(`Error updating project ${projectId}:`, err)
  76. }
  77. }
  78. }
  79. // Run the script
  80. main()
  81. .catch(err => {
  82. console.error('Fatal error:', err)
  83. process.exitCode = 1
  84. })
  85. .finally(() => {
  86. client
  87. .close()
  88. .catch(err => console.error('Error closing MongoDB connection:', err))
  89. })