-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbunnycdn-uploader.js
97 lines (86 loc) · 2.68 KB
/
bunnycdn-uploader.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import https from 'node:https'
import { basename, join } from 'node:path'
import { readFileSync, readdirSync, lstatSync } from 'node:fs'
/**
* At UI Rig we no longer use CDNs, but here's how we used to do it:
* - upload new .mp4 of the `localDir`
* - ignores subdirectories
* - skips already uploaded videos
* - use BunnyCDN's web API because the alternative is clear text FTP.
* Ideally, they should implement `rsync` access so this script shouldn't be needed.
* https://support.bunnycdn.com/hc/en-us/community/posts/360047869832-rsync-support
*
* In Cloudflare, we alias the BunnyCDN domain.
* CNAME v.example.com => example.b-cdn.net
* That way switching CDN (e.g. for failover) is just a matter of a DNS record change.
*/
const BUNNY_CREDENTIAL_HEADER = { // TODO to env var
AccessKey: 'same-as-your-ftp-password'
}
const BUNNY_STORAGE_ZONE = 'enter-your-zone-name'
export async function uploadVideosToBunnyCDN(localDir, zoneDir) {
await uploadMissingFiles(
localDir,
'.mp4',
`https://storage.bunnycdn.com/${BUNNY_STORAGE_ZONE}/${zoneDir}`)
}
async function uploadMissingFiles(dir, ext, bunnyURL) {
try {
const candidates = listFilesWithoutDirs(dir).filter(f => f.endsWith(ext))
const alreadyUploaded = await listAlreadyUploadedFiles(bunnyURL)
const toUpload = candidates.filter(f => !alreadyUploaded.has(f))
if (toUpload.length)
Promise
.all(toUpload.map(f => putFile(`${bunnyURL}/${f}`, `${dir}/${f}`)))
.catch(console.error)
else
console.log(`No ${ext} needed to be uploaded to BunnyCDN.`)
}
catch (error) {
console.error(error)
}
}
function listAlreadyUploadedFiles(url) {
return request(`${url}/`, {
method: 'GET',
headers: { Accept: 'application/json' }
})
.then(res => {
if (res.statusCode !== 200)
throw res.statusCode
const body = JSON.parse(res.body)
const saved = new Set()
for (const f of body)
saved.add(f.ObjectName)
return saved
})
}
function putFile(url, file) {
console.log('Uploading to BunnyCDN', basename(url))
const data = readFileSync(file)
return request(url, {
method: 'PUT',
headers: { 'Content-Length': Buffer.byteLength(data) },
body: data
})
}
function request(url, { method, headers, body = '' }) {
return new Promise((resolve, reject) => {
https.request(url, {
method,
headers: Object.assign(headers, BUNNY_CREDENTIAL_HEADER)
},
response => {
response.setEncoding('utf8')
response.body = ''
response.on('data', chunk => { response.body += chunk })
response.on('end', () => resolve(response))
}
)
.on('error', reject)
.end(body)
})
}
function listFilesWithoutDirs(dir) {
return readdirSync(dir).filter(f => lstatSync(join(dir, f)).isFile())
}