Compare commits

...

37 commits

Author SHA1 Message Date
e813b0f443 ok maybe leave nixpkgs...
we won't depend on it (i force that)
but it's good to allow following
2026-02-17 09:00:14 +10:00
da4cfefe7b force system to be specified 2026-02-17 09:00:14 +10:00
aab6c3bdd3 update TODO 2026-02-17 09:00:14 +10:00
f15e28b76c remove nixpkgs dependency
YIPPIE YIPPIE YIPPIE
2026-02-16 19:42:49 +10:00
d527937829 fix dont use microvm.nixosModules.microvm by default 2026-02-16 08:21:50 +10:00
9ddb124ec2 fix [] ++ {} (im oopid) 2026-02-16 08:20:24 +10:00
fbe9c6e6ce fix self not propagated 2026-02-16 08:19:07 +10:00
619c74fad4 fix nt not propagated 2026-02-16 08:17:33 +10:00
f23830e29a more TODO.md 2026-02-15 23:29:41 +10:00
149f873d97 move nixos-modules/ -> nixos/ 2026-02-15 17:46:37 +10:00
33d217c899 always use root group 2026-02-15 17:35:18 +10:00
b94b42abec 0.2.1 2026-02-15 17:34:54 +10:00
49fcdee320 add new TODO.md 2026-02-15 17:34:29 +10:00
23615355b7 remove overlays
overlays is no longer configured this way
2026-02-15 17:34:12 +10:00
86a0c9fc3d auto-import home-manager and microvm nixosModules 2026-02-15 14:03:06 +10:00
75c87fb6d1 remove nixpkgs-unstable 2026-02-15 13:43:37 +10:00
8ef3ce18cd only inherit inputs 2026-02-15 13:43:19 +10:00
f329d48223 support nixpkgs.channels.*.default 2026-02-14 16:00:34 +10:00
af89d57908 fix overlays bad type 2026-02-14 14:42:02 +10:00
f995411682 add extraSpecialArgs 2026-02-14 14:06:29 +10:00
3ba385ee25 0.2.0 2026-02-13 22:15:34 +10:00
23378831aa IM FUCKING SISYPHUS 2026-02-13 22:13:35 +10:00
30e8e1f6c3 fix nixpkgs.channels system 2026-02-13 19:32:22 +10:00
369041869a add flake.nix#version field 2026-02-13 15:58:04 +10:00
9334235142 ensure system is inherited 2026-02-13 13:54:37 +10:00
d363c47e7f fix missing ... 2026-02-13 13:26:01 +10:00
5397bf5efc progress flake.lock to new upstream 2026-02-13 12:41:18 +10:00
7591af6e3a add support for multiple pkg channels 2026-02-13 12:38:53 +10:00
135f2fb996 update TODO + upstream 2026-02-13 12:08:02 +10:00
Emile Clark-Boman
4bce8ee7b2 auto-propagate home-manager options 2026-02-13 03:55:47 +10:00
Emile Clark-Boman
71ffa820a9 install deploy-rs
also begin adding support for multiple pkgs repos
2026-02-13 03:28:03 +10:00
Emile Clark-Boman
54512e6399 add TODO.md 2026-02-13 03:27:21 +10:00
Emile Clark-Boman
66c10fdb59 should i rewrite nixpkgs.lib.nixosSystem? 2026-02-13 02:53:03 +10:00
Emile Clark-Boman
b151f45dfb duplicate node opts to nexus 2026-02-13 02:35:35 +10:00
Emile Clark-Boman
c07ac05a0d progress flake.lock 2026-02-13 01:46:25 +10:00
Emile Clark-Boman
22d02d49f0 remove old lib 2026-02-13 01:46:16 +10:00
Emile Clark-Boman
2e6bef9ad2 add importOverlays 2026-02-13 01:46:09 +10:00
16 changed files with 623 additions and 774 deletions

61
TODO.md
View file

