# `Castle`
[🔗](https://github.com/ausimian/castle/blob/1.0.0/lib/castle.ex#L1)

Runtime hot-code upgrade support for Elixir releases.

[Forecastle](https://hexdocs.pm/forecastle) prepares releases at build time;
Castle manages versions on the deployed node. It unpacks, installs, commits
and removes releases, resolving a target's config providers before install or
commit.

Call `customize/1` from the release definition in `mix.exs`. The remaining
public functions back the `bin/castle` commands.

Successful commands print their result and return `:ok`. Castle refusals and
`:release_handler` errors raise `Castle.Error`, giving `bin/castle` a non-zero
exit status. Other exceptions, throws and exits propagate unchanged. Treat any
raise over `rpc` as a failed command.

Commands that modify a deployment require the VM's emulator root to match the
release root. This rejects `include_erts: false` and other layouts in which
`:release_handler` would modify a shared Erlang installation. The diagnostic
functions `upgradable/0` and `releases/0` remain available.

# `commit`

```elixir
@spec commit(String.t()) :: :ok
```

Makes `vsn` permanent, so that it is the version a restart boots into.

`bin/castle commit [<vsn>]` calls this function. Without a version, the command
selects the `current` release and exits non-zero when none is awaiting commit.

Castle resolves the target's config providers again before promotion, storing
the configuration a boot at commit time would produce. An explicit commit of
the permanent version still performs this step.

Raises `Castle.Error` if the configuration could not be expanded, or if the
version cannot be promoted. This includes staged, rolled-back, superseded and
unknown versions.

# `customize`

```elixir
@spec customize(keyword()) :: keyword()
```

Makes a Mix release Castle-capable.

Adds Forecastle's build steps and returns the updated release options.

    # mix.exs
    defp releases do
      [
        my_app: fn ->
          [
            include_executables_for: [:unix],
            steps: [:assemble, :tar]
          ]
          |> Castle.customize()
        end
      ]
    end

Define the release with `fn -> ... end` so Mix evaluates it after Castle has
been compiled.

## Steps

Existing steps retain their order. If `:steps` is absent, this function uses
`[:assemble, :tar]`. It preserves an explicit list and warns when the
list has no `:tar`.

Forecastle generates the relup after custom steps that change the release and
immediately before `:tar`. If a custom step packages the release without
`:tar`, place `&Forecastle.generate_relup/1` immediately before that step.
Split steps that both modify and package the release. A step after `:tar` must
not modify or repackage it.

## Upgrades

`upgrade_from:` names the supported source releases:

    my_app: fn ->
      [
        include_executables_for: [:unix],
        upgrade_from: ["tar:artifacts/my_app-1.0.0.tar.gz"]
      ]
      |> Castle.customize()
    end

A baseline may be a shipped `tar:` archive, an assembled `rel:` release or a
`ref:` git ref. A path without a prefix means `rel:`. Forecastle generates
both directions for every baseline. Prefer `tar:` when the shipped artifact
is available because a rebuilt baseline may differ from the deployed release.

Forecastle validates `upgrade_from:`. It rejects malformed or duplicate
values and a project-root `relup` supplied alongside the option. Omitting the
option skips relup generation.

The project must also provide:

  * An appup for each owned application upgraded in place, configured with the
    `:appup` project key and `compilers: Mix.compilers() ++ [:appup]`. See
    `mix help castle.relup` for the rules.
  * A relup generated from `upgrade_from:`, or one generated by
    `mix castle.relup` and left in the project root.
  * `include_executables_for: [:unix]`.

Forecastle appends Castle's setup to the generated `env.sh` or to a custom
`rel/env.sh.eex`.

# `install`

```elixir
@spec install(String.t(), Path.t(), module(), module(), module()) :: :ok
```

Installs `vsn` and makes it the version the system is running.

`bin/castle install <vsn>` calls this function after `unpack/1` stages the
version.

Castle checks the deployment and release record, resolves the target's config
providers in a temporary VM, and prepares any emulator-restart marker before
calling `:release_handler.install_release/1`.

A hot upgrade reports the new and previous running versions. An upgrade that
restarts the emulator reports that the version was installed and remains
provisional. `bin/castle install` then polls `running/1` until the target has
finished booting. Automation that calls `install/1` over `rpc` must perform
the same check.

The installed version remains provisional until `commit/1`. An ordinary
restart before commit boots the previous permanent version. A
`restart_emulator` transition boots the target for its installation restart,
but later restarts still use the permanent version until commit.

Castle serialises installs on the local Erlang node. A pending restart install
owns its launcher marker and blocks another restart install from replacing it.

## The four extra arguments

`install/1` is the supported form. The defaulted arguments on `install/2`
through `install/5` are test seams, not deployment options.

# `releases`

```elixir
@spec releases() :: :ok
```

Lists the releases the system knows of, and the status of each.

`bin/castle releases` calls this function. It prints one line per release with
its `:release_handler` status: `permanent`, `current`, `unpacked` or `old`.

`unpacked` includes staged releases and releases returned to that state after
a failed or rolled-back install. A node with no known releases prints nothing.
This read-only command remains available when mutating commands are refused.

# `remove`

```elixir
@spec remove(String.t()) :: :ok
```

Removes `vsn` from the system, and deletes what nothing else is using.

`bin/castle remove <vsn>` calls this function. It removes the version directory,
unreferenced application directories, and an emulator directory unused by any
remaining release.

Raises `Castle.Error` for the permanent version or an unknown version.

# `running`

```elixir
@spec running(String.t()) :: :ok
```

Confirms that `vsn` is the release the system is running, and has finished
booting.

Success prints nothing. A different running version or an incomplete boot
raises `Castle.Error` with the current state.

`bin/castle install` polls this function. It accepts the `current` release, or
the `permanent` release when no release is current, after the boot script
reaches its `started` marker.

# `unpack`

```elixir
@spec unpack(String.t()) :: :ok
```

Unpacks a release tarball into the deployment, and reports the version.

`bin/castle unpack <vsn>` calls this function. Place
`<release-name>-<vsn>.tar.gz` in the deployment's `releases` directory first.

The command extracts the applications and records the version as `unpacked`.
It does not change the running version.

Raises `Castle.Error` if the node cannot be upgraded from. See
`upgradable/0`. `RELDIR` and the SASL `releases_dir` option are not supported;
see [issue #23](https://github.com/ausimian/castle/issues/23).

# `upgradable`

```elixir
@spec upgradable() :: :ok
```

Checks whether the running node has a valid release record for upgrades.

`bin/castle upgradable` calls this function. Success prints nothing. A node
using a record synthesised by `:release_handler` raises `Castle.Error` with
recovery instructions.

`unpack/1` and `install/1` perform the same check when they act. A prior call
to `upgradable/0` is diagnostic only.

The release-record file is normally `<root>/releases/RELEASES`. `RELDIR` or
the SASL `releases_dir` option can move the file read by `:release_handler`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
