|
#!/usr/bin/env node |
|
|
|
// Requires |
|
const { execSync } = require('child_process'), |
|
{ get } = require('https'), |
|
{ writeFile, unlink } = require('fs').promises, |
|
{ resolve } = require('path'); |
|
|
|
// Constants |
|
const EMBY_GITHUB_URL = `https://api.github.com/repos/MediaBrowser/Emby.Releases/releases`, |
|
TEMP_DIR = `/tmp/`; |
|
|
|
/** |
|
* Program entry point. |
|
*/ |
|
(function main() { |
|
expectRoot(); |
|
|
|
// Detect arguments |
|
const beta = process.argv.indexOf('beta') >= 0; |
|
const force = process.argv.indexOf('force') >= 0; |
|
const help = process.argv.indexOf('help') >= 0 || |
|
process.argv.indexOf('-h') >= 0 || |
|
process.argv.indexOf('--help') >= 0; |
|
|
|
// Display help message. |
|
if (help) { |
|
console.log(`usage: emby-install.js [beta] [force] [help|--help|-h] |
|
beta: Install the beta version instead of the Release. |
|
force: Install the latest version even if the same version is installed. |
|
help: Display this message.`); |
|
process.exit(0); |
|
} |
|
|
|
processUpdate(beta, force) |
|
.catch(err => console.log(err)); |
|
})(); |
|
|
|
/** |
|
* Validate if the current user is root |
|
* If the user isn't root, terminate the process. |
|
*/ |
|
function expectRoot() { |
|
// Need to run script as root |
|
const username = require('os').userInfo().username; |
|
if (username != 'root') { |
|
console.log('This script need to be ran as root.'); |
|
process.exit(1); |
|
} |
|
} |
|
|
|
/** |
|
* The update process |
|
* @param {boolean} beta Install the beta version instead of the official release. |
|
* @param {boolean} force Install the application even if the version is the same. |
|
*/ |
|
async function processUpdate(beta, force) { |
|
try { |
|
const systemVersion = getInstalledVersion(); |
|
const asset = await getDownloadAsset(beta); |
|
|
|
// Only download & install if installation is forced or the online version is different from the currently installed version. |
|
if (force || systemVersion === null || !systemVersion.includes(asset.version)) { |
|
// Download the installer file. |
|
const path = resolve(TEMP_DIR, asset.name); |
|
await downloadFile(asset.browser_download_url, path); |
|
|
|
// Stop the emby server before install. |
|
if (systemVersion !== null) { |
|
execSync('systemctl stop emby-server'); |
|
} |
|
|
|
// Exec dpkg to install deb. |
|
execSync(`dpkg -i ${path}`); |
|
|
|
// Remove file downloaded. |
|
await unlink(path); |
|
|
|
// Restart the server. |
|
execSync('systemctl start emby-server'); |
|
console.log(execSync('systemctl status emby-server').toString()); |
|
} else { |
|
console.log('Already up to date.') |
|
} |
|
} catch(e) { |
|
console.log(e.message); |
|
process.exit(1); |
|
} |
|
} |
|
|
|
/** |
|
* Query the system for the version of Emby installed. |
|
* @returns {string|null} The currently installed version, or null if none. |
|
*/ |
|
function getInstalledVersion() { |
|
const query = execSync(`dpkg-query -s emby-server`); |
|
return /Version: ?([a-z0-9\.]*)/.exec(query)[1] || null; |
|
} |
|
|
|
/** |
|
* Get the Github asset to download |
|
* @param {boolean} beta True if we're looking for a beta, otherwise false. |
|
* @returns {Promise<object>} A promise to return the asset to download, otherwise catch with error. |
|
*/ |
|
async function getDownloadAsset(beta) { |
|
const raw = await httpRequest(EMBY_GITHUB_URL); |
|
|
|
let versions; |
|
try { versions = JSON.parse(raw.toString()); } |
|
catch (e) { throw new Error('Invalid version format.'); } |
|
|
|
return findValidVersionAsset(versions, beta); |
|
} |
|
|
|
/** |
|
* Find a suitable asset version to install. |
|
* @param {object} versions The release version object from github. |
|
* @param {boolean} beta True if we're looking for a beta, otherwise false. |
|
* @returns {object} The asset to be downloaded from a valid release. |
|
*/ |
|
function findValidVersionAsset(versions, beta) { |
|
for (let v of versions) { |
|
// Continue if we're looking for a beta but the current version isn't one, |
|
// or if we aren't looking for a beta and the current version is one. |
|
const isBeta = v.name.includes('beta'); |
|
if ((beta && !isBeta) || (!beta && isBeta)) |
|
continue; |
|
|
|
// Find which asset is a deb file for x86_64 (amd64). |
|
let debAsset = null; |
|
for (let a of v.assets) { |
|
if (/^emby-server-deb(.*)amd64.deb$/.test(a.name)) { |
|
debAsset = a; |
|
debAsset.version = v.tag_name; |
|
break; |
|
} |
|
} |
|
|
|
// If asset is valid finish. |
|
if (debAsset !== null) { |
|
return debAsset; |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Download a file to the disk. |
|
* @param {string} url The URL of the file to download. |
|
* @param {string} filename The path where the file will be created. |
|
*/ |
|
async function downloadFile(url, filename) { |
|
console.log(`Downloading installer to ${filename}`); |
|
const data = await httpRequest(url); |
|
try { await writeFile(filename, data); } |
|
catch (e) { throw new Error(`Failed to write installer to file: ${e.message}`); } |
|
} |
|
|
|
/** |
|
* Makes a HTTP call to a specified URL. |
|
* @param {string} url The URL to query |
|
* @returns {Promise<Buffer>} The server response. |
|
*/ |
|
function httpRequest(url) { |
|
return new Promise((resolve, reject) => { |
|
get(url, { |
|
headers: { |
|
'User-Agent': 'NODE HTTPS' |
|
} |
|
}, (res) => { |
|
console.log(`Request to ${url}: ${res.statusCode}`); |
|
|
|
//res.on('data', data => { console.log(data.toString()) }); |
|
if ([200, 202].includes(res.statusCode)) { |
|
const bufs = []; |
|
let downloaded = 0; |
|
|
|
res.on('data', data => { |
|
downloaded += data.length; |
|
let progress = Math.round((downloaded / parseInt(res.headers['content-length'], 10) * 100) * 100) / 100; |
|
process.stdout.write(`Download progress: ${progress}%${" ".repeat(10)}\r`); |
|
|
|
bufs.push(data); |
|
}); |
|
|
|
res.on('end', () => { |
|
process.stdout.write('\n'); |
|
return resolve(Buffer.concat(bufs)); |
|
}) |
|
} |
|
|
|
// Redirect, redo the request on the redirected URL. |
|
else if ([301, 302].includes(res.statusCode)) |
|
return resolve(httpRequest(res.headers.location)); |
|
|
|
// Incorrect status code. |
|
else |
|
return reject(new Error(`Bad response from server: ${res.statusCode}`)); |
|
}).on('error', err => { |
|
// An error occured when opening the HTTP request. |
|
return reject(new Error(`Request failed: ${err.message}`)); |
|
}); |
|
}); |
|
} |
In theory it only downloads the file if the installed version isn't the same as the latest release.
That is unless you use the
force
parameter, in which case the latest release will be fetched and installed every time.