Skip to content

Branding and OEM customization

do360now edited this page Jan 8, 2020 · 8 revisions

This page describes how to brand and customize Duplicati for use in corporations or institutions.

The first part of this document describes how to accomplish various common tasks by manipulating the web-based user interface. The next part deals with how to package a modified build, and the last part describes options for deeper changes and distribution models.

Simple changes

The simplest for of customization is to add some additional text in the user interface. To avoid conflicting with existing and future updates, all customizations should be added in the Duplicati/Server/webroot/oem folder. For each theme there is an existing folder, e.g. for the ngax theme, you would replace the file Duplicati/Server/webroot/oem/ngax/scripts/oem.js.

This file is empty, but loaded in automatically, such that there is no need to change any html files. A similar oem.css file is also provided for style tweaks, but not shown in this document.

This, combined with the build process makes it simple to tweak the user experience, but does not allow changes beyond the web interface.

Re-branding

Since the ngax theme uses AngularJS, you can register an OEM service here that will be automatically loaded. An example for providing a "powered by" sub-line could be:

backupApp.service('OEMService', function(MyBrandingService) {});
backupApp.service('MyBrandingService', function(BrandingService) {
	BrandingService.state.appName = "Duplicati";
	BrandingService.state.appSubtitle = "Powered by example.com!"
});

The first line registers a an OEMService which Duplicati attempts to load, and ignores if it no service with that name exists. Since it depends on MyBrandingService, that service is loaded as well. Then, MyBrandingService depends on BrandingService which is provided by Duplicati, and gives access to setting these branding values.

Custom backend configuration

If you wish to provide a custom backend, that can be accomplished by hooking into the EditUriBackendConfig service that is provided by Duplicati. In the example below, the SSH backend is hardwired to use example.com as the server and port 22 as the port. You can combine this example with the branding example above, by having OEMService depend on both your services.

backupApp.service('OEMService', function(MyBackendService) {});
backupApp.service('MyBackendService', function(EditUriBackendConfig, EditUriBuiltins, AppUtils) {
	var SERVER_HOST = 'example.com';
	var SERVER_PORT = 22;
	var SERVER_URL = 'ssh://' + SERVER_HOST + ':' + SERVER_PORT;

	EditUriBackendConfig.defaultbackend = 'ssh';
	EditUriBackendConfig.templates['ssh'] = '../oem/ngax/templates/backends/mybackend.html';

	EditUriBackendConfig.builders['ssh'] = function(scope) {
		var opts = {};
		EditUriBackendConfig.merge_in_advanced_options(scope, opts);
		var url = AppUtils.format('ssh://{0}/{1}{2}',
			SERVER_HOST + ':' + SERVER_PORT,
			scope.Path,
			AppUtils.encodeDictAsUrl(opts)
		);

		return url;
	};

	EditUriBackendConfig.validaters['ssh'] = function(scope, continuation) {
		if (EditUriBackendConfig.require_path(scope))
		{
		    if (!EditUriBackendConfig.require_username_and_password(scope))
		    	return;
		}
    	    	continuation();
	};
});

In the example we override the default backend to be ssh instead of the normal file. Next we choose a custom html template instead of the default ssh template. Note that this file is also placed in the oem folder to ensure that there are no clashes with future updates. The html could look like this:

<div class="input text">
    <label for="generic_path">Folder on the MyService server</label>
    <input type="text" name="generic_path" id="generic_path" ng-model="$parent.Path" placeholder="Enter the destination path" />
</div>

<div class="input text">
    <label for="generic_username">MyService Username</label>
    <input type="text" name="generic_username" id="generic_username" ng-model="$parent.Username" placeholder="Authentication username"  />
</div>

<div class="input password">
    <label for="generic_password">MyService Password</label>
    <input type="text" name="generic_password" id="generic_password" ng-model="$parent.Password" placeholder="Authentication password"  />
</div>

Validations on save

It is also possible to hook into the validation logic to enforce organization rules, such as require a passphrase on the backup:

backupApp.service('OEMService', function(MyValidate) {});
backupApp.service('MyValidate', function(EditBackupService) {
	EditBackupService.postValidate = function(scope, continuation) {
		if (scope.Options['passphrase'] == null || scope.Options['passphrase'].length < 8) {
			alert('Your backup must be protected by a passphrase of at least 8 characters');
		} else {
			continuation();
		}
	};
});

The continuation function is only called if the validation succeeds. Again, this can be combined with the above examples.

Third party javascript libraries

Third party javascript libraries can also be included by letting the a service inject them into the DOM. Notice that the library is also placed in the OEM folder to avoid later conflicts:

backupApp.service('OEMService', function(MyLoader) {});
backupApp.service('MyLoader', function() {
	var jse = document.createElement('script');
  	jse.type = 'text/javascript';
  	jse.src = '../oem/jsencrypt.js';
	document.getElementsByTagName('head')[0].appendChild(jse);
});

