# ExInfisical
Elixir client for the [Infisical](https://infisical.com) secrets API, built
primarily to **eliminate manual secret entry in Livebook notebooks**. Point
it at a folder, get every key dropped into the notebook's process
environment in one call.
- **Livebook-first ergonomics** — `ExInfisical.to_env!/2` pulls an entire
Infisical folder into `System.put_env/1` so downstream libs (`genai`,
`noizu_weaviate`, `redix`, …) just read `System.get_env/1` as usual.
- Universal Auth (Machine Identity) login with skew-aware token caching.
- Optional Cloudflare Access service-token headers for self-hosted
deployments behind CF Access (e.g. `https://infisical.noizu.com`).
- Server-side reference expansion (`${otherFolder.KEY}`) enabled by default —
one folder per project can reference shared keys elsewhere without
duplication.
## Installation
Add to `mix.exs`:
```elixir
def deps do
[
{:ex_infisical, "~> 0.1.0"}
]
end
```
## Configuration
```elixir
# config/runtime.exs
import Config
config :ex_infisical,
api_url: System.get_env("INFISICAL_API_URL", "https://app.infisical.com/api"),
client_id: System.fetch_env!("INFISICAL_CLIENT_ID"),
client_secret: System.fetch_env!("INFISICAL_CLIENT_SECRET"),
project_slug: System.get_env("INFISICAL_PROJECT_SLUG"),
project_id: System.get_env("INFISICAL_PROJECT_ID"),
# Only set when your API is behind Cloudflare Access:
cf_access_client_id: System.get_env("CF_ACCESS_CLIENT_ID"),
cf_access_client_secret: System.get_env("CF_ACCESS_CLIENT_SECRET")
```
Either `:project_id` (preferred) or `:project_slug` must be set — if the
slug is given, `ExInfisical` resolves the id once per process lifetime.
## Usage
### Fetch every secret at a path
```elixir
{:ok, secrets} = ExInfisical.get_secrets("/livebook")
#=> %{"LIVEBOOK_COOKIE" => "...", "LIVEBOOK_PASSWORD" => "..."}
```
### Fetch a single key
```elixir
{:ok, key} = ExInfisical.get_secret("LB_GROQ_API_KEY", path: "/livebook")
```
### Dump secrets into the process environment (Livebook / Mix.install)
```elixir
:ok = ExInfisical.to_env!("/livebook", prefix: "LB_")
System.get_env("LB_GROQ_API_KEY") #=> "gsk_..."
```
`to_env!/2` accepts `:prefix`, `:only`, `:except`, and `:transform` on top of
the normal fetch options.
### Options reference
Accepted by every fetch function:
| Option | Default | Description |
| ------ | ------- | ----------- |
| `:env` | `"prod"` | Environment slug. |
| `:path` | `"/"` | Secret folder path. |
| `:recursive` | `false` | Include sub-folders. |
| `:expand` | `true` | Expand `${folder.KEY}` references server-side. |
| `:project_id` / `:project_slug` | from config | Override per call. |
## Livebook recipe
```elixir
Mix.install([{:ex_infisical, "~> 0.1.0"}])
Application.put_all_env(
ex_infisical: [
api_url: "https://infisical.noizu.com/api",
client_id: System.fetch_env!("INFISICAL_CLIENT_ID"),
client_secret: System.fetch_env!("INFISICAL_CLIENT_SECRET"),
project_slug: "k8-infra"
]
)
ExInfisical.to_env!("/livebook", prefix: "LB_")
```
Downstream libs (`genai`, `noizu_weaviate`, `redix`, etc.) can then read
`LB_GROQ_API_KEY`, `LB_WEAVIATE_API_KEY`, and friends via `System.get_env/1`.
## License
MIT © Noizu Labs / Keith Brings