@ -1,5 +1,58 @@
Allow `Cerulean.mkNexus` to be an alias for `flake-parts.lib.mkFlake`
also rename `Cerulean` to `cerulean` in Nix to maintain the naming convention.
- [ ] deploy port should default to the first port given to `services.openssh`
Using `flake-parts` ensures Cerulean is usable without restricting
yourself only to the Cerulean ecosystem.
- [ ] use the Nix module system instead of projectOnto for `cerulean.mkNexus`
- [ ] create an alternative to nixos-install called cerulean-install that
allows people to easily bootstrap new machines (and host it on dobutterfliescry.net)
- [ ] find an alternative to `nix.settings.trusted-users` probably
- [ ] add the ceru-build user,
- [ ] add support for github:microvm-nix/microvm.nix
- [ ] add support for sops-nix
- [ ] it would be cool to enable/disable groups and hosts
- [ ] find a standard for how nixpkgs.nix can have a different base per group
- [ ] go through all flake inputs (recursively) and ENSURE we remove all duplicates by using follows!!
- [X] rename nixos-modules/ to nixos/
- [X] ensure all machines are in groups.all by default
- [X] fix nixpkgs.nix not working (default not respected)
- [X] remove dependence on nixpkgs
- [ ] allow multiple privesc methods, the standard is pam_ssh_agent_auth
## Low Priority
- [ ] rename extraModules to modules?
- [ ] rename specialArgs to args?
- [ ] make an extension to the nix module system (different to mix)
that allows transformations (ie a stop post config, ie outputs, which
it then returns instead of config)
- [ ] what if we automated the process of replacing windows with Nix??
then push this to nixos-anywhere or nix-infect lmaooo
- [ ] patch microvm so that acpi=off https://github.com/microvm-nix/microvm.nix/commit/b59a26962bb324cc0a134756a323f3e164409b72
cause otherwise 2GB causes a failure
- [ ] rewrite the ceru cli in rust
- [ ] make `ceru` do local and remote deployments
```nix
# REF: foxora
vms = {
home-assistant = {
autostart = true;
# matches in vms/*
image = "home-assistant";
options = {
mem = 2048;
};
};
equinox = {
image = "home-assistant";
};
};
```

View file

