⚠ Advisory ⚠
DevOS leverages the flakes feature available via an experimental branch of nix. Until nix 3.0 is released, this project should be considered unstable, though quite usable as flakes have been maturing well for a while.
Introduction
DevOS grants a simple way to use, deploy and manage NixOS systems for personal and productive use. It does this by providing a convenient repository structure; integrating several popular projects like home-manager, and devshell.
Skip the indeterminate nature of other systems, and the perceived difficulty of Nix. It's easier than you think!
Getting Started
Check out the guide to get up and running.
In the Wild
You author maintains his own branch, so you can take inspiration, direction, or make critical comments about my bad code. 😜
Motivation
NixOS provides an amazing abstraction to manage our environment, but that new power can sometimes bring feelings of overwhelm and confusion. Having a turing complete system can easily lead to unlimited complexity if we do it wrong. Instead, we should have a community consensus on how to manage a NixOS system.
Upstream
I'd love to see this in the nix-community should anyone believe its reached a point of maturity to be generally useful, but I'm all for waiting until 1.0#121 to save the cache work, too.
The future is declarative! 🎉
Community Profiles
There are two branches from which to choose: core and community. The community branch builds on core and includes several ready-made profiles for discretionary use.
Every package and NixOS profile declared in community is uploaded to cachix, so everything provided is available without building anything. This is especially useful for the packages that are overridden from master, as without the cache, rebuilds are quite frequent.
Inspiration & Art
License
DevOS is licensed under the MIT License.
Quick Start
The only dependency is nix, so make sure you have it installed.
Get the Template
Here is a snippet that will get you the template without the git history:
nix-shell -p cachix --run cachix use nrdxp
nix-shell https://github.com/divnix/devos/archive/core.tar.gz -A shell \
--run "flk get core"
cd flk
nix-shell
git init
git add .
git commit -m init
This will place you in a new folder named flk
with git initialized, and a
nix-shell that provides all the dependencies, including the unstable nix
version required.
In addition, the binary cache is added for faster deployment.
Notes:
- You can change
core
tocommunity
in the call toflk get
- Flakes ignore files that have not been added to git, so be sure to stage new files before building the system.
- You can choose to simply clone the repo with git if you want to follow upstream changes.
Next Steps:
ISO
Making and writing an installable iso for hosts/NixOS.nix
is as simple as:
flk iso NixOS
dd bs=4M if=result/iso/*.iso of=/dev/$your_installation_device \
status=progress oflag=sync
This works for any file matching hosts/*.nix
excluding default.nix
.
From NixOS
Generate Configuration
Assuming your happy with your existing partition layout, you can generate a basic NixOS configuration for your system using:
flk up
This will make a new file hosts/up-$(hostname).nix
, which you can edit to
your liking.
Make sure your i18n.defaultLocale
and time.timeZone
are set properly for
your region. Keep in mind that networking.hostName
with be automatically
set to the filename of your hosts file, so hosts/my-host.nix
will have the
hostname my-host
.
Now might be a good time to read the docs on suites and profiles and add or create any that you need.
Note:
While the
up
sub-command is provided as a convenience to quickly set up and install a "fresh" NixOS system on current hardware, committing these files is discouraged.They are placed in the git staging area automatically because they would be invisible to the flake otherwise, but it is best to move what you need from them directly into a host module of your own making, and commit that instead.
Installation
Once your ready to deploy hosts/my-host.nix
:
flk my-host switch
This calls nixos-rebuild
with sudo to build and install your configuration.
Notes:
Instead of
switch
, you can passbuild
,test
,boot
, etc just as withnixos-rebuild
.It is convenient to have the template living at
/etc/nixos
so you can simplysudo nixos-rebuild switch
from anywhere on the system, but it is not required.
Layout
Each of the following sections is a directory in the root of the project serving a singular purpose. Select a chapter to read more about its purpose and usage.
Cachix
The cachix directory simple captures the output of sudo cachix use
for the
developers personal cache, as well as the nix-community cache. You can easily
add your own cache, assuming the template lives in /etc/nixos, by simply
running sudo cachix use yourcache
.
These caches are only added to the system after a nixos-rebuild switch
, so it
is recommended to call cachix use divnix
before the initial deployment, as it
will save a lot of build time.
In the future, users will be able to skip this step once the ability to define the nix.conf within the flake is fully fleshed out upstream.
External Art
When you need to use a module, overlay, or pass a value from one of your inputs to the rest of your NixOS configuration, extern is where you do it.
Modules and overlays are self explanatory, and the specialArgs
attribute is
used to extend the arguments passed to all NixOS modules, allowing for
arbitrary values to be passed from flake inputs to the rest of your
configuration.
Hosts
Nix flakes contain an output called nixosConfigurations
declaring an
attribute set of valid NixOS systems. To simplify the management and creation
of these hosts, devos automatically imports every .nix file inside this
directory to the mentioned attribute set, applying the projects defaults to
each. The only hard requirement is that the file contain a valid NixOS module.
As an example, a file hosts/system.nix
will be available via the flake
output nixosConfigurations.system
. You can have as many hosts as you want
and all of them will be automatically imported based on their name.
For each host, the configuration automatically sets the networking.hostName
attribute to the name of the file minus the .nix extension. This is for
convenience, since nixos-rebuild
automatically searches for a configuration
matching the current systems hostname if one is not specified explicitly.
It is recommended that the host modules only contain configuration information specific to a particular piece of hardware. Anything reusable across machines is best saved for profile modules.
This is a good place to import sets of profiles, called suites, that you intend to use on your machine.
Additionally, this is the perfect place to import anything you might need from the nixos-hardware repository.
Example
hosts/librem.nix:
{ suites, hardware, ... }:
{
imports = suites.laptop ++ [ hardware.purism-librem-13v3 ];
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
fileSystems."/" = { device = "/dev/disk/by-label/nixos"; };
}
Modules
The modules directory is a replica of nixpkg's NixOS modules , and follows the same semantics. This allows for trivial upstreaming into nixpkgs proper once your module is sufficiently stable.
All modules linked in module-list.nix are automatically exported via
nixosModules.<file-basename>
, and imported into all hosts.
Note:
This is reserved for declaring brand new module options. If you just want to declare a coherent configuration of already existing and related NixOS options , use profiles instead.
Semantics
In case you've never written a module for nixpkgs before, here is a brief outline of the process.
Declaration
modules/services/service-category/my-service.nix:
{ config, lib, ... }:
let
cfg = config.services.myService;
in
{
options.services.myService = {
enable = lib.mkEnableOption "Description of my new service.";
# additional options ...
};
config = lib.mkIf cfg.enable {
# implementation ...
};
}
Import
modules/module-list.nix:
[
./services/service-category/my-service.nix
]
Usage
Internal
profiles/profile-category/my-profile.nix:
{ ... }:
{
services.MyService.enable = true;
}
External
flake.nix:
{
# inputs omitted
outputs = { self, devos, nixpkgs, ... }: {
nixosConfigurations.myConfig = nixpkgs.lib.nixosSystem {
system = "...";
modules = [
devos.nixosModules.my-service
({ ... }: {
services.MyService.enable = true;
})
];
};
};
}
Overlays
Writing overlays is a common occurence when using a NixOS system. Therefore, we want to keep the process as simple and straightforward as possible.
Any .nix files declared in this directory will be assumed to be a valid
overlay, and will be automatically imported into all hosts, and
exported via overlays.<file-basename>
as well as
packages.<system>.<pkgName>
(for valid systems), so all you have to do is
write it.
Example
overlays/kakoune.nix:
final: prev: {
kakoune = prev.kakoune.override {
configure.plugins = with final.kakounePlugins; [
(kak-fzf.override { fzf = final.skim; })
kak-auto-pairs
kak-buffers
kak-powerline
kak-vertical-selection
];
};
}
Overrides
By default, the NixOS systems are based on the latest release. While it is trivial to change this to nixos-unstable or any other branch of nixpkgs by changing the flake url, sometimes all we want is a single package from another branch.
This is what the overrides are for. By default, they are pulled directly from
nixpkgs/master, but you can change the override
flake input url to
nixos-unstable, or even a specific sha revision.
Example
Packages
The override packages are defined as a regular overlay with an extra arguement
pkgs
. This refers to the packages built from the override
flake.
Pulling the manix package from the override flake:
{
packages = pkgs: final: prev: {
inherit (pkgs) manix;
};
}
Modules
You can also pull modules from override. Simply specify their path relative to
the nixpkgs modules directory. The old version will be added
to disabledModules
and the new version imported into the configuration.
Pulling the zsh module from the override flake:
{
modules = [ "programs/zsh/zsh.nix" ];
}
Note:
Sometimes a modules name will change from one branch to another. This is what the
disabledModules
list is for. If the module name changes, the old version will not automatically be disabled, so simply put it's old name in this list to disable it.
Packages
Similar to modules, the pkgs directory mirrors the upstream nixpkgs/pkgs, and for the same reason; if you ever want to upstream your package, it's as simple as dropping it into the nixpkgs/pkgs directory.
The only minor difference is that, instead of adding the callPackage
call to
all-packages.nix
, you just add it the the default.nix in this directory,
which is defined as a simple overlay.
This overlay is set as the default overlay
output attribute for the flake.
And all the packages are exported via packages.<system>.<pkg-name>
, for all
the supported systems listed in the package's meta.platforms
attribute.
And, as usual, every package in the overlay is also available to any NixOS host.
Example
pkgs/development/libraries/libinih/default.nix:
{ stdenv, meson, ninja, fetchFromGitHub, ... }:
let version = "r50";
in
stdenv.mkDerivation {
pname = "libinih";
inherit version;
src = fetchFromGitHub {
owner = "benhoyt";
repo = "inih";
rev = "${version}";
hash = "sha256-GF+TVEysaXJxSBBjMsTr2IQvRKlzdEu3rlPQ88PE3nI=";
};
buildInputs = [ meson ninja ];
mesonFlags = ''
-Ddefault_library=shared
-Ddistro_install=true
'';
meta = with stdenv.lib; {
description = "Simple .INI file parser in C";
homepage = "https://github.com/benhoyt/inih";
maintainers = [ maintainers.divnix ];
license = licenses.bsd3;
platforms = platforms.all;
inherit version;
};
}
pkgs/default.nix:
final: prev: {
libinih = prev.callPackage ./development/libraries/libinih { };
}
Profiles
Profiles are simply NixOS modules which contain generic expressions suitable for any host. A good example is the configuration for a text editor, or window manager. If you need some concrete examples, just checkout the community branch.
Constraints
For the sake of consistency, there are a few minor constraints. First of all, a
profile should always be defined in a default.nix
, and it should always be a
a function taking a single attribute set as an argument, and returning a NixOS
module which does not define any new module options. If you need to make new
module option declarations, just use modules.
These restrictions help simplify the import logic used to pass profles to suites.
Example
Correct ✔
profiles/develop/default.nix:
{ ... }:
{
programs.zsh.enable = true;
}
Incorrect ❌
profiles/develop.nix:
{
options = {};
}
Subprofiles
Profiles can also define subprofiles. They follow the same constraints outlined above. A good top level profile should be a high level concern, such a your personal development environment, and the subprofiles should be more concrete program configurations such as your text editor, and shell configs. This way, you can either pull in the whole development profile, or pick and choose individual programs.
Conclusion
Profiles are the most important concept in devos. They allow us to keep our nix expressions self contained and modular. This way we can maximize reuse while minimizing boilerplate. Always strive to keep your profiles as generic and modular as possible. Anything machine specific belongs in your host files.
Secrets
Secrets are managed using git-crypt so you can keep your flake in a public repository like GitHub without exposing your password or other sensitive data.
By default, everything in the secrets folder is automatically encrypted. Just
be sure to run git-crypt init
before putting anything in here.
Note:
Currently, there is no mechanism in nix to deploy secrets within the nix/store so, if they end up in the nix/store after deployment, they will be world readable on that machine.
The author of devos intends to implement a workaround for this situation in the near future, but for the time being, simple be aware of this.
Suites
Suites provide a mechanism for users to easily combine and name collecitons of profiles. For good examples, check out the suites defined in the community branch.
In the future, we will use suites as a mechanism for deploying various machine types which don't depend on hardware, such as vm's and containers.
Definition
rec {
workstation = [ profiles.develop profiles.graphical users.nixos ];
mobileWS = workstation ++ [ profiles.laptop ];
}
Usage
hosts/my-laptop.nix
:
{ suites, ... }:
{
imports = suites.mobileWS;
}
Users
Users are a special case of profiles that define system users and home-manager configurations. For your convenience, home manager is wired in by default so all you have to worry about is declaring your users. For a fully fleshed out example, check out the developers personal branch.
Basic Usage
users/myuser/default.nix
:
{ ... }:
{
users.users.myuser = {
isNormalUser = true;
};
home-manager.users.myuser = {
programs.mpv.enable = true;
};
}
External Usage
You can easily use the defined home-manager configurations outside of NixOS
using the hmActivations
meta-package defined in the flakes legacyPackages
output. The flk helper script makes this even easier.
This is great for keeping your environment consistent across Unix systems, including OSX.
From within the projects devshell:
# builds the nixos user defined in the NixOS host
flk home NixOS nixos
# build and activate
flk home NixOS nixos switch
Manually from outside the project:
# build
nix build "github:divnix/devos#hmActivationPackages.NixOS.nixos"
# activate
./result/activate && unlink result
flk command
The devshell for the project incudes a convenient script for managing your
system called flk
. Each of the following chapters is a reference for one of
its subcommands.
Rebuild
Without any of the subcommands, flk
acts as a convenient shortcut for
nixos-rebuild
:
flk NixOS build
Will build hosts/NixOS.nix. You can change out build
for switch
, test
,
etc. Any additional arguments are passed through to the call to
nixos-rebuild
.
Usage
flk help
up
The up
subcommand is a simple shortcut for nixos-generate-config
that is
compatible with devos. There is a short explanation in the the getting started
guide.
update
The update
subcommand is a simple alias for:
nix flake update --recreate-lock-file --commit-lock-file
As it sounds, this will update your lock file, and commit it.
get
The get
subcommand is useful for getting a bare copy of devos without the
git history. You can pull either the core or community branches.
Usage
flk get BRANCH DEST-DIR
If DEST-DIR is ommitted, it defaults to ./flk.
ISO
Making and writing an installable iso for hosts/NixOS.nix
is as simple as:
flk iso NixOS
dd bs=4M if=result/iso/*.iso of=/dev/$your_installation_device \
status=progress oflag=sync
This works for any file matching hosts/*.nix
excluding default.nix
.
install
The install
subcommand is a simple convenience for nixos-install
, similar
to the shortcut for nixos-rebuild
, all additional arguments are passed
through.
Example
flk install NixOS
This will install hosts/NixOS.nix to /mnt. You can override this directory
using standard nixos-install
args.
home
The home
subcommand is for using your home-manager configurations outside of
NixOS, providing an awesome mechanism for keeping your environments
synchronized, even when using other systems.
Usage
The users page contains a good usage example.
Integrations
This section explores some of the optional tools included with devos to provide a solution to common concerns such as ci and remote deployment. An effort is made to choose tools that treat nix, and where possible flakes, as first class citizens.
deploy-rs
Deploy-rs is a tool for managing NixOS remote machines. It was chosen for devos after the author experienced some frustrations with the stateful nature of nixops' db. It was also designed from scratch to support flake based deployments, and so is an excellent tool for the job.
By default, all the hosts are also available as deploy-rs nodes,
configured with the hostname set to networking.hostName
; overridable via
the command line.
Usage
Just add your ssh key to the host:
{ ... }:
{
users.users.${sshUser}.openssh.authorizedKeys.keyFiles = [
../secrets/path/to/key.pub
];
}
And the private key to your user:
{ ... }:
{
home-manager.users.${sshUser}.programs.ssh = {
enable = true;
matchBlocks = {
${host} = {
host = hostName;
identityFile = ../secrets/path/to/key;
extraOptions = { AddKeysToAgent = "yes"; };
};
};
}
}
And run the deployment:
deploy "flk#hostName" --hostname host.example.com
Note:
Your user will need sudo access
Pull Requests
If making a change to core, or adding a feature, please be sure to update the relevant docs. Each directory contains its own README.md, which will automatically be pulled into the mdbook. The book is rendered on every change, so the docs should always be up to date.
Community PRs
While much of your work in this template may be idiosyncratic in nature. Anything
that might be generally useful to the broader NixOS community can be synced to
the community
branch to provide a host of useful NixOS configurations available
"out of the box".
Style
If you wish to contribute please follow these guidelines:
-
format your code with
nixpkgs-fmt
. The default devshell includes a pre-commit hook that does this for you. -
The commit message follows the same semantics as nixpkgs.
- You can use a
#
symbol to specify ambiguities. For example,develop#zsh: <rest of commit message>
would tell me that your updating thezsh
subprofile living under thedevelop
profile.
- You can use a