Skip to content

Instantly share code, notes, and snippets.

@lunix33
Last active February 9, 2024 13:15
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lunix33/4264570fb89963ea3a6425c88274483e to your computer and use it in GitHub Desktop.
Save lunix33/4264570fb89963ea3a6425c88274483e to your computer and use it in GitHub Desktop.
Installation and update script for emby
#!/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}`));
});
});
}

Installation

  1. Be sure all the dependancies are fullfiled on the target computer.
  2. Copy the emby_install.js script on the system where emby needs to be installed/updated.
  3. You can start using the command as displayed in the Usage section.

Requirement

  • Node JS (>= v10.18.1)
  • libc6 (Emby dependancie)
  • 64bit Debian based linux distribution with systemd
  • Root access

Usage

Inside a terminal, execute the command from where the emby update script is installed. This script needs to be executed as root since it needs to be able to control the emby service and install packages.

usage: emby-install.js [beta] [force] [help|--help|-h]

  • beta: Install the beta version instead of the Release.
  • force: Install even if already up to date.
  • help: Display the help menu.

This script will create temporary files in /tmp/ during the installation.

@twproject
Copy link

twproject commented Nov 1, 2018

Hi where i need to put the file and how i can execute it? Do you have an example
I'm using emby into a debian machine
Thanks

@lunix33
Copy link
Author

lunix33 commented Feb 12, 2019

Put the file wherever you want, and set it run permission. (chmod +x ./emby_install.js)
and simply run: ./emby_install.js

The file might create a temporary file, but it will delete it upon completion (Unless the script fail)

@twproject
Copy link

Hi,
is it possible to add a flag to download beta instead normal version?
is it possibile to add a function to download only if version is different from the current installed one?
Thanks

@lunix33
Copy link
Author

lunix33 commented Jan 13, 2020

@twproject Thanks for the feedback, I'm working on it, should have it ready soon.

@lunix33
Copy link
Author

lunix33 commented Jan 14, 2020

@twproject The new features have been implemented, let me know if there's any problem.
You can see how to use the script in the emby-install.md file

@twproject
Copy link

perfect it works like a charm. Thanks

@Wheemer
Copy link

Wheemer commented Oct 25, 2022

Does this download the install file every time? I want to use it in a crontab.

@lunix33
Copy link
Author

lunix33 commented Oct 25, 2022

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.

@Wheemer
Copy link

Wheemer commented Oct 25, 2022

OK because I see downloading count to 100% before it reports already up to date.

@lunix33
Copy link
Author

lunix33 commented Oct 25, 2022

The script is kinda old and some things might have changed since I made it. I'll take a quick look.

@lunix33
Copy link
Author

lunix33 commented Oct 25, 2022

Oh, okay! I think I got it.
If I'm not mistaken you should be seeing something like the following when you are running the script:

Request to https://api.github.com/repos/MediaBrowser/Emby.Releases/releases: 200
Download progress: 100%
Already up to date.

That's normal since I'm using the same process to request the list of versions of emby as when I'm I'm actually downloading the install package.
The progress displayed is only for the HTTP request displayed just over.

If you would have downloaded the actual package it should look like the following, you would be able to see two HTTP request with two download progress:

Request to https://api.github.com/repos/MediaBrowser/Emby.Releases/releases: 200
Download progress: 100%
Request to https://github.com/MediaBrowser/Emby.Releases/releases/download/4.7.8.0/emby-server-deb_4.7.8.0_amd64.deb: 200
Download progress: 100%
...

@Wheemer
Copy link

Wheemer commented Oct 26, 2022

Awesome, thanks for the confirmation. It still seems to be working just fine. I can't find an easier way then this in a cron to update emby.

Thanks!!

@Wheemer
Copy link

Wheemer commented Oct 27, 2022

Hello;

Is there any way this script could set the user after upgrade? I use a different script to change the user but it sometimes messes up and cause emby not to start.

Here's the script I use:

#!/bin/bash

uidToRunAs=media
serviceName=emby-server

serviceFile=/usr/lib/systemd/system/$serviceName.service

newUser=`getent passwd "$uidToRunAs" | cut -d: -f1`
if [ -z $newUser ]; then
   echo "User with id $uidToRunAs not found"
   exit 1
fi

if ! [ -w $serviceFile ]; then
    if [ -r $serviceFile ]; then
        msgText="not writable. Are you root?"
    else 
        msgText="not found"
    fi
    echo "Service File \"$serviceFile\" $msgText" 
    exit 1
fi

existingUser=`grep "User=" $serviceFile | cut -d= -f2`

if [ "..$existingUser.." = "..$newUser.." ]; then
    echo "\"$serviceName\" already running as \"$newUser\""
    exit 0
fi

echo "\"$serviceName\" changing to run as \"$newUser\" (instead of \"$existingUser\")"


string1="User=$existingUser"
string2="User=$newUser"
systemctl stop $serviceName
sed -i "s/$string1/$string2/g" $serviceFile

chown -R $newUser:$newUser /var/lib/emby/

systemctl daemon-reload
systemctl start $serviceName

@lunix33
Copy link
Author

lunix33 commented Oct 27, 2022

I don't think my script should take care of updating the runtime user of the service since it's not in the scope of the script.

Do you know why your script fails to update the service file and restart? How do you call the two scripts? You talked about a cronjob, how does it look?

@Wheemer
Copy link

Wheemer commented Oct 27, 2022

I understand completely and really appreciate your help.

0 0 * * * ./emby_install.js beta && sh changeEmbyOwner.sh

This is my crontab and I think you are right, there must be a problem with it.

@lunix33
Copy link
Author

lunix33 commented Oct 27, 2022

I think a simpler way for you to go about it would be to create a system override of the emby server systemd service (create a new service/drop-in in /etc/systemd/system), override are permanant on your system and takes precedence over the distributed service file.

You can do that by using systemctl edit emby-server and input the following in the designated area:

[Service]
User=media

I never really did a partial override, so I don't know for sure if it will work properly 😅. But you can do a full override by using systemctl edit emby-server --full and just changing the User field by your own user.

That way you won't actually need your script, and won't need to redo the user change after every update.

@Wheemer
Copy link

Wheemer commented Oct 29, 2022

Thank you for the help, really appreciate it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment