Skip to content

Commit 2eea154

Browse files
ausimiancodex
andcommitted
docs: tighten public documentation for 1.0
Co-Authored-By: Codex GPT-5 <noreply@openai.com>
1 parent bc19bf5 commit 2eea154

4 files changed

Lines changed: 265 additions & 871 deletions

File tree

README.md

Lines changed: 120 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,129 +1,157 @@
11
# Castle
22

3-
Runtime support for hot-code upgrades.
3+
Castle adds hot-code upgrade support to Elixir releases. It manages upgrades on
4+
the running node and resolves the target release's runtime configuration before
5+
OTP installs it.
46

5-
`Castle` provides runtime support for hot-code upgrades. In particular, it generates a
6-
valid `sys.config` from `runtime.exs` and/or other [Config Providers](https://hexdocs.pm/elixir/main/Config.Provider.html)
7-
prior to both boot and hot-code upgrade.
7+
[Forecastle](https://hexdocs.pm/forecastle) handles the build-time work. Castle
8+
includes it as a build-time dependency.
89

9-
It relies on [Forecastle](https://hexdocs.pm/forecastle/readme.html) for build-time release generation
10-
and brings it in as a build-time dependency.
10+
## Requirements
11+
12+
- Elixir 1.18 or later.
13+
- A Unix release built with its own ERTS. Castle refuses releases built with
14+
`include_erts: false`.
15+
- An external supervisor such as systemd, Docker, Kubernetes or runit if an
16+
upgrade restarts the emulator.
17+
18+
Use Castle and Forecastle from the same release series. Castle 1.x expects the
19+
release layout produced by Forecastle 1.x.
1120

1221
## Installation
1322

14-
The package can be installed by adding `castle` to your list of dependencies in
15-
`mix.exs`. For projects that don't define a release, but use the `appup` compiler,
16-
it's sufficient to bring `Castle` in as a build-time dependency:
23+
Add Castle to applications that build a release:
1724

1825
```elixir
1926
def deps do
2027
[
21-
{:castle, "~> 0.3.0", runtime: false}
28+
{:castle, "~> 1.0"}
2229
]
2330
end
2431
```
2532

26-
For projects that _do_ define one or more releases, `Castle` should be brought in
27-
as a runtime dependency:
33+
An application that only uses Forecastle's appup compiler can keep Castle out
34+
of its runtime release:
2835

2936
```elixir
3037
def deps do
3138
[
32-
{:castle, "~> 0.3.0"}
39+
{:castle, "~> 1.0", runtime: false}
3340
]
3441
end
3542
```
3643

37-
`Castle` brings in `Forecastle` as a build-time dependency.
44+
## Project setup
3845

39-
## Integration
46+
Point the project at its appup file and add the appup compiler:
47+
48+
```elixir
49+
def project do
50+
[
51+
appup: "appup.exs",
52+
compilers: Mix.compilers() ++ [:appup]
53+
]
54+
end
55+
```
4056

41-
Build-time integration is done via `Forecastle` and more details can be found in its
42-
documentation but, in summary, it will integrate into your release process via the
43-
release assembly process. In particular, it requires that that the `Forecastle.pre_assemble/1`
44-
and `Forecastle.post_assemble/1` functions are placed around the `:assemble` step, e.g.:
57+
Define each release lazily and pass its options to `Castle.customize/1`:
4558

4659
```elixir
4760
defp releases do
4861
[
49-
myapp: [
50-
include_executables_for: [:unix],
51-
steps: [&Forecastle.pre_assemble/1, :assemble, &Forecastle.post_assemble/1, :tar]
52-
]
62+
my_app: fn ->
63+
[include_executables_for: [:unix]]
64+
|> Castle.customize()
65+
end
5366
]
5467
end
5568
```
5669

57-
## Release Management
58-
59-
The script in the `bin` folder supports some extra commands to manage upgrades.
60-
Releases, in their tarred-gzipped form, should first be copied to the `releases`
61-
subfolder on the target system. The following commands can be used to manage
62-
them:
63-
64-
- `releases` - Lists the releases on the system and their status. Status can
65-
be one of the following:
66-
- permanent - the release the system will boot into on next restart.
67-
- current - if it exists, represents the current running release. Will be
68-
different from the permanent version if a new release has been installed
69-
but not yet committed. If no version is listed as current, the permanent
70-
version is the currently running version.
71-
- old - if it exists, a previously installed version.
72-
- unpacked - an unpacked version, but not yet installed.
73-
- `unpack <vsn>` - Unpacks the release called `<name>-<vsn>.tar.gz`.
74-
- `install <vsn>` - Installs the new release. This makes the release the
75-
current one, but not yet the permanent one. Prior to running the relup,
76-
`Castle` generates the version specific `sys.config` for the new version.
77-
- `commit <vsn>` - Makes the specified release the one the permanent one.
78-
- `remove <vsn>` - Remove an old version from the filesystem. Any files
79-
shared with remaining releases are left untouched.
80-
81-
## The Appup Compiler
82-
83-
You are responsible for writing the [appup](https://www.erlang.org/doc/man/appup.html)
84-
scripts for your application, but `Castle` will copy the appup into the `ebin` folder
85-
for you. The steps are as follows:
86-
87-
1. Write a file, in _Elixir form_, describing the application upgrade. e.g.:
88-
```elixir
89-
# You can call the file what you like, e.g. appup.ex,
90-
# but you should # keep it away from the compiler paths.
91-
{
92-
'0.1.1',
93-
[
94-
{'0.1.0', [
95-
{:update, MyApp.Server, {:advanced, []}}
96-
]}
97-
],
98-
[
99-
{'0.1.0', [
100-
{:update, MyApp.Server, {:advanced, []}}
101-
]}
102-
]
103-
}
104-
```
105-
This file will typically be checked in to SCM.
106-
2. Add the appup file to the Mix project definition in mix.exs and add the
107-
`:appup` compiler.
108-
```elixir
109-
# Mix.exs
110-
def project do
111-
[
112-
appup: "appup.ex", # Relative to the project root.
113-
compilers: Mix.compilers() ++ [:appup]
114-
]
115-
end
116-
```
117-
118-
## Relup Generation
119-
120-
Castle contains a mix task, `castle.relup`, that simplifies the generation of
121-
the relup file. Assuming you have two _unpacked_ releases e.g. `0.1.0` and `0.1.1`
122-
and you wish to generate a relup between them:
70+
The function wrapper is required. Mix loads `mix.exs` before dependencies have
71+
been compiled during commands such as `mix deps.get`. It evaluates the release
72+
function later, when Castle is available.
73+
74+
`Castle.customize/1` adds Forecastle's assembly steps around `:assemble`. When
75+
`:steps` is omitted, it uses `[:assemble, :tar]`. An explicit steps list is
76+
preserved; Castle warns if it has no `:tar` step.
77+
78+
You can also provide `rel/env.sh.eex`. Forecastle keeps its contents and appends
79+
the launcher setup Castle needs.
80+
81+
## Appups and relups
82+
83+
Write an appup for each application whose code changes during a hot upgrade.
84+
The appup file uses Erlang terms written in Elixir syntax:
85+
86+
```elixir
87+
{
88+
~c"1.1.0",
89+
[
90+
{~c"1.0.0", [{:update, MyApp.Server, {:advanced, []}}]}
91+
],
92+
[
93+
{~c"1.0.0", [{:update, MyApp.Server, {:advanced, []}}]}
94+
]
95+
}
96+
```
97+
98+
Generate a relup between assembled releases:
99+
100+
```shell
101+
mix forecastle.relup \
102+
--target _build/prod/rel/my_app/releases/1.1.0/my_app \
103+
--fromto _build/prod/rel/my_app/releases/1.0.0/my_app
104+
```
105+
106+
The task writes `relup` to the project root by default. Leave it there for the
107+
next release build to package. Use `--hot` to require a hot transition or
108+
`--restart` to force a one-stage emulator restart.
109+
110+
## Managing releases
111+
112+
Build the new release, then copy `<name>-<vsn>.tar.gz` into the running
113+
deployment's `releases` directory. Manage it with `bin/castle`:
123114

124115
```shell
125-
> mix castle.relup --target myapp/releases/0.1.1/myapp --fromto myapp/releases/0.1.0/myapp
116+
# Show known releases and their status.
117+
my_app/bin/castle releases
118+
119+
# Check whether this node can be upgraded. Success prints nothing.
120+
my_app/bin/castle upgradable
121+
122+
# Stage, install and make version 1.1.0 permanent.
123+
my_app/bin/castle unpack 1.1.0
124+
my_app/bin/castle install 1.1.0
125+
my_app/bin/castle commit
126+
127+
# Remove a version that is no longer needed.
128+
my_app/bin/castle remove 1.0.0
126129
```
127130

128-
If the generated file is in the project root, it will be copied during
129-
post-assembly to the release.
131+
Release statuses are:
132+
133+
- `permanent`: the version used on the next ordinary restart.
134+
- `current`: the running version, installed but not committed.
135+
- `old`: a superseded version that can be removed.
136+
- `unpacked`: a staged version, or one returned to that state after a failed or
137+
rolled-back install.
138+
139+
`install` resolves the target version's config providers in a temporary VM
140+
running that version's code. It then asks OTP to install the release. The
141+
version remains provisional until `commit` writes it as permanent. If a
142+
provisional hot upgrade fails or the service restarts, the previous permanent
143+
version boots again.
144+
145+
For a relup containing `restart_emulator`, `install` waits across the restart
146+
until the target version has finished booting. The external supervisor must
147+
restart the process. Castle does not support OTP's two-stage
148+
`restart_new_emulator` transition.
149+
150+
## Limitations
151+
152+
- Windows launchers are not supported.
153+
- `RELDIR` and the SASL `releases_dir` option are not supported yet. Castle and
154+
`:release_handler` must use the same release directory. See
155+
[issue #23](https://github.com/ausimian/castle/issues/23).
156+
- Castle serialises installs within one Erlang node. Do not run Castle from a
157+
second VM against the same deployment.

0 commit comments

Comments
 (0)