diff options
author | Chocobozzz <me@florianbigard.com> | 2019-07-11 17:23:24 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2019-07-24 10:58:16 +0200 |
commit | 8d2be0ed7bb87283a1ec98609df6b82d83db706a (patch) | |
tree | 31a36b252df32be83ceb77658a53b57f9d15e8ac /server | |
parent | dba85a1e9e9f603ba52e1ea42deaf3fdd799b1d8 (diff) | |
download | PeerTube-8d2be0ed7bb87283a1ec98609df6b82d83db706a.tar.gz PeerTube-8d2be0ed7bb87283a1ec98609df6b82d83db706a.tar.zst PeerTube-8d2be0ed7bb87283a1ec98609df6b82d83db706a.zip |
WIP plugins: move plugin CLI in peertube script
Install/uninstall/list plugins remotely
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/api/plugins.ts | 16 | ||||
-rw-r--r-- | server/controllers/themes.ts | 30 | ||||
-rw-r--r-- | server/helpers/core-utils.ts | 8 | ||||
-rw-r--r-- | server/helpers/custom-validators/video-channels.ts | 4 | ||||
-rw-r--r-- | server/helpers/peertube-crypto.ts | 9 | ||||
-rw-r--r-- | server/middlewares/validators/plugins.ts | 15 | ||||
-rw-r--r-- | server/models/server/plugin.ts | 6 | ||||
-rw-r--r-- | server/tests/api/search/search-activitypub-video-channels.ts | 2 | ||||
-rw-r--r-- | server/tools/cli.ts | 18 | ||||
-rw-r--r-- | server/tools/peertube-auth.ts | 12 | ||||
-rw-r--r-- | server/tools/peertube-import-videos.ts | 34 | ||||
-rw-r--r-- | server/tools/peertube-plugins.ts | 162 | ||||
-rw-r--r-- | server/tools/peertube-upload.ts | 55 | ||||
-rw-r--r-- | server/tools/peertube.ts | 5 |
14 files changed, 268 insertions, 108 deletions
diff --git a/server/controllers/api/plugins.ts b/server/controllers/api/plugins.ts index f17e8cab9..8e59f27cf 100644 --- a/server/controllers/api/plugins.ts +++ b/server/controllers/api/plugins.ts | |||
@@ -21,6 +21,7 @@ import { | |||
21 | import { PluginManager } from '../../lib/plugins/plugin-manager' | 21 | import { PluginManager } from '../../lib/plugins/plugin-manager' |
22 | import { InstallPlugin } from '../../../shared/models/plugins/install-plugin.model' | 22 | import { InstallPlugin } from '../../../shared/models/plugins/install-plugin.model' |
23 | import { ManagePlugin } from '../../../shared/models/plugins/manage-plugin.model' | 23 | import { ManagePlugin } from '../../../shared/models/plugins/manage-plugin.model' |
24 | import { logger } from '../../helpers/logger' | ||
24 | 25 | ||
25 | const pluginRouter = express.Router() | 26 | const pluginRouter = express.Router() |
26 | 27 | ||
@@ -46,7 +47,7 @@ pluginRouter.get('/:npmName/registered-settings', | |||
46 | authenticate, | 47 | authenticate, |
47 | ensureUserHasRight(UserRight.MANAGE_PLUGINS), | 48 | ensureUserHasRight(UserRight.MANAGE_PLUGINS), |
48 | asyncMiddleware(existingPluginValidator), | 49 | asyncMiddleware(existingPluginValidator), |
49 | asyncMiddleware(getPluginRegisteredSettings) | 50 | getPluginRegisteredSettings |
50 | ) | 51 | ) |
51 | 52 | ||
52 | pluginRouter.put('/:npmName/settings', | 53 | pluginRouter.put('/:npmName/settings', |
@@ -101,7 +102,14 @@ function getPlugin (req: express.Request, res: express.Response) { | |||
101 | async function installPlugin (req: express.Request, res: express.Response) { | 102 | async function installPlugin (req: express.Request, res: express.Response) { |
102 | const body: InstallPlugin = req.body | 103 | const body: InstallPlugin = req.body |
103 | 104 | ||
104 | await PluginManager.Instance.install(body.npmName) | 105 | const fromDisk = !!body.path |
106 | const toInstall = body.npmName || body.path | ||
107 | try { | ||
108 | await PluginManager.Instance.install(toInstall, undefined, fromDisk) | ||
109 | } catch (err) { | ||
110 | logger.warn('Cannot install plugin %s.', toInstall, { err }) | ||
111 | return res.sendStatus(400) | ||
112 | } | ||
105 | 113 | ||
106 | return res.sendStatus(204) | 114 | return res.sendStatus(204) |
107 | } | 115 | } |
@@ -114,10 +122,10 @@ async function uninstallPlugin (req: express.Request, res: express.Response) { | |||
114 | return res.sendStatus(204) | 122 | return res.sendStatus(204) |
115 | } | 123 | } |
116 | 124 | ||
117 | async function getPluginRegisteredSettings (req: express.Request, res: express.Response) { | 125 | function getPluginRegisteredSettings (req: express.Request, res: express.Response) { |
118 | const plugin = res.locals.plugin | 126 | const plugin = res.locals.plugin |
119 | 127 | ||
120 | const settings = await PluginManager.Instance.getSettings(plugin.name) | 128 | const settings = PluginManager.Instance.getSettings(plugin.name) |
121 | 129 | ||
122 | return res.json({ | 130 | return res.json({ |
123 | settings | 131 | settings |
diff --git a/server/controllers/themes.ts b/server/controllers/themes.ts deleted file mode 100644 index 104c285ad..000000000 --- a/server/controllers/themes.ts +++ /dev/null | |||
@@ -1,30 +0,0 @@ | |||
1 | import * as express from 'express' | ||
2 | import { join } from 'path' | ||
3 | import { RegisteredPlugin } from '../lib/plugins/plugin-manager' | ||
4 | import { serveThemeCSSValidator } from '../middlewares/validators/themes' | ||
5 | |||
6 | const themesRouter = express.Router() | ||
7 | |||
8 | themesRouter.get('/:themeName/:themeVersion/css/:staticEndpoint(*)', | ||
9 | serveThemeCSSValidator, | ||
10 | serveThemeCSSDirectory | ||
11 | ) | ||
12 | |||
13 | // --------------------------------------------------------------------------- | ||
14 | |||
15 | export { | ||
16 | themesRouter | ||
17 | } | ||
18 | |||
19 | // --------------------------------------------------------------------------- | ||
20 | |||
21 | function serveThemeCSSDirectory (req: express.Request, res: express.Response) { | ||
22 | const plugin: RegisteredPlugin = res.locals.registeredPlugin | ||
23 | const staticEndpoint = req.params.staticEndpoint | ||
24 | |||
25 | if (plugin.css.includes(staticEndpoint) === false) { | ||
26 | return res.sendStatus(404) | ||
27 | } | ||
28 | |||
29 | return res.sendFile(join(plugin.path, staticEndpoint)) | ||
30 | } | ||
diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts index c5b139378..64818d036 100644 --- a/server/helpers/core-utils.ts +++ b/server/helpers/core-utils.ts | |||
@@ -3,7 +3,6 @@ | |||
3 | Useful to avoid circular dependencies. | 3 | Useful to avoid circular dependencies. |
4 | */ | 4 | */ |
5 | 5 | ||
6 | import * as bcrypt from 'bcrypt' | ||
7 | import * as createTorrent from 'create-torrent' | 6 | import * as createTorrent from 'create-torrent' |
8 | import { createHash, HexBase64Latin1Encoding, pseudoRandomBytes } from 'crypto' | 7 | import { createHash, HexBase64Latin1Encoding, pseudoRandomBytes } from 'crypto' |
9 | import { isAbsolute, join } from 'path' | 8 | import { isAbsolute, join } from 'path' |
@@ -258,9 +257,6 @@ function promisify2WithVoid<T, U> (func: (arg1: T, arg2: U, cb: (err: any) => vo | |||
258 | const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes) | 257 | const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes) |
259 | const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey) | 258 | const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey) |
260 | const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey) | 259 | const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey) |
261 | const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare) | ||
262 | const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt) | ||
263 | const bcryptHashPromise = promisify2<any, string | number, string>(bcrypt.hash) | ||
264 | const createTorrentPromise = promisify2<string, any, any>(createTorrent) | 260 | const createTorrentPromise = promisify2<string, any, any>(createTorrent) |
265 | const execPromise2 = promisify2<string, any, string>(exec) | 261 | const execPromise2 = promisify2<string, any, string>(exec) |
266 | const execPromise = promisify1<string, string>(exec) | 262 | const execPromise = promisify1<string, string>(exec) |
@@ -287,13 +283,11 @@ export { | |||
287 | 283 | ||
288 | promisify0, | 284 | promisify0, |
289 | promisify1, | 285 | promisify1, |
286 | promisify2, | ||
290 | 287 | ||
291 | pseudoRandomBytesPromise, | 288 | pseudoRandomBytesPromise, |
292 | createPrivateKey, | 289 | createPrivateKey, |
293 | getPublicKey, | 290 | getPublicKey, |
294 | bcryptComparePromise, | ||
295 | bcryptGenSaltPromise, | ||
296 | bcryptHashPromise, | ||
297 | createTorrentPromise, | 291 | createTorrentPromise, |
298 | execPromise2, | 292 | execPromise2, |
299 | execPromise | 293 | execPromise |
diff --git a/server/helpers/custom-validators/video-channels.ts b/server/helpers/custom-validators/video-channels.ts index e1a2f9503..f818ce8f1 100644 --- a/server/helpers/custom-validators/video-channels.ts +++ b/server/helpers/custom-validators/video-channels.ts | |||
@@ -51,7 +51,9 @@ export { | |||
51 | 51 | ||
52 | function processVideoChannelExist (videoChannel: VideoChannelModel, res: express.Response) { | 52 | function processVideoChannelExist (videoChannel: VideoChannelModel, res: express.Response) { |
53 | if (!videoChannel) { | 53 | if (!videoChannel) { |
54 | `` | 54 | res.status(404) |
55 | .json({ error: 'Video channel not found' }) | ||
56 | .end() | ||
55 | 57 | ||
56 | return false | 58 | return false |
57 | } | 59 | } |
diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts index 9148df2eb..1424949d0 100644 --- a/server/helpers/peertube-crypto.ts +++ b/server/helpers/peertube-crypto.ts | |||
@@ -1,12 +1,17 @@ | |||
1 | import { Request } from 'express' | 1 | import { Request } from 'express' |
2 | import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants' | 2 | import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants' |
3 | import { ActorModel } from '../models/activitypub/actor' | 3 | import { ActorModel } from '../models/activitypub/actor' |
4 | import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey, sha256 } from './core-utils' | 4 | import { createPrivateKey, getPublicKey, promisify1, promisify2, sha256 } from './core-utils' |
5 | import { jsig, jsonld } from './custom-jsonld-signature' | 5 | import { jsig, jsonld } from './custom-jsonld-signature' |
6 | import { logger } from './logger' | 6 | import { logger } from './logger' |
7 | import { cloneDeep } from 'lodash' | 7 | import { cloneDeep } from 'lodash' |
8 | import { createVerify } from 'crypto' | 8 | import { createVerify } from 'crypto' |
9 | import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils' | 9 | import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils' |
10 | import * as bcrypt from 'bcrypt' | ||
11 | |||
12 | const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare) | ||
13 | const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt) | ||
14 | const bcryptHashPromise = promisify2<any, string | number, string>(bcrypt.hash) | ||
10 | 15 | ||
11 | const httpSignature = require('http-signature') | 16 | const httpSignature = require('http-signature') |
12 | 17 | ||
@@ -147,3 +152,5 @@ export { | |||
147 | cryptPassword, | 152 | cryptPassword, |
148 | signJsonLDObject | 153 | signJsonLDObject |
149 | } | 154 | } |
155 | |||
156 | // --------------------------------------------------------------------------- | ||
diff --git a/server/middlewares/validators/plugins.ts b/server/middlewares/validators/plugins.ts index a06add6b8..a1634ded4 100644 --- a/server/middlewares/validators/plugins.ts +++ b/server/middlewares/validators/plugins.ts | |||
@@ -6,6 +6,7 @@ import { isPluginNameValid, isPluginTypeValid, isPluginVersionValid, isNpmPlugin | |||
6 | import { PluginManager } from '../../lib/plugins/plugin-manager' | 6 | import { PluginManager } from '../../lib/plugins/plugin-manager' |
7 | import { isBooleanValid, isSafePath } from '../../helpers/custom-validators/misc' | 7 | import { isBooleanValid, isSafePath } from '../../helpers/custom-validators/misc' |
8 | import { PluginModel } from '../../models/server/plugin' | 8 | import { PluginModel } from '../../models/server/plugin' |
9 | import { InstallPlugin } from '../../../shared/models/plugins/install-plugin.model' | ||
9 | 10 | ||
10 | const servePluginStaticDirectoryValidator = [ | 11 | const servePluginStaticDirectoryValidator = [ |
11 | param('pluginName').custom(isPluginNameValid).withMessage('Should have a valid plugin name'), | 12 | param('pluginName').custom(isPluginNameValid).withMessage('Should have a valid plugin name'), |
@@ -48,13 +49,25 @@ const listPluginsValidator = [ | |||
48 | ] | 49 | ] |
49 | 50 | ||
50 | const installPluginValidator = [ | 51 | const installPluginValidator = [ |
51 | body('npmName').custom(isNpmPluginNameValid).withMessage('Should have a valid npm name'), | 52 | body('npmName') |
53 | .optional() | ||
54 | .custom(isNpmPluginNameValid).withMessage('Should have a valid npm name'), | ||
55 | body('path') | ||
56 | .optional() | ||
57 | .custom(isSafePath).withMessage('Should have a valid safe path'), | ||
52 | 58 | ||
53 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 59 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
54 | logger.debug('Checking installPluginValidator parameters', { parameters: req.body }) | 60 | logger.debug('Checking installPluginValidator parameters', { parameters: req.body }) |
55 | 61 | ||
56 | if (areValidationErrors(req, res)) return | 62 | if (areValidationErrors(req, res)) return |
57 | 63 | ||
64 | const body: InstallPlugin = req.body | ||
65 | if (!body.path && !body.npmName) { | ||
66 | return res.status(400) | ||
67 | .json({ error: 'Should have either a npmName or a path' }) | ||
68 | .end() | ||
69 | } | ||
70 | |||
58 | return next() | 71 | return next() |
59 | } | 72 | } |
60 | ] | 73 | ] |
diff --git a/server/models/server/plugin.ts b/server/models/server/plugin.ts index 60abaec65..226c08342 100644 --- a/server/models/server/plugin.ts +++ b/server/models/server/plugin.ts | |||
@@ -142,15 +142,17 @@ export class PluginModel extends Model<PluginModel> { | |||
142 | count: number, | 142 | count: number, |
143 | sort: string | 143 | sort: string |
144 | }) { | 144 | }) { |
145 | const { uninstalled = false } = options | ||
145 | const query: FindAndCountOptions = { | 146 | const query: FindAndCountOptions = { |
146 | offset: options.start, | 147 | offset: options.start, |
147 | limit: options.count, | 148 | limit: options.count, |
148 | order: getSort(options.sort), | 149 | order: getSort(options.sort), |
149 | where: {} | 150 | where: { |
151 | uninstalled | ||
152 | } | ||
150 | } | 153 | } |
151 | 154 | ||
152 | if (options.type) query.where['type'] = options.type | 155 | if (options.type) query.where['type'] = options.type |
153 | if (options.uninstalled) query.where['uninstalled'] = options.uninstalled | ||
154 | 156 | ||
155 | return PluginModel | 157 | return PluginModel |
156 | .findAndCountAll(query) | 158 | .findAndCountAll(query) |
diff --git a/server/tests/api/search/search-activitypub-video-channels.ts b/server/tests/api/search/search-activitypub-video-channels.ts index 8a008b8c6..d5f0a5457 100644 --- a/server/tests/api/search/search-activitypub-video-channels.ts +++ b/server/tests/api/search/search-activitypub-video-channels.ts | |||
@@ -67,6 +67,8 @@ describe('Test ActivityPub video channels search', function () { | |||
67 | }) | 67 | }) |
68 | 68 | ||
69 | it('Should not find a remote video channel', async function () { | 69 | it('Should not find a remote video channel', async function () { |
70 | this.timeout(15000) | ||
71 | |||
70 | { | 72 | { |
71 | const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server3' | 73 | const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server3' |
72 | const res = await searchVideoChannel(servers[ 0 ].url, search, servers[ 0 ].accessToken) | 74 | const res = await searchVideoChannel(servers[ 0 ].url, search, servers[ 0 ].accessToken) |
diff --git a/server/tools/cli.ts b/server/tools/cli.ts index 2eec51aa4..67755022c 100644 --- a/server/tools/cli.ts +++ b/server/tools/cli.ts | |||
@@ -1,7 +1,8 @@ | |||
1 | import { Netrc } from 'netrc-parser' | 1 | import { Netrc } from 'netrc-parser' |
2 | import { getAppNumber, isTestInstance } from '../helpers/core-utils' | 2 | import { getAppNumber, isTestInstance } from '../helpers/core-utils' |
3 | import { join } from 'path' | 3 | import { join } from 'path' |
4 | import { getVideoChannel, root } from '../../shared/extra-utils' | 4 | import { root } from '../../shared/extra-utils/miscs/miscs' |
5 | import { getVideoChannel } from '../../shared/extra-utils/videos/video-channels' | ||
5 | import { Command } from 'commander' | 6 | import { Command } from 'commander' |
6 | import { VideoChannel, VideoPrivacy } from '../../shared/models/videos' | 7 | import { VideoChannel, VideoPrivacy } from '../../shared/models/videos' |
7 | 8 | ||
@@ -64,7 +65,11 @@ function deleteSettings () { | |||
64 | }) | 65 | }) |
65 | } | 66 | } |
66 | 67 | ||
67 | function getRemoteObjectOrDie (program: any, settings: Settings, netrc: Netrc) { | 68 | function getRemoteObjectOrDie ( |
69 | program: any, | ||
70 | settings: Settings, | ||
71 | netrc: Netrc | ||
72 | ): { url: string, username: string, password: string } { | ||
68 | if (!program['url'] || !program['username'] || !program['password']) { | 73 | if (!program['url'] || !program['username'] || !program['password']) { |
69 | // No remote and we don't have program parameters: quit | 74 | // No remote and we don't have program parameters: quit |
70 | if (settings.remotes.length === 0 || Object.keys(netrc.machines).length === 0) { | 75 | if (settings.remotes.length === 0 || Object.keys(netrc.machines).length === 0) { |
@@ -161,6 +166,13 @@ async function buildVideoAttributesFromCommander (url: string, command: Command, | |||
161 | return videoAttributes | 166 | return videoAttributes |
162 | } | 167 | } |
163 | 168 | ||
169 | function getServerCredentials (program: any) { | ||
170 | return Promise.all([ getSettings(), getNetrc() ]) | ||
171 | .then(([ settings, netrc ]) => { | ||
172 | return getRemoteObjectOrDie(program, settings, netrc) | ||
173 | }) | ||
174 | } | ||
175 | |||
164 | // --------------------------------------------------------------------------- | 176 | // --------------------------------------------------------------------------- |
165 | 177 | ||
166 | export { | 178 | export { |
@@ -172,6 +184,8 @@ export { | |||
172 | writeSettings, | 184 | writeSettings, |
173 | deleteSettings, | 185 | deleteSettings, |
174 | 186 | ||
187 | getServerCredentials, | ||
188 | |||
175 | buildCommonVideoOptions, | 189 | buildCommonVideoOptions, |
176 | buildVideoAttributesFromCommander | 190 | buildVideoAttributesFromCommander |
177 | } | 191 | } |
diff --git a/server/tools/peertube-auth.ts b/server/tools/peertube-auth.ts index 1035d664a..d4ad56e47 100644 --- a/server/tools/peertube-auth.ts +++ b/server/tools/peertube-auth.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as program from 'commander' | 1 | import * as program from 'commander' |
2 | import * as prompt from 'prompt' | 2 | import * as prompt from 'prompt' |
3 | import { getSettings, writeSettings, getNetrc } from './cli' | 3 | import { getNetrc, getSettings, writeSettings } from './cli' |
4 | import { isHostValid } from '../helpers/custom-validators/servers' | ||
5 | import { isUserUsernameValid } from '../helpers/custom-validators/users' | 4 | import { isUserUsernameValid } from '../helpers/custom-validators/users' |
5 | import { getAccessToken, login } from '../../shared/extra-utils' | ||
6 | 6 | ||
7 | const Table = require('cli-table') | 7 | const Table = require('cli-table') |
8 | 8 | ||
@@ -76,6 +76,14 @@ program | |||
76 | } | 76 | } |
77 | } | 77 | } |
78 | }, async (_, result) => { | 78 | }, async (_, result) => { |
79 | // Check credentials | ||
80 | try { | ||
81 | await getAccessToken(result.url, result.username, result.password) | ||
82 | } catch (err) { | ||
83 | console.error(err.message) | ||
84 | process.exit(-1) | ||
85 | } | ||
86 | |||
79 | await setInstance(result.url, result.username, result.password, program['default']) | 87 | await setInstance(result.url, result.username, result.password, program['default']) |
80 | 88 | ||
81 | process.exit(0) | 89 | process.exit(0) |
diff --git a/server/tools/peertube-import-videos.ts b/server/tools/peertube-import-videos.ts index d7bb00e02..1f0350442 100644 --- a/server/tools/peertube-import-videos.ts +++ b/server/tools/peertube-import-videos.ts | |||
@@ -11,7 +11,7 @@ import * as prompt from 'prompt' | |||
11 | import { remove } from 'fs-extra' | 11 | import { remove } from 'fs-extra' |
12 | import { sha256 } from '../helpers/core-utils' | 12 | import { sha256 } from '../helpers/core-utils' |
13 | import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl' | 13 | import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl' |
14 | import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getNetrc, getRemoteObjectOrDie, getSettings } from './cli' | 14 | import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getServerCredentials } from './cli' |
15 | 15 | ||
16 | type UserInfo = { | 16 | type UserInfo = { |
17 | username: string | 17 | username: string |
@@ -36,27 +36,25 @@ command | |||
36 | .option('-v, --verbose', 'Verbose mode') | 36 | .option('-v, --verbose', 'Verbose mode') |
37 | .parse(process.argv) | 37 | .parse(process.argv) |
38 | 38 | ||
39 | Promise.all([ getSettings(), getNetrc() ]) | 39 | getServerCredentials(command) |
40 | .then(([ settings, netrc ]) => { | 40 | .then(({ url, username, password }) => { |
41 | const { url, username, password } = getRemoteObjectOrDie(program, settings, netrc) | 41 | if (!program[ 'targetUrl' ]) { |
42 | console.error('--targetUrl field is required.') | ||
42 | 43 | ||
43 | if (!program[ 'targetUrl' ]) { | 44 | process.exit(-1) |
44 | console.error('--targetUrl field is required.') | 45 | } |
45 | |||
46 | process.exit(-1) | ||
47 | } | ||
48 | 46 | ||
49 | removeEndSlashes(url) | 47 | removeEndSlashes(url) |
50 | removeEndSlashes(program[ 'targetUrl' ]) | 48 | removeEndSlashes(program[ 'targetUrl' ]) |
51 | 49 | ||
52 | const user = { username, password } | 50 | const user = { username, password } |
53 | 51 | ||
54 | run(url, user) | 52 | run(url, user) |
55 | .catch(err => { | 53 | .catch(err => { |
56 | console.error(err) | 54 | console.error(err) |
57 | process.exit(-1) | 55 | process.exit(-1) |
58 | }) | 56 | }) |
59 | }) | 57 | }) |
60 | 58 | ||
61 | async function run (url: string, user: UserInfo) { | 59 | async function run (url: string, user: UserInfo) { |
62 | if (!user.password) { | 60 | if (!user.password) { |
diff --git a/server/tools/peertube-plugins.ts b/server/tools/peertube-plugins.ts new file mode 100644 index 000000000..d5e024383 --- /dev/null +++ b/server/tools/peertube-plugins.ts | |||
@@ -0,0 +1,162 @@ | |||
1 | import * as program from 'commander' | ||
2 | import { PluginType } from '../../shared/models/plugins/plugin.type' | ||
3 | import { getAccessToken } from '../../shared/extra-utils/users/login' | ||
4 | import { getMyUserInformation } from '../../shared/extra-utils/users/users' | ||
5 | import { installPlugin, listPlugins, uninstallPlugin } from '../../shared/extra-utils/server/plugins' | ||
6 | import { getServerCredentials } from './cli' | ||
7 | import { User, UserRole } from '../../shared/models/users' | ||
8 | import { PeerTubePlugin } from '../../shared/models/plugins/peertube-plugin.model' | ||
9 | import { isAbsolute } from 'path' | ||
10 | |||
11 | const Table = require('cli-table') | ||
12 | |||
13 | program | ||
14 | .name('plugins') | ||
15 | .usage('[command] [options]') | ||
16 | |||
17 | program | ||
18 | .command('list') | ||
19 | .description('List installed plugins') | ||
20 | .option('-u, --url <url>', 'Server url') | ||
21 | .option('-U, --username <username>', 'Username') | ||
22 | .option('-p, --password <token>', 'Password') | ||
23 | .option('-t, --only-themes', 'List themes only') | ||
24 | .option('-P, --only-plugins', 'List plugins only') | ||
25 | .action(() => pluginsListCLI()) | ||
26 | |||
27 | program | ||
28 | .command('install') | ||
29 | .description('Install a plugin or a theme') | ||
30 | .option('-u, --url <url>', 'Server url') | ||
31 | .option('-U, --username <username>', 'Username') | ||
32 | .option('-p, --password <token>', 'Password') | ||
33 | .option('-P --path <path>', 'Install from a path') | ||
34 | .option('-n, --npm-name <npmName>', 'Install from npm') | ||
35 | .action((options) => installPluginCLI(options)) | ||
36 | |||
37 | program | ||
38 | .command('uninstall') | ||
39 | .description('Uninstall a plugin or a theme') | ||
40 | .option('-u, --url <url>', 'Server url') | ||
41 | .option('-U, --username <username>', 'Username') | ||
42 | .option('-p, --password <token>', 'Password') | ||
43 | .option('-n, --npm-name <npmName>', 'NPM plugin/theme name') | ||
44 | .action(options => uninstallPluginCLI(options)) | ||
45 | |||
46 | if (!process.argv.slice(2).length) { | ||
47 | program.outputHelp() | ||
48 | } | ||
49 | |||
50 | program.parse(process.argv) | ||
51 | |||
52 | // ---------------------------------------------------------------------------- | ||
53 | |||
54 | async function pluginsListCLI () { | ||
55 | const { url, username, password } = await getServerCredentials(program) | ||
56 | const accessToken = await getAdminTokenOrDie(url, username, password) | ||
57 | |||
58 | let type: PluginType | ||
59 | if (program['onlyThemes']) type = PluginType.THEME | ||
60 | if (program['onlyPlugins']) type = PluginType.PLUGIN | ||
61 | |||
62 | const res = await listPlugins({ | ||
63 | url, | ||
64 | accessToken, | ||
65 | start: 0, | ||
66 | count: 100, | ||
67 | sort: 'name', | ||
68 | type | ||
69 | }) | ||
70 | const plugins: PeerTubePlugin[] = res.body.data | ||
71 | |||
72 | const table = new Table({ | ||
73 | head: ['name', 'version', 'homepage'], | ||
74 | colWidths: [ 50, 10, 50 ] | ||
75 | }) | ||
76 | |||
77 | for (const plugin of plugins) { | ||
78 | const npmName = plugin.type === PluginType.PLUGIN | ||
79 | ? 'peertube-plugin-' + plugin.name | ||
80 | : 'peertube-theme-' + plugin.name | ||
81 | |||
82 | table.push([ | ||
83 | npmName, | ||
84 | plugin.version, | ||
85 | plugin.homepage | ||
86 | ]) | ||
87 | } | ||
88 | |||
89 | console.log(table.toString()) | ||
90 | process.exit(0) | ||
91 | } | ||
92 | |||
93 | async function installPluginCLI (options: any) { | ||
94 | if (!options['path'] && !options['npmName']) { | ||
95 | console.error('You need to specify the npm name or the path of the plugin you want to install.\n') | ||
96 | program.outputHelp() | ||
97 | process.exit(-1) | ||
98 | } | ||
99 | |||
100 | if (options['path'] && !isAbsolute(options['path'])) { | ||
101 | console.error('Path should be absolute.') | ||
102 | process.exit(-1) | ||
103 | } | ||
104 | |||
105 | const { url, username, password } = await getServerCredentials(options) | ||
106 | const accessToken = await getAdminTokenOrDie(url, username, password) | ||
107 | |||
108 | try { | ||
109 | await installPlugin({ | ||
110 | url, | ||
111 | accessToken, | ||
112 | npmName: options['npmName'], | ||
113 | path: options['path'] | ||
114 | }) | ||
115 | } catch (err) { | ||
116 | console.error('Cannot install plugin.', err) | ||
117 | process.exit(-1) | ||
118 | return | ||
119 | } | ||
120 | |||
121 | console.log('Plugin installed.') | ||
122 | process.exit(0) | ||
123 | } | ||
124 | |||
125 | async function uninstallPluginCLI (options: any) { | ||
126 | if (!options['npmName']) { | ||
127 | console.error('You need to specify the npm name of the plugin/theme you want to uninstall.\n') | ||
128 | program.outputHelp() | ||
129 | process.exit(-1) | ||
130 | } | ||
131 | |||
132 | const { url, username, password } = await getServerCredentials(options) | ||
133 | const accessToken = await getAdminTokenOrDie(url, username, password) | ||
134 | |||
135 | try { | ||
136 | await uninstallPlugin({ | ||
137 | url, | ||
138 | accessToken, | ||
139 | npmName: options[ 'npmName' ] | ||
140 | }) | ||
141 | } catch (err) { | ||
142 | console.error('Cannot uninstall plugin.', err) | ||
143 | process.exit(-1) | ||
144 | return | ||
145 | } | ||
146 | |||
147 | console.log('Plugin uninstalled.') | ||
148 | process.exit(0) | ||
149 | } | ||
150 | |||
151 | async function getAdminTokenOrDie (url: string, username: string, password: string) { | ||
152 | const accessToken = await getAccessToken(url, username, password) | ||
153 | const resMe = await getMyUserInformation(url, accessToken) | ||
154 | const me: User = resMe.body | ||
155 | |||
156 | if (me.role !== UserRole.ADMINISTRATOR) { | ||
157 | console.error('Cannot list plugins if you are not administrator.') | ||
158 | process.exit(-1) | ||
159 | } | ||
160 | |||
161 | return accessToken | ||
162 | } | ||
diff --git a/server/tools/peertube-upload.ts b/server/tools/peertube-upload.ts index c00205e8f..d9f9a8ead 100644 --- a/server/tools/peertube-upload.ts +++ b/server/tools/peertube-upload.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as program from 'commander' | 1 | import * as program from 'commander' |
2 | import { access, constants } from 'fs-extra' | 2 | import { access, constants } from 'fs-extra' |
3 | import { isAbsolute } from 'path' | 3 | import { isAbsolute } from 'path' |
4 | import { getClient, login } from '../../shared/extra-utils' | 4 | import { getAccessToken } from '../../shared/extra-utils' |
5 | import { uploadVideo } from '../../shared/extra-utils/' | 5 | import { uploadVideo } from '../../shared/extra-utils/' |
6 | import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getNetrc, getRemoteObjectOrDie, getSettings } from './cli' | 6 | import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getServerCredentials } from './cli' |
7 | 7 | ||
8 | let command = program | 8 | let command = program |
9 | .name('upload') | 9 | .name('upload') |
@@ -11,7 +11,6 @@ let command = program | |||
11 | command = buildCommonVideoOptions(command) | 11 | command = buildCommonVideoOptions(command) |
12 | 12 | ||
13 | command | 13 | command |
14 | |||
15 | .option('-u, --url <url>', 'Server url') | 14 | .option('-u, --url <url>', 'Server url') |
16 | .option('-U, --username <username>', 'Username') | 15 | .option('-U, --username <username>', 'Username') |
17 | .option('-p, --password <token>', 'Password') | 16 | .option('-p, --password <token>', 'Password') |
@@ -20,44 +19,28 @@ command | |||
20 | .option('-f, --file <file>', 'Video absolute file path') | 19 | .option('-f, --file <file>', 'Video absolute file path') |
21 | .parse(process.argv) | 20 | .parse(process.argv) |
22 | 21 | ||
23 | Promise.all([ getSettings(), getNetrc() ]) | 22 | getServerCredentials(command) |
24 | .then(([ settings, netrc ]) => { | 23 | .then(({ url, username, password }) => { |
25 | const { url, username, password } = getRemoteObjectOrDie(program, settings, netrc) | 24 | if (!program[ 'videoName' ] || !program[ 'file' ]) { |
26 | 25 | if (!program[ 'videoName' ]) console.error('--video-name is required.') | |
27 | if (!program[ 'videoName' ] || !program[ 'file' ]) { | 26 | if (!program[ 'file' ]) console.error('--file is required.') |
28 | if (!program[ 'videoName' ]) console.error('--video-name is required.') | ||
29 | if (!program[ 'file' ]) console.error('--file is required.') | ||
30 | 27 | ||
31 | process.exit(-1) | 28 | process.exit(-1) |
32 | } | 29 | } |
33 | 30 | ||
34 | if (isAbsolute(program[ 'file' ]) === false) { | 31 | if (isAbsolute(program[ 'file' ]) === false) { |
35 | console.error('File path should be absolute.') | 32 | console.error('File path should be absolute.') |
36 | process.exit(-1) | 33 | process.exit(-1) |
37 | } | 34 | } |
38 | 35 | ||
39 | run(url, username, password).catch(err => { | 36 | run(url, username, password).catch(err => { |
40 | console.error(err) | 37 | console.error(err) |
41 | process.exit(-1) | 38 | process.exit(-1) |
42 | }) | 39 | }) |
43 | }) | 40 | }) |
44 | 41 | ||
45 | async function run (url: string, username: string, password: string) { | 42 | async function run (url: string, username: string, password: string) { |
46 | const resClient = await getClient(url) | 43 | const accessToken = await getAccessToken(url, username, password) |
47 | const client = { | ||
48 | id: resClient.body.client_id, | ||
49 | secret: resClient.body.client_secret | ||
50 | } | ||
51 | |||
52 | const user = { username, password } | ||
53 | |||
54 | let accessToken: string | ||
55 | try { | ||
56 | const res = await login(url, client, user) | ||
57 | accessToken = res.body.access_token | ||
58 | } catch (err) { | ||
59 | throw new Error('Cannot authenticate. Please check your username/password.') | ||
60 | } | ||
61 | 44 | ||
62 | await access(program[ 'file' ], constants.F_OK) | 45 | await access(program[ 'file' ], constants.F_OK) |
63 | 46 | ||
diff --git a/server/tools/peertube.ts b/server/tools/peertube.ts index daa5586c3..e79a7e041 100644 --- a/server/tools/peertube.ts +++ b/server/tools/peertube.ts | |||
@@ -18,13 +18,10 @@ program | |||
18 | .command('get-access-token', 'get a peertube access token', { noHelp: true }).alias('token') | 18 | .command('get-access-token', 'get a peertube access token', { noHelp: true }).alias('token') |
19 | .command('watch', 'watch a video in the terminal ✩°。⋆').alias('w') | 19 | .command('watch', 'watch a video in the terminal ✩°。⋆').alias('w') |
20 | .command('repl', 'initiate a REPL to access internals') | 20 | .command('repl', 'initiate a REPL to access internals') |
21 | .command('plugins [action]', 'manage plugins on a local instance').alias('p') | ||
21 | 22 | ||
22 | /* Not Yet Implemented */ | 23 | /* Not Yet Implemented */ |
23 | program | 24 | program |
24 | .command('plugins [action]', | ||
25 | 'manage plugins on a local instance', | ||
26 | { noHelp: true } as program.CommandOptions | ||
27 | ).alias('p') | ||
28 | .command('diagnostic [action]', | 25 | .command('diagnostic [action]', |
29 | 'like couple therapy, but for your instance', | 26 | 'like couple therapy, but for your instance', |
30 | { noHelp: true } as program.CommandOptions | 27 | { noHelp: true } as program.CommandOptions |