Compare commits

..

8 Commits

Author SHA1 Message Date
eric
99658b27dc feat: rework to modular 2026-04-10 17:25:08 +02:00
eric
28dad81816 fix: remove py 2026-04-02 23:03:19 +02:00
eric
1de34c1869 feat: move things around 2026-03-18 17:41:10 +01:00
eric
f558ab4ba9 feat: add spinner 2026-03-18 13:28:51 +01:00
eric
f150afec0a fix: probe correct value 2026-03-18 03:21:10 +01:00
eric
9e0eb5b583 fix: correct field from bao 2026-03-18 03:15:11 +01:00
eric
005cd7b60e docs: remove instruction plan 2026-03-18 02:45:19 +01:00
eric
19f9b0594a feat: add nix server provision 2026-03-18 02:44:54 +01:00
39 changed files with 786 additions and 51 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
bootstrap/

132
README.md Normal file
View File

@@ -0,0 +1,132 @@
# nix-nodeiwest
Composable company Nix for NodeiWest workstations and project shells.
This repo is now structured as a shared SDK:
- `modules/` holds focused Home Manager building blocks
- `profiles/` bundles those modules into opinionated employee entrypoints
- `shells/` exposes reusable flake dev shells for project repos
- `systems/` adapts the shared modules into Darwin or standalone Linux Home Manager configs
- `lib/` holds the small helpers that keep composition consistent
- `templates/` bootstraps downstream user flakes
It does not define users or machines directly. Downstream flakes decide who uses which profile.
## Layout
```text
.
├── flake.nix
├── lib/
├── modules/
│ ├── base/
│ ├── dev/
│ ├── optional/
│ ├── roles/
│ ├── secrets/
│ └── services/
├── profiles/
├── shells/
├── systems/
└── templates/
```
## Flake Interface
Primary outputs:
- `homeManagerModules.base.*`: low-level base modules
- `homeManagerModules.dev.*`: language and workflow modules
- `homeManagerModules.roles.*`: reusable role bundles
- `homeManagerModules.profiles.*`: ready-made employee profiles
- `homeManagerModules.default`: compatibility shim for the old default home module
- `lib.mkSystem`: chooses the Darwin or Linux adapter for a downstream flake
- `lib.shells.*`: shell factories for repo-local dev environments
- `devShells.<system>.*`: ready-to-use company shells
- `templates.user-flake`: starter personal flake
## Workstation Consumption
Downstream user flakes own the actual machine definitions. They consume profiles from this repo:
```nix
{
inputs.company.url = "git+ssh://git@git.dgren.dev/employees/company-nix.git";
outputs = { company, ... }: {
darwinConfigurations.eric = company.lib.mkSystem {
target = "darwin";
system = "aarch64-darwin";
username = "eric";
homeDirectory = "/Users/eric";
modules = [
company.homeManagerModules.profiles.frontend
];
};
};
}
```
For Linux Home Manager:
```nix
{
inputs.company.url = "git+ssh://git@git.dgren.dev/employees/company-nix.git";
outputs = { company, ... }: {
homeConfigurations."eric@work" = company.lib.mkSystem {
target = "linux";
system = "x86_64-linux";
username = "eric";
homeDirectory = "/home/eric";
modules = [
company.homeManagerModules.profiles.backend
];
};
};
}
```
## Project Shell Consumption
Project repos should keep their own flake and compose shells from this repo instead of outsourcing project ownership here.
Use the ready-made shell directly:
```nix
{
inputs.company.url = "git+ssh://git@git.dgren.dev/employees/company-nix.git";
outputs = { nixpkgs, company, ... }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
in
{
devShells.${system}.default = company.lib.shells.node {
inherit pkgs;
extraPackages = [ pkgs.ffmpeg ];
};
};
}
```
Or extend the published company shell in place:
```nix
devShells.${system}.default = pkgs.mkShell {
inputsFrom = [ company.devShells.${system}.node ];
packages = [ pkgs.ffmpeg ];
};
```
## Template
Bootstrap a personal flake with:
```bash
nix flake init -t .#user-flake
```
That template is intentionally small. Add machine-specific modules in the personal repo, not here.

64
flake.lock generated
View File

