Castle derives the releases directory as Path.join(to_string(:code.root_dir()), "releases") (lib/castle.ex, rel_dir/0, with rel_vsn_dir/1 built on it).
:release_handler does not. Its init/1 takes the releases directory from
application:get_env(sasl, releases_dir), falling back to os:getenv("RELDIR"),
and only then to init:get_argument(root) — OTP's own comment says "Default is
$ROOT/releases. $RELDIR overrides, and {sasl, releases_dir} overrides both."
Mix sets neither, so on an ordinary Mix release the two agree and nothing is
wrong today. A deployment that sets either — both are documented OTP
configuration — makes them disagree, and Castle then works on files the handler
will not read:
make_releases/0 writes RELEASES under code:root_dir(), while the
handler looks under the configured directory. The file appears and is never
read.
- Worse, the remedy the record check names becomes false. With no
RELEASES
where the handler looks, it synthesises a record whose application list is
empty; Castle.Commands.ensure_upgradable/2 correctly refuses, and its message
says to restart the system because "the release creates the file before it
starts". On such a deployment the restart writes the file to the same wrong
place, so the refusal is permanent and its stated remedy never works.
materialise/3 resolves releases/<vsn>/sys.config the same way, so the
target's configuration is written where the handler will not look for it
either. The env.sh hook also re-runs the preboot VM on every start, since the
file it checks for never appears.
What to do
Derive the releases directory the way init/1 does — {sasl, releases_dir},
then RELDIR, then <root>/releases — in one place, and build rel_vsn_dir/1
on it. That is a handful of lines and it makes Castle agree with OTP exactly
where it currently guesses.
Worth a test that the precedence is the same as OTP's, including that
{sasl, releases_dir} beats RELDIR, since getting the order backwards would be
invisible on any deployment that sets only one.
Why it is filed rather than fixed alongside the ERTS guard
Found while correcting the ERTS guard's documentation, which had asserted that
releases/RELEASES necessarily resolves under code:root_dir(). That assertion
is what this issue is really about: it is true for the default configuration and
false under configuration OTP documents. The guard's own reasoning does not
depend on it — the guard rests on application extraction, lib/<app>-<vsn>
resolution and erts-<vsn> deletion, which are anchored to the root and are
not relocatable — so the wording was corrected there and the behaviour left
alone rather than growing that branch into this.
Does not gate 1.0.0: it takes deliberate, non-Mix configuration to reach, and
AGENTS.md now records the limitation where the derivation lives.
Castle derives the releases directory as
Path.join(to_string(:code.root_dir()), "releases")(lib/castle.ex,rel_dir/0, withrel_vsn_dir/1built on it).:release_handlerdoes not. Itsinit/1takes the releases directory fromapplication:get_env(sasl, releases_dir), falling back toos:getenv("RELDIR"),and only then to
init:get_argument(root)— OTP's own comment says "Default is$ROOT/releases. $RELDIR overrides, and {sasl, releases_dir} overrides both."
Mix sets neither, so on an ordinary Mix release the two agree and nothing is
wrong today. A deployment that sets either — both are documented OTP
configuration — makes them disagree, and Castle then works on files the handler
will not read:
make_releases/0writesRELEASESundercode:root_dir(), while thehandler looks under the configured directory. The file appears and is never
read.
RELEASESwhere the handler looks, it synthesises a record whose application list is
empty;
Castle.Commands.ensure_upgradable/2correctly refuses, and its messagesays to restart the system because "the release creates the file before it
starts". On such a deployment the restart writes the file to the same wrong
place, so the refusal is permanent and its stated remedy never works.
materialise/3resolvesreleases/<vsn>/sys.configthe same way, so thetarget's configuration is written where the handler will not look for it
either. The
env.shhook also re-runs the preboot VM on every start, since thefile it checks for never appears.
What to do
Derive the releases directory the way
init/1does —{sasl, releases_dir},then
RELDIR, then<root>/releases— in one place, and buildrel_vsn_dir/1on it. That is a handful of lines and it makes Castle agree with OTP exactly
where it currently guesses.
Worth a test that the precedence is the same as OTP's, including that
{sasl, releases_dir}beatsRELDIR, since getting the order backwards would beinvisible on any deployment that sets only one.
Why it is filed rather than fixed alongside the ERTS guard
Found while correcting the ERTS guard's documentation, which had asserted that
releases/RELEASESnecessarily resolves undercode:root_dir(). That assertionis what this issue is really about: it is true for the default configuration and
false under configuration OTP documents. The guard's own reasoning does not
depend on it — the guard rests on application extraction,
lib/<app>-<vsn>resolution and
erts-<vsn>deletion, which are anchored to the root and arenot relocatable — so the wording was corrected there and the behaviour left
alone rather than growing that branch into this.
Does not gate 1.0.0: it takes deliberate, non-Mix configuration to reach, and
AGENTS.mdnow records the limitation where the derivation lives.