Copies specific files (firmware blobs, binaries, libraries) from block device partitions to the local filesystem. Handles device readiness, mounting, manifest-driven file copying, and marker-based idempotency.
Designed for embedded Linux systems (e.g. Nerves) where firmware blobs or vendor files need to be copied from a dedicated partition at boot time.
- Idempotent -- uses a marker file so the copy is a no-op on subsequent boots
- Manifest-driven -- declaratively specify which files to copy, with glob support
- Resilient -- waits for block devices to appear and retries mounts, handling early-boot timing issues
- Partial failure tolerance -- non-critical entries can fail without blocking
the operation; only
critical: trueentries cause overall failure - Read-only mounting -- source partition is always mounted read-only
- Zero runtime dependencies -- pure Elixir library
Add blob_copy to your list of dependencies in mix.exs:
def deps do
[
{:blob_copy, "~> 0.1.0"}
]
endconfig = %BlobCopy.Config{
partition: "/dev/mmcblk0p10",
mount_point: "/tmp/vendor-mount",
marker_file: "/var/lib/blob-copy-done",
manifest: [
%{source: "lib/firmware", dest: "/lib/firmware"},
%{source: "usr/bin/tool", dest: "/usr/bin/tool", critical: true},
%{source: "etc/*config*", dest: "/etc"}
]
}
BlobCopy.ensure_copied(config)# config/config.exs
config :my_app, :blob_copy,
partition: "/dev/mmcblk0p10",
mount_point: "/tmp/vendor-mount",
marker_file: "/var/lib/blob-copy-done",
manifest: [
%{source: "lib/firmware", dest: "/lib/firmware", critical: true}
]
# In your application code
Application.fetch_env!(:my_app, :blob_copy)
|> BlobCopy.Config.new!()
|> BlobCopy.ensure_copied()BlobCopy.Config accepts the following fields:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
partition |
string | yes | -- | Block device path (e.g. "/dev/mmcblk0p10") |
mount_point |
string | yes | -- | Temporary mount point path |
marker_file |
string | yes | -- | Path to idempotency marker file |
manifest |
list | yes | -- | List of file entries to copy |
log_prefix |
string | no | "BlobCopy" |
Prefix for log messages |
partition_wait_timeout |
integer | no | 30_000 |
Ms to wait for the block device to appear |
mount_retries |
integer | no | 10 |
Number of mount retry attempts |
mount_retry_delay |
integer | no | 1_000 |
Ms between mount retries |
Each manifest entry is a map with:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
source |
string | yes | -- | Path relative to mount point; may contain glob characters (*, ?, [) |
dest |
string | yes | -- | Absolute destination path |
permissions |
integer | no | 0o755 |
File permissions (ignored for directory copies) |
critical |
boolean | no | false |
If true, failure to copy this entry fails the entire operation |
The copy mode is auto-detected per entry:
- Glob -- source contains
*,?, or[: all matches are copied intodest - Directory -- resolved source is a directory: copied recursively
- File -- single file copy with permissions set
BlobCopy.ensure_copied(%Config{})-- idempotent copy operation. Returns:okor{:error, reason}.BlobCopy.copied?(%Config{})-- returnstrueif the marker file exists.
- Linux -- uses
mount,umount,mountpoint,cp, and reads/proc/mounts - Root privileges (or appropriate capabilities) for mount/unmount operations
- Elixir >= 1.14
mix deps.get
mix compile
mix docs # generate documentation
mix test # run testsMIT -- see LICENSE for details.