Skip to content

Commit db1e773

Browse files
authored
Merge branch 'main' into feature/enforce-credo-code-quality
2 parents 16a73b4 + 1619a9f commit db1e773

File tree

20 files changed

+306
-133
lines changed

20 files changed

+306
-133
lines changed

.cspell.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
}
1212
],
1313
"dictionaries": [
14-
"cpp",
14+
"cpp",
1515
"elixir",
1616
"en-gb",
1717
"fhunleth-cspell-dictionary",
@@ -20,10 +20,13 @@
2020
],
2121
"ignorePaths": [
2222
"**/*.csv",
23+
"docker/*",
24+
"Dockerfile",
2325
"config",
2426
"assets/{node_modules,static}",
2527
"mix.exs",
2628
"priv",
29+
"rel",
2730
"test"
2831
],
2932
"words": [

Dockerfile

Lines changed: 76 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ ARG RUNNER_IMAGE="ubuntu:${DISTRO}"
88
ARG DEBIAN_FRONTEND=noninteractive
99

1010
###
11-
### First Stage - Fetch deps for building web assets
11+
### Fetch deps for building web assets
1212
###
13-
FROM ${BUILDER_IMAGE} as deps
13+
FROM ${BUILDER_IMAGE} AS deps
1414

1515
ENV MIX_ENV=prod
1616

@@ -24,9 +24,9 @@ RUN mix deps.get --only $MIX_ENV
2424

2525

2626
###
27-
### Second Stage - Build web assets
27+
### Build web assets
2828
###
29-
FROM node:${NODE_VERSION} as assets
29+
FROM node:${NODE_VERSION} AS assets
3030

3131
RUN mkdir -p /priv/static
3232

@@ -42,13 +42,14 @@ RUN npm ci && npm cache clean --force && npm run deploy
4242

4343

4444
###
45-
### Third Stage - Building the Release
45+
### Build the NervesHub release
4646
###
47-
FROM ${BUILDER_IMAGE} as build
47+
FROM ${BUILDER_IMAGE} AS build
4848

4949
# install dependencies
50-
RUN apt-get update -y && apt-get install -y build-essential git ca-certificates curl gnupg \
51-
&& apt-get clean && rm -f /var/lib/apt/lists/*_*
50+
RUN apt-get update -y && apt-get install -y build-essential git ca-certificates curl gnupg && \
51+
apt-get clean && \
52+
rm -f /var/lib/apt/lists/*_*
5253

5354
WORKDIR /build
5455

@@ -95,25 +96,83 @@ COPY rel rel
9596
RUN mix release
9697

9798

99+
###
100+
### Build a static FWUP
101+
###
102+
103+
FROM ${RUNNER_IMAGE} AS fwup
104+
105+
RUN apt-get update -y && \
106+
apt-get upgrade -y && \
107+
apt-get install -y git curl build-essential autoconf pkg-config libtool mtools unzip zip help2man libconfuse-dev libarchive-dev xdelta3 dosfstools
108+
109+
RUN git clone https://github.com/fwup-home/fwup /tmp/fwup
110+
111+
WORKDIR /tmp/fwup
112+
113+
RUN git checkout v1.13.2 && \
114+
./scripts/download_deps.sh && \
115+
./scripts/build_deps.sh && \
116+
./autogen.sh && \
117+
PKG_CONFIG_PATH=$PWD/build/host/deps/usr/lib/pkgconfig ./configure --enable-shared=no && \
118+
make && \
119+
make check && \
120+
make install
121+
122+
123+
###
124+
### Build jemalloc - GCC 14
125+
###
126+
127+
FROM ${RUNNER_IMAGE} AS jemalloc
128+
129+
RUN apt-get update -y && \
130+
apt-get upgrade -y && \
131+
apt-get install -y git autoconf cmake make software-properties-common && \
132+
add-apt-repository ppa:ubuntu-toolchain-r/ppa -y && \
133+
apt-get update -y && \
134+
apt-get install -y gcc-14 g++-14 && \
135+
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 14 --slave /usr/bin/g++ g++ /usr/bin/g++-14
136+
137+
# Build the latest jemalloc
138+
139+
RUN git clone https://github.com/facebook/jemalloc /tmp/jemalloc
140+
141+
WORKDIR /tmp/jemalloc
142+
143+
RUN autoconf && \
144+
./configure && \
145+
make && \
146+
make install
147+
148+
98149
###
99150
### Last Stage - Setup the Runtime Environment
100151
###
101152

102153
FROM ${RUNNER_IMAGE} AS app
103154

104-
RUN apt-get update -y \
105-
&& apt-get install -y libstdc++6 openssl libncurses6 locales bash openssl curl python3 python3-pip jq xdelta3 zip unzip wget \
106-
&& wget https://github.com/fwup-home/fwup/releases/download/v1.13.2/fwup_1.13.2_amd64.deb \
107-
&& dpkg -i fwup_1.13.2_amd64.deb && rm fwup_1.13.2_amd64.deb \
108-
&& apt-get clean && rm -rf /var/lib/apt/lists/*
155+
RUN apt-get update -y && \
156+
apt-get install -y openssl ca-certificates locales bash xdelta3 && \
157+
apt-get autoremove -y && \
158+
apt-get clean && \
159+
rm -rf /var/lib/apt/lists/*
109160

110161
# Set the locale
111162
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
112163

113-
ENV LANG en_US.UTF-8
114-
ENV LANGUAGE en_US:en
115-
ENV LC_ALL en_US.UTF-8
164+
ENV LANG=en_US.UTF-8
165+
ENV LANGUAGE=en_US:en
166+
ENV LC_ALL=en_US.UTF-8
167+
168+
# Use jemalloc for memory allocation
169+
COPY --from=jemalloc /usr/local/lib/libjemalloc.so.2 /usr/local/lib/libjemalloc.so.2
170+
ENV LD_PRELOAD=/usr/local/lib/libjemalloc.so.2
116171

172+
# Copy over the statically built fwup
173+
COPY --from=fwup /usr/local/bin/fwup /usr/local/bin/fwup
174+
175+
# Copy over NervesHub
117176
WORKDIR /app
118177

119178
COPY --from=build /build/_build/prod/rel/nerves_hub ./
@@ -124,4 +183,5 @@ ENV SECRET_KEY_BASE=nokey
124183
ENV PORT=4000
125184

126185
ENTRYPOINT ["bin/nerves_hub"]
186+
127187
CMD ["start"]

lib/nerves_hub/firmwares.ex

Lines changed: 46 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -421,55 +421,58 @@ defmodule NervesHub.Firmwares do
421421
{:ok, source_url} = firmware_upload_config().download_file(source_firmware)
422422
{:ok, target_url} = firmware_upload_config().download_file(target_firmware)
423423

424-
case update_tool().create_firmware_delta_file(source_url, target_url) do
424+
case update_tool().create_firmware_delta_file(
425+
{source_firmware.uuid, source_url},
426+
{target_firmware.uuid, target_url}
427+
) do
425428
{:ok, created} ->
426-
firmware_delta_filename = Path.basename(created.filepath)
427-
428-
result =
429-
Repo.transaction(
430-
fn ->
431-
with upload_metadata <-
432-
firmware_upload_config().metadata(org.id, firmware_delta_filename),
433-
{:ok, firmware_delta} <-
434-
complete_firmware_delta(
435-
firmware_delta,
436-
created.tool,
437-
created.size,
438-
created.source_size,
439-
created.target_size,
440-
created.tool_metadata,
441-
upload_metadata
442-
),
443-
{:ok, firmware_delta} <- get_firmware_delta(firmware_delta.id),
444-
:ok <-
445-
firmware_upload_config().upload_file(created.filepath, upload_metadata),
446-
:ok <- update_tool().cleanup_firmware_delta_files(created.filepath) do
447-
Logger.info(
448-
"Created firmware delta successfully.",
429+
Repo.transact(
430+
fn ->
431+
with upload_metadata <-
432+
firmware_upload_config().delta_metadata(
433+
org.id,
434+
source_firmware.uuid,
435+
target_firmware.uuid
436+
),
437+
{:ok, firmware_delta} <-
438+
complete_firmware_delta(
439+
firmware_delta,
440+
created.tool,
441+
created.size,
442+
created.source_size,
443+
created.target_size,
444+
created.tool_metadata,
445+
upload_metadata
446+
),
447+
{:ok, firmware_delta} <- get_firmware_delta(firmware_delta.id),
448+
:ok <-
449+
firmware_upload_config().upload_file(created.filepath, upload_metadata),
450+
:ok <- update_tool().cleanup_firmware_delta_files(created.filepath) do
451+
Logger.info(
452+
"Created firmware delta successfully.",
453+
product_id: source_firmware.product_id,
454+
source_firmware: source_firmware.uuid,
455+
target_firmware: target_firmware.uuid
456+
)
457+
458+
{:ok, firmware_delta}
459+
else
460+
{:error, error} ->
461+
update_tool().cleanup_firmware_delta_files(created.filepath)
462+
463+
Logger.error(
464+
"Failed to create firmware delta: #{inspect(error)}",
449465
product_id: source_firmware.product_id,
450466
source_firmware: source_firmware.uuid,
451467
target_firmware: target_firmware.uuid
452468
)
453469

454-
firmware_delta
455-
else
456-
{:error, error} ->
457-
update_tool().cleanup_firmware_delta_files(created.filepath)
458-
459-
Logger.error(
460-
"Failed to create firmware delta: #{inspect(error)}",
461-
product_id: source_firmware.product_id,
462-
source_firmware: source_firmware.uuid,
463-
target_firmware: target_firmware.uuid
464-
)
465-
466-
Repo.rollback(error)
467-
end
468-
end,
469-
timeout: 30_000
470-
)
471-
472-
case result do
470+
{:error, error}
471+
end
472+
end,
473+
timeout: 30_000
474+
)
475+
|> case do
473476
{:ok, _delta} ->
474477
:ok
475478

lib/nerves_hub/firmwares/update_tool.ex

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,10 @@ defmodule NervesHub.Firmwares.UpdateTool do
8989
@doc """
9090
Called to create a firmware delta file on the local filesystem
9191
"""
92-
@callback create_firmware_delta_file(String.t(), String.t()) ::
92+
@callback create_firmware_delta_file(
93+
{source_id :: String.t(), source_url :: String.t()},
94+
{target_id :: String.t(), target_url :: String.t()}
95+
) ::
9396
{:ok, delta_created()} | {:error, term()}
9497

9598
@doc """

lib/nerves_hub/firmwares/update_tool/fwup.ex

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,8 @@ defmodule NervesHub.Firmwares.UpdateTool.Fwup do
4545
end
4646

4747
@impl NervesHub.Firmwares.UpdateTool
48-
def create_firmware_delta_file(source_url, target_url) do
49-
uuid = Ecto.UUID.generate()
50-
work_dir = Path.join(System.tmp_dir(), uuid) |> Path.expand()
48+
def create_firmware_delta_file({source_uuid, source_url}, {target_uuid, target_url}) do
49+
work_dir = Path.join(System.tmp_dir(), "#{source_uuid}_#{target_uuid}")
5150
_ = File.mkdir_p(work_dir)
5251

5352
try do
@@ -57,7 +56,7 @@ defmodule NervesHub.Firmwares.UpdateTool.Fwup do
5756
dl!(source_url, source_path)
5857
dl!(target_url, target_path)
5958

60-
case do_delta_file(source_path, target_path, work_dir) do
59+
case do_delta_file({source_uuid, source_path}, {target_uuid, target_path}, work_dir) do
6160
{:ok, output} ->
6261
{:ok, output}
6362

@@ -139,7 +138,7 @@ defmodule NervesHub.Firmwares.UpdateTool.Fwup do
139138
end)
140139
end
141140

142-
def do_delta_file(source_path, target_path, work_dir) do
141+
def do_delta_file({source_uuid, source_path}, {target_uuid, target_path}, work_dir) do
143142
source_work_dir = Path.join(work_dir, "source")
144143
target_work_dir = Path.join(work_dir, "target")
145144
output_work_dir = Path.join(work_dir, "output")
@@ -156,7 +155,7 @@ defmodule NervesHub.Firmwares.UpdateTool.Fwup do
156155
{:ok, target_meta_conf} <- File.read(Path.join(target_work_dir, "meta.conf")),
157156
{:ok, tool_metadata} <- get_tool_metadata(Path.join(target_work_dir, "meta.conf")),
158157
:ok <- Confuse.Fwup.validate_delta(source_meta_conf, target_meta_conf),
159-
{:ok, deltas} <- Confuse.Fwup.get_delta_files(Path.join(target_work_dir, "meta.conf")),
158+
{:ok, deltas} <- Confuse.Fwup.get_delta_files_from_config(target_meta_conf),
160159
{:ok, all_delta_files} <- delta_files(deltas) do
161160
Logger.info("Generating delta for files: #{Enum.join(all_delta_files, ", ")}")
162161

@@ -210,7 +209,7 @@ defmodule NervesHub.Firmwares.UpdateTool.Fwup do
210209
end
211210
end
212211

213-
{:ok, delta_zip_path} = Plug.Upload.random_file("generated_delta_zip_file")
212+
{:ok, delta_zip_path} = Plug.Upload.random_file("#{source_uuid}_#{target_uuid}_delta")
214213

215214
{:ok, _} =
216215
:zip.create(to_charlist(delta_zip_path), generate_file_list(output_work_dir),

lib/nerves_hub/firmwares/upload.ex

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,13 @@ defmodule NervesHub.Firmwares.Upload do
3333
Called to generate the upload_metadata that will be persisted for later lookup.
3434
"""
3535
@callback metadata(Org.id(), String.t()) :: upload_metadata()
36+
37+
@doc """
38+
Called to generate the upload_metadata specific to delta firmwares that will be persisted for later lookup.
39+
"""
40+
@callback delta_metadata(
41+
Org.id(),
42+
source_firmware_uuid :: String.t(),
43+
target_firmware_uuid :: String.t()
44+
) :: upload_metadata()
3645
end

lib/nerves_hub/firmwares/upload/file.ex

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ defmodule NervesHub.Firmwares.Upload.File do
3535

3636
config = Application.get_env(:nerves_hub, __MODULE__)
3737

38-
common_path = "#{org_id}"
39-
local_path = Path.join([config[:local_path], common_path, filename])
38+
common_path = Path.join([key_prefix(), Integer.to_string(org_id), filename])
39+
40+
local_path = Path.join([config[:local_path], common_path])
4041

4142
port =
4243
if Enum.member?([443, 80], web_config[:url][:port]),
@@ -53,4 +54,42 @@ defmodule NervesHub.Firmwares.Upload.File do
5354
local_path: local_path
5455
}
5556
end
57+
58+
@impl NervesHub.Firmwares.Upload
59+
def delta_metadata(org_id, source_firmware_uuid, target_firmware_uuid) do
60+
web_config = Application.get_env(:nerves_hub, NervesHubWeb.Endpoint)
61+
62+
config = Application.get_env(:nerves_hub, __MODULE__)
63+
64+
common_path =
65+
Path.join([
66+
key_prefix(),
67+
Integer.to_string(org_id),
68+
source_firmware_uuid,
69+
"#{target_firmware_uuid}.delta.fw"
70+
])
71+
72+
local_path = Path.join([config[:local_path], common_path])
73+
74+
port =
75+
if Enum.member?([443, 80], web_config[:url][:port]),
76+
do: "",
77+
else: ":#{web_config[:url][:port]}"
78+
79+
public_path =
80+
Path.join([
81+
"#{web_config[:url][:scheme]}://#{web_config[:url][:host]}#{port}/",
82+
config[:public_path],
83+
common_path
84+
])
85+
86+
%{
87+
public_path: public_path,
88+
local_path: local_path
89+
}
90+
end
91+
92+
def key_prefix() do
93+
"firmware"
94+
end
5695
end

0 commit comments

Comments
 (0)