Nerves.BuildPlan (nerves v2.0.0-pre.2)

Copy Markdown View Source

Nerves firmware build plan

This struct gets filled in by Nerves and Nerves-aware packages before compilation starts. It then gets passed around during the firmware build process to guide everything from pre-compiled artifact downloads to building root filesystems and final firmware assembly. If you'd like to adjust the firmware build process, this is usually the place to look.

Summary

Types

How to download a file

Download validation strategy

Specify how download files are processed

Per-package information that's kept in the BuildPlan

t()

The Nerves.BuildPlan struct has the following fields

Functions

Return the interpolated value for the specified key

Return a map of environment variables with all values interpolated

Return the interpolated value for the specified key

Find build plan information about a package

Merge a new set of OS environment variables into the plan

Prepend a path to the PATH environment variable

Replace the specified package info

Validate the build plan to detect issues that may cause problems later

Types

download_spec()

@type download_spec() ::
  String.t()
  | {:github_releases, keyword()}
  | {:github_api, keyword()}
  | {:gitea_releases, keyword()}
  | {:gitea_api, keyword()}

How to download a file

URL

Raw strings are interpreted as URLs. Schemes may be file, http, or https

GitHub Releases

Public GitHub repositories may use URLs to download release artifacts, but using the :github_releases or :github_api download strategies enables GitHub authentication. In addition to supporting private GitHub repositories, this can also help get past unauthenticated download errors.

The :github_releases strategy supports unauthenticated GitHub downloads. If GitHub auth tokens aren't available, it still tries. This is good for public repositories.

The :github_api strategy uses the GitHub API to find the release artifact. It will fail with GitHub auth tokens aren't available.

GitHub auth tokens are found in this order:

  1. GITHUB_TOKEN environment variable
  2. GH_TOKEN environment variable
  3. :custom_auth_token option
  4. Calling out to gh if the :use_gh_cli? option is true

