diff options
Diffstat (limited to 'server/tools/peertube-redundancy.ts')
-rw-r--r-- | server/tools/peertube-redundancy.ts | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/server/tools/peertube-redundancy.ts b/server/tools/peertube-redundancy.ts new file mode 100644 index 000000000..1ab58a438 --- /dev/null +++ b/server/tools/peertube-redundancy.ts | |||
@@ -0,0 +1,197 @@ | |||
1 | // eslint-disable @typescript-eslint/no-unnecessary-type-assertion | ||
2 | |||
3 | import { registerTSPaths } from '../helpers/register-ts-paths' | ||
4 | registerTSPaths() | ||
5 | |||
6 | import * as program from 'commander' | ||
7 | import { getAdminTokenOrDie, getServerCredentials } from './cli' | ||
8 | import { VideoRedundanciesTarget, VideoRedundancy } from '@shared/models' | ||
9 | import { addVideoRedundancy, listVideoRedundancies, removeVideoRedundancy } from '@shared/extra-utils/server/redundancy' | ||
10 | import validator from 'validator' | ||
11 | import * as CliTable3 from 'cli-table3' | ||
12 | import { URL } from 'url' | ||
13 | import { uniq } from 'lodash' | ||
14 | |||
15 | import bytes = require('bytes') | ||
16 | |||
17 | program | ||
18 | .name('plugins') | ||
19 | .usage('[command] [options]') | ||
20 | |||
21 | program | ||
22 | .command('list-remote-redundancies') | ||
23 | .description('List remote redundancies on your videos') | ||
24 | .option('-u, --url <url>', 'Server url') | ||
25 | .option('-U, --username <username>', 'Username') | ||
26 | .option('-p, --password <token>', 'Password') | ||
27 | .action(() => listRedundanciesCLI('my-videos')) | ||
28 | |||
29 | program | ||
30 | .command('list-my-redundancies') | ||
31 | .description('List your redundancies of remote videos') | ||
32 | .option('-u, --url <url>', 'Server url') | ||
33 | .option('-U, --username <username>', 'Username') | ||
34 | .option('-p, --password <token>', 'Password') | ||
35 | .action(() => listRedundanciesCLI('remote-videos')) | ||
36 | |||
37 | program | ||
38 | .command('add') | ||
39 | .description('Duplicate a video in your redundancy system') | ||
40 | .option('-u, --url <url>', 'Server url') | ||
41 | .option('-U, --username <username>', 'Username') | ||
42 | .option('-p, --password <token>', 'Password') | ||
43 | .option('-v, --video <videoId>', 'Video id to duplicate') | ||
44 | .action((options) => addRedundancyCLI(options)) | ||
45 | |||
46 | program | ||
47 | .command('remove') | ||
48 | .description('Remove a video from your redundancies') | ||
49 | .option('-u, --url <url>', 'Server url') | ||
50 | .option('-U, --username <username>', 'Username') | ||
51 | .option('-p, --password <token>', 'Password') | ||
52 | .option('-v, --video <videoId>', 'Video id to remove from redundancies') | ||
53 | .action((options) => removeRedundancyCLI(options)) | ||
54 | |||
55 | if (!process.argv.slice(2).length) { | ||
56 | program.outputHelp() | ||
57 | } | ||
58 | |||
59 | program.parse(process.argv) | ||
60 | |||
61 | // ---------------------------------------------------------------------------- | ||
62 | |||
63 | async function listRedundanciesCLI (target: VideoRedundanciesTarget) { | ||
64 | const { url, username, password } = await getServerCredentials(program) | ||
65 | const accessToken = await getAdminTokenOrDie(url, username, password) | ||
66 | |||
67 | const redundancies = await listVideoRedundanciesData(url, accessToken, target) | ||
68 | |||
69 | const table = new CliTable3({ | ||
70 | head: [ 'video id', 'video name', 'video url', 'files', 'playlists', 'by instances', 'total size' ] | ||
71 | }) as any | ||
72 | |||
73 | for (const redundancy of redundancies) { | ||
74 | const webtorrentFiles = redundancy.redundancies.files | ||
75 | const streamingPlaylists = redundancy.redundancies.streamingPlaylists | ||
76 | |||
77 | let totalSize = '' | ||
78 | if (target === 'remote-videos') { | ||
79 | const tmp = webtorrentFiles.concat(streamingPlaylists) | ||
80 | .reduce((a, b) => a + b.size, 0) | ||
81 | |||
82 | totalSize = bytes(tmp) | ||
83 | } | ||
84 | |||
85 | const instances = uniq( | ||
86 | webtorrentFiles.concat(streamingPlaylists) | ||
87 | .map(r => r.fileUrl) | ||
88 | .map(u => new URL(u).host) | ||
89 | ) | ||
90 | |||
91 | table.push([ | ||
92 | redundancy.id.toString(), | ||
93 | redundancy.name, | ||
94 | redundancy.url, | ||
95 | webtorrentFiles.length, | ||
96 | streamingPlaylists.length, | ||
97 | instances.join('\n'), | ||
98 | totalSize | ||
99 | ]) | ||
100 | } | ||
101 | |||
102 | console.log(table.toString()) | ||
103 | process.exit(0) | ||
104 | } | ||
105 | |||
106 | async function addRedundancyCLI (options: { videoId: number }) { | ||
107 | const { url, username, password } = await getServerCredentials(program) | ||
108 | const accessToken = await getAdminTokenOrDie(url, username, password) | ||
109 | |||
110 | if (!options['video'] || validator.isInt('' + options['video']) === false) { | ||
111 | console.error('You need to specify the video id to duplicate and it should be a number.\n') | ||
112 | program.outputHelp() | ||
113 | process.exit(-1) | ||
114 | } | ||
115 | |||
116 | try { | ||
117 | await addVideoRedundancy({ | ||
118 | url, | ||
119 | accessToken, | ||
120 | videoId: options['video'] | ||
121 | }) | ||
122 | |||
123 | console.log('Video will be duplicated by your instance!') | ||
124 | |||
125 | process.exit(0) | ||
126 | } catch (err) { | ||
127 | if (err.message.includes(409)) { | ||
128 | console.error('This video is already duplicated by your instance.') | ||
129 | } else if (err.message.includes(404)) { | ||
130 | console.error('This video id does not exist.') | ||
131 | } else { | ||
132 | console.error(err) | ||
133 | } | ||
134 | |||
135 | process.exit(-1) | ||
136 | } | ||
137 | } | ||
138 | |||
139 | async function removeRedundancyCLI (options: { videoId: number }) { | ||
140 | const { url, username, password } = await getServerCredentials(program) | ||
141 | const accessToken = await getAdminTokenOrDie(url, username, password) | ||
142 | |||
143 | if (!options['video'] || validator.isInt('' + options['video']) === false) { | ||
144 | console.error('You need to specify the video id to remove from your redundancies.\n') | ||
145 | program.outputHelp() | ||
146 | process.exit(-1) | ||
147 | } | ||
148 | |||
149 | const videoId = parseInt(options['video'] + '', 10) | ||
150 | |||
151 | let redundancies = await listVideoRedundanciesData(url, accessToken, 'my-videos') | ||
152 | let videoRedundancy = redundancies.find(r => videoId === r.id) | ||
153 | |||
154 | if (!videoRedundancy) { | ||
155 | redundancies = await listVideoRedundanciesData(url, accessToken, 'remote-videos') | ||
156 | videoRedundancy = redundancies.find(r => videoId === r.id) | ||
157 | } | ||
158 | |||
159 | if (!videoRedundancy) { | ||
160 | console.error('Video redundancy not found.') | ||
161 | process.exit(-1) | ||
162 | } | ||
163 | |||
164 | try { | ||
165 | const ids = videoRedundancy.redundancies.files | ||
166 | .concat(videoRedundancy.redundancies.streamingPlaylists) | ||
167 | .map(r => r.id) | ||
168 | |||
169 | for (const id of ids) { | ||
170 | await removeVideoRedundancy({ | ||
171 | url, | ||
172 | accessToken, | ||
173 | redundancyId: id | ||
174 | }) | ||
175 | } | ||
176 | |||
177 | console.log('Video redundancy removed!') | ||
178 | |||
179 | process.exit(0) | ||
180 | } catch (err) { | ||
181 | console.error(err) | ||
182 | process.exit(-1) | ||
183 | } | ||
184 | } | ||
185 | |||
186 | async function listVideoRedundanciesData (url: string, accessToken: string, target: VideoRedundanciesTarget) { | ||
187 | const res = await listVideoRedundancies({ | ||
188 | url, | ||
189 | accessToken, | ||
190 | start: 0, | ||
191 | count: 100, | ||
192 | sort: 'name', | ||
193 | target | ||
194 | }) | ||
195 | |||
196 | return res.body.data as VideoRedundancy[] | ||
197 | } | ||