Allow installing plugins declaratively

This commit is contained in:
Infinidoge 2024-08-16 03:04:47 -04:00
parent 1e3a5b25c3
commit 97b06f2455
No known key found for this signature in database
4 changed files with 105 additions and 5 deletions

View File

@ -20,6 +20,7 @@ import { decachePlugin } from '@server/helpers/decache.js'
import { ApplicationModel } from '@server/models/application/application.js' import { ApplicationModel } from '@server/models/application/application.js'
import { MOAuthTokenUser, MUser } from '@server/types/models/index.js' import { MOAuthTokenUser, MUser } from '@server/types/models/index.js'
import { isLibraryCodeValid, isPackageJSONValid } from '../../helpers/custom-validators/plugins.js' import { isLibraryCodeValid, isPackageJSONValid } from '../../helpers/custom-validators/plugins.js'
import { execShell } from '../../helpers/core-utils.js'
import { logger } from '../../helpers/logger.js' import { logger } from '../../helpers/logger.js'
import { CONFIG } from '../../initializers/config.js' import { CONFIG } from '../../initializers/config.js'
import { PLUGIN_GLOBAL_CSS_PATH } from '../../initializers/constants.js' import { PLUGIN_GLOBAL_CSS_PATH } from '../../initializers/constants.js'
@ -334,13 +335,93 @@ export class PluginManager implements ServerHook {
// ###################### Installation ###################### // ###################### Installation ######################
async installDeclarativePlugins () {
const pluginDirectory = CONFIG.STORAGE.PLUGINS_DIR
let declaredPlugins
try {
declaredPlugins = await readJSON(join(pluginDirectory, "declarative_plugins.json"));
}
catch (err) {
logger.info("No declarative plugins file found.")
return
}
const existingDeclaredPlugins: PluginModel[] = await PluginModel.listDeclarativePluginsAndThemes()
for (const plugin of existingDeclaredPlugins) {
const npmName = PluginModel.buildNpmName(plugin.name, plugin.type)
if (!(npmName in declaredPlugins)) {
logger.info("Removing previously declared plugin %s", npmName)
await this.uninstall({npmName: npmName, unregister: false})
}
else {
logger.info("Keeping still-declared plugin %s.", npmName)
}
}
for (const npmName of Object.keys(declaredPlugins)) {
const {
pluginPath = null,
version = null,
extraArgs = null,
preInstall = null,
postInstall = null
} = declaredPlugins[npmName];
if (preInstall != null) {
logger.info("Running pre-install command for plugin %s.", npmName)
try {
await execShell(preInstall, { cwd: pluginDirectory })
} catch (result) {
logger.error("Cannot exec pre-install command.", { preInstall, err: result.err, stderr: result.stderr })
throw result.err
}
}
if (pluginPath != null) {
logger.info("Installing declared plugin %s from disk (path %s).", npmName, pluginPath)
await this.install({
toInstall: pluginPath,
fromDisk: true,
declarative: true,
extraArgs: extraArgs,
})
}
else if (version != null) {
logger.info("Installing declared plugin %s (version %s) from npm.", npmName, version)
await this.install({
toInstall: npmName,
version: version,
declarative: true,
extraArgs: extraArgs,
})
}
if (postInstall != null) {
logger.info("Running post-install command for plugin %s.", npmName)
try {
await execShell(postInstall, { cwd: pluginDirectory })
} catch (result) {
logger.error("Cannot exec post-install command.", { postInstall, err: result.err, stderr: result.stderr })
throw result.err
}
}
}
this.sortHooksByPriority()
}
async install (options: { async install (options: {
toInstall: string toInstall: string
version?: string version?: string
fromDisk?: boolean // default false fromDisk?: boolean // default false
register?: boolean // default true register?: boolean // default true
declarative?: boolean // default false
extraArgs?: string // default empty
}) { }) {
const { toInstall, version, fromDisk = false, register = true } = options const { toInstall, version, fromDisk = false, register = true, declarative = false, extraArgs = null } = options
let plugin: PluginModel let plugin: PluginModel
let npmName: string let npmName: string
@ -349,8 +430,8 @@ export class PluginManager implements ServerHook {
try { try {
fromDisk fromDisk
? await installNpmPluginFromDisk(toInstall) ? await installNpmPluginFromDisk(toInstall, extraArgs)
: await installNpmPlugin(toInstall, version) : await installNpmPlugin(toInstall, version, extraArgs)
npmName = fromDisk ? basename(toInstall) : toInstall npmName = fromDisk ? basename(toInstall) : toInstall
const pluginType = PluginModel.getTypeFromNpmName(npmName) const pluginType = PluginModel.getTypeFromNpmName(npmName)
@ -368,6 +449,7 @@ export class PluginManager implements ServerHook {
version: packageJSON.version, version: packageJSON.version,
enabled: true, enabled: true,
uninstalled: false, uninstalled: false,
declarative: declarative,
peertubeEngine: packageJSON.engine.peertube peertubeEngine: packageJSON.engine.peertube
}, { returning: true }) }, { returning: true })

View File

@ -6,7 +6,7 @@ import { logger } from '../../helpers/logger.js'
import { CONFIG } from '../../initializers/config.js' import { CONFIG } from '../../initializers/config.js'
import { getLatestPluginVersion } from './plugin-index.js' import { getLatestPluginVersion } from './plugin-index.js'
async function installNpmPlugin (npmName: string, versionArg?: string) { async function installNpmPlugin (npmName: string, versionArg?: string, extraArgs?: string) {
// Security check // Security check
checkNpmPluginNameOrThrow(npmName) checkNpmPluginNameOrThrow(npmName)
if (versionArg) checkPluginVersionOrThrow(versionArg) if (versionArg) checkPluginVersionOrThrow(versionArg)
@ -15,13 +15,15 @@ async function installNpmPlugin (npmName: string, versionArg?: string) {
let toInstall = npmName let toInstall = npmName
if (version) toInstall += `@${version}` if (version) toInstall += `@${version}`
if (extraArgs) toInstall += ` ${extraArgs}`
const { stdout } = await execYarn('add ' + toInstall) const { stdout } = await execYarn('add ' + toInstall)
logger.debug('Added a yarn package.', { yarnStdout: stdout }) logger.debug('Added a yarn package.', { yarnStdout: stdout })
} }
async function installNpmPluginFromDisk (path: string) { async function installNpmPluginFromDisk (path: string, extraArgs?: string) {
if (extraArgs) path += ` ${extraArgs}`
await execYarn('add file:' + path) await execYarn('add file:' + path)
} }

View File

@ -86,6 +86,10 @@ export class PluginModel extends SequelizeModel<PluginModel> {
@Column(DataType.JSONB) @Column(DataType.JSONB)
storage: any storage: any
@AllowNull(true)
@Column
declarative: boolean
@CreatedAt @CreatedAt
createdAt: Date createdAt: Date
@ -103,6 +107,17 @@ export class PluginModel extends SequelizeModel<PluginModel> {
return PluginModel.findAll(query) return PluginModel.findAll(query)
} }
static listDeclarativePluginsAndThemes (): Promise<MPlugin[]> {
const query = {
where: {
declarative: true,
uninstalled: false
}
}
return PluginModel.findAll(query)
}
static loadByNpmName (npmName: string): Promise<MPlugin> { static loadByNpmName (npmName: string): Promise<MPlugin> {
const name = this.normalizePluginName(npmName) const name = this.normalizePluginName(npmName)
const type = this.getTypeFromNpmName(npmName) const type = this.getTypeFromNpmName(npmName)

View File

@ -352,6 +352,7 @@ async function startApplication () {
server.listen(port, hostname, async () => { server.listen(port, hostname, async () => {
if (cliOptions.plugins) { if (cliOptions.plugins) {
try { try {
await PluginManager.Instance.installDeclarativePlugins()
await PluginManager.Instance.rebuildNativePluginsIfNeeded() await PluginManager.Instance.rebuildNativePluginsIfNeeded()
await PluginManager.Instance.registerPluginsAndThemes() await PluginManager.Instance.registerPluginsAndThemes()