From 1168a0fce6bb468fdefdeefeb14b094859c50b99 Mon Sep 17 00:00:00 2001 From: Lincoln Bryant Date: Fri, 3 Jul 2026 10:01:43 -0400 Subject: [PATCH 01/10] add TOML dependency --- rebar.config | 1 + rebar.lock | 9 ++++++--- src/ebb.app.src | 3 ++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/rebar.config b/rebar.config index 5496171..109c19b 100644 --- a/rebar.config +++ b/rebar.config @@ -2,6 +2,7 @@ {deps, [ %{khepri, {git, "https://github.com/rabbitmq/khepri", {branch, main}}}, {erl_cidr, "1.2.1"}, + {tomerl, "0.5.0"}, {eqwalizer_support, {git_subdir, "https://github.com/whatsapp/eqwalizer.git", {branch, "main"}, "eqwalizer_support"}} diff --git a/rebar.lock b/rebar.lock index 2586c72..7e137f8 100644 --- a/rebar.lock +++ b/rebar.lock @@ -4,10 +4,13 @@ {ref,"6370858ff6f8a02e3c853cf034cdf3c5a85034dc"}, "eqwalizer_support"}, 0}, - {<<"erl_cidr">>,{pkg,<<"erl_cidr">>,<<"1.2.1">>},0}]}. + {<<"erl_cidr">>,{pkg,<<"erl_cidr">>,<<"1.2.1">>},0}, + {<<"tomerl">>,{pkg,<<"tomerl">>,<<"0.5.0">>},0}]}. [ {pkg_hash,[ - {<<"erl_cidr">>, <<"921BB463BCF10EAD937AE491E5BCAC2823E550B339A9893AC6574518553CC067">>}]}, + {<<"erl_cidr">>, <<"921BB463BCF10EAD937AE491E5BCAC2823E550B339A9893AC6574518553CC067">>}, + {<<"tomerl">>, <<"6E7CEAC151AC573B87B985F3D2112F29933B89BA0693FA741318D899A17AC318">>}]}, {pkg_hash_ext,[ - {<<"erl_cidr">>, <<"FCF5D51B1CC1B26D1748AB39F9B614AB445AD17172A9396D48C6B5C15EA9EFCF">>}]} + {<<"erl_cidr">>, <<"FCF5D51B1CC1B26D1748AB39F9B614AB445AD17172A9396D48C6B5C15EA9EFCF">>}, + {<<"tomerl">>, <<"2A7FB62F9EBF0E75561B39255638BC2B805B437C86FEC538657E7C3B576979FA">>}]} ]. diff --git a/src/ebb.app.src b/src/ebb.app.src index 689c064..b8d7643 100644 --- a/src/ebb.app.src +++ b/src/ebb.app.src @@ -5,7 +5,8 @@ {mod, {ebb_app, []}}, {applications, [ kernel, - stdlib + stdlib, + tomerl ]}, {env, []}, {modules, []}, From cf5d8e11a1489a06727e73ef8878b907fce148ec Mon Sep 17 00:00:00 2001 From: Lincoln Bryant Date: Fri, 3 Jul 2026 10:05:46 -0400 Subject: [PATCH 02/10] update a bit --- rebar.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebar.lock b/rebar.lock index 7e137f8..5ed236c 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,7 +1,7 @@ {"1.2.0", [{<<"eqwalizer_support">>, {git_subdir,"https://github.com/whatsapp/eqwalizer.git", - {ref,"6370858ff6f8a02e3c853cf034cdf3c5a85034dc"}, + {ref,"01f151ca206bb019db0999a2c9c8c8aaae79bbab"}, "eqwalizer_support"}, 0}, {<<"erl_cidr">>,{pkg,<<"erl_cidr">>,<<"1.2.1">>},0}, From 86fe5df376a85100f484ce2bd0baa2b8b67e29b5 Mon Sep 17 00:00:00 2001 From: Lincoln Bryant Date: Fri, 3 Jul 2026 11:07:48 -0400 Subject: [PATCH 03/10] read config file --- src/ebb_app.erl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ebb_app.erl b/src/ebb_app.erl index ebd2ad4..1db2cac 100644 --- a/src/ebb_app.erl +++ b/src/ebb_app.erl @@ -10,7 +10,13 @@ -export([start/2, stop/1]). start(_StartType, _StartArgs) -> - ebb_sup:start_link(). + case ebb_config:load() of + ok -> + ebb_sup:start_link(); + {error, Reason} -> + logger:critical("Refusing to start: ~s", [Reason]), + {error, {config_error, lists:flatten(Reason)}} + end. stop(_State) -> ok. From 399113f0571f326ff64ababb7912bc7f6a811278 Mon Sep 17 00:00:00 2001 From: Lincoln Bryant Date: Fri, 3 Jul 2026 11:08:00 -0400 Subject: [PATCH 04/10] add base config file, dont use it yet --- ebb.toml | 19 ++++++ src/ebb_config.erl | 157 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 ebb.toml create mode 100644 src/ebb_config.erl diff --git a/ebb.toml b/ebb.toml new file mode 100644 index 0000000..3ecd71d --- /dev/null +++ b/ebb.toml @@ -0,0 +1,19 @@ +# ebb configuration. +# +# ebb looks for this file at $EBB_CONFIG, ./ebb.toml, then /etc/ebb/ebb.toml, +# and refuses to start without it. All keys below are required. + +[dhcp] +# UDP port to bind. DHCP servers listen on 67; ports below 1024 need +# root or CAP_NET_BIND_SERVICE. +listen_port = 67 + +# IPv4 CIDR of the managed subnet. Leases are allocated from this range. +range = "172.16.0.0/16" + +# How long granted leases last, in seconds. +lease_seconds = 3600 + +# How long an unaccepted OFFER is held before its IP is reclaimed, +# in seconds. +offer_timeout_seconds = 300 diff --git a/src/ebb_config.erl b/src/ebb_config.erl new file mode 100644 index 0000000..c874e42 --- /dev/null +++ b/src/ebb_config.erl @@ -0,0 +1,157 @@ +-module(ebb_config). +-moduledoc """ +Configuration for ebb, loaded from a TOML file into persistent terms. A +configuration file is required to boot ebb. The file is resolved in this order: + +1. The path in the `EBB_CONFIG` environment variable +2. `./ebb.toml` +3. `/etc/ebb/ebb.toml` +""". + +-export([load/0, get/1]). +%% Exported for tests +-export([load_file/1]). + +-define(PT_KEY, ?MODULE). +-define(LOCAL_PATH, "ebb.toml"). +-define(SYSTEM_PATH, "/etc/ebb/ebb.toml"). + +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- + +-doc """ +Returns an error if no file can be found, the file is unreadable or +unparseable, or any required key is missing. +""". +-spec load() -> ok | {error, io_lib:chars()}. +load() -> + case resolve_path() of + {path, Path} -> + load_file(Path); + {error, Reason} -> + {error, Reason} + end. + +-doc """ +Load configuration from an explicit path. See also `load/0`. +""". +-spec load_file(file:name_all()) -> ok | {error, io_lib:chars()}. +load_file(Path) -> + case tomerl:read_file(Path) of + {ok, Raw} -> + try + store(Path, atomize(Raw)) + catch + throw:{unknown_key, Key} -> + {error, + io_lib:format("unknown configuration key \"~s\" in ~s", [ + Key, Path + ])} + end; + {error, Reason} -> + {error, io_lib:format("cannot read ~s: ~p", [Path, Reason])} + end. + +store(Path, Config) -> + case missing_keys(Config) of + [] -> + persistent_term:put(?PT_KEY, Config), + logger:notice("Loaded configuration from ~s", [Path]), + ok; + Missing -> + {error, + io_lib:format("~s is missing required keys: ~s", [ + Path, string:join(Missing, ", ") + ])} + end. + +-doc """ +Fetch a configuration value by path, e.g. `get([dhcp, listen_port])`. +Crashes on unknown paths; see `required/0`. +""". +-spec get([atom()]) -> term(). +get(Path) when is_list(Path) -> + lists:foldl(fun maps:get/2, persistent_term:get(?PT_KEY), Path). + +%%-------------------------------------------------------------------- +%% Internal +%%-------------------------------------------------------------------- + +%% Key paths that must be present for ebb to boot. +required() -> + [ + [dhcp, listen_port], + [dhcp, range], + [dhcp, lease_seconds], + [dhcp, offer_timeout_seconds] + ]. + +missing_keys(Config) -> + [path_to_string(Path) || Path <- required(), not has_path(Config, Path)]. + +has_path(Value, []) when Value =/= undefined -> + true; +has_path(Map, [Key | Rest]) when is_map(Map) -> + case Map of + #{Key := Value} -> has_path(Value, Rest); + #{} -> false + end; +has_path(_, _) -> + false. + +path_to_string(Path) -> + string:join([atom_to_list(Key) || Key <- Path], "."). + +resolve_path() -> + case os:getenv("EBB_CONFIG") of + false -> search_default_paths(); + "" -> search_default_paths(); + Path -> explicit_path(Path) + end. + +search_default_paths() -> + case lists:search(fun filelib:is_regular/1, [?LOCAL_PATH, ?SYSTEM_PATH]) of + {value, Path} -> + {path, Path}; + false -> + {error, + io_lib:format( + "no configuration file found; provide ~s, ~s," + " or set EBB_CONFIG", + [?LOCAL_PATH, ?SYSTEM_PATH] + )} + end. + +explicit_path(Path) -> + case filelib:is_regular(Path) of + true -> + {path, Path}; + false -> + {error, + io_lib:format( + "EBB_CONFIG points at ~s, which does not exist" + " or is not a regular file", + [Path] + )} + end. + +%% tomerl produces binary keys; convert them (and table arrays) to the +%% atom-keyed shape used internally. Values are left untouched. Key atoms +%% must already exist, i.e. be mentioned somewhere in loaded code; a key +%% nothing could ever read is reported as a config error. +atomize(Map) when is_map(Map) -> + maps:fold( + fun(K, V, Acc) -> Acc#{atomize_key(K) => atomize(V)} end, #{}, Map + ); +atomize(List) when is_list(List) -> + [atomize(V) || V <- List]; +atomize(Value) -> + Value. + +atomize_key(Key) -> + try + binary_to_existing_atom(Key) + catch + error:badarg -> throw({unknown_key, Key}) + end. From e8e3201fc5a45a009894bce6344e737b2faa031a Mon Sep 17 00:00:00 2001 From: Lincoln Bryant Date: Fri, 3 Jul 2026 11:27:34 -0400 Subject: [PATCH 05/10] use the persistent term values rather than defaults from the hrl --- include/dhcp.hrl | 6 ------ src/ebb_dhcp_pool_mem.erl | 8 +++++--- src/ebb_dhcpd.erl | 7 ++++--- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/include/dhcp.hrl b/include/dhcp.hrl index 63144ca..a7e5611 100644 --- a/include/dhcp.hrl +++ b/include/dhcp.hrl @@ -1,14 +1,8 @@ -define(DHCP_MAGIC_COOKIE, 16#63825363). - --define(DEFAULT_LEASE_SECONDS, 3600). % TODO %-define(MAX_LEASE_SECONDS, 86400). %-define(MIN_LEASE_SECONDS, 300). --define(DEFAULT_OFFER_TIMEOUT_SECONDS, 300). - --define(DEFAULT_CIDR_RANGE, "172.16.0.0/16"). - -record(dhcp_message, { % Header op :: op(), diff --git a/src/ebb_dhcp_pool_mem.erl b/src/ebb_dhcp_pool_mem.erl index 362e9b4..c0aca72 100644 --- a/src/ebb_dhcp_pool_mem.erl +++ b/src/ebb_dhcp_pool_mem.erl @@ -61,7 +61,7 @@ dump() -> init([]) -> {ok, #{ pool => [], - range => ?DEFAULT_CIDR_RANGE + range => ebb_config:get([dhcp, range]) }}. handle_call({get_offer, Msg}, _From, #{pool := Pool} = State) -> @@ -85,7 +85,7 @@ handle_call({create_offer, Msg}, _From, State) -> % otherwise give them the default Value; _ -> - ?DEFAULT_LEASE_SECONDS + ebb_config:get([dhcp, lease_seconds]) end, % See next_ip/2 deficiencies {ok, ClientIP} = next_ip(Pool, Range), @@ -95,7 +95,9 @@ handle_call({create_offer, Msg}, _From, State) -> subnet_mask = to_mask(Range), state = offered, expiration = erlang:send_after( - ?DEFAULT_OFFER_TIMEOUT_SECONDS * 1000, self(), {expire, ClientID} + ebb_config:get([dhcp, offer_timeout_seconds]) * 1000, + self(), + {expire, ClientID} ), duration = LeaseDuration }, diff --git a/src/ebb_dhcpd.erl b/src/ebb_dhcpd.erl index 8c7de6e..8407d96 100644 --- a/src/ebb_dhcpd.erl +++ b/src/ebb_dhcpd.erl @@ -30,7 +30,8 @@ start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). init([]) -> - {ok, Socket} = gen_udp:open(67, [{active, once}, {broadcast, true}, binary]), + Port = ebb_config:get([dhcp, listen_port]), + {ok, Socket} = gen_udp:open(Port, [{active, once}, {broadcast, true}, binary]), {ok, #{ socket => Socket }}. @@ -254,7 +255,7 @@ server_ip(Socket) -> end. guess_server_ip() -> - Cidr = inet_cidr:parse(?DEFAULT_CIDR_RANGE), + Cidr = inet_cidr:parse(ebb_config:get([dhcp, range])), {Start, _End, _Prefix} = Cidr, {ok, Addrs} = inet:getifaddrs(), case @@ -275,5 +276,5 @@ send_broadcast(Socket, Packet) -> gen_udp:send(Socket, Bcast, 68, Packet). subnet_broadcast() -> - {_Start, End, _Prefix} = inet_cidr:parse(?DEFAULT_CIDR_RANGE), + {_Start, End, _Prefix} = inet_cidr:parse(ebb_config:get([dhcp, range])), End. From f6f9a8b611f8bbfa1d8c79b6d664a21e4873f953 Mon Sep 17 00:00:00 2001 From: Lincoln Bryant Date: Fri, 3 Jul 2026 18:27:12 -0400 Subject: [PATCH 06/10] simplify comments, also fix default range --- ebb.toml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/ebb.toml b/ebb.toml index 3ecd71d..c903a57 100644 --- a/ebb.toml +++ b/ebb.toml @@ -1,19 +1,20 @@ # ebb configuration. # # ebb looks for this file at $EBB_CONFIG, ./ebb.toml, then /etc/ebb/ebb.toml, -# and refuses to start without it. All keys below are required. +# and refuses to start without it. +# +# Features are enabled by presence: a feature runs iff its section exists. +# When a section is present, all of its keys are required. [dhcp] -# UDP port to bind. DHCP servers listen on 67; ports below 1024 need -# root or CAP_NET_BIND_SERVICE. +# If the ERTS user doesn't have privileges to bind to port 67, this will silently fail. listen_port = 67 # IPv4 CIDR of the managed subnet. Leases are allocated from this range. -range = "172.16.0.0/16" +range = "172.16.0.0/12" -# How long granted leases last, in seconds. +# How long granted leases last. lease_seconds = 3600 -# How long an unaccepted OFFER is held before its IP is reclaimed, -# in seconds. +# How long an unaccepted OFFER is held before its IP is reclaimed. offer_timeout_seconds = 300 From c2f95beb3cf3c9390d829cd28fa62d95553c0a8c Mon Sep 17 00:00:00 2001 From: Lincoln Bryant Date: Fri, 3 Jul 2026 18:27:51 -0400 Subject: [PATCH 07/10] useful message for self --- src/ebb.app.src | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ebb.app.src b/src/ebb.app.src index b8d7643..0b2fc9e 100644 --- a/src/ebb.app.src +++ b/src/ebb.app.src @@ -7,6 +7,12 @@ kernel, stdlib, tomerl + % NOTE: we also depend on the erl_cidr package, whose OTP app is + % confusingly named inet_cidr. Listing either name here breaks + % rebar3 dialyzer (app/package name mismatch). It is a pure + % library (no processes to start), so omitting it is safe in + % dev; a future release definition must include inet_cidr + % explicitly. ]}, {env, []}, {modules, []}, From 3e649d6e28c63feb3b90ff23fc8f86bb5875e54d Mon Sep 17 00:00:00 2001 From: Lincoln Bryant Date: Fri, 3 Jul 2026 18:30:42 -0400 Subject: [PATCH 08/10] add feature gating, various refactoring --- src/ebb_config.erl | 75 ++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/src/ebb_config.erl b/src/ebb_config.erl index c874e42..8d7a3ca 100644 --- a/src/ebb_config.erl +++ b/src/ebb_config.erl @@ -8,7 +8,7 @@ configuration file is required to boot ebb. The file is resolved in this order: 3. `/etc/ebb/ebb.toml` """. --export([load/0, get/1]). +-export([load/0, get/1, enabled/1]). %% Exported for tests -export([load_file/1]). @@ -57,7 +57,9 @@ store(Path, Config) -> case missing_keys(Config) of [] -> persistent_term:put(?PT_KEY, Config), - logger:notice("Loaded configuration from ~s", [Path]), + logger:notice("Loaded configuration from ~s, enabled features: ~p", [ + Path, [F || F <- maps:keys(features()), is_map_key(F, Config)] + ]), ok; Missing -> {error, @@ -68,40 +70,48 @@ store(Path, Config) -> -doc """ Fetch a configuration value by path, e.g. `get([dhcp, listen_port])`. -Crashes on unknown paths; see `required/0`. +Crashes on unknown paths; see `features/0`. """. -spec get([atom()]) -> term(). get(Path) when is_list(Path) -> lists:foldl(fun maps:get/2, persistent_term:get(?PT_KEY), Path). +-doc """ +Features are gated by presence: a feature is enabled iff its section +exists in the configuration file. +""". +-spec enabled(atom()) -> boolean(). +enabled(Feature) -> + is_map_key(Feature, persistent_term:get(?PT_KEY)). + %%-------------------------------------------------------------------- %% Internal %%-------------------------------------------------------------------- -%% Key paths that must be present for ebb to boot. -required() -> - [ - [dhcp, listen_port], - [dhcp, range], - [dhcp, lease_seconds], - [dhcp, offer_timeout_seconds] - ]. +%% Known feature sections and the keys each requires when present. +%% An absent section simply disables the feature. +features() -> + #{ + dhcp => [listen_port, range, lease_seconds, offer_timeout_seconds] + }. missing_keys(Config) -> - [path_to_string(Path) || Path <- required(), not has_path(Config, Path)]. - -has_path(Value, []) when Value =/= undefined -> - true; -has_path(Map, [Key | Rest]) when is_map(Map) -> - case Map of - #{Key := Value} -> has_path(Value, Rest); - #{} -> false - end; -has_path(_, _) -> - false. - -path_to_string(Path) -> - string:join([atom_to_list(Key) || Key <- Path], "."). + maps:fold( + fun(Feature, RequiredKeys, Acc) -> + case Config of + #{Feature := Section} -> + Acc ++ + [ + atom_to_list(Feature) ++ "." ++ atom_to_list(Key) + || Key <- RequiredKeys, not is_map_key(Key, Section) + ]; + #{} -> + Acc + end + end, + [], + features() + ). resolve_path() -> case os:getenv("EBB_CONFIG") of @@ -137,21 +147,14 @@ explicit_path(Path) -> end. %% tomerl produces binary keys; convert them (and table arrays) to the -%% atom-keyed shape used internally. Values are left untouched. Key atoms -%% must already exist, i.e. be mentioned somewhere in loaded code; a key -%% nothing could ever read is reported as a config error. +%% atom-keyed shape used internally. atomize(Map) when is_map(Map) -> maps:fold( - fun(K, V, Acc) -> Acc#{atomize_key(K) => atomize(V)} end, #{}, Map + fun(K, V, Acc) -> Acc#{binary_to_existing_atom(K) => atomize(V)} end, + #{}, + Map ); atomize(List) when is_list(List) -> [atomize(V) || V <- List]; atomize(Value) -> Value. - -atomize_key(Key) -> - try - binary_to_existing_atom(Key) - catch - error:badarg -> throw({unknown_key, Key}) - end. From 9feeae9216aec5d025aec8c554a90165ca8c26d6 Mon Sep 17 00:00:00 2001 From: Lincoln Bryant Date: Sat, 4 Jul 2026 11:11:59 -0400 Subject: [PATCH 09/10] add pxe boot test script for OS X --- priv/scripts/osx/pxe-boot.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100755 priv/scripts/osx/pxe-boot.sh diff --git a/priv/scripts/osx/pxe-boot.sh b/priv/scripts/osx/pxe-boot.sh new file mode 100755 index 0000000..9203d7b --- /dev/null +++ b/priv/scripts/osx/pxe-boot.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Subnet must match ebb's ?DEFAULT_CIDR_RANGE ("172.16.0.0/16"). +NET_START="172.16.0.1" +NET_END="172.16.255.254" +NET_MASK="255.255.0.0" + +exec sudo qemu-system-x86_64 \ + -machine q35 \ + -cpu qemu64 \ + -m 512 \ + -netdev vmnet-host,id=net0,start-address="${NET_START}",end-address="${NET_END}",subnet-mask="${NET_MASK}" \ + -device e1000,netdev=net0,mac=52:54:00:12:34:56 \ + -boot n \ + -nographic \ + -no-reboot From 6607aebedb03049ad5c0bffc5edc1e347dd5123c Mon Sep 17 00:00:00 2001 From: Lincoln Bryant Date: Sat, 4 Jul 2026 11:12:23 -0400 Subject: [PATCH 10/10] add supervisors for different funcitonalities --- src/ebb_provision_sup.erl | 34 ++++++++++++++++++++++++++ src/ebb_sup.erl | 50 +++++++++++++++++++-------------------- 2 files changed, 58 insertions(+), 26 deletions(-) create mode 100644 src/ebb_provision_sup.erl diff --git a/src/ebb_provision_sup.erl b/src/ebb_provision_sup.erl new file mode 100644 index 0000000..f618bee --- /dev/null +++ b/src/ebb_provision_sup.erl @@ -0,0 +1,34 @@ +-module(ebb_provision_sup). +-moduledoc """ +Supervisor for the provisioning feature, including: + - the DHCP listener and its lease pool. + +Started by `ebb_sup` only when the `[dhcp]` section is present in the +configuration. +""". + +-behaviour(supervisor). + +-export([start_link/0]). +-export([init/1]). + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +init([]) -> + SupFlags = #{ + strategy => one_for_all, + intensity => 3, + period => 5 + }, + ChildSpecs = [ + #{ + id => ebb_dhcp_pool_mem, + start => {ebb_dhcp_pool_mem, start_link, []} + }, + #{ + id => ebb_dhcpd, + start => {ebb_dhcpd, start_link, []} + } + ], + {ok, {SupFlags, ChildSpecs}}. diff --git a/src/ebb_sup.erl b/src/ebb_sup.erl index 93aa3df..4c13714 100644 --- a/src/ebb_sup.erl +++ b/src/ebb_sup.erl @@ -1,14 +1,11 @@ -%%%------------------------------------------------------------------- -%% @doc ebb top level supervisor. -%% @end -%%%------------------------------------------------------------------- - -module(ebb_sup). +-moduledoc """ +ebb top level supervisor. +""". -behaviour(supervisor). -export([start_link/0]). - -export([init/1]). -define(SERVER, ?MODULE). @@ -16,31 +13,32 @@ start_link() -> supervisor:start_link({local, ?SERVER}, ?MODULE, []). -%% sup_flags() = #{strategy => strategy(), % optional -%% intensity => non_neg_integer(), % optional -%% period => pos_integer()} % optional -%% child_spec() = #{id => child_id(), % mandatory -%% start => mfargs(), % mandatory -%% restart => restart(), % optional -%% shutdown => shutdown(), % optional -%% type => worker(), % optional -%% modules => modules()} % optional init([]) -> SupFlags = #{ - strategy => one_for_all, + strategy => one_for_one, intensity => 3, period => 5 }, - ChildSpecs = [ - #{ - id => ebb_dhcp_pool_mem, - start => {ebb_dhcp_pool_mem, start_link, []} - }, - #{ - id => ebb_dhcpd, - start => {ebb_dhcpd, start_link, []} - } - ], + ChildSpecs = provision_specs(), + case ChildSpecs of + [] -> logger:warning("No features enabled, ebb is running idle"); + _ -> ok + end, {ok, {SupFlags, ChildSpecs}}. %% internal functions + +%% Provisioning, gated on the [dhcp] config section. +provision_specs() -> + case ebb_config:enabled(dhcp) of + true -> + [ + #{ + id => ebb_provision_sup, + start => {ebb_provision_sup, start_link, []}, + type => supervisor + } + ]; + false -> + [] + end.