@@ -1,30 +1,52 @@
{
"nodes": {
"flake-parts": {
"home-manager": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1772408722,
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
"lastModified": 1775781825,
"narHash": "sha256-L5yKTpR+alrZU2XYYvIxCeCP4LBHU5jhwSj7H1VAavg=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "e35c39fca04fee829cecdf839a50eb9b54d8a701",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"owner": "nix-community",
"repo": "home-manager",
"type": "github"
}
},
"nix-darwin": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1775037210,
"narHash": "sha256-KM2WYj6EA7M/FVZVCl3rqWY+TFV5QzSyyGE2gQxeODU=",
"owner": "LnL7",
"repo": "nix-darwin",
"rev": "06648f4902343228ce2de79f291dd5a58ee12146",
"type": "github"
},
"original": {
"owner": "LnL7",
"repo": "nix-darwin",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1773628058,
"narHash": "sha256-hpXH0z3K9xv0fHaje136KY872VT2T5uwxtezlAskQgY=",
"lastModified": 1775763530,
"narHash": "sha256-BuTK9z1QEwWPOIakQ1gCN4pa4VwVJpfptYCviy2uOGc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "f8573b9c935cfaa162dd62cc9e75ae2db86f85df",
"rev": "b0188973b4b2a5b6bdba8b65381d6cd09a533da0",
"type": "github"
},
"original": {
@@ -34,24 +56,10 @@
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1772328832,
"narHash": "sha256-e+/T/pmEkLP6BHhYjx6GmwP5ivonQQn0bJdH9YrRB+Q=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "c185c7a5e5dd8f9add5b2f8ebeff00888b070742",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"root": {
"inputs": {
"flake-parts": "flake-parts",
"home-manager": "home-manager",
"nix-darwin": "nix-darwin",
"nixpkgs": "nixpkgs"
}
}

134
flake.nix
View File

