Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a paradigm for multi-application configuration #75

Open
llakala opened this issue Oct 26, 2024 · 4 comments
Open

Create a paradigm for multi-application configuration #75

llakala opened this issue Oct 26, 2024 · 4 comments

Comments

@llakala
Copy link
Owner

llakala commented Oct 26, 2024

We've created many paradigms in the lifetime of this project. These include:

  • Using home-manager as a module for a far simpler filetree
  • Using a custom import function to avoid default.nix, which can cause bugs
  • Making said import function recursive, but limited to 1 depth, to allow splitting a single file's configuration into multiple files in a subfolder
  • Using myLib to separate real lib functions from our custom ones
  • Using helper functions such as lib.singleton to de-nest code
  • Using an hm alias to easily configure something via home-manager
  • Using hostVars for easy access to something that may differ between hosts
  • Ensuring any custom modules don't hide functionality, and only abstract boilerplate
  • Running unfree packages without a reliance on an external input via mkUnfreeNixpkgs

The length of this list, and the number of paradigms I'm probably forgetting, serves as an example of the many problems we have solved. Antipatterns that once seemed impossible to avoid are now no longer a concern.

So, what's the point of all this? The point is that we can solve problems.

And with that, let's get into the problem.

I am the lorax, I speak for the trees

Our configuration attempts to take the stance that most files should be a single actor that isn't load-bearing. What does this mean?
Well, let's think of a NixOS configuration as a tree.

The flake.nix is the trunk. It's reasonable to expect that a change to the flake could easily break something. The flake is connected to limbs. We can think of the functions in myLib as a limb. If we broke mkUnfreeNixpkgs, we could expect an error elsewhere in the configuration. The same is true for our custom modules. The nature of these files is that they provide functionality to other files, and are thus expected to have a larger impact if they're changed.

But, what about leaves? These are the individual files. These leaves can rely on branches. Any file relying on the existence of the hm alias, or hostVars, or myLib, or a custom module, But, ideally, they shouldn't rely on each other. A leaf should only expect the tree to remain unchanged. When we encounter a leaf relying on another leaf, it typically means we should create an abstraction within the tree.

One of these recent abstractions are twigs, or subfolders. Previously, something like configuring Firefox was expected to be limited to a single leaf. But, if Firefox required a lot of configuration, the file's length ended up ballooning out of control. To solve this, we introduced subfolders. Each leaf now represented a single aspect of the Firefox configuration. These leaves are expected to be relatively independent, but they can rely on the existence of another. For anything within the Firefox home-manager module to be configured, hm.programs.firefox.enable has to be true. The other files can expect that this will be true, and just set things up under this assumption.

But, what if two leaves need to be friends?

Leafeo and Leafiet: A love story

Let's use an example. Yazi has a plugin that integrates Starship. We want to enable this plugin, but only on the condition that we're using Starship. If we ever stop using Starship, it should be made clear to the user that we have to disable this integration too.

This integration, like most others, follow a pattern of:

# yazi.nix
if starship.isUsed then
   yazi.integrations = starship.package;

Let's make. From now on, we'll call the program integrating another program the actor. The program being integrated will be the prop. Rewriting our code to accommodate this new terminology:

# actor.nix
if prop.isUsed then
   actor.integrations = prop.package;

Now, we're set up to go through some hypothetical solutions to this problem,

@llakala
Copy link
Owner Author

llakala commented Oct 28, 2024

Solution 1: The actor buys the prop

In this solution, the actor moves its config to a folder, so we can have a twig with multiple files. Their structure will look something like this:

apps/cli/actor
├── actor.nix
└── prop.nix

The contents of prop.nix would look something like this:

{ config, lib, ... }:
let
  propExists = config.programs.prop.enabled;
in
{
  actor.integrations = lib.mkIf propExists
  [
    config.programs.prop.package
  ];
}

There are pros and cons to this, which I'll go into later after editing this!

@llakala
Copy link
Owner Author

llakala commented Oct 28, 2024

Solution 1b: the prop has the actor's address written on it

TODO: WRITE THIS

@llakala
Copy link
Owner Author

llakala commented Oct 28, 2024

Solution 2: The prop says that it's available via a module

TODO: WRITE THIS

@llakala
Copy link
Owner Author

llakala commented Oct 28, 2024

Solution 3: The integration is stored at a storage unit

TODO: WRITE THIS

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant