<div align="center">
# ๐งฉ MishkaInstaller
**A runtime plugin / event engine and pre-built library installer for Elixir.** โจ
[](https://hex.pm/packages/mishka_installer)
[](https://hex.pm/packages/mishka_installer)
[](https://github.com/mishka-group/mishka_installer/actions/workflows/ci.yml)
[](https://github.com/mishka-group/mishka_installer/blob/master/LICENSE)
[](https://github.com/sponsors/mishka-group)
[](https://www.buymeacoffee.com/mishkagroup)
</div>
---
> [!WARNING]
> **๐ง Low maintenance.** The author currently only responds to pull requests. Don't use `master`; the current release line is `0.1.7`.
---
## ๐ญ Why?
Build apps whose features are **activated as plugins at runtime** โ registration, SMS, social login โ **without touching the core source**. Each feature becomes an **event**; anyone can attach plugins to it from outside your app.
---
## โจ Features
- ๐ **Events & Hooks** โ register plugins for an event; they run in **priority + dependency** order.
- โก **Fast dispatch** โ each event compiles to a module, so `Hook.call/3` is a direct call: no GenServer, no DB on the read path.
- ๐ฉบ **Health checks** โ optional per-plugin `health_check/0`, inspected via `Hook.event_health/1`.
- ๐ **Multi-node** โ the Mnesia-backed store joins & replicates across a cluster automatically.
- ๐ฆ **Runtime installer** โ load **pre-built `ebin`** artifacts (local path, URL, or GitHub release). Works in a `mix release`.
---
## ๐ Events & Hooks
Define a plugin for an event โ it auto-registers and runs whenever the event is called:
```elixir
defmodule RegisterEmailSender do
use MishkaInstaller.Event.Hook, event: "after_success_login"
@impl true
def call(entries), do: {:reply, entries}
end
```
```elixir
# tweak defaults
use MishkaInstaller.Event.Hook,
event: "after_success_login",
initial: %{depends: [SomeEvent], priority: 20}
```
Call every plugin registered for an event:
```elixir
alias MishkaInstaller.Event.Hook
Hook.call("after_success_login", params)
Hook.call("after_success_login", params, private: keep_this) # extra data, untouched by plugins
Hook.call("after_success_login", params, return: true) # return the original input
```
To start a plugin automatically, add its module to your supervision tree:
```elixir
children = [RegisterEmailSender, ...]
```
> [!NOTE]
> A plugin's `depends` always run **before** it (cycles are rejected at registration), and a plugin can return `{:reply, :halt, state}` to stop the rest of the chain. See `MishkaInstaller.Event.Hook`.
[](https://livebook.dev/run?url=https%3A%2F%2Fgithub.com%2Fmishka-group%2Fmishka_installer%2Fblob%2Fmaster%2Fguidance%2Fevent%2Fhook.livemd)
---
## ๐ฆ Installer
Load an **already-compiled `ebin`** at runtime โ no source compilation, so it's release-safe:
```elixir
alias MishkaInstaller.Installer.Installer
Installer.install(%{app: "demo", version: "0.1.0", type: :path, path: "/ext/demo-0.1.0"})
Installer.install(%{app: "demo", version: "0.1.0", type: :url, path: "https://.../demo-ebin.tar.gz"})
Installer.install(%{app: "demo", version: "0.1.0", type: :github_tag, path: "owner/repo", tag: "0.1.0"})
Installer.install(%{app: "demo", version: "0.1.0", type: :github_latest_release, path: "owner/repo"})
Installer.uninstall(%{app: "demo", version: "0.1.0"})
```
> [!NOTE]
> Remote installs (`:url`/`:github_*`) are **fail-closed**: they require the source host/repo in `config :mishka_installer, :allowlist, url_hosts:/github_repos:`. Optional `checksum:` (sha256) pins the artifact; `:protected_apps` guards apps from being overwritten/removed. See `MishkaInstaller.Installer.Installer`.
---
## ๐ญ Production deployment
An installed library is a pre-built `ebin` on disk plus a Mnesia record; on every boot it is replayed (put back on the code path and started). So **both** the `ebin`s and the Mnesia data must live on a **persistent (mounted) volume** โ otherwise installs do not survive a restart/redeploy. Point both at your volume (here `/data`):
```elixir
# config/runtime.exs โ /data is your mounted volume
config :mishka_installer,
project_path: "/data",
extensions_path: "/data/extensions"
config :mishka_installer, MishkaInstaller.MnesiaRepo,
mnesia_dir: "/data/mnesia",
essential: [MishkaInstaller.Event.Event, MishkaInstaller.Installer.Installer]
```
`:extensions_path` is where `ebin`s are written; `mnesia_dir` is where the records live; `:essential` are the tables created on boot (the plugin and install stores โ keep both). Just depend on `:mishka_installer`; its supervision tree starts automatically outside `:test`.
> A real release restart proof lives in `test/integration/production_release/` โ run it with `mix test --only production_release`.
---
## ๐ Installation
```elixir
def deps do
[{:mishka_installer, "~> 0.1.7"}]
end
```
Docs: [hexdocs.pm/mishka_installer](https://hexdocs.pm/mishka_installer)
---
## ๐ Funding & sponsorship
MishkaInstaller is open-source software developed by [Mishka Group](https://github.com/mishka-group). If your team or company benefits from it, please consider supporting continued development:
<div align="center">
[](https://github.com/sponsors/mishka-group)
[](https://www.buymeacoffee.com/mishkagroup)
**โ Donate / sponsor:**
[github.com/sponsors/mishka-group](https://github.com/sponsors/mishka-group) ยท [buymeacoffee.com/mishkagroup](https://www.buymeacoffee.com/mishkagroup)
</div>
Thank you. ๐
---
## ๐ License
Apache License 2.0 โ see [`LICENSE`](https://github.com/mishka-group/mishka_installer/blob/master/LICENSE).
Copyright ยฉ Mishka Group and contributors.