Files
incudal/server/scripts/backfill-instance-network-addresses.ts
2026-06-20 14:26:29 +08:00

165 lines
4.8 KiB
TypeScript

import * as db from '../src/db/index.js'
import { prisma } from '../src/db/prisma.js'
import { getIncusClient, getInstance, getInstanceState } from '../src/lib/incus/index.js'
import {
persistResolvedInstanceNetworkAddresses,
resolveInstanceNetworkAddresses,
type IncusNetworkStateLike
} from '../src/lib/instance-network-sync.js'
function parseArgs(argv: string[]): { dryRun: boolean; hostId: number | null } {
let dryRun = false
let hostId: number | null = null
for (const arg of argv) {
if (arg === '--dry-run') {
dryRun = true
continue
}
if (arg.startsWith('--host-id=')) {
const value = Number(arg.slice('--host-id='.length))
if (!Number.isNaN(value)) {
hostId = value
}
}
}
return { dryRun, hostId }
}
async function main(): Promise<void> {
const { dryRun, hostId } = parseArgs(process.argv.slice(2))
const allHosts = await db.getAllHosts()
const hosts = allHosts.filter(host => host.status !== 'offline' && (hostId === null || host.id === hostId))
if (hosts.length === 0) {
console.log('[BackfillInstanceNetwork] No matching online hosts found')
return
}
let scanned = 0
let changedInstances = 0
let ipv4ChangedCount = 0
let ipv6ChangedCount = 0
let failed = 0
console.log(`[BackfillInstanceNetwork] Starting${dryRun ? ' dry-run' : ''} for ${hosts.length} host(s)`)
for (const host of hosts) {
console.log(`[BackfillInstanceNetwork] Host #${host.id} ${host.name}`)
let client: Awaited<ReturnType<typeof getIncusClient>>
try {
client = await getIncusClient(host)
} catch (error) {
failed++
const message = error instanceof Error ? error.message : String(error)
console.error(`[BackfillInstanceNetwork] Failed to connect host #${host.id}: ${message}`)
continue
}
const instances = await prisma.instance.findMany({
where: {
hostId: host.id,
status: { notIn: ['deleted', 'creating'] }
},
orderBy: { id: 'asc' },
select: {
id: true,
name: true,
incusId: true,
networkMode: true,
ipv4: true,
ipv6: true
}
})
for (const instance of instances) {
scanned++
try {
const incusInstance = await getInstance(client, instance.incusId)
let state: IncusNetworkStateLike | null = null
try {
state = await getInstanceState(client, instance.incusId) as IncusNetworkStateLike
} catch {
state = null
}
const resolved = resolveInstanceNetworkAddresses(
{
id: instance.id,
name: instance.name,
networkMode: instance.networkMode,
ipv4: instance.ipv4,
ipv6: instance.ipv6
},
incusInstance,
state
)
const ipv4WillChange = (instance.ipv4 ?? null) !== (resolved.ipv4 ?? null)
const ipv6WillChange = (instance.ipv6 ?? null) !== (resolved.ipv6 ?? null)
if (!ipv4WillChange && !ipv6WillChange) {
continue
}
changedInstances++
if (ipv4WillChange) ipv4ChangedCount++
if (ipv6WillChange) ipv6ChangedCount++
if (dryRun) {
console.log(
`[BackfillInstanceNetwork] DRY-RUN instance #${instance.id} ${instance.name}: `
+ `ipv4 ${instance.ipv4 ?? '-'} -> ${resolved.ipv4 ?? '-'}, `
+ `ipv6 ${instance.ipv6 ?? '-'} -> ${resolved.ipv6 ?? '-'}`
)
continue
}
const persisted = await persistResolvedInstanceNetworkAddresses(
{
id: instance.id,
name: instance.name,
networkMode: instance.networkMode,
ipv4: instance.ipv4,
ipv6: instance.ipv6
},
resolved,
{ updateLastSyncedAt: true }
)
console.log(
`[BackfillInstanceNetwork] Updated instance #${instance.id} ${instance.name}: `
+ `ipv4 ${persisted.oldIpv4 ?? '-'} -> ${persisted.newIpv4 ?? '-'}, `
+ `ipv6 ${persisted.oldIpv6 ?? '-'} -> ${persisted.newIpv6 ?? '-'}`
)
} catch (error) {
failed++
const message = error instanceof Error ? error.message : String(error)
console.error(`[BackfillInstanceNetwork] Failed instance #${instance.id} ${instance.name}: ${message}`)
}
}
}
console.log(
`[BackfillInstanceNetwork] Completed${dryRun ? ' dry-run' : ''}: `
+ `scanned=${scanned}, changed=${changedInstances}, `
+ `ipv4Changed=${ipv4ChangedCount}, ipv6Changed=${ipv6ChangedCount}, failed=${failed}`
)
}
main()
.catch(error => {
const message = error instanceof Error ? error.stack || error.message : String(error)
console.error(`[BackfillInstanceNetwork] Fatal error: ${message}`)
process.exitCode = 1
})
.finally(async () => {
await prisma.$disconnect()
})