Packages

Nerves System - OpenWRT One (MediaTek MT7981B / Filogic 820)

Current section

Files

Jump to
nerves_system_openwrt_one lib mix tasks openwrt_one.upload.ex
Raw

lib/mix/tasks/openwrt_one.upload.ex

defmodule Mix.Tasks.OpenwrtOne.Upload do
@shortdoc "Apply an OTA update to a running OpenWRT One Nerves device"
@moduledoc """
Ship the freshly-built firmware to a running OpenWRT One Nerves
device via the system's `scripts/upload-ota.sh` (volume-level
A/B-slot UBI OTA: writes the inactive `fit_*` UBI volume via
`ubiupdatevol`, flips `nerves_fw_active` via `fw_setenv`, reboots).
Designed to be chained after `firmware` via a mix alias:
defp aliases do
[
firmware: ["firmware", "openwrt_one.firmware_post"],
upload: ["firmware", "openwrt_one.upload"]
]
end
Why this is needed: stock `mix upload` (Nerves bootstrap's task)
assumes a block-device firmware layout and writes the `.fw` with a
remote `fwup` invocation. The OpenWRT One stores its rootfs in UBI
volumes on SPI NAND, and writing to `/dev/ubi*` needs the
`UBI_IOCVOLUP` ioctl that `fwup` doesn't speak (raw_write /
path_write / pipe_write all return `EPERM`). `upload-ota.sh` uses
`ubiupdatevol` + `fw_setenv` over SSH instead.
## Target host
Resolved from, in order:
* the first CLI arg, if any
* the `MIX_TARGET_HOST` env var
* the literal `nerves.local`
The task prefixes `root@` automatically.
## Examples
MIX_TARGET=openwrt_one mix upload
MIX_TARGET=openwrt_one mix upload 192.168.1.100
MIX_TARGET_HOST=myrouter.lan MIX_TARGET=openwrt_one mix upload
"""
use Mix.Task
@impl Mix.Task
def run(args) do
unless Mix.target() == :openwrt_one do
Mix.raise(
"mix openwrt_one.upload requires MIX_TARGET=openwrt_one (got #{inspect(Mix.target())})"
)
end
fw = firmware_path()
File.exists?(fw) || Mix.raise("Firmware not found at #{fw} — `mix firmware` did not produce it")
script = upload_script!()
target = target_host(args)
Mix.shell().info("Uploading #{Path.relative_to_cwd(fw)} to root@#{target}")
case System.cmd(script, [fw, "root@#{target}"], into: IO.stream()) do
{_, 0} -> :ok
{_, code} -> Mix.raise("upload-ota.sh exited with code #{code}")
end
end
defp target_host([host | _]) when is_binary(host) and host != "", do: host
defp target_host(_), do: System.get_env("MIX_TARGET_HOST", "nerves.local")
defp firmware_path do
app = Mix.Project.config()[:app]
Path.join([Mix.Project.build_path(), "nerves", "images", "#{app}.fw"])
end
defp upload_script! do
case Mix.Project.deps_paths()[:nerves_system_openwrt_one] do
nil ->
Mix.raise("nerves_system_openwrt_one is not in your project's deps")
path ->
script = Path.join([Path.expand(path), "scripts", "upload-ota.sh"])
File.exists?(script) || Mix.raise("upload-ota.sh not found at #{script}")
script
end
end
end