resilient.is/blog

LibResilient CLI

LibResilient now has a large number of transport plugins, offering a lot of flexibility for website administrators. Some of these plugins rely on more than one way of making information available — for example dnslink-ipfs expects content to be published on IPFS, and the latest IPFS address to then be pushed to DNS.

How to push this information and data out was so far left to website administrators, creating a relatively large obstacle to LibResilient adoption. Now this might gradually start getting solved, thanks to lrcli, the LibResilient CLI.

The Problem

The big problem with creating a consistent CLI for a tool like LibResilient is that basically all relevant functionality is related to specific LibResilient plugins. Additionally, at least in case of some plugins there is more than one way to push the information where it needs to be for LibResilient to be able to make use of it.

Consider the alt-fetch plugin, which allows LibResilient to fetch content from alternative HTTPS endpoints. Can the lrcli make assumptions regarding how content should be pushed to them? Should FTP or SFTP be used? Or maybe some REST API needs to be used instead and PUT HTTPS requests need to be issued? Or perhaps it’s some kind of proprietary service that requires a specific proprietary protocol?

There is a growing number of plugins that might need some CLI functionality to make Libresilient easy to deploy when using them. And in case of many if not most of these plugins there are simply too many possible ways of pushing the information out for there to be a general CLI that implements all of them.

The Approach

The plugin-based approach seems to work well for LibResilient itself. It might work well for the CLI itself, then, as well. After all, the author of a plugin probably knows best what kind of tools might a website administrator need to properly push the content and any necessary additional information out for the plugin to be able to make use of it.

LibResilient CLI is built around a simple plugin architecture. It assumes a cli.js file in plugin’s main directory. The file should be a valid Deno module (lrcli is written for Deno JS runtime), and export an object that defines the name, description, version, and actions implemented by the plugin. Based on that, CLI knows how to run specific actions and interpret relevant command line arguments.

Here is a simple example for the basic-integrity plugin.

The Plan

With time, more plugins will gain a CLI component. For some of them — like the basic-integrity or signed-integrity, which already have it — CLI’s role is going to be limited to generate data locally, for use with other tools in the publishing pipeline. For other plugins — for example, IPFS-based transport plugins — it makes sense to implement actions that push content out, actually publishing it.

And in some cases, this will remain somewhat complicated. There are simply too many ways to push out content to a simple HTTPS endpoint, that are often also very specific to a given website, for them to all be implementable in a single LibResilient CLI plugin. Same is probably true for pushing out DNS updates required for DNSLink-based plugins. In such cases, most broadly used mechanisms will probably be implemented (FTP/SFTP for fetch-based plugins? DNS UPDATE for DNSLink-based plugins?), but anything fancier than that will have to be left to the website admin, who knows their infrastructure and how to distribute content on it.

Example Usage

When run, lrcli expects the name of the plugin to load, and tries to be helpful in guiding the user in how its usage:

$ cli/lrcli.js 

Command-line interface for LibResilient.

This script creates a common interface to CLI actions implemented by LibResilient plugins.

Usage:
    lrcli.js [options] [plugin-name [plugin-options]]

Options:

    -h, --help [plugin-name]
        Print this message, if no plugin-name is given.
        If plugin-name is provided, print usage information of that plugin.

Plugin names are assumed to be sub-directories under plugins/ directory in LibResilient’s code directory:

$ cli/lrcli.js no-such-plugin

*** TypeError: Module not found "file:///home/user/Projects/libresilient/plugins/no-such-plugin/cli.js". ***

If plugin exists, usage information can be printed, based on data exported by the plugin’s cli.js module:

$ cli/lrcli.js basic-integrity

*** No action specified for plugin ***

CLI plugin:
    basic-integrity

Plugin Description:
    Verifying subresource integrity for resources fetched by other plugins.
    CLI used to generate subresource integrity hashes for provided files.
    
Usage:
    lrcli.js [general-options] basic-integrity [plugin-action [action-options]]

General Options:

    -h, --help [plugin-name]
        Print this message, if no plugin-name is given.
        If plugin-name is provided, print usage information of that plugin.

Actions and Action Options:

    get-integrity [options...] <file...>
        calculate subresource integrity hashes for provided files

         <file...>
            paths of files to be processed

        --algorithm (default: SHA-256)
            SubtleCrypto.digest-compatible algorithm names to use when calculating digests (default: "SHA-256")

        --output (default: json)
            a string, defining output mode ('json' or 'text'; 'json' is default)

The plugin controls its output, but a good practice is to provide support at least for json and text when useful data is returned, to simplify integration into any other tools the website admin chooses to use in their deployment pipeline:

$ cli/lrcli.js basic-integrity get-integrity libresilient.js 
{"libresilient.js":["sha256-UrkUn2KwKBQ93jS/pSd3Kt0/+9XkDT6Rj93jec/lOZY="]}

$ cli/lrcli.js basic-integrity get-integrity libresilient.js --output text
libresilient.js: sha256-UrkUn2KwKBQ93jS/pSd3Kt0/+9XkDT6Rj93jec/lOZY=