2024-06-05 09:01:40 +02:00
/* eslint-disable max-len */
import { InvalidArgumentError , createCommand } from '@commander-js/extra-typings'
import { FileStorage } from '@peertube/peertube-models'
import { escapeForRegex } from '@server/helpers/regexp.js'
import { initDatabaseModels , sequelizeTypescript } from '@server/initializers/database.js'
import { QueryTypes } from 'sequelize'
2024-06-05 14:43:41 +02:00
import { askConfirmation , displayPeerTubeMustBeStoppedWarning } from './shared/common.js'
2024-06-05 09:01:40 +02:00
const program = createCommand ( )
. description ( 'Update PeerTube object file URLs after an object storage migration.' )
. requiredOption ( '-f, --from <url>' , 'Previous object storage base URL' , parseUrl )
. requiredOption ( '-t, --to <url>' , 'New object storage base URL' , parseUrl )
. parse ( process . argv )
const options = program . opts ( )
run ( )
. then ( ( ) = > process . exit ( 0 ) )
. catch ( err = > {
console . error ( err )
process . exit ( - 1 )
} )
async function run ( ) {
await initDatabaseModels ( true )
2024-06-05 14:43:41 +02:00
displayPeerTubeMustBeStoppedWarning ( )
2024-06-05 09:01:40 +02:00
const fromRegexp = ` ^ ${ escapeForRegex ( options . from ) } `
const to = options . to
const replacements = { fromRegexp , to , storage : FileStorage.OBJECT_STORAGE }
// Candidates
{
const queries = [
` SELECT COUNT(*) AS "c", 'videoFile->fileUrl: ' || COUNT(*) AS "t" FROM "videoFile" WHERE "fileUrl" ~ :fromRegexp AND "storage" = :storage ` ,
` SELECT COUNT(*) AS "c", 'videoStreamingPlaylist->playlistUrl: ' || COUNT(*) AS "t" FROM "videoStreamingPlaylist" WHERE "playlistUrl" ~ :fromRegexp AND "storage" = :storage ` ,
` SELECT COUNT(*) AS "c", 'videoStreamingPlaylist->segmentsSha256Url: ' || COUNT(*) AS "t" FROM "videoStreamingPlaylist" WHERE "segmentsSha256Url" ~ :fromRegexp AND "storage" = :storage ` ,
` SELECT COUNT(*) AS "c", 'userExport->fileUrl: ' || COUNT(*) AS "t" FROM "userExport" WHERE "fileUrl" ~ :fromRegexp AND "storage" = :storage ` ,
` SELECT COUNT(*) AS "c", 'videoSource->fileUrl: ' || COUNT(*) AS "t" FROM "videoSource" WHERE "fileUrl" ~ :fromRegexp AND "storage" = :storage `
]
let hasResults = false
console . log ( 'Candidate URLs to update:' )
for ( const query of queries ) {
const [ row ] = await sequelizeTypescript . query ( query , { replacements , type : QueryTypes . SELECT as QueryTypes . SELECT } )
if ( row [ 'c' ] !== 0 ) hasResults = true
console . log ( ` ${ row [ 't' ] } ` )
}
console . log ( '\n' )
if ( ! hasResults ) {
console . log ( 'No candidate URLs found, exiting.' )
process . exit ( 0 )
}
}
2024-06-05 14:43:41 +02:00
const res = await askUpdateConfirmation ( )
2024-06-05 09:01:40 +02:00
if ( res !== true ) {
console . log ( 'Exiting without updating URLs.' )
process . exit ( 0 )
}
// Execute
{
const queries = [
` UPDATE "videoFile" SET "fileUrl" = regexp_replace("fileUrl", :fromRegexp, :to) WHERE "storage" = :storage ` ,
` UPDATE "videoStreamingPlaylist" SET "playlistUrl" = regexp_replace("playlistUrl", :fromRegexp, :to) WHERE "storage" = :storage ` ,
` UPDATE "videoStreamingPlaylist" SET "segmentsSha256Url" = regexp_replace("segmentsSha256Url", :fromRegexp, :to) WHERE "storage" = :storage ` ,
` UPDATE "userExport" SET "fileUrl" = regexp_replace("fileUrl", :fromRegexp, :to) WHERE "storage" = :storage ` ,
` UPDATE "videoSource" SET "fileUrl" = regexp_replace("fileUrl", :fromRegexp, :to) WHERE "storage" = :storage `
]
for ( const query of queries ) {
await sequelizeTypescript . query ( query , { replacements } )
}
console . log ( 'URLs updated.' )
}
}
function parseUrl ( value : string ) {
if ( ! value || /^https?:\/\// . test ( value ) !== true ) {
throw new InvalidArgumentError ( 'Must be a valid URL (starting with http:// or https://).' )
}
return value
}
2024-06-05 14:43:41 +02:00
async function askUpdateConfirmation ( ) {
return askConfirmation (
'These URLs can be updated, but please check your backups first (bugs happen). ' +
'Can we update these URLs?'
)
2024-06-05 09:01:40 +02:00
}