happyuc-client-binaries
v1.0.7
Published
Download Happyuc client binaries for your OS
Downloads
29
Maintainers
Readme
happyuc-client-binaries
Download Happyuc client binaries for your OS.
When you wish to run a local Happyuc client node it would be beneficial to first scan for existing node client binaries on the machine and then download appropriate client binaries if none found. This package does both.
It is structured so that it can be optionally be used in conjunction with a UI, e.g. if one wishes to allow a user to select the client software they wish to download.
Features:
- Configurable client types (Ghuc, Huc, Parity, etc)
- Security: Binary sanity checks, URL regex checks, SHA256 hash checks
- Can scan and download to specific folders
- Logging can be toggled on/off at runtime
- Can be integrated into Electron.js apps
Installation
npm install --save happyuc-client-binaries
Usage
Config object
First a config object needs to be defined. This specifies the possible clients and the platforms they support.
For example, a config object which specifies the Ghuc client for only 64-bit Linux platforms and the Parity client for only 32-bit Windows platforms might be:
const config = {
"clients": {
"Ghuc": {
"platforms": {
"linux": {
"x64": {
"download": {
"url": "https://ghuc.com/latest.tgz",
"type": "tar",
"bin": "ghuc-linux-x64",
"sha256": "8359e8e647b168dbd053ec56438ab4cea8d76bd5153d681d001c5ce1a390401c",
},
"bin": "ghuc",
"commands": {
"sanity": {
"args": ["version"],
"output": [ "Ghuc", "1.4.12" ]
}
}
},
}
}
},
"Parity": {
"platforms": {
"win": {
"ia32": {
"download": {
"url": "https://parity.com/latest.zip",
"type": "zip"
},
"bin": "parity",
"commands": {
"sanity": {
"args": ["version"],
"output": [ "Parity", "11.0" ]
}
}
},
}
}
}
}
}
Every client must specify one or more platforms, each of which must specify
one or more architectures. Supported platforms are as documented for Node's process.platform except that mac
is used instead of darwin
and win
is used instead of win32
. Supported architectures are as documented for Node's process.arch.
Each platform-arch entry needs to specify a bin
key which holds the name of the executable on the system, a download
key which holds info on where the binary can be downloaded from if needed, and a commands
key which holds information on different kinds of commands that can be run against the binary.
The download
key holds the download url
, the type
of archive being downloaded, and - optionally - the filename of the binary (bin
) inside the archive in case it differs from the expected filename of the binary. As a security measure, a sha256
key equalling the SHA256 hash calculation of the downloadable file may be provided, in which the downloaded file's hash is tested
for equality with this value.
The sanity
command is mandatory and is a way to check a found binary to ensure that is is actually a valid client binary and not something else. In the above config the sanity
command denotes that running ghuc version
should return output containing both Ghuc
and 1.4.12
.
Now we can construct a Manager
with this config:
const Manager = require('happyuc-client-binaries').Manager;
// construct
const mgr = new Manager(config);
Note: If no config is provided then the default config (src/config.json) gets used.
Scanning for binaries
Initialising a manager tells it to scan the system for available binaries:
// initialise (scan for existing binaries on system)
mgr.init()
.then(() => {
console.log( 'Client config: ', mgr.clients );
})
.catch(process.exit);
Let's say the current platform is linux
with an x64
architecture, and that ghuc
has been resolved successfully to /usr/local/bin/ghuc
, the mgr.clients
property will look like:
/*
[
{
id: 'Ghuc',
state: {
available: true,
},
platforms: { .... same as original ... }
activeCli: {
"download": {
"url": "https://ghuc.com/latest.tgz",
"type": "tar"
},
"bin": "ghuc",
"commands": {
"sanity": {
"args": ["version"],
"output": [ "Ghuc", "1.4.12" ]
}
},
fullPath: '/usr/local/bin/ghuc'
}
}
]
*/
The state.available
property is the key property to check. If false
then state.failReason
will also be set. There are currently two possible values for state.failReason
:
notFound
- a binary with matching name (ghuc
in above example) could not be found.sanityCheckFail
- a binary with matching name was found, but it failed the sanity check when executed.
The activeCli.fullPath
property denotes the full path to the resolved client binary - this is only valid if state.available
is true
.
NOTE: The Parity client isn't present in mgr.clients
shown above because there is no linux-x64 entry specified in the Parity config shown earlier. Thus, only possible clients (as per the original config) will be present in mgr.clients
.
Scan additional folders
By default the manager only scan the system PATH
for available binaries, i.e. it doesn't do a full-disk scan. You can specify additional folders to scan using the folders
option:
mgr.init({
folders: [
'/path/to/my/folder/1',
'/path/to/my/folder/2'
]
})
.then(...)
.catch(...)
This features is useful if you have previously downloaded the client binaries elsewhere or you already know that client binaries will be located within specific folders.
Download client binaries
Client binaries can be downloaded whether already available on the system or not. The downloading mechanism supports downloading and unpacking ZIP and TAR files.
The initial config object specifies where a package can be downloaded from, e.g:
"download": {
"url": "https://ghuc.com/latest.tgz",
"type": "tar"
},
To perform the download, specify the client id:
mgr.download("Ghuc")
.then(console.log)
.catch(console.error);
The returned result will be an object which looks like:
{
downloadFolder: /* where archive got downloaded */,
downloadFile: /* the downloaded archive file */,
unpackFolder: /* folder archive was unpacked to */,
client: {
id: 'Ghuc',
state: {...},
platforms: {...},
activeCli: {...},
}
}
The client
entry in the returned info will be the same as is present for the given client within the mgr.clients
property (see above).
After downloading and unpacking the client binary the sanity check is run against it to check that it is indeed the required binary, which means that the client's state.available
and state.failReason
keys will be updated with the results.
Download to specific folder
By default the client binary archive will be downloaded to a temporarily created folder. But you can override this using the downloadFolder
option:
mgr.download("Ghuc", {
downloadFolder: '/path/to/my/folder'
})
.then(...)
.catch(...)
If download and unpacking is successful the returned object will look something like:
{
downloadFolder: '/path/to/my/folder',
downloadFile: '/path/to/my/folder/archive.tgz',
unpackFolder: '/path/to/my/folder/unpacked',
}
The next time you initialise the manager you can pass in /path/to/my/folder/unpacked
as an additional folder to scan for binaries in:
mgr.init({
folders: [
`/path/to/my/folder/unpacked`
]
});
URL regular expression (regex) check
Even though you can check the SHA 256 hash of the downloaded package (as shown above) you may additionally wish to ensure that the download URL points to a domain you control. This is important if for example you are obtaining the initial JSON config object from a remote server.
This is how you use it:
mgr.download("Ghuc", {
urlRegex: /^https:\/\/happyuc.org\/.+$/
})
.then(...)
.catch(...)
The above regex states that ONLY download URLs beginning with
https://happyuc.org/
are valid and allowed.
### Logging
By default internal logging is silent. But you can turn on logging at any time by setting the logger property:
mgr.logger = console; /* log everything to console */
The supplied logger object must have 3 methods: info, warn and error. If any one of these methods isn't provided then the built-in method (i.e. silent method) get used. For example:
// let's output only the error messages
mgr.logger = {
error: console.error.bind(console)
}
Development
To build and run the tests:
$ npm install
$ npm test
Contributions
Contributions welcome - see CONTRIBUTING.md
License
MIT - see LICENSE.md