@@ -1,22 +1,134 @@
{
description = "NodeiWest dev environment module";
description = "NodeiWest company Nix SDK";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
nix-darwin = {
url = "github:LnL7/nix-darwin";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
inputs:
inputs.flake-parts.lib.mkFlake { inherit inputs; } {
systems = [
"aarch64-darwin"
"x86_64-darwin"
"x86_64-linux"
];
inputs@{
self,
nixpkgs,
...
}:
let
lib = nixpkgs.lib;
defaults = import ./lib/defaults.nix { inherit lib; };
inherit (defaults) forAllSystems supportedSystems;
flake = {
homeManagerModules.default = ./modules/home.nix;
shellFactories = {
default = args: import ./shells/default.nix args;
node = args: import ./shells/node.nix (args // { inherit lib; });
go = args: import ./shells/go.nix args;
rust = args: import ./shells/rust.nix (args // { inherit lib; });
};
profileModules = {
backend = import ./profiles/backend.nix;
frontend = import ./profiles/frontend.nix;
minimal = import ./profiles/minimal.nix;
};
darwinSystem = args: import ./systems/darwin/default.nix ({ inherit inputs; } // args);
linuxHome = args: import ./systems/linux/default.nix ({ inherit inputs; } // args);
in
{
lib = {
inherit (defaults) companySessionVariables forAllSystems supportedSystems;
mkProfile = import ./lib/mkProfile.nix;
mkSystem = import ./lib/mkSystem.nix {
systems = {
darwin = darwinSystem;
linux = linuxHome;
};
};
shells = shellFactories;
systems = {
darwin = darwinSystem;
linux = linuxHome;
};
};
homeManagerModules = {
base = {
nix = import ./modules/base/nix.nix;
core = import ./modules/base/core.nix;
shell = import ./modules/base/shell.nix;
fonts = import ./modules/base/fonts.nix;
};
dev = {
node = import ./modules/dev/node.nix;
go = import ./modules/dev/go.nix;
rust = import ./modules/dev/rust.nix;
docker = import ./modules/dev/docker.nix;
};
roles = {
backend = import ./modules/roles/backend.nix;
frontend = import ./modules/roles/frontend.nix;
infra = import ./modules/roles/infra.nix;
minimal = import ./modules/roles/minimal.nix;
};
services = {
tailscale = import ./modules/services/tailscale.nix;
ssh = import ./modules/services/ssh.nix;
gpg = import ./modules/services/gpg.nix;
};
secrets = {
env = import ./modules/secrets/env.nix;
openbao = import ./modules/secrets/openbao.nix;
};
optional = {
homebrew = import ./modules/optional/homebrew.nix;
gui = import ./modules/optional/gui.nix;
devtools = import ./modules/optional/devtools.nix;
};
profiles = profileModules;
default = import ./modules/home.nix;
};
devShells = forAllSystems (
system:
let
pkgs = import nixpkgs { inherit system; };
in
{
default = shellFactories.default { inherit pkgs; };
node = shellFactories.node { inherit pkgs; };
go = shellFactories.go { inherit pkgs; };
rust = shellFactories.rust { inherit pkgs; };
}
);
formatter = forAllSystems (
system:
let
pkgs = import nixpkgs { inherit system; };
in
pkgs.nixfmt
);
templates = {
user-flake = {
path = ./templates/user-flake;
description = "Starter personal flake wired to NodeiWest profiles.";
};
};
};
}

20
lib/defaults.nix Normal file
View File

@@ -0,0 +1,20 @@
{ lib }:
let
supportedSystems = [
"aarch64-darwin"
"x86_64-darwin"
"x86_64-linux"
];
in
{
inherit supportedSystems;
forAllSystems = f: lib.genAttrs supportedSystems f;
companySessionVariables = {
BAO_ADDR = "https://secrets.api.nodeiwest.se";
SOME_REGISTRY = "git.dgren.dev";
};
stateVersion = "24.11";
}

7
lib/mkProfile.nix Normal file
View File

@@ -0,0 +1,7 @@
{
modules ? [ ],
extraModules ? [ ],
}:
{
imports = modules ++ extraModules;
}

15
lib/mkSystem.nix Normal file
View File

@@ -0,0 +1,15 @@
{ systems }:
{
target,
...
}@args:
let
adapter =
if target == "darwin" then
systems.darwin
else if target == "linux" then
systems.linux
else
throw "Unsupported target `${target}`. Expected `darwin` or `linux`.";
in
adapter (builtins.removeAttrs args [ "target" ])

19
modules/base/core.nix Normal file
View File

@@ -0,0 +1,19 @@
{ lib, pkgs, ... }:
{
home.packages = with pkgs; [
age
curl
fd
git
jq
just
ripgrep
sops
];
programs.git = {
enable = true;
lfs.enable = true;
signing.format = lib.mkDefault "openpgp";
};
}

11
modules/base/fonts.nix Normal file
View File

@@ -0,0 +1,11 @@
{ lib, pkgs, ... }:
let
jetbrainsMono = lib.attrByPath [ "nerd-fonts" "jetbrains-mono" ] null pkgs;
in
{
fonts.fontconfig.enable = pkgs.stdenv.isLinux;
home.packages = builtins.filter (pkg: pkg != null) [
jetbrainsMono
];
}

14
modules/base/nix.nix Normal file
View File

@@ -0,0 +1,14 @@
{ lib, pkgs, ... }:
{
programs.home-manager.enable = true;
nix.package = lib.mkDefault pkgs.nix;
nix.settings = {
experimental-features = [
"nix-command"
"flakes"
];
warn-dirty = false;
};
}

20
modules/base/shell.nix Normal file
View File

@@ -0,0 +1,20 @@
{ ... }:
{
programs.bash.enable = true;
programs.zsh = {
enable = true;
autocd = true;
enableCompletion = true;
shellAliases = {
l = "ls -CF";
la = "ls -A";
ll = "ls -alF";
};
};
programs.direnv = {
enable = true;
nix-direnv.enable = true;
};
}

16
modules/dev/docker.nix Normal file
View File

@@ -0,0 +1,16 @@
{ lib, pkgs, ... }:
let
optionalPackage = path: lib.attrByPath path null pkgs;
in
{
home.packages = builtins.filter (pkg: pkg != null) (
[
(optionalPackage [ "docker-client" ])
(optionalPackage [ "docker-compose" ])
(optionalPackage [ "lazydocker" ])
]
++ lib.optionals pkgs.stdenv.isDarwin [
(optionalPackage [ "colima" ])
]
);
}

7
modules/dev/go.nix Normal file
View File

@@ -0,0 +1,7 @@
{ pkgs, ... }:
{
home.packages = with pkgs; [
go
gopls
];
}

10
modules/dev/node.nix Normal file
View File

@@ -0,0 +1,10 @@
{ lib, pkgs, ... }:
let
nodejs = lib.attrByPath [ "nodejs_20" ] pkgs.nodejs pkgs;
in
{
home.packages = [
nodejs
pkgs.pnpm
];
}

13
modules/dev/rust.nix Normal file
View File

@@ -0,0 +1,13 @@
{ lib, pkgs, ... }:
let
optionalPackage = path: lib.attrByPath path null pkgs;
in
{
home.packages = builtins.filter (pkg: pkg != null) [
pkgs.cargo
pkgs.rustc
(optionalPackage [ "rust-analyzer" ])
(optionalPackage [ "rustfmt" ])
(optionalPackage [ "clippy" ])
];
}

View File

@@ -1,15 +1,9 @@
{ pkgs, lib, ... }:
{ pkgs, ... }:
{
# Company env vars — available in all shells
home.sessionVariables = {
BAO_ADDR = "https://secrets.api.nodeiwest.se";
SOME_REGISTRY = "git.dgren.dev";
# etc.
};
home.packages = with pkgs; [
# Tools every dev needs
openbao
# etc.
imports = [
../profiles/minimal.nix
./secrets/openbao.nix
];
home.packages = [ pkgs.colmena ];
}

View File

@@ -0,0 +1,11 @@
{ lib, pkgs, ... }:
let
optionalPackage = path: lib.attrByPath path null pkgs;
in
{
home.packages = builtins.filter (pkg: pkg != null) [
(optionalPackage [ "nil" ])
(optionalPackage [ "nixd" ])
(optionalPackage [ "nixfmt" ])
];
}

9
modules/optional/gui.nix Normal file
View File

@@ -0,0 +1,9 @@
{ lib, pkgs, ... }:
let
wezterm = lib.attrByPath [ "wezterm" ] null pkgs;
in
{
home.packages = builtins.filter (pkg: pkg != null) [
wezterm
];
}

View File

@@ -0,0 +1,6 @@
{ ... }:
{
# Intentionally empty. This is the seam downstream workstations can use to
# compose nix-homebrew or Homebrew-specific activation without coupling it
# into the shared base roles.
}

13
modules/roles/backend.nix Normal file
View File

@@ -0,0 +1,13 @@
{ ... }:
{
imports = [
./minimal.nix
../dev/node.nix
../dev/go.nix
../dev/docker.nix
../services/ssh.nix
../services/gpg.nix
../secrets/openbao.nix
../optional/devtools.nix
];
}

View File

@@ -0,0 +1,11 @@
{ ... }:
{
imports = [
./minimal.nix
../base/fonts.nix
../dev/node.nix
../services/gpg.nix
../optional/devtools.nix
../optional/gui.nix
];
}

15
modules/roles/infra.nix Normal file
View File

@@ -0,0 +1,15 @@
{ pkgs, ... }:
{
imports = [
./minimal.nix
../dev/go.nix
../dev/docker.nix
../services/ssh.nix
../services/tailscale.nix
../services/gpg.nix
../secrets/openbao.nix
../optional/devtools.nix
];
home.packages = [ pkgs.colmena ];
}

View File

@@ -0,0 +1,9 @@
{ ... }:
{
imports = [
../base/nix.nix
../base/core.nix
../base/shell.nix
../secrets/env.nix
];
}

7
modules/secrets/env.nix Normal file
View File

@@ -0,0 +1,7 @@
{ lib, ... }:
let
defaults = import ../../lib/defaults.nix { inherit lib; };
in
{
home.sessionVariables = defaults.companySessionVariables;
}

View File

@@ -0,0 +1,6 @@
{ pkgs, ... }:
{
imports = [ ./env.nix ];
home.packages = [ pkgs.openbao ];
}

11
modules/services/gpg.nix Normal file
View File

@@ -0,0 +1,11 @@
{ pkgs, ... }:
{
programs.gpg.enable = true;
services.gpg-agent = {
enable = true;
enableBashIntegration = true;
enableZshIntegration = true;
pinentry.package = pkgs.pinentry-curses;
};
}

22
modules/services/ssh.nix Normal file
View File

@@ -0,0 +1,22 @@
{ lib, ... }:
{
programs.ssh = {
enable = true;
enableDefaultConfig = false;
matchBlocks."*" = {
addKeysToAgent = lib.mkDefault "yes";
compression = lib.mkDefault false;
controlMaster = lib.mkDefault "no";
controlPath = lib.mkDefault "~/.ssh/master-%r@%n:%p";
controlPersist = lib.mkDefault "no";
forwardAgent = lib.mkDefault true;
hashKnownHosts = lib.mkDefault false;
serverAliveCountMax = lib.mkDefault 3;
serverAliveInterval = lib.mkDefault 0;
userKnownHostsFile = lib.mkDefault "~/.ssh/known_hosts";
};
};
services.ssh-agent.enable = true;
}

View File

@@ -0,0 +1,9 @@
{ lib, pkgs, ... }:
let
tailscale = lib.attrByPath [ "tailscale" ] null pkgs;
in
{
home.packages = builtins.filter (pkg: pkg != null) [
tailscale
];
}

8
profiles/backend.nix Normal file
View File

@@ -0,0 +1,8 @@
let
mkProfile = import ../lib/mkProfile.nix;
in
mkProfile {
modules = [
../modules/roles/backend.nix
];
}

8
profiles/frontend.nix Normal file
View File

@@ -0,0 +1,8 @@
let
mkProfile = import ../lib/mkProfile.nix;
in
mkProfile {
modules = [
../modules/roles/frontend.nix
];
}

10
profiles/minimal.nix Normal file
View File

@@ -0,0 +1,10 @@
let
mkProfile = import ../lib/mkProfile.nix;
in
mkProfile {
modules = [
../modules/roles/minimal.nix
../modules/services/ssh.nix
../modules/optional/devtools.nix
];
}

20
shells/default.nix Normal file
View File

@@ -0,0 +1,20 @@
{
pkgs,
extraPackages ? [ ],
inputsFrom ? [ ],
shellHook ? "",
}:
pkgs.mkShell {
inherit inputsFrom shellHook;
packages =
(with pkgs; [
git
jq
just
nil
nixd
nixfmt
])
++ extraPackages;
}

16
shells/go.nix Normal file
View File

@@ -0,0 +1,16 @@
{
pkgs,
extraPackages ? [ ],
inputsFrom ? [ ],
shellHook ? "",
}:
pkgs.mkShell {
inherit inputsFrom shellHook;
packages =
(with pkgs; [
go
gopls
])
++ extraPackages;
}

19
shells/node.nix Normal file
View File

@@ -0,0 +1,19 @@
{
lib,
pkgs,
extraPackages ? [ ],
inputsFrom ? [ ],
shellHook ? "",
}:
let
nodejs = lib.attrByPath [ "nodejs_20" ] pkgs.nodejs pkgs;
in
pkgs.mkShell {
inherit inputsFrom shellHook;
packages = [
nodejs
pkgs.pnpm
]
++ extraPackages;
}

24
shells/rust.nix Normal file
View File

@@ -0,0 +1,24 @@
{
lib,
pkgs,
extraPackages ? [ ],
inputsFrom ? [ ],
shellHook ? "",
}:
let
optionalPackage = path: lib.attrByPath path null pkgs;
in
pkgs.mkShell {
inherit inputsFrom shellHook;
packages = builtins.filter (pkg: pkg != null) (
[
pkgs.cargo
pkgs.rustc
(optionalPackage [ "rust-analyzer" ])
(optionalPackage [ "rustfmt" ])
(optionalPackage [ "clippy" ])
]
++ extraPackages
);
}

View File

@@ -0,0 +1,42 @@
{
inputs,
system,
username,
homeDirectory,
modules ? [ ],
darwinStateVersion ? 6,
stateVersion ? "24.11",
extraSpecialArgs ? { },
}:
inputs.nix-darwin.lib.darwinSystem {
inherit system;
specialArgs = extraSpecialArgs // {
inherit inputs;
};
modules = [
inputs.home-manager.darwinModules.home-manager
{
system.stateVersion = darwinStateVersion;
users.users = {
${username}.home = homeDirectory;
};
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.extraSpecialArgs = extraSpecialArgs // {
inherit inputs;
};
home-manager.users = {
${username} = {
imports = modules;
home = {
inherit stateVersion username;
};
};
};
}
];
}

24
systems/linux/default.nix Normal file
View File

@@ -0,0 +1,24 @@
{
inputs,
system,
username,
homeDirectory,
modules ? [ ],
stateVersion ? "24.11",
extraSpecialArgs ? { },
}:
inputs.home-manager.lib.homeManagerConfiguration {
pkgs = import inputs.nixpkgs { inherit system; };
extraSpecialArgs = extraSpecialArgs // {
inherit inputs;
};
modules = [
{
home = {
inherit homeDirectory stateVersion username;
};
}
]
++ modules;
}

View File

@@ -0,0 +1,36 @@
{
description = "Starter personal flake for NodeiWest employees";
inputs = {
# Adjust the repo path if you publish this under a different name.
company.url = "git+ssh://git@git.dgren.dev/employees/company-nix.git";
};
outputs =
{ company, ... }:
let
username = "your-name";
in
{
# Remove whichever target you do not need.
homeConfigurations."${username}@linux" = company.lib.mkSystem {
target = "linux";
system = "x86_64-linux";
inherit username;
homeDirectory = "/home/${username}";
modules = [
company.homeManagerModules.profiles.backend
];
};
darwinConfigurations."${username}-mac" = company.lib.mkSystem {
target = "darwin";
system = "aarch64-darwin";
inherit username;
homeDirectory = "/Users/${username}";
modules = [
company.homeManagerModules.profiles.frontend
];
};
};
}