Installs signed precompiled libraries into a running Nerves device, with no Mix, no Hex and no compiler.
Precompiled.install(:recon)
Precompiled.install({:circuits_gpio, "2.3.1"})
Precompiled.install([:recon, {:circuits_gpio, "~> 2.3"}])
Precompiled.install(:recon, dry_run: true)
Precompiled.install(:recon, source: "file:///media/usb/precompiled")
Precompiled.list()
Precompiled.uninstall(:recon)A call selects the build that matches the device (its Nerves system and version, OTP, Elixir, CPU and the apps it already has, by name and exact version), verifies every byte through the signed root, batch, manifest and file hashes, stages the files under the store directory, and loads and starts them. Absent runtime dependencies are installed with it, as one unit for the running VM: anything that fails leaves the code path and the loaded apps as they were. The store is a cache and may change before a later step fails: least recently used entries can be evicted for room, and builds that verified and were committed stay in it, unloaded, for a later call. Nothing is compiled, nothing unsigned is loaded, and nothing already present is replaced.
It is available by default. Nothing runs on its own: only someone with a shell on the device calls it. The files stay in the store across reboots as a cache, but nothing is loaded at boot.
Cache
Installed builds stay in dir across reboots, but nothing is loaded at
boot. Installing the same build again loads it from there without
downloading, after checking every cached file against the signed hashes
of the current batch. The cache is bounded by max_bytes and evicts the
least recently used builds that are not loaded. Precompiled.clean/0
empties it.
Options
| Option | Default | Meaning |
|---|---|---|
:source | config :precompiled, :device, source:, else the official API | The API root (https://..., or http:// on loopback only), or file://DIR for a bundle. The default is https://compatibility.nerves-project.org/api/precompiled, checked against the root this library ships. Any other source must be configured or passed. |
:content, :deadline_seconds | as on the host | Same meaning as the host config keys. |
:cacerts_path | none | A PEM CA bundle to verify an https source with (a path). |
:dir | /data/precompiled | The store. Its trust state lives in DIR/.trust: a call with another :dir gets its own trust state (root chain and sequence floors), starting again from the shipped root. |
:start | true | Start the installed apps (:temporary) after loading them. |
:closure | true | Install absent runtime dependencies. false refuses with {:needs, [...]} instead. |
:max_closure, :max_bytes | 25, 64 MB | Bounds on the apps of one call and on the store's size. |
:dry_run | false | Select and plan, verify the trust chain, fetch the manifests and .app files only. Returns the plan. It writes no entry, staging dir or eviction: only the trust state in DIR/.trust, and the store dir and its marker when they do not exist yet. |
:report | none | With dry_run: true, write the device facts and the plan as JSON to this path (for mix precompiled.bundle --device-report). |
:quiet | false | Print nothing. |
:lock_retries | 60 | For tests: how often to retry a store another call holds. |
Another trust anchor than the shipped priv/precompiled/root.json is
set in the firmware's config only (config :precompiled, :device, root: path), never per call.
The device facts, the clock check and the trust anchor have test seams
(:env, :clock_synchronized?, :root). A call may pass them only while this library's
application env has test_seams: true, as its own test suite sets;
otherwise they are refused as unknown options, and the firmware's
:device config never sets them.
config :precompiled, :device, enabled: false in the firmware
disables install/2 ({:error, :disabled}); list/1, uninstall/2
and clean/1 keep working, so what is installed can still be removed.
allow: [app] limits installs to those apps, closure members included.
Both are read from the firmware's config only: a call can neither pass,
widen nor clear them. The same config may set the defaults of source,
content, root, cacerts_path, deadline_seconds, dir,
max_bytes, max_closure, start, closure and quiet; any other key
there is ignored.
An online call needs a synchronized clock: on an unsynchronized one it
refuses before any request. A file:// bundle with a signed
offline.json needs no clock and no network at all; one without it is
bounded by its batch's own expiry, judged against the device clock (no
NTP needed, but a clock before this library's build is refused).
Return values
{:ok, [%Precompiled.Installed{}]}, the apps this call installed (empty when everything was already there);- with
dry_run: true,{:ok, %Precompiled.Plan{}}; {:error, reason}, wherereasonis one of:{:bad_request, term},{:bad_option, key},{:bad_source, s};{:clock_unsynchronized, now};{:missing_app, app}, a network app the firmware leaves out;{:batch, why}, the trust chain refused (whyis the client's batch refusal text);{:no_build, pkg, why}, the first failed rule of the build that got furthest;{:needs, [{app, vsn}]}, withclosure: false;{:not_allowed, app};{:already_present, app, vsn};{:module_loaded | :module_conflict, mod, path};{:noexec, mount};{:conflict, app, [vsn]}, two versions of one app in one call;{:too_big, needed, free},{:too_many, n, max};{:enospc, path}, a full disk while staging or committing;{:unsafe_store, why};{:rename, posix},{:bad_staging, path}, or a posix atom such as:eacces, when the store refuses a commit;{:fetch, pkg, why}, a file that did not verify;{:elf, why}, a native library built for another CPU;{:on_load_failed, mod, reason};{:start_failed, app, reason};{:in_use, mod, pids},{:in_use_by, [app]},{:not_installed, app};- from
uninstall/2:{:stop_failed, app, why}, the app would not stop or unload, and{:in_use, app}, withremove: true, when its files are still in use; {:report, path, why}, the dry run's report could not be written;:busy, another call holds the store;:disabled;{:internal, message}.
install/2 never raises on the network or on bad input.
The store's lock serializes calls on this node. An installed app whose
start/2 itself calls install/2 waits out the retries and gets
{:error, :busy}.
Summary
Functions
Empties the cache: removes every store entry that is not loaded in this
VM. Keeps .trust and whatever this VM is running from. Runs under the
store's lock. Prints one summary line unless quiet: true.
Installs, loads and starts request and its absent runtime
dependencies. See the module documentation for the options and reasons.
The apps in the store, each with whether it is loaded in this VM ([]
when there is no store yet), or {:error, reason}: a bad option, an
unsafe store, {:internal, message}. Prints one line per app unless
quiet: true.
Unloads an installed app: stops it, unloads it, purges its modules and
takes its directory off the code path. Its files stay in the store as a
cache unless remove: true.
Types
Functions
@spec clean(keyword()) :: {:ok, %{removed: [atom()], bytes: non_neg_integer(), kept: [atom()]}} | {:error, term()}
Empties the cache: removes every store entry that is not loaded in this
VM. Keeps .trust and whatever this VM is running from. Runs under the
store's lock. Prints one summary line unless quiet: true.
@spec install(request() | [request()], keyword()) :: {:ok, [Precompiled.Installed.t()]} | {:ok, Precompiled.Plan.t()} | {:error, term()}
Installs, loads and starts request and its absent runtime
dependencies. See the module documentation for the options and reasons.
@spec list(keyword()) :: [Precompiled.Installed.t()] | {:error, term()}
The apps in the store, each with whether it is loaded in this VM ([]
when there is no store yet), or {:error, reason}: a bad option, an
unsafe store, {:internal, message}. Prints one line per app unless
quiet: true.
Unloads an installed app: stops it, unloads it, purges its modules and
takes its directory off the code path. Its files stay in the store as a
cache unless remove: true.
Refuses {:in_use_by, apps} while other installed apps that are loaded
need it, unless cascade: true (which unloads them first). Refuses
{:in_use, mod, pids} while other processes run its code, unless
force: true.
"In use" means code: processes running the app's modules, as
:code.soft_purge/1 sees them. Data is not detected: an open NIF
resource or a reference into the app (a GPIO handle from
Circuits.GPIO.open/2, say) held by a shell or another process does not
stop the uninstall. Close such handles (Circuits.GPIO.close/1) before
uninstalling.