/* 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' import { askConfirmation, displayPeerTubeMustBeStoppedWarning } from './shared/common.js' const program = createCommand() .description('Update PeerTube object file URLs after an object storage migration.') .requiredOption('-f, --from ', 'Previous object storage base URL', parseUrl) .requiredOption('-t, --to ', '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) displayPeerTubeMustBeStoppedWarning() 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) } } const res = await askUpdateConfirmation() 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 } async function askUpdateConfirmation () { return askConfirmation( 'These URLs can be updated, but please check your backups first (bugs happen). ' + 'Can we update these URLs?' ) }