WIP plugins: move plugin CLI in peertube script
Install/uninstall/list plugins remotely
This commit is contained in:
parent
dba85a1e9e
commit
8d2be0ed7b
|
@ -32,8 +32,6 @@
|
|||
"clean:server:test": "scripty",
|
||||
"watch:client": "scripty",
|
||||
"watch:server": "scripty",
|
||||
"plugin:install": "node ./dist/scripts/plugin/install.js",
|
||||
"plugin:uninstall": "node ./dist/scripts/plugin/uninstall.js",
|
||||
"danger:clean:dev": "scripty",
|
||||
"danger:clean:prod": "scripty",
|
||||
"danger:clean:modules": "scripty",
|
||||
|
@ -45,6 +43,7 @@
|
|||
"dev": "scripty",
|
||||
"dev:server": "scripty",
|
||||
"dev:client": "scripty",
|
||||
"dev:cli": "scripty",
|
||||
"start": "node dist/server",
|
||||
"start:server": "node dist/server --no-client",
|
||||
"update-host": "node ./dist/scripts/update-host.js",
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
rm -rf ./dist/server/tools/
|
||||
|
||||
(
|
||||
cd ./server/tools
|
||||
yarn install --pure-lockfile
|
||||
)
|
||||
|
||||
mkdir -p "./dist/server/tools"
|
||||
cp -r "./server/tools/node_modules" "./dist/server/tools"
|
||||
|
||||
npm run tsc -- --watch --project ./server/tools/tsconfig.json
|
|
@ -1,39 +0,0 @@
|
|||
import { initDatabaseModels } from '../../server/initializers/database'
|
||||
import * as program from 'commander'
|
||||
import { PluginManager } from '../../server/lib/plugins/plugin-manager'
|
||||
import { isAbsolute } from 'path'
|
||||
|
||||
program
|
||||
.option('-n, --plugin-name [pluginName]', 'Plugin name to install')
|
||||
.option('-v, --plugin-version [pluginVersion]', 'Plugin version to install')
|
||||
.option('-p, --plugin-path [pluginPath]', 'Path of the plugin you want to install')
|
||||
.parse(process.argv)
|
||||
|
||||
if (!program['pluginName'] && !program['pluginPath']) {
|
||||
console.error('You need to specify a plugin name with the desired version, or a plugin path.')
|
||||
process.exit(-1)
|
||||
}
|
||||
|
||||
if (program['pluginName'] && !program['pluginVersion']) {
|
||||
console.error('You need to specify a the version of the plugin you want to install.')
|
||||
process.exit(-1)
|
||||
}
|
||||
|
||||
if (program['pluginPath'] && !isAbsolute(program['pluginPath'])) {
|
||||
console.error('Plugin path should be absolute.')
|
||||
process.exit(-1)
|
||||
}
|
||||
|
||||
run()
|
||||
.then(() => process.exit(0))
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
process.exit(-1)
|
||||
})
|
||||
|
||||
async function run () {
|
||||
await initDatabaseModels(true)
|
||||
|
||||
const toInstall = program['pluginName'] || program['pluginPath']
|
||||
await PluginManager.Instance.install(toInstall, program['pluginVersion'], !!program['pluginPath'])
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
import { initDatabaseModels } from '../../server/initializers/database'
|
||||
import * as program from 'commander'
|
||||
import { PluginManager } from '../../server/lib/plugins/plugin-manager'
|
||||
import { isAbsolute } from 'path'
|
||||
|
||||
program
|
||||
.option('-n, --package-name [packageName]', 'Package name to install')
|
||||
.parse(process.argv)
|
||||
|
||||
if (!program['packageName']) {
|
||||
console.error('You need to specify the plugin name.')
|
||||
process.exit(-1)
|
||||
}
|
||||
|
||||
run()
|
||||
.then(() => process.exit(0))
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
process.exit(-1)
|
||||
})
|
||||
|
||||
async function run () {
|
||||
await initDatabaseModels(true)
|
||||
|
||||
const toUninstall = program['packageName']
|
||||
await PluginManager.Instance.uninstall(toUninstall)
|
||||
}
|
|
@ -21,6 +21,7 @@ import {
|
|||
import { PluginManager } from '../../lib/plugins/plugin-manager'
|
||||
import { InstallPlugin } from '../../../shared/models/plugins/install-plugin.model'
|
||||
import { ManagePlugin } from '../../../shared/models/plugins/manage-plugin.model'
|
||||
import { logger } from '../../helpers/logger'
|
||||
|
||||
const pluginRouter = express.Router()
|
||||
|
||||
|
@ -46,7 +47,7 @@ pluginRouter.get('/:npmName/registered-settings',
|
|||
authenticate,
|
||||
ensureUserHasRight(UserRight.MANAGE_PLUGINS),
|
||||
asyncMiddleware(existingPluginValidator),
|
||||
asyncMiddleware(getPluginRegisteredSettings)
|
||||
getPluginRegisteredSettings
|
||||
)
|
||||
|
||||
pluginRouter.put('/:npmName/settings',
|
||||
|
@ -101,7 +102,14 @@ function getPlugin (req: express.Request, res: express.Response) {
|
|||
async function installPlugin (req: express.Request, res: express.Response) {
|
||||
const body: InstallPlugin = req.body
|
||||
|
||||
await PluginManager.Instance.install(body.npmName)
|
||||
const fromDisk = !!body.path
|
||||
const toInstall = body.npmName || body.path
|
||||
try {
|
||||
await PluginManager.Instance.install(toInstall, undefined, fromDisk)
|
||||
} catch (err) {
|
||||
logger.warn('Cannot install plugin %s.', toInstall, { err })
|
||||
return res.sendStatus(400)
|
||||
}
|
||||
|
||||
return res.sendStatus(204)
|
||||
}
|
||||
|
@ -114,10 +122,10 @@ async function uninstallPlugin (req: express.Request, res: express.Response) {
|
|||
return res.sendStatus(204)
|
||||
}
|
||||
|
||||
async function getPluginRegisteredSettings (req: express.Request, res: express.Response) {
|
||||
function getPluginRegisteredSettings (req: express.Request, res: express.Response) {
|
||||
const plugin = res.locals.plugin
|
||||
|
||||
const settings = await PluginManager.Instance.getSettings(plugin.name)
|
||||
const settings = PluginManager.Instance.getSettings(plugin.name)
|
||||
|
||||
return res.json({
|
||||
settings
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
import * as express from 'express'
|
||||
import { join } from 'path'
|
||||
import { RegisteredPlugin } from '../lib/plugins/plugin-manager'
|
||||
import { serveThemeCSSValidator } from '../middlewares/validators/themes'
|
||||
|
||||
const themesRouter = express.Router()
|
||||
|
||||
themesRouter.get('/:themeName/:themeVersion/css/:staticEndpoint(*)',
|
||||
serveThemeCSSValidator,
|
||||
serveThemeCSSDirectory
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
themesRouter
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function serveThemeCSSDirectory (req: express.Request, res: express.Response) {
|
||||
const plugin: RegisteredPlugin = res.locals.registeredPlugin
|
||||
const staticEndpoint = req.params.staticEndpoint
|
||||
|
||||
if (plugin.css.includes(staticEndpoint) === false) {
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
return res.sendFile(join(plugin.path, staticEndpoint))
|
||||
}
|
|
@ -3,7 +3,6 @@
|
|||
Useful to avoid circular dependencies.
|
||||
*/
|
||||
|
||||
import * as bcrypt from 'bcrypt'
|
||||
import * as createTorrent from 'create-torrent'
|
||||
import { createHash, HexBase64Latin1Encoding, pseudoRandomBytes } from 'crypto'
|
||||
import { isAbsolute, join } from 'path'
|
||||
|
@ -258,9 +257,6 @@ function promisify2WithVoid<T, U> (func: (arg1: T, arg2: U, cb: (err: any) => vo
|
|||
const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes)
|
||||
const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey)
|
||||
const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey)
|
||||
const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare)
|
||||
const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt)
|
||||
const bcryptHashPromise = promisify2<any, string | number, string>(bcrypt.hash)
|
||||
const createTorrentPromise = promisify2<string, any, any>(createTorrent)
|
||||
const execPromise2 = promisify2<string, any, string>(exec)
|
||||
const execPromise = promisify1<string, string>(exec)
|
||||
|
@ -287,13 +283,11 @@ export {
|
|||
|
||||
promisify0,
|
||||
promisify1,
|
||||
promisify2,
|
||||
|
||||
pseudoRandomBytesPromise,
|
||||
createPrivateKey,
|
||||
getPublicKey,
|
||||
bcryptComparePromise,
|
||||
bcryptGenSaltPromise,
|
||||
bcryptHashPromise,
|
||||
createTorrentPromise,
|
||||
execPromise2,
|
||||
execPromise
|
||||
|
|
|
@ -51,7 +51,9 @@ export {
|
|||
|
||||
function processVideoChannelExist (videoChannel: VideoChannelModel, res: express.Response) {
|
||||
if (!videoChannel) {
|
||||
``
|
||||
res.status(404)
|
||||
.json({ error: 'Video channel not found' })
|
||||
.end()
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
import { Request } from 'express'
|
||||
import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants'
|
||||
import { ActorModel } from '../models/activitypub/actor'
|
||||
import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey, sha256 } from './core-utils'
|
||||
import { createPrivateKey, getPublicKey, promisify1, promisify2, sha256 } from './core-utils'
|
||||
import { jsig, jsonld } from './custom-jsonld-signature'
|
||||
import { logger } from './logger'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { createVerify } from 'crypto'
|
||||
import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils'
|
||||
import * as bcrypt from 'bcrypt'
|
||||
|
||||
const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare)
|
||||
const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt)
|
||||
const bcryptHashPromise = promisify2<any, string | number, string>(bcrypt.hash)
|
||||
|
||||
const httpSignature = require('http-signature')
|
||||
|
||||
|
@ -147,3 +152,5 @@ export {
|
|||
cryptPassword,
|
||||
signJsonLDObject
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
@ -6,6 +6,7 @@ import { isPluginNameValid, isPluginTypeValid, isPluginVersionValid, isNpmPlugin
|
|||
import { PluginManager } from '../../lib/plugins/plugin-manager'
|
||||
import { isBooleanValid, isSafePath } from '../../helpers/custom-validators/misc'
|
||||
import { PluginModel } from '../../models/server/plugin'
|
||||
import { InstallPlugin } from '../../../shared/models/plugins/install-plugin.model'
|
||||
|
||||
const servePluginStaticDirectoryValidator = [
|
||||
param('pluginName').custom(isPluginNameValid).withMessage('Should have a valid plugin name'),
|
||||
|
@ -48,13 +49,25 @@ const listPluginsValidator = [
|
|||
]
|
||||
|
||||
const installPluginValidator = [
|
||||
body('npmName').custom(isNpmPluginNameValid).withMessage('Should have a valid npm name'),
|
||||
body('npmName')
|
||||
.optional()
|
||||
.custom(isNpmPluginNameValid).withMessage('Should have a valid npm name'),
|
||||
body('path')
|
||||
.optional()
|
||||
.custom(isSafePath).withMessage('Should have a valid safe path'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking installPluginValidator parameters', { parameters: req.body })
|
||||
|
||||
if (areValidationErrors(req, res)) return
|
||||
|
||||
const body: InstallPlugin = req.body
|
||||
if (!body.path && !body.npmName) {
|
||||
return res.status(400)
|
||||
.json({ error: 'Should have either a npmName or a path' })
|
||||
.end()
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
]
|
||||
|
|
|
@ -142,15 +142,17 @@ export class PluginModel extends Model<PluginModel> {
|
|||
count: number,
|
||||
sort: string
|
||||
}) {
|
||||
const { uninstalled = false } = options
|
||||
const query: FindAndCountOptions = {
|
||||
offset: options.start,
|
||||
limit: options.count,
|
||||
order: getSort(options.sort),
|
||||
where: {}
|
||||
where: {
|
||||
uninstalled
|
||||
}
|
||||
}
|
||||
|
||||
if (options.type) query.where['type'] = options.type
|
||||
if (options.uninstalled) query.where['uninstalled'] = options.uninstalled
|
||||
|
||||
return PluginModel
|
||||
.findAndCountAll(query)
|
||||
|
|
|
@ -67,6 +67,8 @@ describe('Test ActivityPub video channels search', function () {
|
|||
})
|
||||
|
||||
it('Should not find a remote video channel', async function () {
|
||||
this.timeout(15000)
|
||||
|
||||
{
|
||||
const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server3'
|
||||
const res = await searchVideoChannel(servers[ 0 ].url, search, servers[ 0 ].accessToken)
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { Netrc } from 'netrc-parser'
|
||||
import { getAppNumber, isTestInstance } from '../helpers/core-utils'
|
||||
import { join } from 'path'
|
||||
import { getVideoChannel, root } from '../../shared/extra-utils'
|
||||
import { root } from '../../shared/extra-utils/miscs/miscs'
|
||||
import { getVideoChannel } from '../../shared/extra-utils/videos/video-channels'
|
||||
import { Command } from 'commander'
|
||||
import { VideoChannel, VideoPrivacy } from '../../shared/models/videos'
|
||||
|
||||
|
@ -64,7 +65,11 @@ function deleteSettings () {
|
|||
})
|
||||
}
|
||||
|
||||
function getRemoteObjectOrDie (program: any, settings: Settings, netrc: Netrc) {
|
||||
function getRemoteObjectOrDie (
|
||||
program: any,
|
||||
settings: Settings,
|
||||
netrc: Netrc
|
||||
): { url: string, username: string, password: string } {
|
||||
if (!program['url'] || !program['username'] || !program['password']) {
|
||||
// No remote and we don't have program parameters: quit
|
||||
if (settings.remotes.length === 0 || Object.keys(netrc.machines).length === 0) {
|
||||
|
@ -161,6 +166,13 @@ async function buildVideoAttributesFromCommander (url: string, command: Command,
|
|||
return videoAttributes
|
||||
}
|
||||
|
||||
function getServerCredentials (program: any) {
|
||||
return Promise.all([ getSettings(), getNetrc() ])
|
||||
.then(([ settings, netrc ]) => {
|
||||
return getRemoteObjectOrDie(program, settings, netrc)
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
|
@ -172,6 +184,8 @@ export {
|
|||
writeSettings,
|
||||
deleteSettings,
|
||||
|
||||
getServerCredentials,
|
||||
|
||||
buildCommonVideoOptions,
|
||||
buildVideoAttributesFromCommander
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import * as program from 'commander'
|
||||
import * as prompt from 'prompt'
|
||||
import { getSettings, writeSettings, getNetrc } from './cli'
|
||||
import { isHostValid } from '../helpers/custom-validators/servers'
|
||||
import { getNetrc, getSettings, writeSettings } from './cli'
|
||||
import { isUserUsernameValid } from '../helpers/custom-validators/users'
|
||||
import { getAccessToken, login } from '../../shared/extra-utils'
|
||||
|
||||
const Table = require('cli-table')
|
||||
|
||||
|
@ -76,6 +76,14 @@ program
|
|||
}
|
||||
}
|
||||
}, async (_, result) => {
|
||||
// Check credentials
|
||||
try {
|
||||
await getAccessToken(result.url, result.username, result.password)
|
||||
} catch (err) {
|
||||
console.error(err.message)
|
||||
process.exit(-1)
|
||||
}
|
||||
|
||||
await setInstance(result.url, result.username, result.password, program['default'])
|
||||
|
||||
process.exit(0)
|
||||
|
|
|
@ -11,7 +11,7 @@ import * as prompt from 'prompt'
|
|||
import { remove } from 'fs-extra'
|
||||
import { sha256 } from '../helpers/core-utils'
|
||||
import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl'
|
||||
import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getNetrc, getRemoteObjectOrDie, getSettings } from './cli'
|
||||
import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getServerCredentials } from './cli'
|
||||
|
||||
type UserInfo = {
|
||||
username: string
|
||||
|
@ -36,10 +36,8 @@ command
|
|||
.option('-v, --verbose', 'Verbose mode')
|
||||
.parse(process.argv)
|
||||
|
||||
Promise.all([ getSettings(), getNetrc() ])
|
||||
.then(([ settings, netrc ]) => {
|
||||
const { url, username, password } = getRemoteObjectOrDie(program, settings, netrc)
|
||||
|
||||
getServerCredentials(command)
|
||||
.then(({ url, username, password }) => {
|
||||
if (!program[ 'targetUrl' ]) {
|
||||
console.error('--targetUrl field is required.')
|
||||
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
import * as program from 'commander'
|
||||
import { PluginType } from '../../shared/models/plugins/plugin.type'
|
||||
import { getAccessToken } from '../../shared/extra-utils/users/login'
|
||||
import { getMyUserInformation } from '../../shared/extra-utils/users/users'
|
||||
import { installPlugin, listPlugins, uninstallPlugin } from '../../shared/extra-utils/server/plugins'
|
||||
import { getServerCredentials } from './cli'
|
||||
import { User, UserRole } from '../../shared/models/users'
|
||||
import { PeerTubePlugin } from '../../shared/models/plugins/peertube-plugin.model'
|
||||
import { isAbsolute } from 'path'
|
||||
|
||||
const Table = require('cli-table')
|
||||
|
||||
program
|
||||
.name('plugins')
|
||||
.usage('[command] [options]')
|
||||
|
||||
program
|
||||
.command('list')
|
||||
.description('List installed plugins')
|
||||
.option('-u, --url <url>', 'Server url')
|
||||
.option('-U, --username <username>', 'Username')
|
||||
.option('-p, --password <token>', 'Password')
|
||||
.option('-t, --only-themes', 'List themes only')
|
||||
.option('-P, --only-plugins', 'List plugins only')
|
||||
.action(() => pluginsListCLI())
|
||||
|
||||
program
|
||||
.command('install')
|
||||
.description('Install a plugin or a theme')
|
||||
.option('-u, --url <url>', 'Server url')
|
||||
.option('-U, --username <username>', 'Username')
|
||||
.option('-p, --password <token>', 'Password')
|
||||
.option('-P --path <path>', 'Install from a path')
|
||||
.option('-n, --npm-name <npmName>', 'Install from npm')
|
||||
.action((options) => installPluginCLI(options))
|
||||
|
||||
program
|
||||
.command('uninstall')
|
||||
.description('Uninstall a plugin or a theme')
|
||||
.option('-u, --url <url>', 'Server url')
|
||||
.option('-U, --username <username>', 'Username')
|
||||
.option('-p, --password <token>', 'Password')
|
||||
.option('-n, --npm-name <npmName>', 'NPM plugin/theme name')
|
||||
.action(options => uninstallPluginCLI(options))
|
||||
|
||||
if (!process.argv.slice(2).length) {
|
||||
program.outputHelp()
|
||||
}
|
||||
|
||||
program.parse(process.argv)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
async function pluginsListCLI () {
|
||||
const { url, username, password } = await getServerCredentials(program)
|
||||
const accessToken = await getAdminTokenOrDie(url, username, password)
|
||||
|
||||
let type: PluginType
|
||||
if (program['onlyThemes']) type = PluginType.THEME
|
||||
if (program['onlyPlugins']) type = PluginType.PLUGIN
|
||||
|
||||
const res = await listPlugins({
|
||||
url,
|
||||
accessToken,
|
||||
start: 0,
|
||||
count: 100,
|
||||
sort: 'name',
|
||||
type
|
||||
})
|
||||
const plugins: PeerTubePlugin[] = res.body.data
|
||||
|
||||
const table = new Table({
|
||||
head: ['name', 'version', 'homepage'],
|
||||
colWidths: [ 50, 10, 50 ]
|
||||
})
|
||||
|
||||
for (const plugin of plugins) {
|
||||
const npmName = plugin.type === PluginType.PLUGIN
|
||||
? 'peertube-plugin-' + plugin.name
|
||||
: 'peertube-theme-' + plugin.name
|
||||
|
||||
table.push([
|
||||
npmName,
|
||||
plugin.version,
|
||||
plugin.homepage
|
||||
])
|
||||
}
|
||||
|
||||
console.log(table.toString())
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
async function installPluginCLI (options: any) {
|
||||
if (!options['path'] && !options['npmName']) {
|
||||
console.error('You need to specify the npm name or the path of the plugin you want to install.\n')
|
||||
program.outputHelp()
|
||||
process.exit(-1)
|
||||
}
|
||||
|
||||
if (options['path'] && !isAbsolute(options['path'])) {
|
||||
console.error('Path should be absolute.')
|
||||
process.exit(-1)
|
||||
}
|
||||
|
||||
const { url, username, password } = await getServerCredentials(options)
|
||||
const accessToken = await getAdminTokenOrDie(url, username, password)
|
||||
|
||||
try {
|
||||
await installPlugin({
|
||||
url,
|
||||
accessToken,
|
||||
npmName: options['npmName'],
|
||||
path: options['path']
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Cannot install plugin.', err)
|
||||
process.exit(-1)
|
||||
return
|
||||
}
|
||||
|
||||
console.log('Plugin installed.')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
async function uninstallPluginCLI (options: any) {
|
||||
if (!options['npmName']) {
|
||||
console.error('You need to specify the npm name of the plugin/theme you want to uninstall.\n')
|
||||
program.outputHelp()
|
||||
process.exit(-1)
|
||||
}
|
||||
|
||||
const { url, username, password } = await getServerCredentials(options)
|
||||
const accessToken = await getAdminTokenOrDie(url, username, password)
|
||||
|
||||
try {
|
||||
await uninstallPlugin({
|
||||
url,
|
||||
accessToken,
|
||||
npmName: options[ 'npmName' ]
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Cannot uninstall plugin.', err)
|
||||
process.exit(-1)
|
||||
return
|
||||
}
|
||||
|
||||
console.log('Plugin uninstalled.')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
async function getAdminTokenOrDie (url: string, username: string, password: string) {
|
||||
const accessToken = await getAccessToken(url, username, password)
|
||||
const resMe = await getMyUserInformation(url, accessToken)
|
||||
const me: User = resMe.body
|
||||
|
||||
if (me.role !== UserRole.ADMINISTRATOR) {
|
||||
console.error('Cannot list plugins if you are not administrator.')
|
||||
process.exit(-1)
|
||||
}
|
||||
|
||||
return accessToken
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
import * as program from 'commander'
|
||||
import { access, constants } from 'fs-extra'
|
||||
import { isAbsolute } from 'path'
|
||||
import { getClient, login } from '../../shared/extra-utils'
|
||||
import { getAccessToken } from '../../shared/extra-utils'
|
||||
import { uploadVideo } from '../../shared/extra-utils/'
|
||||
import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getNetrc, getRemoteObjectOrDie, getSettings } from './cli'
|
||||
import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getServerCredentials } from './cli'
|
||||
|
||||
let command = program
|
||||
.name('upload')
|
||||
|
@ -11,7 +11,6 @@ let command = program
|
|||
command = buildCommonVideoOptions(command)
|
||||
|
||||
command
|
||||
|
||||
.option('-u, --url <url>', 'Server url')
|
||||
.option('-U, --username <username>', 'Username')
|
||||
.option('-p, --password <token>', 'Password')
|
||||
|
@ -20,10 +19,8 @@ command
|
|||
.option('-f, --file <file>', 'Video absolute file path')
|
||||
.parse(process.argv)
|
||||
|
||||
Promise.all([ getSettings(), getNetrc() ])
|
||||
.then(([ settings, netrc ]) => {
|
||||
const { url, username, password } = getRemoteObjectOrDie(program, settings, netrc)
|
||||
|
||||
getServerCredentials(command)
|
||||
.then(({ url, username, password }) => {
|
||||
if (!program[ 'videoName' ] || !program[ 'file' ]) {
|
||||
if (!program[ 'videoName' ]) console.error('--video-name is required.')
|
||||
if (!program[ 'file' ]) console.error('--file is required.')
|
||||
|
@ -43,21 +40,7 @@ Promise.all([ getSettings(), getNetrc() ])
|
|||
})
|
||||
|
||||
async function run (url: string, username: string, password: string) {
|
||||
const resClient = await getClient(url)
|
||||
const client = {
|
||||
id: resClient.body.client_id,
|
||||
secret: resClient.body.client_secret
|
||||
}
|
||||
|
||||
const user = { username, password }
|
||||
|
||||
let accessToken: string
|
||||
try {
|
||||
const res = await login(url, client, user)
|
||||
accessToken = res.body.access_token
|
||||
} catch (err) {
|
||||
throw new Error('Cannot authenticate. Please check your username/password.')
|
||||
}
|
||||
const accessToken = await getAccessToken(url, username, password)
|
||||
|
||||
await access(program[ 'file' ], constants.F_OK)
|
||||
|
||||
|
|
|
@ -18,13 +18,10 @@ program
|
|||
.command('get-access-token', 'get a peertube access token', { noHelp: true }).alias('token')
|
||||
.command('watch', 'watch a video in the terminal ✩°。⋆').alias('w')
|
||||
.command('repl', 'initiate a REPL to access internals')
|
||||
.command('plugins [action]', 'manage plugins on a local instance').alias('p')
|
||||
|
||||
/* Not Yet Implemented */
|
||||
program
|
||||
.command('plugins [action]',
|
||||
'manage plugins on a local instance',
|
||||
{ noHelp: true } as program.CommandOptions
|
||||
).alias('p')
|
||||
.command('diagnostic [action]',
|
||||
'like couple therapy, but for your instance',
|
||||
{ noHelp: true } as program.CommandOptions
|
||||
|
|
|
@ -11,6 +11,7 @@ export * from './server/follows'
|
|||
export * from './requests/requests'
|
||||
export * from './requests/check-api-params'
|
||||
export * from './server/servers'
|
||||
export * from './server/plugins'
|
||||
export * from './videos/services'
|
||||
export * from './videos/video-playlists'
|
||||
export * from './users/users'
|
||||
|
|
|
@ -8,7 +8,7 @@ import { pathExists, readFile } from 'fs-extra'
|
|||
import * as ffmpeg from 'fluent-ffmpeg'
|
||||
|
||||
const expect = chai.expect
|
||||
let webtorrent = new WebTorrent()
|
||||
let webtorrent: WebTorrent.Instance
|
||||
|
||||
function immutableAssign <T, U> (target: T, source: U) {
|
||||
return Object.assign<{}, T, U>({}, target, source)
|
||||
|
@ -27,6 +27,9 @@ function wait (milliseconds: number) {
|
|||
}
|
||||
|
||||
function webtorrentAdd (torrent: string, refreshWebTorrent = false) {
|
||||
const WebTorrent = require('webtorrent')
|
||||
|
||||
if (!webtorrent) webtorrent = new WebTorrent()
|
||||
if (refreshWebTorrent === true) webtorrent = new WebTorrent()
|
||||
|
||||
return new Promise<WebTorrent.Torrent>(res => webtorrent.add(torrent, res))
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
import { makeGetRequest, makePostBodyRequest } from '../requests/requests'
|
||||
import { PluginType } from '../../models/plugins/plugin.type'
|
||||
|
||||
function listPlugins (parameters: {
|
||||
url: string,
|
||||
accessToken: string,
|
||||
start?: number,
|
||||
count?: number,
|
||||
sort?: string,
|
||||
type?: PluginType,
|
||||
expectedStatus?: number
|
||||
}) {
|
||||
const { url, accessToken, start, count, sort, type, expectedStatus = 200 } = parameters
|
||||
const path = '/api/v1/plugins'
|
||||
|
||||
return makeGetRequest({
|
||||
url,
|
||||
path,
|
||||
token: accessToken,
|
||||
query: {
|
||||
start,
|
||||
count,
|
||||
sort,
|
||||
type
|
||||
},
|
||||
statusCodeExpected: expectedStatus
|
||||
})
|
||||
}
|
||||
|
||||
function getPlugin (parameters: {
|
||||
url: string,
|
||||
accessToken: string,
|
||||
npmName: string,
|
||||
expectedStatus?: number
|
||||
}) {
|
||||
const { url, accessToken, npmName, expectedStatus = 200 } = parameters
|
||||
const path = '/api/v1/plugins/' + npmName
|
||||
|
||||
return makeGetRequest({
|
||||
url,
|
||||
path,
|
||||
token: accessToken,
|
||||
statusCodeExpected: expectedStatus
|
||||
})
|
||||
}
|
||||
|
||||
function getPluginSettings (parameters: {
|
||||
url: string,
|
||||
accessToken: string,
|
||||
npmName: string,
|
||||
expectedStatus?: number
|
||||
}) {
|
||||
const { url, accessToken, npmName, expectedStatus = 200 } = parameters
|
||||
const path = '/api/v1/plugins/' + npmName + '/settings'
|
||||
|
||||
return makeGetRequest({
|
||||
url,
|
||||
path,
|
||||
token: accessToken,
|
||||
statusCodeExpected: expectedStatus
|
||||
})
|
||||
}
|
||||
|
||||
function getPluginRegisteredSettings (parameters: {
|
||||
url: string,
|
||||
accessToken: string,
|
||||
npmName: string,
|
||||
expectedStatus?: number
|
||||
}) {
|
||||
const { url, accessToken, npmName, expectedStatus = 200 } = parameters
|
||||
const path = '/api/v1/plugins/' + npmName + '/registered-settings'
|
||||
|
||||
return makeGetRequest({
|
||||
url,
|
||||
path,
|
||||
token: accessToken,
|
||||
statusCodeExpected: expectedStatus
|
||||
})
|
||||
}
|
||||
|
||||
function installPlugin (parameters: {
|
||||
url: string,
|
||||
accessToken: string,
|
||||
path?: string,
|
||||
npmName?: string
|
||||
expectedStatus?: number
|
||||
}) {
|
||||
const { url, accessToken, npmName, path, expectedStatus = 204 } = parameters
|
||||
const apiPath = '/api/v1/plugins/install'
|
||||
|
||||
return makePostBodyRequest({
|
||||
url,
|
||||
path: apiPath,
|
||||
token: accessToken,
|
||||
fields: { npmName, path },
|
||||
statusCodeExpected: expectedStatus
|
||||
})
|
||||
}
|
||||
|
||||
function uninstallPlugin (parameters: {
|
||||
url: string,
|
||||
accessToken: string,
|
||||
npmName: string
|
||||
expectedStatus?: number
|
||||
}) {
|
||||
const { url, accessToken, npmName, expectedStatus = 204 } = parameters
|
||||
const apiPath = '/api/v1/plugins/uninstall'
|
||||
|
||||
return makePostBodyRequest({
|
||||
url,
|
||||
path: apiPath,
|
||||
token: accessToken,
|
||||
fields: { npmName },
|
||||
statusCodeExpected: expectedStatus
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
listPlugins,
|
||||
installPlugin,
|
||||
getPlugin,
|
||||
uninstallPlugin,
|
||||
getPluginSettings,
|
||||
getPluginRegisteredSettings
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
import { ChildProcess, exec, fork } from 'child_process'
|
||||
import { join } from 'path'
|
||||
import { root, wait } from '../miscs/miscs'
|
||||
import { copy, readdir, readFile, remove } from 'fs-extra'
|
||||
import { copy, pathExists, readdir, readFile, remove } from 'fs-extra'
|
||||
import { existsSync } from 'fs'
|
||||
import { expect } from 'chai'
|
||||
import { VideoChannel } from '../../models/videos'
|
||||
|
@ -241,20 +241,22 @@ async function reRunServer (server: ServerInfo, configOverride?: any) {
|
|||
return server
|
||||
}
|
||||
|
||||
async function checkTmpIsEmpty (server: ServerInfo) {
|
||||
return checkDirectoryIsEmpty(server, 'tmp')
|
||||
function checkTmpIsEmpty (server: ServerInfo) {
|
||||
return checkDirectoryIsEmpty(server, 'tmp', [ 'plugins-global.css' ])
|
||||
}
|
||||
|
||||
async function checkDirectoryIsEmpty (server: ServerInfo, directory: string) {
|
||||
async function checkDirectoryIsEmpty (server: ServerInfo, directory: string, exceptions: string[] = []) {
|
||||
const testDirectory = 'test' + server.internalServerNumber
|
||||
|
||||
const directoryPath = join(root(), testDirectory, directory)
|
||||
|
||||
const directoryExists = existsSync(directoryPath)
|
||||
const directoryExists = await pathExists(directoryPath)
|
||||
expect(directoryExists).to.be.true
|
||||
|
||||
const files = await readdir(directoryPath)
|
||||
expect(files).to.have.lengthOf(0)
|
||||
const filtered = files.filter(f => exceptions.includes(f) === false)
|
||||
|
||||
expect(filtered).to.have.lengthOf(0)
|
||||
}
|
||||
|
||||
function killallServers (servers: ServerInfo[]) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as request from 'supertest'
|
||||
|
||||
import { ServerInfo } from '../server/servers'
|
||||
import { getClient } from '../server/clients'
|
||||
|
||||
type Client = { id: string, secret: string }
|
||||
type User = { username: string, password: string }
|
||||
|
@ -38,6 +39,23 @@ async function userLogin (server: Server, user: User, expectedStatus = 200) {
|
|||
return res.body.access_token as string
|
||||
}
|
||||
|
||||
async function getAccessToken (url: string, username: string, password: string) {
|
||||
const resClient = await getClient(url)
|
||||
const client = {
|
||||
id: resClient.body.client_id,
|
||||
secret: resClient.body.client_secret
|
||||
}
|
||||
|
||||
const user = { username, password }
|
||||
|
||||
try {
|
||||
const res = await login(url, client, user)
|
||||
return res.body.access_token
|
||||
} catch (err) {
|
||||
throw new Error('Cannot authenticate. Please check your username/password.')
|
||||
}
|
||||
}
|
||||
|
||||
function setAccessTokensToServers (servers: ServerInfo[]) {
|
||||
const tasks: Promise<any>[] = []
|
||||
|
||||
|
@ -55,6 +73,7 @@ export {
|
|||
login,
|
||||
serverLogin,
|
||||
userLogin,
|
||||
getAccessToken,
|
||||
setAccessTokensToServers,
|
||||
Server,
|
||||
Client,
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import * as request from 'supertest'
|
||||
import { makeGetRequest, makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests'
|
||||
|
||||
import { UserCreate, UserRole } from '../../index'
|
||||
import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests'
|
||||
import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type'
|
||||
import { ServerInfo, userLogin } from '..'
|
||||
import { UserAdminFlag } from '../../models/users/user-flag.model'
|
||||
import { UserRegister } from '../../models/users/user-register.model'
|
||||
import { UserRole } from '../../models/users/user-role'
|
||||
import { ServerInfo } from '../server/servers'
|
||||
import { userLogin } from './login'
|
||||
|
||||
type CreateUserArgs = { url: string,
|
||||
accessToken: string,
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import * as request from 'supertest'
|
||||
import { VideoChannelCreate, VideoChannelUpdate } from '../../models/videos'
|
||||
import { VideoChannelUpdate } from '../../models/videos/channel/video-channel-update.model'
|
||||
import { VideoChannelCreate } from '../../models/videos/channel/video-channel-create.model'
|
||||
import { makeGetRequest, updateAvatarRequest } from '../requests/requests'
|
||||
import { getMyUserInformation, ServerInfo } from '..'
|
||||
import { User } from '../..'
|
||||
import { ServerInfo } from '../server/servers'
|
||||
import { User } from '../../models/users/user.model'
|
||||
import { getMyUserInformation } from '../users/users'
|
||||
|
||||
function getVideoChannelsList (url: string, start: number, count: number, sort?: string) {
|
||||
const path = '/api/v1/video-channels'
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export interface InstallPlugin {
|
||||
npmName: string
|
||||
npmName?: string
|
||||
path?: string
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue