v0.3.0 (2026-10-29)
Breaking. The hooks apu needs to adopt the generated ctl (macaw#92), health as terms (macaw#188), and a cleaner test run.
Upgrading an adopter: regenerate the script (mix macaw.host.ctl): every script gains BIND, loopback by default, so state bind: "0.0.0.0" in the description if the proxy is on another host, and a host-network app must state it. Ctl.AppCommands.status_lines/1 becomes status_lines(env, report). A health report holds terms: word it with Macaw.Host.Health.to_wire/1 before sending it yourself (Collpa: its admin health controller and Release).
- Breaking: a health report holds its checks' results as terms,
:ok,{:warn, detail}or{:error, detail}, andhealthyis decided from the terms (a warning keeps it healthy, an error doesn't;Macaw.Host.Health.healthy?/1), never from text. Text is made at the edge byMacaw.Host.Health.to_wire/1, whichMacaw.Host.Health.PlugandMacaw.Host.Release.status/1call; the wire form is unchanged ("ok","warning: <detail>","error: <detail>"), so the generated script'sstatusprints the same. A check that returns anything else, a string above all, is an error naming it (check :legacy returned "ok", not :ok, {:warn, detail} or {:error, detail}; it wascheck returned …);to_wire/1refuses a result that is text, naming the check, and a map that isn't a report, soRelease.status/1no longer emits any map. An adopter that sends its report itself words it first: Collpa changesjson(report)inlib/collpa/web/admin/health_controller.extojson(Macaw.Host.Health.to_wire(report))(the status code still fromreport.healthy), andMap.merge(report, %{…})inCollpa.Release.status/0(lib/collpa/release.ex) toMap.merge(Macaw.Host.Health.to_wire(report), %{…}); its tests readingCollpa.Health.report().checks(test/collpa/web/code_url_test.exs) compare terms,:okand{:warn, "a page route shadows live codes: 1NV1TE"}. Its checks already return terms (spec 03 §4, 04 §1–§3; #23, macaw#188 H1). - Breaking:
Ctl.AppCommands.status_lines/1is nowstatus_lines(env, report).reportis the running app's own report asstatusread it (the last JSON object ofrpc <Release>.status(), string keys), ornilwhen the app isn't running or didn't answer, so a host line can say what only the app knows (apu's plain-HTTP warning).statusasks for the report before printing the app's lines; the printed order is unchanged. Astatus_lines/1is no longer called, and the generator refuses it:Ctl.AppCommands.status_lines/1 is now status_lines/2 (env, report). Collpa changesdef status_lines(env)inctl/app_commands.extodef status_lines(env, _report)(its ports line needs nothing of the report) and regenerates its script (spec 06 §4, 08 §8; #17, macaw#92). updatewith a TAG other thanlatestsayspinned to <TAG>, which does not move: <ref> is unchangedwhen the pull brings nothing new, instead ofalready on the newest <ref>, whichlatestkeeps. The description's new:update_notekey (a non-empty string,nilby default) is a lineupdateprints once it has recreated the container, itself or through the script it handed the restart to, and only then: apu's says the new image migrates the volume and rolling back restores code, not data (spec 06 §3, 08 §7; #16, macaw#92).config reset(the script,Macaw.Host.Release.config_command/5andMacaw.Host.Config.reset!/1): aconfig.exsthat no longer parses, which blocks the app's boot and every otherconfigcommand, is set aside asconfig.exs.broken(0600, its bytes intact) and an empty one written, only once the rename has succeeded; the answer is{"ok":true,"reset":true,"set_aside":"config.exs.broken","restart":"restart to apply"}. Refused, both files untouched, with no file, a file that parses (config unset NAMEremoves a setting), or aconfig.exs.brokenalready there, which is never overwritten.resetis the one config command that doesn't read the file first. The usage and help name it (spec 01 §6, 03 §3, 07 §8, 08 §4; #15, macaw#92).Ctl.AppCommands.init_flags/0andafter_init/2, optional:inittakes the app's own flags, declared as data ([{"--user", :value}, {"--quiet", :switch}]), beside--forceand--migrate, and gives them toafter_init/2as a map once the data directory is adopted and seeded (or migrated); its answer is init's, and it runs on every successful init, so it must be safe to rerun. An undeclared flag is refused with the usage as before, a value flag without its value withinit <flag> needs a value, and a flag given twice (--forceincluded) withinit takes <flag> once.ready:is now printed after the app's step (spec 06 §4, 08 §2, #14, macaw#92).Ctl.AppCommands.before_start/1, optional: the app's precondition forstart, asked once the image and the data are ready and before the container is touched (spec 06 §4, 08 §5, #13, macaw#92).- Every generated script changes: a seventh host setting,
BIND, the address PORT is published on. On a docker network the run writes-p BIND:PORT:<container port>, and BIND defaults to127.0.0.1(default closed: only this host, a proxy on it included, reaches the app). The description's new:bindkey sets the default (an IP address;"0.0.0.0"withnetwork: "host"). An adopter whose proxy is on another host statesbind: "0.0.0.0"(or its hosts set BIND); otherwise the app stops answering from that host when its script is regenerated.env setandstartrefuse a BIND that isn't an IP address, and, withNETWORK=host, one other than0.0.0.0.statusprints where the port is published:published: 127.0.0.1:4000 (this host only),(every interface),(that address), or the host's own network (spec 06 §3, spec 07 §5–6, spec 08 §5, §8; #12, macaw#92).
v0.2.1 (2026-10-29)
Operator output: status, config set and migrate say what an operator needs, plainly (milestone "macaw_host operator output").
Release.migrate/1answers the versions it applied ("versions": [...]) with Ecto's log off, so the JSON answer is the only thing on stdout (spec 03 §2, #10, macaw#107).config setnames what a bare word should have been: "not a literal" whether or not the word is a known atom, and, when the setting's type takes the text as a string, how to quote it (spec 01 §1.6, #9, macaw#106).statuspads each level of the app's report to its widest key plus two spaces, so a key never runs into its value, and prints a list as words and nil as a dash, not JSON;config's help line names@FILE(spec 07, spec 08 §8, #8, #7).
v0.2.0 (2026-10-28)
- App hooks in
Ctl.AppCommands, each optional:validate/1(the app's own checks of its host settings, on everyenv set,startand throwaway container),seeds/1(app configinitseeds from this host's settings) andstatus_lines/1(host lines instatus).statusprints a list of maps one element a line (#1, #5). - Description keys
host_network(falserefusesNETWORK=host) and:app_keys(namesenv setrefuses as the app's own configuration) (#5). - Fixed: the generated script reaches the terminal by path (
/proc/<pid>/fd), since a child of the BEAM has no/dev/tty:shelland othertty: truecommands work again;prompt_secret/1turns echo off before asking and refuses without a terminal (#1, #4; collpa#128, #148). Ctl.World.write_local/2, andCtl.Commands.last_object/1public, for an app's own commands (#4).config set KEY @FILEin the generated script reads the value from a host file and sends it in the environment, never on a command line;config getof a map or a list prints it formatted on several lines.Ctl.World.read_local/1, and the Recorder's:filesoption (#1, #3).{:custom, module}setting types (Macaw.Host.Config.Type): the module checks, casts, shows and describes its values, so a secret inside a structure is masked wherever a value is shown, a value that did not check included (#1, #2).- Fixed:
Macaw.Host.Config.put!/4stores the literal as typed, never the check's result, which could have resolved a reference to a secret into the file (#2).
v0.1.5 (2026-10-28)
- The macaw logo in the docs and the README (macaw#175).
v0.1.4 (2026-10-28)
- The README opens with the early-release notice (macaw#174).
v0.1.3 (2026-10-28)
- Spec and README links to the public home,
github.com/dcoai/macaw_host, and the install snippet from Hex (macaw#171, #173). - Package metadata for Hex: the public home, the changelog link, the files shipped, and docs with
ex_doc(macaw_compose#155).
v0.1.2 (2026-10-27)
- Adopt surfex; compare/2 marks either text's end; v0.1.2 (part of macaw_compose#65)
v0.1.1 (2026-10-27)
- macaw_host 0.1.1: the nine gaps found writing its spec
v0.1.0 (2026-10-26)
- macaw_host 0.1.0: Config, Secrets, Release, Health, Build, the ctl generator