| 1 | /** |
| 2 | * Finds log damage no node could replay, which breaks every clone, and repairs it with --apply (0034). Run on the API host: its local caches are the only place missing objects can be recovered from; elsewhere it can only drop them. |
| 3 | * Safe beside a running API: a repair commits through the same compare-and-swap as a push, and starts over if a push lands first. |
| 4 | * node dist/scripts/repair-repositories.js [--apply], after `nest build` |
| 5 | */ |
| 6 | import { type Database, |
| 7 | import { Module } |
| 8 | import { ConfigModule } |
| 9 | import { NestFactory } |
| 10 | import { eq } |
| 11 | |
| 12 | import { DATABASE, DatabaseModule |
| 13 | import { ownerNameOf } |
| 14 | import { |
| 15 | type RepairReport, |
| 16 | RepositoryRepairService, |
| 17 | } from '../services/git/repair/repository-repair.service.js'; |
| 18 | import { RepositoryStorageService } |
| 19 | import { PushTransactionService } |
| 20 | import { WalStoreService } |
| 21 | import { S3Service } |
| 22 | import { errorMessage } |
| 23 | |
| 24 | // Only what a repair needs, so the script opens no HTTP or SSH listener and starts no poller. |
| 25 | @Module({ |
| 26 | imports: [ConfigModule.forRoot |
| 27 | providers: [ |
| 28 | RepositoryRepairService, |
| 29 | RepositoryStorageService, |
| 30 | PushTransactionService, |
| 31 | WalStoreService, |
| 32 | S3Service, |
| 33 | ], |
| 34 | }) |
| 35 | class RepairModule {} |
| 36 | |
| 37 | const apply = process |
| 38 | |
| 39 | const app = await |
| 40 | logger: ['warn', |
| 41 | }); |
| 42 | const db = app |
| 43 | const repairs = app |
| 44 | |
| 45 | const repositories = await |
| 46 | .select({ |
| 47 | id: schema.repository |
| 48 | slug: schema.repository |
| 49 | owner: ownerNameOf(schema |
| 50 | }) |
| 51 | .from(schema.repository |
| 52 | .innerJoin(schema.user |
| 53 | .leftJoin( |
| 54 | schema.organization, |
| 55 | eq(schema.organization |
| 56 | ); |
| 57 | |
| 58 | let broken = 0 |
| 59 | let failed = 0 |
| 60 | for (const { id |
| 61 | try { |
| 62 | const report = |
| 63 | if (!report) |
| 64 | broken++; |
| 65 | console.log(`${ |
| 66 | print(report); |
| 67 | } catch (error) |
| 68 | failed++; |
| 69 | console.error( |
| 70 | `${owner}/${slug |
| 71 | ); |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | console.log( |
| 76 | `${repositories.length} repositories checked, |
| 77 | ); |
| 78 | if (!apply && |
| 79 | await app.close() |
| 80 | process.exitCode = failed |
| 81 | |
| 82 | function print({ layers |
| 83 | for (const { |
| 84 | console.log(` entry |
| 85 | } |
| 86 | for (const ref |
| 87 | console.log(` |
| 88 | } |
| 89 | for (const { |
| 90 | console.log(` |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | function firstLine(text: |
| 95 | return text.split |
| 96 | } |