Options:

  • :github_url - the main GitHub URL (defaults to https://github.com)
  • :github_api_url - the GitHub API endpoint (defaults to https://api.github.com)
  • :org - the GitHub organization (required)
  • :repo - the GitHub repository (required)
  • :tag - the release tag (required)
  • :filename - the filename to download (required)
  • :custom_auth_token - specify an auth token. This is rarely needed.
  • :use_gh_cli? - allow the gh CLI utility to be used to get auth tokens. Defaults to true

Gitea Releases

Gitea support is very similar to GitHub support with the exception of the URLs and naming of auth tokens. It is similar in that :gitea_releases is intended for public assets that don't require authentication and :gitea_api works for private release assets.

Gitea auth tokens are found in this order:

  1. GITEA_TOKEN environment variable
  2. :token option

Options:

  • :base_url - the base Gitea URL (required)
  • :org - the Gitea organization (required)
  • :repo - the Gitea repository (required)
  • :tag - the release tag (required)
  • :filename - the filename to download (required)
  • :token - specify an auth token

download_validator()

@type download_validator() ::
  :archive | {:openssl_signature, keyword()} | {:skip, keyword()}

Download validation strategy

Nerves doesn't allow unvalidated downloads to proceed to subsequent build steps. One size doesn't fit all when it comes to validation, so Nerves-aware packages can customize it to their needs.

Skip

If the download step suffices in getting trusted binaries, then validation isn't needed. It is still necessary to explicitly skip trusted binaries.

Options:

  • :filename - the filename to skip validation on

OpenSSL Signature

This is the simplest type of validation that can be done using the openssl command line tool. To create a public/private key pair, run:

openssl genrsa -aes128 -passout pass:<passphrase> -out private.pem 2048
openssl rsa -in private.pem -passin pass:<passphrase> -pubout -out public.pem

Then to sign a file, run:

openssl dgst -sha256 -sign $privatekey -out /tmp/$filename.out $filename
openssl base64 -in /tmp/$filename.out -out $filename.sig
rm /tmp/$filename.out

To manually verify a file, run:

openssl base64 -d -in $filename.sig -out /tmp/$filename.out
openssl dgst -sha256 -verify $publickey -signature /tmp/$filename.out $filename
rm /tmp/$filename.out

Options:

  • :filename - the file to verify (required)
  • :signature - the signature file (defaults to <filename>.sig)
  • :public_keys - a list of public keys in PEM form and as strings

extractor_spec()

@type extractor_spec() :: {:untar, keyword()}

Specify how download files are processed

The normal procedure is to untar the file that was downloaded. This is not always the case, and this option allows other processing to be done.

Untar

Run tar to extract the contents into the artifact directory.

Options:

  • :filename - the file to untar (required)

package_info()

@type package_info() :: %{
  app: atom(),
  path: Path.t(),
  version: String.t(),
  deps: [atom()],
  artifact_path: Path.t(),
  download_path: Path.t(),
  downloads: [download_spec()],
  download_validators: [download_validator()],
  extractors: [extractor_spec()],
  source_fingerprint: String.t(),
  artifact_source_files: [Path.t()],
  validated_files: [Path.t()],
  dockerfile: Path.t() | nil
}

Per-package information that's kept in the BuildPlan

  • :app - the name of the package
  • :artifact_path - Path to where artifacts can be extracted and custom files added
  • :dockerfile - Dockerfile used by mix nerves.artifact.build and mix nerves.artifact.shell
  • :deps - List of Nerves-aware packages that this one depends on
  • :download_path - Path to where to download any files for this package
  • :download_validators - A list of validators for checking the package's download. Post-download steps may only access validated files.
  • :downloads - a list of download plans
  • :extractors - a list of extractors for expanding archives into the artifact_path
  • :path - path to package source under the deps directory
  • :source_fingerprint - calculated source fingerprints. The fingerprint catches configs that diverge from artifact inputs.
  • :artifact_source_files - List of files required to build the artifact.
  • :validated_files - a map of download files that have passed validation keyed by package name
  • :version - the version of this package

t()

@type t() :: %Nerves.BuildPlan{
  actions: [module() | {module(), keyword()}],
  config: map(),
  env: map(),
  erts: Path.t() | boolean(),
  host_build?: boolean(),
  packages: [package_info()],
  rootfs_overlays: [Path.t()]
}

The Nerves.BuildPlan struct has the following fields:

  • :config - a map of key-value pairs containing the overall configuration
  • :env - a map of key-value pairs that are exported in the OS environment to guide cross-compilation. See the Nerves advanced configuration for official environment variables.
  • :host_build? - true if building for the host
  • :packages - package-specific plans and config. List is ordered by compilation order.
  • :actions - a list of build actions for creating the firmware
  • :rootfs_overlays - a list of paths or .tar files that get overlaid to create the root filesystem
  • :erts - Path to the version of erts to include in releases

The :config map should be used to record all configuration. The build plan is intended to be the sole source of truth. Keys from non-Nerves packages should prepend their package name to avoid conflicts. The following keys are used:

  • :source_date_epoch - the Unix date (integer) timestamp to use for all date/time references
  • :fwup_conf - an optional user-app overridden fwup.conf path
  • :fwup_provisioning_conf - an optional user-app overridden provisioning config for fwup
  • :fwup_compression - use :best (default) or :fast compression. Numbers from 1 to 9 are also supported.
  • :rootfs_overlay - an optional user-app overridden rootfs overlay (TODO? Add to main rootfs_overlays field?)
  • :rootfs_type - either type or {type, mkfs_options} tuple. The type can be :squashfs, :erofs, or :ext4. options is a string list and if nil or omitted uses Nerves' defaults
  • :host_tuple - the compiler target tuple for making binaries on the computer running the build. See Nerves.TargetTuple

Functions

fetch_interpolated_env(build_plan, key)

@spec fetch_interpolated_env(t(), String.t()) :: {:ok, String.t()} | :error

Return the interpolated value for the specified key

This is a non-raising version of fetch_interpolated_env/2.

fetch_interpolated_env!(build_plan)

@spec fetch_interpolated_env!(t()) :: %{required(String.t()) => String.t()}

Return a map of environment variables with all values interpolated

Raises KeyError on undefined variables or self-referential variables

fetch_interpolated_env!(build_plan, key)

@spec fetch_interpolated_env!(t(), String.t()) :: String.t()

Return the interpolated value for the specified key

Raises KeyError on undefined variables or self-referential variables

find_package(build_plan, app)

@spec find_package(t(), atom()) :: package_info() | nil

Find build plan information about a package

merge_config(build_plan, config)

@spec merge_config(t(), Enumerable.t()) :: t()

merge_env(build_plan, env)

@spec merge_env(t(), Enumerable.t()) :: t()

Merge a new set of OS environment variables into the plan

Variables should be passed either as a map or a list of key/value tuples. Variables overwrite ones with the same keys.

Values support interpolation using ${VAR_NAME} syntax. Interpolation is postponed until later so it's fine to set values that can't be interpolated until a future variable gets added. Unlike a Unix shell, though, values referencing unknown values raise rather than substitute an empty string.

Except for $PATH handling, the user's OS environment is ignored.

prepend_path(build_plan, path)

@spec prepend_path(t(), Path.t()) :: t()

Prepend a path to the PATH environment variable

If the path already exists in $PATH, then this is a no-op.

replace_package(build_plan, package)

@spec replace_package(t(), package_info()) :: t()

Replace the specified package info

run_planning_actions(build_plan, fun)

@spec run_planning_actions(t(), atom()) :: t()

run_release_actions(build_plan, release, fun)

@spec run_release_actions(t(), Mix.Release.t(), atom()) :: Mix.Release.t()

run_simple_actions(build_plan, fun)

@spec run_simple_actions(t(), atom()) :: :ok

validate!(build_plan)

@spec validate!(t()) :: t()

Validate the build plan to detect issues that may cause problems later

Returns the build plan for use in pipelines