# `FlyDeploy.BlueGreen`

Blue-green deploys via `:peer` nodes.

Instead of hot-patching code in a running BEAM (suspend → load → code_change → resume),
this starts the user's app in a child BEAM process and swaps to a new one on upgrade.
No suspension, no `code_change/3`, clean start.

## Setup

In your `Application` module, rename `start/2` to `start_app/2` and delegate:

    defmodule MyApp.Application do
      use Application

      def start(type, args) do
        FlyDeploy.BlueGreen.start_link(
          [
            {DNSCluster, query: Application.get_env(:my_app, :dns_cluster_query) || :ignore}
          ],
          otp_app: :my_app,
          start: {__MODULE__, :start_app, [type, args]}
        )
      end

      def start_app(_type, _args) do
        children = [
          MyApp.Repo,
          {Phoenix.PubSub, name: MyApp.PubSub},
          MyAppWeb.Endpoint
        ]

        Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
      end
    end

The first argument is a list of child specs to run on the **parent** node.
This is important for children like `DNSCluster` that rely on a consistent
node basename for discovery — peer nodes have machine-specific names that
prevent cross-machine discovery, but parent nodes share a consistent basename
set by `RELEASE_NODE`.

## How it works

- **Dev/test**: Calls your `start_app` directly. Zero overhead.
- **Fly (parent)**: Starts the BlueGreen supervisor (PeerManager + Poller), which boots
  your app in a peer BEAM process. Returns `{:ok, supervisor_pid}`.
- **Fly (peer)**: Calls your `start_app` directly. Endpoint binds via SO_REUSEPORT.

## Options

- `:otp_app` - Your OTP application name (required)
- `:start` - `{module, function, args}` MFA for starting your supervision tree (required)
- `:endpoint` - Your Phoenix Endpoint module (auto-detected if not given)
- `:poll_interval` - How often to poll S3 in ms (default: 1000)
- `:shutdown_timeout` - Max time in ms to wait for the outgoing peer to shut down
  before force-killing it. `nil` (default) means wait indefinitely, trusting
  the app's supervision tree timeouts.
- `:before_cutover` - `{mod, fun, args}` MFA invoked on the **outgoing peer**
  before it shuts down. The incoming peer's node name is prepended to `args`.
  Its return value is passed as the last argument to `:after_cutover`. Useful
  for collecting handoff state (locks held, in-flight work, etc.). Runs
  synchronously; failures are logged and return `nil`.
- `:after_cutover` - `{mod, fun, args}` MFA invoked on the **incoming peer**
  after the outgoing peer has fully shut down. The return value of
  `:before_cutover` (or `nil`) is prepended to `args`. Runs in a Task so it
  won't crash the parent if it fails.

# `get_all_handoff`

Returns all handoff data as a map. Useful for debugging or bulk reads.

# `get_handoff`

Retrieves a handoff value stored with `put_handoff/2`.

Returns `nil` if the key is not found or if not running in blue-green mode.
Can be called from either the parent or a peer node.

# `put_handoff`

Stores a handoff value that survives peer transitions.

The data is stored in an ETS table on the **parent** node, so it persists
across blue-green cutovers. Multiple processes can write concurrently with
unique keys. Data is cleared at the start of each upgrade cycle.

Can be called from either the parent or a peer node. When called from a peer,
it RPCs to the parent automatically.

## Example

    # In before_cutover (runs on outgoing peer):
    FlyDeploy.BlueGreen.put_handoff(:my_locks, held_locks)
    FlyDeploy.BlueGreen.put_handoff(:my_cache, cache_snapshot)

    # In after_cutover or process init (runs on incoming peer):
    locks = FlyDeploy.BlueGreen.get_handoff(:my_locks)

# `start_link`

Entry point for blue-green mode without parent-level children.

See `start_link/2` for the variant that accepts parent children.

# `start_link`

Entry point for blue-green mode with parent-level children.

The first argument is a list of child specs to supervise on the parent node.
These start before PeerManager and Poller, making them ideal for clustering
(e.g., `DNSCluster`) that needs the parent's consistent node basename.

See module docs for setup instructions.

---

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