Packaging changes

When packaging changes, the Windows and OSX platforms can use an existing Duplicati binary build package as input, and merge in the oem folder as described above. The Linux platform requires that packages are built from source, and thus has a few extra steps.

The build process automatically checks to see if there is an oem folder in either ./oem, ../oem or ../../oem and merges it into the existing webroot. This allows you to store the OEM files inside the Installer folder, or in an external repository.

Windows

To use an existing package, download the binary distribution you want to base your customized package on. Then use the Installer/Windows/build-msi.bat script to create an MSI based installer file by running it with the zip file as input.

You need:

  • msbuild (tested with Visual Studio 2015 and 2017).
  • WiX.
  • 7-Zip installed.
  • 7zip and Wix directories added to the Windows path.

build-msi.bat must be run from the Installer/Windows directory. If you have installed Visual Studio 2017 you need to run Command Prompt for VS 2017 instead of the regular Command. This way the Visual Studio environment variables and path will be properly initialized.

Mac OSX

To use an existing package, download the binary distribution you want to base your customized package on. Then use the Installer/OSX/make-dmg.sh script to create both a DMG and a PKG based installer file by running it with the zip file as input.

You need to have the xcode tools installed to make the PKG, as well as xbuild from Mono to build.

Fedora RPM package

To create the RPM package, you need to install the dependencies and grab the source code from github. The source code then contains a helper script that builds a the RPM package:

sudo dnf install mono-devel gnome-sharp-devel dos2unix rpmbuild git
git clone https://github.com/duplicati/duplicati
cd Installer/fedora
ENV=../.. bash build-package.sh

Debian DEB package

To create the DEB package, you need to install the dependencies and grab the source code from github. The source code then contains a helper script that builds a the DEB package:

sudo apt-get install mono-devel gtk-sharp2 debhelper git
git clone https://github.com/duplicati/duplicati
cd Installer/debian
ENV=../.. bash build-package.sh

Handling updates

The above build and customization steps are geared towards creating a package with as little effort as possible. All the generated builds will be based on the Duplicati releases, and users of these packages will be able to update to the latests Duplicati builds as they become available.

The update packages that the users download are the normal Duplicati packages and do not contain the oem folder you have created. Instead, the webserver will serve the oem folder from the base install, and not the update folder. For simple modifications, such as adding a branding sub-line, this is likely to work with future updates.

This means less maintenance as the Duplicati project handles all infrastructure, but it also has a few drawbacks:

  • If it breaks there is no way for you to fix it; all users must install a new package from you.
  • If you need to provide a custom binary, you cannot
  • If you want to test releases first, you cannot
  • If you want to change/extend your customizations later, you cannot

If these scenarios seem likely to you, you should consider hosting your own updates. Like the oem folder described above, you may also store a few OEM overrides that are meant to give you control over the update process.

Like the oem folder all the files are searched for in ./oem, ../oem or ../../oem and copied into the package during the build process.

oem-app-name.txt

This file changes the name reported by the application in various places, and goes a bit deeper than the BrandingService described above, but does not change executable names etc.

oem-update-url.txt

This file contains a list of URLs that the updater checks for packages. URLs must be separated by semicolons and should be of the form:

https://example.com/stable/item.manifest;https://other.example.com/stable/item.manifest

The item stable is identified by Duplicati and will be replaced with the build channel the user has chosen. Currently the build channels are: nightly, canary, experimental, beta and stable.

If you supply the update URL, it is possible to delay updates, such that they can be checked before the users see them. However, the packages are signed by the Duplicati key, thus you cannot change a package if you discover a problem or want to add a feature.

If you simply wish to disable the automatic update, put in an empty file. This is not recommended, except if you organization has another way of rolling out updates (i.e. centralized installation).

oem-update-key.txt

This file contains the public part of an RSA key in XML format that is used to verify an update package. You can supply a custom key that will override the default signing key. This allows you to fully control the updates that the user receives. You can take a Duplicati binary zip file, extract the contents, modify them and repackage it with the AutoUpdateBuilder tool.

One use of the resigning option would be to remove unwanted backends, so only approved backends are present. Since the backends are loaded dynamically this would simply require that the unwanted backend files are deleted before repacking and signing.

Note that when you supply a custom key, users can no longer use the updates from the Duplicati repository.

Fully custom

If you want even more control over the user experience, you can examine the script build-release.sh to see how Duplicati releases are built. This allows you to build a fully custom version of Duplicati, including renamed binaries etc.

Final words

Even though the simple version seem alluring with the lack of effort, you should consider using a custom signing key to avoid trouble in future versions.

Only for the simplest rebranding should you consider piggy-backing on the Duplicati release key, but even here you may end up with users seeing an unbranded version if the branding system changes.