- Rust 63%
- Nix 37%
| crates/json-to-rsc | ||
| examples | ||
| modules | ||
| .envrc | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| default.nix | ||
| LICENSE | ||
| overlay.nix | ||
| README.md | ||
| shell.nix | ||
mikrotik.nix
Declaratively manage your MikroTik router, or any other system running RouterOS.
Caution
While some configurations can already be expressed using
mikrotik.nix, it's still a work in progress. Expect regular syntax breakage and behavior breakage. Additionally, the module isn't actually type checked yet, and neither do all the helpers have human-friendly errors or verification mechanisms. This means that there's no string escaping, value checking, or anything of sorts, so despite using MikroTik's safe mode, the script can apply succesfully, yet the configuration can be not what you expected.
Example configuration
The following is a small snippet which adds a specific DHCP lease.
More examples can be found in the ./examples directory.
# MikroTik syntax, which is translated into mikrotik.nix
# /ip dhcp-server lease
# add address=192.168.88.10 mac-address=AA:AA:AA:AA:AA:AA server=defconf
{
mikrotik."ip/dhcp-server/lease" = {
# _key meta attribute tells mikrotik.nix two things:
# - this represents a table (we'll use add/remove instead of just set)
# - which field to use when identifying items in this table
#
# it's also possible to just say _table = true, in which case _key is assumed to be "name".
_key = "comment";
my-server = {
address = "192.168.88.10";
mac-address = "AA:AA:AA:AA:AA:AA";
server = "defconf";
}
}
}
Which can then be applied by doing:
nix-build module.nix -A config.build.activate
./result admin@192.168.88.1
Implementation gaps
The project is barely in its infancy. As it stands it's only possible to use the "low-level" Nix DSL which translates to MikroTik's scripting language.
The immediate plan is to build a solid enough foundation to start building higher level modules on top of it, so that people don't need to learn both MikroTik's syntax, as well as how mikrotik.nix represets it.
With all of that said...
Design ideas welcome
I'm still not convinced this is the best way to represent RouterOS configuration. If you have any ideas, or even can come up with a configuration that cannot be represented, please open an issue.
Options
Because there's no high-level modules yet, there's also no module documentation yet. The "raw" DSL is all freeform, because MikroTik likes to change things up, as well as to accomodate any optional packages, optional hardware, etc.
Despite that, here's how every raw entry under mikrotik. is structured,
as well as some other attributes:
{config, ...}: {
mikrotik."<path>" = {
# Marks this resource as a table.
# This means the generated activation script will use add/remove
# instead of just plain set.
# By default it's false, unless _key or _managed is set.
_table = true;
# Tells scriptBuilder which field to use for identifying entries in the table.
# TODO: More complex options? Giving the ability to provide a custom key query?
_key = "name";
# Tells scriptBuilder whether the resources under this path are
# considered managed.
# Which means, whether they are created and destroyed by mikrotik.nix,
# or managed by the router itself.
# An example of this is /interface/ethernet resource, which
# behaves like a table, but we can't create or remove physical interfaces.
# In that case we'd set _managed = false. It's true by default.
_managed = true;
# The prefix to use for this specific path.
# Normally also shouldn't be needed, but some modules,
# particularly if they don't have a dedicated name and/or comment field,
# cannot use arbitrary keys (e.g. upnp interfaces).
_prefix = "nix:";
# Lists of paths to decide the ordering of final statements.
# Usually shouldn't be needed, but e.g. /ip/pool has to be before /ip/dhcp-server.
_before = [];
_after = [];
# If _table is set to true, the following structure is available
"<name>" = {
# The final name of the resource, including the prefix.
# It's mostly here for convenience, so that a single attribute can be referenced,
# instead of having to specifically reference .comment, .name, or whatever else
# is used as the identifier field.
_name = "foo:bar";
# The field specified by _key will automatically have the same value as _name.
# You can override it, which will also update _name.
# In general it's discouraged to modify it as it can break mikrotik.nix's logic,
# but it's an option to prevent infinite recursion errors.
${_key} = "foo:bar";
# And other simple values
type = "external";
# You can reference other resources too.
# Don't forget to set appropriate _before/_after, so that the resource actually is available!
interface = config.mikrotik."interface/bridge".bridge._name;
};
# Otherwise only simple values can be set
disabled = false;
time-zone-name = "Europe/Berlin";
};
meta = {
# Default prefix of all managed resources.
# Not only identifies them for the sake of the user, but also means
# mikrotik.nix will clean up any stale firewall rules, interfaces, et al.
# which contain the prefix, but are no longer referenced in the configuration.
prefix = "nix:";
};
build = {
# The derivation which contains `json-to-rsc` binary.
# This could probably be implemented in Nix but it was easier for me to write it in Rust.
scriptBuilder = pkgs.mikrotik-nix;
# The derivation whose output is the final .rsc script,
# which will be executed on the router to apply the config.
# It's produced by passing `config.mikrotik` as an argument to `json-to-rsc`.
applyScript = "<derivation>";
# The derivation whose output is a script,
# which when called with SSH credentials like admin@192.168.88.1,
# will open an SSH connection and safely run the `applyScript` on the target.
# The changes will automatically be rolled back if there's any errors.
activate = "<derivation>";
}
}
Roadmap
Immediate plans for the project, roughly in order of importance:
- Instead of generating a script which calls SCP and SSH a bunch of times, have a program which manages the interactions.
- Possibly use the API instead of SSH.
- Cleanup stale entries which match
prefix. - Introduce escape-hatches for more complex expressions (e.g. customizing how the entry key is looked up).
- Improve UX and error reporting.
- Start introducing high-level modules.