~ / projects / glixos

glixos

Beginner-friendly NixOS that keeps your whole config in one readable file.

status: active kind: platform #Go#Nix#NixOS#Flakes#home-manager#Docusaurus

Why I built it

I came to Nix for the declarative promise and then spent weeks combing through forums just to get the tools I actually wanted installed. By the time it worked I had a sprawling list of imported modules “organised” mostly by being jammed into folders, and my config lived in three different places at once: flakes, modules imported into configuration.nix, and more modules imported into my home-manager config. Three months later I’d go to install something and have to hunt for the correct place to put it, then find out that program was already half-configured somewhere else entirely, sometimes in two places at once.

glixos is the framework I wish I’d had. The goal isn’t to hide Nix or replace it; it’s to guide you into one standardised structure so that future-you always knows where a thing goes, and the state of a machine is something you can read in one sitting instead of reconstructing by spelunking through imports.

How it works

The whole config collapses to one file per host, a glix.toml manifest that lists every package you’ve installed, the scope it lives in (system or home), the user a home-scope package targets, and any per-package settings. Everything else is static Nix that just reads that manifest, so the manifest is the source of truth and the rest is projection.

Each package is still a normal flake. Your own config lives as small flakes in the repo, and glix’s real superpower is making someone else’s flake trivial to install: a friendly name or a flake URI becomes one line in the manifest, no hand-wiring of inputs and module imports.

A small Go CLI called glix is held to one strict rule: it is the only writer. It mutates the manifest and two anchored regions of one generated flake.nix, and it never parses arbitrary Nix or touches your own code, so the rest of your flake.nix stays yours. The semantic work of turning the decoded TOML into NixOS and home-manager module lists happens in a pure importManifest function in the core layer. A resolver chain turns a friendly name into a canonical flake URI (it tries the glixos registry, then your local Nix registry, then takes a verbatim URI if you hand it one), and a glix doctor command helps when things drift. Every mutation is one git commit, so rolling back is just glix rollback (or nixos-rebuild --rollback).

glix init --host nixbox --user donnis --system x86_64-linux
glix add github:donnismoore/nisaba --scope home --user donnis  # someone's flake
sudo glix rebuild switch

What a config looks like

A glixos repo has one obvious place for everything:

glixos-donnis/
├── flake.nix            # generated input wiring + glix's anchored region
├── flake.lock
├── hosts/
│   └── nixbox/
│       └── glix.toml    # the manifest, the one file you actually edit
├── packages/            # your own config, each a small flake
│   ├── system-core/
│   ├── system-desktop/
│   ├── home-shell/
│   ├── home-dev/
│   └── home-media/
└── secrets/             # agenix-encrypted, referenced from packages

The manifest reads like an inventory. Your own flakes go in by path:, anyone else’s by URI, each with a scope and (for home packages) a user:

# glixos manifest. Managed by glix.
schema = 1

[settings]
default_scope = "system"
system        = "x86_64-linux"
primary_user  = "donnis"
registry_url  = "https://raw.githubusercontent.com/glixos/registry/main/registry.json"

# Your own config, kept as small flakes in ./packages
[packages.system-desktop]
flake   = "path:./packages/system-desktop"
scope   = "system"
enabled = true

[packages.home-shell]
flake   = "path:./packages/home-shell"
scope   = "home"
enabled = true
user    = "donnis"

# Someone else's flake, added with a single `glix add`
[packages.nisaba]
flake   = "github:donnismoore/nisaba"
scope   = "home"
enabled = true
user    = "donnis"

Wiring it into a host is small and boring on purpose, since the manifest does the work via manifest.systemModules:

{
  inputs.glixos-core.url = "github:donnismoore/glixos";

  outputs = { nixpkgs, glixos-core, ... }@inputs: {
    nixosConfigurations.nixbox = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules =
        let
          manifest = glixos-core.lib.importManifest {
            manifestPath = ./hosts/nixbox/glix.toml;
            inherit inputs;
            defaultUser = "donnis";
          };
        in [
          glixos-core.nixosModules.glixos
          ./hosts/nixbox
        ] ++ manifest.systemModules;
    };
  };
}

Status

Pre-release (CLI 0.1.0-m7, manifest schema 1), so early rather than finished, but the contract underneath is solid: one file declares your system, one writer maintains it, and you can read the whole thing. Full docs at donnismoore.github.io/glixos.