Skip to content
Oeiuwq Faith Blog OpenSource Porfolio

karpfediem/rx.nix

Reactive Nix: Enabling Functional Reactive Configuration with mgmt

karpfediem/rx.nix.json
{
"createdAt": "2025-10-12T22:28:55Z",
"defaultBranch": "main",
"description": "Reactive Nix: Enabling Functional Reactive Configuration with mgmt",
"fullName": "karpfediem/rx.nix",
"homepage": "",
"language": "Nix",
"name": "rx.nix",
"pushedAt": "2026-02-01T08:43:57Z",
"stargazersCount": 14,
"topics": [
"functional-reactive-programming",
"mgmtconfig",
"nix",
"nixos"
],
"updatedAt": "2026-02-07T02:46:13Z",
"url": "https://github.com/karpfediem/rx.nix"
}

Enabling Functional Reactive Configuration with mgmt

Nix lets us define entire systems as pure functions - deterministic, reproducible, and immutable. Each build yields a perfectly reproducible system generation, a snapshot of declarative intent made concrete.

However, once evaluated, these functions produce fixed values: the system stops evolving until we rebuild it. The world changes - secrets rotate, services start, fail and stop, peers reappear on the network - but the configuration does not react.

In Nix, a derivation represents a pure mathematical function:

system = f(configuration)

This guarantees purity and reproducibility - but it also locks time out of the model. A system generation can be correct now and outdated a moment later, and the only way to react is to rebuild.

Functional Reactive Programming (FRP) extends this idea:

system(t) = f(configuration, environment(t))

Here, configurations remain pure, yet they depend on signals - values that evolve continuously as their environment changes. FRP enables declarative systems that stay correct over time.


rx.nix (Reactive Nix) explores how Nix’s functional evaluation model can integrate with mgmt’s FRP runtime - a configuration management engine that reacts to change in real time.

While Nix provides the specification of a system, mgmt provides the motion: a continuous reconciliation loop that enforces the declared state automatically.

The combination is simple but potentially very powerful:

  • Nix defines what the system should look like.
  • mgmt ensures it stays that way - continuously, safely, and declaratively.

This fusion opens the door to systems that are not only reproducible at build time but reactively self-correcting at runtime.


1. Static evaluation Nix systems are static between rebuilds. Drift or external changes require explicit intervention.

2. Secret management Secrets in Nix are typically static, baked into store paths or decrypted at build time. Integrating a reactive engine allows external secret stores (Vault, 1Password, etc.) to feed live data into configuration safely.

3. Declarative dynamic Configuration Today, “dynamic” configuration in Nix often means templating or scripting (custom logic running on activation, or as a background service). FRP makes pure dynamism possible - logic that depends on live system signals while preserving functional semantics.

4. State awareness Traditional Nix evaluation is blind to runtime state. mgmt introduces live feedback loops that can reapply, heal, or reconfigure in response to drift or dependency changes.


rx.nix is a nixos module that lets users define reactive configuration inside regular nixosConfigurations. It also provides additional outputs via a flake module.

{
imports = [ rx.flakeModules.default ];
nixosConfigurations.demo = lib.nixosSystem {
system = "x86_64-linux";
modules = [
rx.nixosModules.default
({ ... }: {
rx.enable = true; # enable module
rx.mgmt.enable = true; # enable mgmt systemd service
rx.mcl.imports = [ "datetime" "golang" ]; # set up required imports (either here or in MCL code)
rx.mcl.vars.d = "datetime.now()"; # reactive variables
# Write raw MCL code
# Define your own reactive files (try to `watch -n 0.1 cat /tmp/now`)
rx.mcl.raw = [
''
file "/tmp/now" {
state => $const.res.file.state.exists,
content => golang.template("Hello! It is now: {{ datetime_print . }}\n", $d),
}
''
];
})
];
};
}

The nixosModule will generate a bundled up MCL module and set it as your current profile on system activation. The included systemd service will ensure your configuration is applied continuously during runtime of your system.

Additionally, the flake module provides these new outputs:

OutputDescription
rxSystems.<system>.<host>Built mgmt generations per host
rxIrForHost.<host>Per-host intermediate representation
rxGenForHost.<host>Materialized mgmt configuration payload
apps.rxSwitchForHost.<host>CLI app to switch the active generation

{
"demo": {
"files": [
{
"path": "/etc/hosts",
"owner": "root",
"mode": "0644",
"__source": "/nix/store/...-hosts"
},
{
"path": "/tmp/hello",
"owner": "root",
"mode": "0644",
"__content": "Hello from rx module\n"
}
]
}
}

This Intermediate Representation (IR) serves as the bridge between Nix’s static world and mgmt’s reactive runtime.


Reactive Nix isn’t about replacing NixOS or mgmt. It’s about exploring what happens when we bring time into functional configuration.

The questions driving this project are:

  • Can we cleanly combine purity and reactivity without giving up determinism?
  • Which new possibilities emerge when configuration is truly alive?

PhaseFeature / ExplorationStatus
IFile resources (rx.files, rx.include.files)✅ Implemented
IIPer-host evaluation (rxGenForHost, rxIrForHost)✅ Implemented
IIIFull MCL resource mapping (via codegen)✅ Implemented
IVExternal secrets integration (Vault, 1Password, SOPS)🔄 Planned
VLive mgmt evaluation backend (MCL codegen refactor)🔄 Planned
VIFull dynamic dependencies (signals between hosts)🚧 Research
VIICross-platform expansion (macOS via nix-darwin)🚧 Research
VIIIConceptual FRP primitives in Nix (mkSignal, mkDynamic)🚧 Research