@ -13,23 +13,24 @@
# limitations under the License.
{
mix,
deploy-rs,
inputs,
...
} @ inputs:
mix.newMixture inputs (mixture: {
} @ args:
mix.newMixture args (mixture: {
includes.public = [
./nexus
];
version = "0.2.1";
nixosModules = rec {
default = cerulean;
cerulean = ./nixos;
};
overlays = [
# build deploy-rs as a package not from the flake input,
# hence we can rely on a nixpkg binary cache.
deploy-rs.overlays.default
# (self: super: {
# deploy-rs = {
# inherit (super) deploy-rs;
# lib = super.deploy-rs.lib;
# };
# })
inputs.deploy-rs.overlays.default
];
})

View file

@ -11,8 +11,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
{mix, ...} @ inputs:
mix.newMixture inputs (mixture: {
{mix, ...} @ args:
mix.newMixture args (mixture: {
includes.public = [
./nodes.nix
./nexus.nix

View file

@ -14,11 +14,8 @@
{
self,
this,
nixpkgs,
nixpkgs-unstable,
nt,
lib,
deploy-rs,
inputs,
...
}: let
inherit
@ -42,20 +39,22 @@
mapNodes
;
inherit
(nt)
findImport
;
templateNexus = let
inherit
(nt.naive.terminal)
Terminal
;
missing = msg: path:
Terminal (abort ''
Each Cerulean Nexus node is required to specify ${msg}!
Ensure `nexus.${path}` exists under your call to `cerulean.mkNexus`.
'');
in {
base = null;
extraModules = [];
specialArgs = Terminal {};
groups = Terminal {};
overlays = [];
nodes = Terminal {};
};
@ -66,7 +65,8 @@
isAttrs g
|| throw ''
Cerulean Nexus groups must be provided as attribute sets, got "${typeOf g}" instead!
Ensure all the `groups` definitions are attribute sets under your call to `cerulean.mkNexus`.
Ensure all the group definitions are attribute sets under your call to `cerulean.mkNexus`.
NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP`
'';
delegate = parent: gName: g: let
result =
@ -90,7 +90,7 @@
assert isAttrs nexus
|| abort ''
Cerulean Nexus config must be provided as an attribute set, got "${typeOf nexus}" instead!
Ensure all the `nexus` declaration is an attribute set under your call to `cerulean.mkNexus`.
Ensure the `nexus` declaration is an attribute set under your call to `cerulean.mkNexus`.
''; let
base = nt.projectOnto templateNexus nexus;
in
@ -121,11 +121,56 @@
in
final;
# XXX: TODO: create a function in NixTypes that handles this instead
findImport = path:
if pathExists path
then path
else path + ".nix";
getGroupModules = root: nodeName: node:
assert isList node.groups
|| throw ''
Cerulean Nexus node "${nodeName}" does not declare group membership as a list, got "${typeOf node.groups}" instead!
Ensure `nexus.nodes.${nodeName}.groups` is a list under your call to `cerulean.mkNexus`.
'';
# ensure root group is always added
(node.groups
++ [
{
_parent = null;
_name = ROOT_GROUP_NAME;
}
])
# ensure all members are actually groups
|> map (group: let
got =
if ! isAttrs group
then toString group
else
group
|> attrNames
|> map (name: "${name} = <${typeOf (getAttr name group)}>;")
|> concatStringsSep " "
|> (x: "{ ${x} }");
in
if group ? _name
then group
else
throw ''
Cerulean Nexus node "${nodeName}" is a member of an incorrectly structured group.
Got "${got}" of primitive type "${typeOf group}".
NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP`
'')
# add all inherited groups via _parent
|> map (let
delegate = g:
if g._parent == null
then [g]
else [g] ++ delegate (g._parent);
in
delegate)
# flatten recursion result
|> concatLists
# find import location
|> map (group: findImport (root + "/groups/${group._name}"))
# filter by uniqueness
|> nt.prim.unique
# ignore missing groups
|> filter pathExists;
in {
mkNexus = root: outputsBuilder: let
decl = parseDecl outputsBuilder;
@ -137,118 +182,47 @@ in {
customOutputs = removeAttrs decl ["nexus"];
outputs = rec {
nixosConfigurations = mapNodes nexus.nodes (
nodeName: node:
lib.nixosSystem {
nixosConfigurations = mapNodes nexus (
{
lib,
nodeName,
node,
...
}: let
nixosDecl = lib.nixosSystem {
system = node.system;
modules = let
host = findImport (root + "/hosts/${nodeName}");
# XXX: TODO: don't use a naive type for this (ie _name property)
# XXX: TODO: i really need NixTypes to be stable and use that instead
# groups =
# node.groups
# |> map (group:
# assert group ? _name
# || throw (let
# got =
# if ! isAttrs group
# then toString group
# else
# group
# |> attrNames
# |> map (name: "${name} = <${typeOf (getAttr name group)}>;")
# |> concatStringsSep " "
# |> (x: "{ ${x} }");
# in ''
# Cerulean Nexus node "${nodeName}" is a member of a nonexistent group.
# Got "${got}" of primitive type "${typeOf group}".
# NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP`
# '');
# findImport (root + "/groups/${group._name}"))
# |> nt.prim.unique; # filter by uniqueness
groups = assert isList node.groups
|| throw ''
Cerulean Nexus node "${nodeName}" does not declare group membership as a list, got "${typeOf node.groups}" instead!
Ensure `nexus.nodes.${nodeName}.groups` is a list under your call to `cerulean.mkNexus`.
'';
node.groups
# ensure all members are actually groups
|> map (group: let
got =
if ! isAttrs group
then toString group
else
group
|> attrNames
|> map (name: "${name} = <${typeOf (getAttr name group)}>;")
|> concatStringsSep " "
|> (x: "{ ${x} }");
in
if group ? _name
then group
else
throw ''
Cerulean Nexus node "${nodeName}" is a member of an incorrectly structured group.
Got "${got}" of primitive type "${typeOf group}".
NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP`
'')
# add all inherited groups via _parent
|> map (let
delegate = g: let
got =
if ! isAttrs g
then toString g
else
g
|> attrNames
|> map (name: "${name} = <${typeOf (getAttr name g)}>;")
|> concatStringsSep " "
|> (x: "{ ${x} }");
in
assert g ? _parent
|| throw ''
Cerulean Nexus node "${nodeName}" is a member of an incorrectly structured group.
Got "${got}" of primitive type "${typeOf g}".
NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP`
'';
if g._parent == null
then [g]
else [g] ++ delegate (g._parent);
in
delegate)
# flatten recursion result
|> concatLists
# find import location
|> map (group: findImport (root + "/groups/${group._name}"))
# filter by uniqueness
|> nt.prim.unique
# ignore missing groups
|> filter pathExists;
in
[../nixos-module host] ++ groups ++ node.extraModules;
# nix passes these to every single module
specialArgs = let
pkgConfig =
{
specialArgs =
nexus.specialArgs
// node.specialArgs
// {
inherit root specialArgs;
inherit (node) system;
# XXX: WARNING: TODO: i've stopped caring
# XXX: WARNING: TODO: just figure out a better solution to pkgConfig
config.allowUnfree = true;
overlays = self.overlays ++ nexus.overlays ++ node.overlays;
}
// node.extraPkgConfig;
_deploy-rs = inputs.deploy-rs;
};
in
node.specialArgs
// {
inherit root;
pkgs = import nixpkgs pkgConfig;
upkgs = import nixpkgs-unstable pkgConfig;
};
}
specialArgs;
modules =
[
self.nixosModules.default
(findImport (root + "/hosts/${nodeName}"))
inputs.home-manager.nixosModules.default
# inputs.microvm.nixosModules.microvm
]
++ (getGroupModules root nodeName node)
++ node.extraModules
++ nexus.extraModules;
};
in
nixosDecl
);
deploy.nodes = mapNodes nexus.nodes (nodeName: node: let
deploy.nodes = mapNodes nexus ({
nodeName,
node,
...
}: let
inherit
(node.deploy)
activationTimeout
@ -262,7 +236,7 @@ in {
user
;
nixosFor = system: deploy-rs.lib.${system}.activate.nixos;
nixosFor = system: inputs.deploy-rs.lib.${system}.activate.nixos;
in {
hostname = ssh.host;
@ -298,7 +272,7 @@ in {
};
});
checks = mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib;
checks = mapAttrs (system: deployLib: deployLib.deployChecks deploy) inputs.deploy-rs.lib;
};
in
outputs // customOutputs;

View file

@ -25,14 +25,20 @@ in rec {
(nt.naive.terminal)
Terminal
;
missing = msg: path:
Terminal (abort ''
Each Cerulean Nexus node is required to specify ${msg}!
Ensure `nexus.${path}` exists under your call to `cerulean.mkNexus`.
'');
in {
system = "x86_64-linux"; # sane default (i hope...)
enabled = true;
system = missing "its system architecture" "system";
groups = [];
extraModules = [];
specialArgs = Terminal {};
overlays = [];
# XXX: WARNING: extraPkgConfig is a terrible solution (but im lazy for now)
extraPkgConfig = Terminal {};
base = null;
deploy = {
user = "root";
@ -69,7 +75,25 @@ in rec {
in
nt.projectOnto templateAttrs nodeAttrs;
mapNodes = nodes: f:
nodes
|> mapAttrs (nodeName: nodeAttrs: f nodeName (parseNode nodeName nodeAttrs));
mapNodes = nexus: f:
nexus.nodes
|> mapAttrs (nodeName: nodeAttrs: let
node = parseNode nodeName nodeAttrs;
# use per-node base or default to nexus base
base =
if node.base != null
then node.base
else if nexus.base != null
then nexus.base
else
abort ''
Cerulean cannot construct nexus node "${nodeName}" without a base package source.
Ensure `nexus.nodes.*.base` or `nexus.base` is a flake reference to the github:NixOS/nixpkgs repository.
'';
in
f {
inherit nodeName node;
lib = base.lib;
});
}

108
cerulean/nexus/snow.nix Normal file
View file

@ -0,0 +1,108 @@
# Copyright 2026 Emile Clark-Boman
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
{
inputs,
lib,
...
}: {
# nexus
options = let
inherit
(lib)
mkOption
types
;
in {
extraModules = mkOption {
type = types.listOf types.path;
};
specialArgs = mkOption {
type = types.attrs;
};
groups = mkOption {
type = types.attrs;
};
nodes = mkOption {
type = types.attrsOf (types.submoduleWith ({...}: {
options = {
enabled = mkOption {
type = types.bool;
default = true;
};
system = mkOption {
type = types.enum inputs.systems;
};
groups = mkOption {
type = types.list;
};
modules = mkOption {
type = types.list;
};
args = mkOption {
type = types.attrs;
};
deploy = {
user = mkOption {
type = types.str;
};
sudoCmd = mkOption {
type = types.str;
};
interactiveSudo = mkOption {
type = types.bool;
};
remoteBuild = mkOption {
type = types.bool;
};
autoRollback = mkOption {
type = types.bool;
};
magicRollback = mkOption {
type = types.bool;
};
activationTimeout = mkOption {
type = types.int;
};
confirmTimeout = mkOption {
type = types.int;
};
ssh = {
host = mkOption {
type = types.str;
};
user = mkOption {
type = types.str;
};
port = mkOption {
type = types.int;
};
opts = mkOption {
type = types.listOf types.str;
};
};
};
};
}));
};
};
config = {
};
}

View file

@ -11,10 +11,22 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
{lib, ...}: {
cerulean.nexus.node = {
group = lib.mkOption {
type = lib.types.enum;
};
};
{
root,
system,
_deploy-rs,
...
} @ args: {
imports = [
# user configuration
(import (root + "/nixpkgs.nix"))
# options declarations
(import ./nixpkgs.nix (args // {contextName = "hosts";}))
./home-manager.nix
];
environment.systemPackages = [
_deploy-rs.packages.${system}.default
];
}

View file

@ -0,0 +1,50 @@
# Copyright 2026 Emile Clark-Boman
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
{
root,
system,
config,
lib,
specialArgs,
...
} @ args: let
inherit
(builtins)
attrNames
filter
pathExists
;
in {
config = {
home-manager = {
users =
config.users.users
|> attrNames
|> filter (x: pathExists (root + "/homes/${x}"))
|> (x: lib.genAttrs x (y: import (root + "/homes/${y}")));
extraSpecialArgs = {inherit root system;} // (specialArgs.inputs or {});
sharedModules = [
# user configuration
(import (root + "/nixpkgs.nix"))
# options declarations
(import ./nixpkgs.nix (args // {contextName = "homes";}))
];
# disable home-manager trying anything fancy
# we control the pkgs now!!
# useGlobalPkgs = true;
};
};
}

View file

@ -11,4 +11,3 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
{...}: {}

View file

@ -11,8 +11,3 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
{...} @ inputs: {
options = import ./options.nix inputs;
config = import ./config.nix inputs;
}

101
cerulean/nixos/nixpkgs.nix Normal file
View file

@ -0,0 +1,101 @@
# Copyright 2026 Emile Clark-Boman
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
{
lib,
system,
config,
contextName,
...
}: let
inherit
(builtins)
mapAttrs
;
cfg = config.nixpkgs.channels;
in {
options.nixpkgs.channels = lib.mkOption {
type = lib.types.attrsOf (lib.types.attrs);
default = {};
description = "Declare package repositories per module context (nixos, home-manager, etc)";
example = {
"homes" = {
"pkgs" = {
source = "inputs.nixpkgs";
system = "x86-64-linux";
config = {
allowUnfree = true;
allowBroken = false;
};
};
"upkgs" = {
source = "inputs.nixpkgs-unstable";
system = "x86-64-linux";
config = {
allowUnfree = true;
allowBroken = false;
};
};
};
};
};
config = let
# TODO: use lib.types.submodule to restrict what options
# TODO: can be given to `nixpkgs.channels.${moduleName}.${name}`
decl =
cfg.${contextName} or cfg.default;
repos =
decl
|> mapAttrs (
name: args:
lib.mkForce (
assert args ? source
|| abort ''
${toString ./.}
`nixpkgs.channels.${contextName}.${name}` missing required attribute "source"
'';
((removeAttrs args ["source"])
// {inherit system;})
|> import args.source
)
);
in {
# NOTE: _module.args is a special option that allows us to
# NOTE: set extend specialArgs from inside the modules.
_module.args = repos;
nixpkgs = let
defaultPkgs =
decl.default or (throw ''
Your `nixpkgs.nix` file does not declare a default package source.
Ensure you set `nixpkgs.channels.*.default = ...;`
'');
in
if contextName == "hosts"
then {
flake.source = lib.mkOverride 200 defaultPkgs.source;
config = lib.mkOverride 200 defaultPkgs.config;
}
else if contextName == "homes"
then {
# XXX: XXX: XXX: OH OH OH OMG, its because aurora never defines pkgs
config = lib.mkOverride 200 (defaultPkgs.config or {});
# XXX: WARNING: TODO: modify options so overlays must always be given as the correct type
overlays = lib.mkOverride 200 (defaultPkgs.overlays or []);
}
else {};
};
}

146
flake.lock generated
View file

@ -36,43 +36,71 @@
"type": "github"
}
},
"mix": {
"flake-parts": {
"inputs": {
"nib": [
"nib"
"nixpkgs-lib": [
"nt",
"nix-unit",
"nixpkgs"
]
},
"locked": {
"lastModified": 1768525804,
"narHash": "sha256-jlpNb7Utqfdq2HESAB1mtddWHOsxKlTjPiLFRLd35r8=",
"owner": "emilelcb",
"repo": "mix",
"rev": "617d8915a6518a3d4e375b87c50ae34d9daee6c6",
"lastModified": 1762440070,
"narHash": "sha256-xxdepIcb39UJ94+YydGP221rjnpkDZUlykKuF54PsqI=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "26d05891e14c88eb4a5d5bee659c0db5afb609d8",
"type": "github"
},
"original": {
"owner": "emilelcb",
"repo": "mix",
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"nib": {
"nix-github-actions": {
"inputs": {
"systems": [
"systems"
"nixpkgs": [
"nt",
"nix-unit",
"nixpkgs"
]
},
"locked": {
"lastModified": 1768472076,
"narHash": "sha256-bdVRCDy6oJx/CZiyxkke783FgtBW//wDuOAITUsQcNc=",
"owner": "emilelcb",
"repo": "nib",
"rev": "42ac66dfc180a13af1cc8850397db66ec5556991",
"lastModified": 1737420293,
"narHash": "sha256-F1G5ifvqTpJq7fdkT34e/Jy9VCyzd5XfJ9TO8fHhJWE=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "f4158fa080ef4503c8f4c820967d946c2af31ec9",
"type": "github"
},
"original": {
"owner": "emilelcb",
"repo": "nib",
"owner": "nix-community",
"repo": "nix-github-actions",
"type": "github"
}
},
"nix-unit": {
"inputs": {
"flake-parts": "flake-parts",
"nix-github-actions": "nix-github-actions",
"nixpkgs": [
"nt",
"nixpkgs"
],
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1762774186,
"narHash": "sha256-hRADkHjNt41+JUHw2EiSkMaL4owL83g5ZppjYUdF/Dc=",
"owner": "nix-community",
"repo": "nix-unit",
"rev": "1c9ab50554eed0b768f9e5b6f646d63c9673f0f7",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-unit",
"type": "github"
}
},
@ -124,14 +152,49 @@
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1767313136,
"narHash": "sha256-16KkgfdYqjaeRGBaYsNrhPRRENs0qzkQVUooNHtoy2w=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "ac62194c3917d5f474c1a844b6fd6da2db95077d",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nt": {
"inputs": {
"nix-unit": "nix-unit",
"nixpkgs": "nixpkgs_3",
"systems": "systems_2"
},
"locked": {
"lastModified": 1770975056,
"narHash": "sha256-ZXTz/P3zUbbM6lNXzt91u8EwfNqhXpYMu8+wvFZqQHE=",
"owner": "cry128",
"repo": "nt",
"rev": "f42dcdd49a7921a7f433512e83d5f93696632412",
"type": "github"
},
"original": {
"owner": "cry128",
"repo": "nt",
"type": "github"
}
},
"root": {
"inputs": {
"deploy-rs": "deploy-rs",
"mix": "mix",
"nib": "nib",
"nixpkgs": "nixpkgs_2",
"nixpkgs-unstable": "nixpkgs-unstable",
"systems": "systems_2"
"nt": "nt",
"systems": "systems_3"
}
},
"systems": {
@ -164,6 +227,43 @@
"type": "github"
}
},
"systems_3": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"nt",
"nix-unit",
"nixpkgs"
]
},
"locked": {
"lastModified": 1762410071,
"narHash": "sha256-aF5fvoZeoXNPxT0bejFUBXeUjXfHLSL7g+mjR/p5TEg=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "97a30861b13c3731a84e09405414398fbf3e109f",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems"

View file

@ -17,23 +17,36 @@
inputs = {
systems.url = "github:nix-systems/default";
# WARNING: nixpkgs is ONLY included so flakes using Cerulean can
# WARNING: force Cerulean's inputs to follow a specific revision.
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
nt.url = "github:emilelcb/nt";
nt.url = "github:cry128/nt";
deploy-rs.url = "github:serokell/deploy-rs";
home-manager = {
url = "github:nix-community/home-manager/release-25.11";
inputs.nixpkgs.follows = "nixpkgs";
};
deploy-rs = {
url = "github:serokell/deploy-rs";
inputs.nixpkgs.follows = "nixpkgs";
};
microvm = {
url = "github:microvm-nix/microvm.nix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = {
nixpkgs,
self,
nt,
...
} @ inputs:
import ./cerulean
(inputs
// {
inherit (nixpkgs) lib;
inherit (nt) mix;
});
{
inherit inputs self nt;
inherit (nt) mix;
};
}

View file

@ -1,149 +0,0 @@
# Copyright 2025 Emile Clark-Boman
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
{
lib,
config,
pkgs,
pkgs-unstable,
...
} @ args: let
getModule = name: "../modules/homemanager/${name}.nix";
getModules = map (x: getModule x);
in {
imports = getModules [
"term/foot"
"editor/vscode"
"wm/hyprland"
"wm/hyprland/hyprlock"
"dm/sddm"
"dm/sddm/themes/corners"
"apps/firefox"
"apps/thunderbird"
"apps/obsidian"
"apps/rider"
"apps/winbox"
"apps/gitkraken"
"apps/thunar"
"wm/kanshi"
"wm/mako"
];
home = {
pointerCursor = {
gtk.enable = true;
# x11.enable = true # dont enable since im on hyprland
package = pkgs.bibata-cursors;
name = "Bibata-Modern-Ice";
size = 16;
};
packages = with pkgs; [
# for services.gnome-keyring
(
if config.cerulean.isGraphical
then seahorse # gui
else null
)
fuzzel
];
};
gtk = {
enable = true;
font.name = "Victor Mono SemiBold 12";
theme = {
name = "Dracula";
package = pkgs.dracula-theme;
};
iconTheme = {
name = "kora";
package = pkgs.kora-icon-theme;
};
# TODO: use a variable to mirror this cursor size
# with the `home.pointerCurser.size`
cursorTheme = {
package = pkgs.bibata-cursors;
name = "Bibata-Modern-Ice";
size = 16;
};
};
qt = {
enable = true;
platformTheme.name = "gtk2";
style.name = "gtk2";
};
services = {
# Set display manager (login screen)
displayManager = {
# sddm relies on pkgs.libsForQt5.qt5.qtgraphicaleffects
sddm = {
enable = true;
wayland.enable = true; # experimental
theme = "corners";
};
defaultSession =
"hyprland"
+ (
if config.programs.hyprland.withUWSM
then "-uwsm"
else null
);
};
# Multimedia Framework
# With backwards compatability for alsa/pulseaudio/jack
pipewire = {
enable = true;
wireplumber.enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
jack.enable = true;
};
};
# ---- ENVIRONMENT ----
environment = {
sessionVariables = {
# Hint Electron apps to use support Wayland
NIXOS_OZONE_WL = "1";
};
};
# ---- SYSTEM PACKAGES ----
environment.systemPackages = with pkgs; [
# User Environment
swww
helvum
easyeffects
pavucontrol
hyprpicker # colour picking utility
hyprshot # screenshot utility
qbittorrent
signal-desktop # MAKE THIS ONLY FOR THE DESKTOP FOR END USERS, NOT SERVERS
kdePackages.gwenview # image viewer
libreoffice
wl-clipboard # clipboard for wayland
];
security.rtkit.enable = true; # I *think* this is for pipewire
}

View file

@ -1,330 +0,0 @@
# Copyright 2025 Emile Clark-Boman
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
{
inputs,
lib,
config,
pkgs,
pkgs-unstable,
homemanager,
cerulean,
...
} @ args: let
getModule = name: "../modules/nixos/${name}.nix";
getModules = map (x: getModule x);
getHostModule = name: "TODO";
in {
imports = getModules [
(getHostModule "hardware-configuration")
(import "${homemanager}/nixos")
"shell/bash"
"shell/bash/bashistrans.nix"
"shell/zsh"
"shell/fish"
"cli/git"
"cli/bat"
"cli/btop"
"cli/tmux"
"cli/nvim"
"lang/asm"
"lang/bash" # TODO: (YES THIS IS DIFFERENT TO shell/bash, this provides language support ie pkgs.shellcheck)
"lang/c-family"
"lang/dotnet"
# "lang/go"
# "lang/haskell"
# "lang/java"
# "lang/nim"
"lang/python"
# "lang/rust"
# "lang/sage"
"editor/helix"
];
nix.settings = {
# REF: https://nix.dev/manual/nix/2.24/development/experimental-features
experimental-features = [
# Significant
"flakes"
"nix-command"
"pipe-operators"
# Minor
"no-url-literals"
"parse-toml-timestamps"
"recursive-nix"
];
download-buffer-size = 524288000; # 500 MiB
# making wheel group members "trusted users" allows
# them to import packages not signed by a trusted key
# (aka super duper easier to remote deploy)
trusted-users = ["root" "@wheel"];
};
nixpkgs = {
overlays = cerulean.lib.importOverlaysNixOS;
config = if config.cerulean.allowUnfreeWhitelist != []
then {
allowUnfreePredicate =
pkg: builtins.elem
(lib.getName pkg)
config.cerulean.allowUnfreeWhitelist;
}
else {
allowUnfree = config.cerulean.allowUnfree;
};
};
# colmena deployment configuration
deployment = {
targetHost = config.cerulean.domain ?? config.cerulean.ip;
targetUser = "cerulean";
targetPort = "22";
sshOptions = [
"-A" # forward ssh-agent
];
buildOnTarget = false; # build locally then deploy
};
time.timeZone = config.cerulean.timeZone;
i18n.defaultLocale = "en_US.UTF-8";
# Enable initrd hook for virtual console customisation
# aka cool colours when booting yay!!
console = {
enable = true;
earlySetup = true; # initrd pre hook
keyMap = "us";
font = "Lat2-Terminus16";
# ANSI 24-bit color definitions (theme: dracula)
colors = [
"21222c"
"ff5555"
"50fa7b"
"f1fa8c"
"bd93f9"
"ff79c6"
"8be9fd"
"f8f8f2"
"6272a4"
"ff6e6e"
"69ff94"
"ffffa5"
"d6acff"
"ff92df"
"a4ffff"
"ffffff"
];
};
# super duper minimum grub2 config
boot.loader = {
efi = {
canTouchEfiVariables = true;
efiSysMountPoint = "/boot/efi";
};
grub = {
enable = true;
device = "nodev";
};
# GitHub: vinceliuice/grub2-themes
grub2-theme = {
enable = true;
theme = "whitesur"; # stylish, vimix, or whitesur
footer = true;
# TODO: switch my cables to switch default grub display
customResolution = "3840x2160";
};
};
networking = {
hostName = config.cerulean.hostname;
networkmanager.enable = true;
firewall = {
enable = true;
allowedTCPPorts = [
22 # sshd
80 # nginx (http)
443 # nginx (https)
# 5678 # MikroTik WinBox
];
};
};
# ------- USERS -------
security.sudo.wheelNeedsPassword = true;
users = {
defaultUserShell = pkgs.bash;
users = cerulean.lib.importUsersNixOS;
};
home-manager = {
users = cerulean.lib.importUsersHomeManager;
extraSpecialArgs = { inherit inputs pkgs pkgs-unstable; };
sharedModules = [];
};
# ---- ENVIRONMENT ----
environment = {
# always install "dev"/"man" derivation outputs
extraOutputsToInstall = ["dev" "man"];
systemPackages = with pkgs; [
# User Environment
bluetui
# Shell
bash
fish
shellcheck
grc # colorise command outputs
moreutils
# Systems Programming & Compilation
qemu # Fellice Bellard's Quick Emulator
# GNU Utils
gnumake
# Binaries
binutils
strace
ltrace
perf-tools # ftrace + perf
radare2
gdb
# ASM
nasm
(callPackage ../packages/x86-manpages {})
# C Family
gcc
clang
clang-tools
# Rust
cargo
rustc
# Go
go
# Nim
nim
nimble
# Haskell
ghc
ghcid
haskell-language-server
ormolu
# Python
python312 # I use 3.12 since it's in a pretty stable state now
python314 # also 3.14 for latest features
poetry
openvpn
inetutils
# security tools
nmap
httpie
curlie
zoxide
doggo
tldr
btop
eza
yazi
lazygit
ripgrep
viddy # modern `watch` command
thefuck
# TODO: once upgraded past Nix-24.07 this line won't be necessary (I think)
# helix will support nixd by default
# SOURCE: https://github.com/nix-community/nixd/blob/main/nixd/docs/editor-setup.md#Helix
# nixd # lsp for nix # DEBUG
# Pretty necessary
nix-prefetch-git
brightnessctl
acpi
powertop
imagemagick
# "Standard" Unix Commands
vim
file
wget
tree
pstree
unzip
unrar-free
lz4
man-pages
man-pages-posix
# Cryptography
gnupg
openssl
libargon2
];
};
programs = {
nix-ld.enable = true;
};
documentation = {
enable = true;
doc.enable = true; # install /share/doc packages
man.enable = true; # install manpages
info.enable = true; # install GNU info
dev.enable = true; # install docs intended for developers
nixos = {
enable = true; # install NixOS documentation (ie man -k nix, & nixos-help)
options.splitBuild = true;
# includeAllModules = true;
};
};
virtualisation.docker.enable = true;
hardware = {
graphics = {
enable = true;
enable32Bit = true;
};
bluetooth = let
btSupported = config.cerulean.bluetoothSupported;
in {
enable = btSupported;
powerOnBoot = btSupported;
};
};
system.stateVersion = config.cerulean.stateVersion; # DO NOT MODIFY
}

View file

@ -1,102 +0,0 @@
# Copyright 2025 Emile Clark-Boman
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
{
lib,
config,
pkgs,
pkgs-unstable,
...
} @ args: let
getModule = name: "../modules/homemanager/${name}.nix";
getModules = map (x: getModule x);
in {
imports = getModules [
"shell/fish"
"cli/git"
"cli/bat"
"cli/btop"
"cli/tmux"
"editor/helix"
];
nixpkgs.config.allowUnfreePredicate = pkg:
builtins.elem (lib.GetName pkg) [
"vscode-extension-ms-dotnettools-csharp"
];
home = {
stateVersion = config.cerulean.stateVersion; # DO NOT MODIFY
username = config.cerulean.username;
homeDirectory = "/home/${config.cerulean.username}";
shellAliases = {
rg = "batgrep"; # bat + ripgrep
man = "batman"; # bat + man
};
sessionVariables = {
NIX_SHELL_PRESERVE_PROMPT = 1;
};
packages = with pkgs; [
# for services.gnome-keyring
gcr # provides org.gnome.keyring.SystemPrompter
speedtest-cli
];
};
programs = {
home-manager.enable = true;
zsh = {
enable = true;
enableCompletion = true;
autosuggestion.enable = true;
syntaxHighlighting.enable = true;
history = {
size = 10000;
ignoreAllDups = true;
path = "$HOME/.zsh_history";
ignorePatterns = [
"rm *"
];
};
};
# set ssh profiles
# NOTE: (IMPORTANT) this DOES NOT start the ssh-agent
# for that you need to use `services.ssh-agent.enable`
ssh = {
enable = true;
forwardAgent = false;
addKeysToAgent = "no";
};
};
services = {
# enable OpenSSH private key agent
ssh-agent.enable = true;
gnome-keyring.enable = true;
};
# the ssh-agent won't set this for itself...
systemd.user.sessionVariables.SSH_AUTH_SOCK = "$XDG_RUNTIME_DIR/ssh-agent";
# Nicely reload system units when changing configs
systemd.user.startServices = "sd-switch";
}