All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
1.0.0 - 2026-09-28
Forecastle 1.0 stops intercepting runtime configuration and stops replacing the
Mix launcher. Release management moves to a separate bin/castle, relups can be
generated during mix release, and new tasks check and draft appups. It
requires Castle 1.x and Elixir 1.18 or later; an older Castle cannot install a
release built by this Forecastle. Read Upgrading an existing deployment below
before upgrading: the security fix in this release does not reach an existing
deployment through a hot upgrade.
Added
bin/castle, a release management CLI installed alongside the standard launcher, withreleases,upgradable,unpack,install,commitandremove.upgradablereports whether the node can be upgraded, and exits non-zero with the reason when it cannot.commitwith no version commits the release awaiting commit, and exits non-zero if there is none.installconfirms that the installed version is running before it reports success, waiting across an emulator restart.CASTLE_INSTALL_TIMEOUT(default 300 seconds) bounds the wait. A wrong cookie or a node that is down looks the same as a restart in progress, soinstallwaits out the timeout before failing. Interruptinginstallstops the wait, not the upgrade.installrefuses aRELEASE_TMPthat is world-writable and not sticky. (forecastle#13)
- One-stage
restart_emulatorupgrades under systemd, Docker, Kubernetes or runit. The release starts OTPheartconfigured to do nothing, becauserelease_handlerneeds it to prepare a restart. After the restart the node boots the installed version provisionally, until it is committed. (forecastle#10, castle#14) - Upgrade strategies for
mix castle.relup.auto, the default, keeps a transition hot unless the ERTS changes or a dependency changes version without a matching appup.--hotfails rather than restart.--restartmakes every transition an emulator restart. (forecastle#4) - Baseline specs for
mix castle.relup:rel:an assembled release,tar:a shipped tarball, andref:a git ref built in a worktree. A bare path meansrel:. Resolvedtar:andref:baselines are cached under_build/castle/baselines. Prefertar:, because a rebuilt baseline may differ from the release that was deployed. (forecastle#26) mix castle.relup --dry-run, which reports whether a relup could be generated, and which transitions would restart, without writing it. (forecastle#31)mix castle.appup, which fails when a module changed between two builds and no appup instruction mentions it. (forecastle#27)mix castle.appup.gen, which drafts missing appup entries for review. (forecastle#29)- Appups for dependencies, supplied under
rel/appupsand placed into the assembled release, never intodeps/. (forecastle#30) - Relup generation during assembly. An
upgrade_from:release option names the baselines, and onemix releaseproduces a tarball containing the relup. (forecastle#28, forecastle#40) Forecastle.UpgradeCaseandForecastle.Deployment, a harness for testing upgrades of a project's own release. (forecastle#32)
Changed
- Breaking:
mix forecastle.relupis nowmix castle.relup. There is no compatibility alias, so rename the task in build pipelines.mix compile.appupis unchanged. (forecastle#24) - Breaking: the standard Mix launcher,
bin/<release>, is no longer replaced. The release management commands move frombin/<release>tobin/castle. Castle's integration is appended toenv.sh, after anyrel/env.sh.eexthe project supplies. (forecastle#3) - Breaking: Forecastle no longer intercepts runtime configuration. Mix
configures the release as it would without Forecastle,
sys.configis no longer renamed tobuild.config, and Castle resolves the target's configuration when it installs. This requires Castle 1.x. (forecastle#6, castle#13) - Breaking: the
:appupcompiler fails the build when the:appupkey names a missing file. Set the key tonilto turn an appup off, for exampleappup: if(Mix.env() == :prod, do: "appup.exs"). - Starts no longer run a preboot VM to expand configuration. Only the first
start of a deployment runs one, to create
releases/RELEASES; if it cannot, the start warns and continues, but the node cannot be upgraded. bin/castle unpackandinstallrefuse a node that started without an acceptedRELEASESfile, becauserelease_handlerwould upgrade it incompletely. Restart the node to recover. If the file exists but cannot be read, fix or remove it first.mix castle.relupwith no strategy switch isauto, and two cases now generate differently. An ERTS change becomes a one-stagerestart_emulatorrather than the unsupported two-stagerestart_new_emulator. An emulator restart requested by an appup is announced if it isrestart_emulatorand refused if it isrestart_new_emulator.- A failed
mix castle.relupwrites nothing, and a relup is published atomically. mix castle.reluprequires at least one of--fromto,--upfromor--downto.- Windows releases now boot, but have no
bin/castleand so cannot be upgraded. Assembly warns about this. - The minimum supported Elixir version is 1.18.
Security
- The launcher generated by Forecastle 0.1.x built its RPC expressions by
interpolating the version argument into Elixir source, so a version such as
1.2.3));System.stop(1)#ran arbitrary code on the node with the release cookie's authority.bin/castlerefuses versions containing sigil, escape or interpolation characters, path separators or control characters, and shows rejected values percent-encoded so they cannot inject lines into its output. An existing deployment keeps the old launcher until itsbindirectory is replaced; see Upgrading an existing deployment.
Fixed
mix castle.relupfailed in projects that do not depend on:sasl, because Elixir prunes unused OTP applications from the code path.mix castle.relupexited 0 when:systoolscould not generate a relup, so a build could go on to package a stale one. It now fails, and passes:systoolswarnings on. (forecastle#7)mix castle.relup --outdirwas ignored, and the relup always went to the current directory. (forecastle#7)mix castle.relupignored unrecognised arguments and raisedKeyErrorwithout--target. Both are now errors, as is a repeated--targetor--outdir.mix castle.reluprefuses a baseline with the same version as the target, which produced an entryrelease_handlercan never use.- Assembly packaged any
relupin the project root without checking it. The relup's target version and structure are now checked before assembly begins. - A
:runtime_config_pathother thanconfig/runtime.exswas ignored, and config providers declared with a non-keyword argument received a rewritten one. Mix now handles both. (forecastle#6) - Concurrent
start,daemonandevalinvocations no longer overwrite each other'ssys.config. releases/RELEASESwas created relative to the working directory, so a release started from anywhere but its root could not manage its own releases. Such a node could then upgrade incompletely, leaving applications running from the superseded release's directory.- The
:appupcompiler left a stale<app>.appupinebinafter its source or the:appupkey was removed, so incremental builds packaged obsolete upgrade instructions. (forecastle#8) - The
:appupkey is resolved relative to the project file, not the working directory. - Mix discarded the
:appupcompiler's diagnostics, and the compiler reported a failed write as success. - An appup containing non-ASCII characters failed to write, or was written in a
form
:systoolscannot read. It is now encoded as UTF-8. - The Hex package's GitHub link pointed at the Castle repository.
Upgrading an existing deployment
- A hot upgrade from a release built by Forecastle 0.1.x keeps the old
bin/<release>, becauserelease_handlerdoes not overwrite existing top-level files.bin/castleappears, but the old launcher and its release management commands remain, including the vulnerability fixed above. Replace the contents ofbinfrom the new release when migrating. Later changes tobin/castledo not reach a deployment through a hot upgrade either. - The first upgrade from a 0.1.x deployment cannot be a restart transition. The
running node has no
heartprocess, so the install fails before rebooting. Reach this release with a hot upgrade or a redeploy first; restart transitions work after that. - A deployment part way through the migration stays coherent. The running
version keeps its
build.configand its own copy of Castle, and the new version uses itssys.config, resolved by the new Castle. Nothing needs converting in place.
Known limitations
- Emulator restarts need an external supervisor such as systemd, Docker, Kubernetes or runit. The release does not restart itself, so a node started by hand stays down after such an upgrade until it is started again, and then boots the installed version.
restart_new_emulatoris not supported. An ERTS change is generated as a one-stagerestart_emulatorinstead, and arestart_new_emulatorin an appup is refused.- A node that cannot write
releases/RELEASEScan run and restart but cannot be upgraded. Fix the reported error and restart once before upgrading. - Windows releases have no
bin/castle.
0.1.3 - 2025-01-19
Fixed
- Elixir 1.18 compatibility fixes.
0.1.2 - 2023-06-10
Fixed
- Corrected the release name used in the generated
binscript.
0.1.1 - 2023-05-27
Fixed
- Corrected the package URL.
0.1.0 - 2023-05-27
Added
- Initial release.