About

Helps static file/configuration creation with Nix and devshell.

There is a bunch of ways static file/configuration are hard, this will help you generate, validate and distribute JSON, YAML, TOML or TXT.

Generate

Your content will be defined in Nix Language, it means you can use variables, functions, imports, read files, etc.

The modular system helps layering configurations, hiding complexity and making it easier for OPS teams.

Validate

Your content modules could optionally be well defined and type checked in build proccess with this same tool.

Or you could use Nix as package manager and install any tool to validate your configuration (ie integrating it with existing JSON Schema).

Distribute

Nix integrates well with git and http, it could be also used to read JSON, YAML, TOML, zip and gz files.

In fact Nix isn't a configuration tool but a package manger, we are only using it as configuration tool because the language is simple and flexible.

You can recreate files of a repository directly to your local machine by running nix develop <flake-uri> --build, example:

# copy all my dogfood to your current folder
nix develop github:cruel-intentions/devshell-files --build

With help of Nix and devshell you could install any development or deployment tool of its 80 000 packages.

Instructions

Installing Nix

curl -sSf -L https://install.determinate.systems/nix | sh -s -- install

Configuring new projects:

nix flake new -t github:cruel-intentions/devshell-files my-project
cd my-project
git init
git add *.nix flake.lock

Configuring existing projects:

nix flake new -t github:cruel-intentions/devshell-files ./
git add *.nix
git add flake.lock

Generating files:

nix develop --build

or entering in shell with all commands and alias

nix develop -c $SHELL
# to list commands and alias
# now run: menu 

Examples

Creating JSON, TEXT, TOML or YAML files

# examples/hello.nix
#
# this is one nix file
{
  files.json."/generated/hello.json".hello = "world";
  files.toml."/generated/hello.toml".hello = "world";
  files.yaml."/generated/hello.yaml".hello = "world";
  files.hcl."/generated/hello.hcl".hello   = "world";
  files.text."/generated/hello.txt" = "world";
}

Your file can be complemented with another module

# examples/world.nix
# almost same as previous example
# but show some language feature
let 
  name = "hello"; # a variable
in
{
  files = {
    json."/generated/${name}.json".baz = ["foo" "bar" name];
    toml."/generated/${name}.toml".baz = ["foo" "bar" name];
    yaml = {
      "/generated/${name}.yaml" = {
        baz = [
          "foo"
          "bar"
          name
        ];
      };
    };
  };
}

Content generated by those examples are in generated

# ie ./generated/hello.yaml
baz:
  - foo
  - bar
  - hello
hello: world

Dogfooding

This project is configured by module project.nix

# ./project.nix
{
  # import other modules
  imports = [
    ./examples/hello.nix
    ./examples/world.nix
    ./examples/readme.nix
    ./examples/gitignore.nix
    ./examples/license.nix
    ./examples/interpolation.nix
    ./examples/docs.nix
    ./examples/book.nix
    ./examples/services.nix
    ./examples/nim.nix
    ./examples/nushell.nix
    ./examples/watch.nix
  ];

  # My shell name
  devshell.name = "devshell-files";

  # install development or deployment tools
  packages = [
    "convco"
    # now we can use 'convco' command https://convco.github.io

    # but could be:
    # "awscli"
    # "azure-cli"
    # "cargo"
    # "conda"
    # "go"
    # "nim"
    # "nodejs"
    # "nodejs-18_x"
    # "nushell"
    # "pipenv"
    # "python39"
    # "ruby"
    # "rustc"
    # "terraform"
    # "yarn"
    # look at https://search.nixos.org for more packages
  ];

  # create alias
  files.alias.feat = ''convco commit --feat $@'';
  files.alias.fix  = ''convco commit --fix  $@'';
  files.alias.docs = ''convco commit --docs $@'';
  files.alias.alou = ''
    #!/usr/bin/env python
    print("Alo!") # is hello in portuguese
  '';

  # now we can use feat, fix, docs and alou commands

  # create .envrc for direnv
  files.direnv.enable = true;

  # disabe file creation when entering in the shell
  # call devshell-files instead
  # files.on-call = true;
}

This README.md is also a module defined as above

# There is a lot things we could use to write static file
# Basic intro to nix language https://github.com/tazjin/nix-1p
# Some nix functions https://teu5us.github.io/nix-lib.html
{lib, ...}:
{
  files.text."/README.md" = builtins.concatStringsSep "\n" [
    "# Devshell Files Maker"
    (builtins.readFile ./readme/toc.md)
    (builtins.readFile ./readme/about.md)
    (builtins.readFile ./readme/installation.md)
    (builtins.import   ./readme/examples.nix)
    ((builtins.import  ./readme/modules.nix) lib)
    (builtins.readFile ./readme/todo.md)
    (builtins.readFile ./readme/issues.md)
    (builtins.readFile ./readme/seeAlso.md)
  ];
}

Our .gitignore is defined like this

# ./examples/gitignore.nix
{
  # create my .gitignore copying ignore patterns from
  # github.com/github/gitignore
  files.gitignore.enable = true;
  files.gitignore.template."Global/Archives" = true;
  files.gitignore.template."Global/Backup"   = true;
  files.gitignore.template."Global/Diff"     = true;
  files.gitignore.pattern."**/.data"         = true;
  files.gitignore.pattern."**/.direnv"       = true;
  files.gitignore.pattern."**/.envrc"        = true;
  files.gitignore.pattern."**/.gitignore"    = true;
  files.gitignore.pattern."**/flake.lock"    = true;
}

And our LICENSE file is

# ./examples/license.nix
{
  # LICENSE file creation
  # using templates from https://github.com/spdx/license-list-data
  files.license.enable = true;
  files.license.spdx.name = "MIT";
  files.license.spdx.vars.year = "2023";
  files.license.spdx.vars."copyright holders" = "Cruel Intentions";
}

Writing new modules

Nix lang

Jump this part if aready know Nix Lang, if don't there is a small concise content of Nix Lang.

If one page is too much to you, the basic is:

  • : defines a new function, arg: "Hello ${arg}"
  • that's why we use = instaed of :, { attr-key = "value"; }
  • ; instead of , and they aren't optional
  • array aren't separated by , ie. [ "some" "value" ]

JSON as NIX

nameJSONNIX
nullnullnull
booltruetrue
int123123
float12.312.3
string"string""string"
array["some","array"]["some" "array"]
object{"some":"value"}{ some = "value"; }
multiline-string''... multiline string ... ''
variableslet my-var = 1; other-var = 2; in my-var + other-var
functionmy-arg: "Hello ${my-arg}!"
variable-functionlet my-function = my-arg: "Hello ${my-arg}!"; in ...
calling-a-function... in my-function "World"

Module

Modules can be defined in two formats:

As attrset, aka. object (JSON), dict (Python):

{                            #  <|
  imports = [];              #   |
  config  = {};              #   | module info
  options = {};              #   |
}                            #  <|

All those attributes are optional

  • imports: array with paths to other modules
  • config: object with actual configurations
  • options: object with our config type definition

As function:

Functions has following arguments:

  • config with all evaluated configs values,
  • pkgs with all nixpkgs available.
  • lib library of useful functions.
  • And may receive others (we use ... to ignore them)
{ config, pkgs, lib, ... }:  #  <| function args
{                            #  <|
  imports = [];              #   |
  config  = {};              #   | module info
  options = {};              #   |
}                            #  <|

Imports

Points to other modules files to be imported in this module

{ 
  imports = [
    ./gh-actions-options.nix
    ./gh-actions-impl.nix
  ];
}

Hint, split modules in two files:

  • One mostly with options, where your definition goes
  • Other with config, where your information goes

It has two advantages, let share options definitions across projects more easily.

And it hides complexity, hiding complexity is what abstraction is all about, we didn't share options definitions across projects to type less, but because we could reuse an abstraction that helps hiding complexity.

Config

Are values to our options

We can set value by ourself, or use lib functions to import json/toml/text files.

{ lib, ...}:
{
  config.files.text."/HW.txt" = "Hello World!";
  config.files.text."/EO.txt" = lib.concatStringsSep "" ["48" "65" "6c" "6c" "6f"];
  config.files.text."/LR.txt" = (lib.importJSON   ./hello.json).msg; # { "msg": "Hello World!" }
  config.files.text."/LL.txt" = (lib.importTOML   ./hello.toml).msg; # msg = Hello World!
  config.files.text."/OD.txt" = lib.readFile      ./hello.txt;       # Hello World!
}

If file has no options., config. can be ommited.

And this file produce the same result

{ lib, ...}:
{
  files.text."/HW.txt" = "Hello World!";
  files.text."/EO.txt" = lib.concatStringsSep "" ["48" "65" "6c" "6c" "6f"];
  files.text."/LR.txt" = (lib.importJSON   ./hello.json).msg; # { "msg": "Hello World!" }
  files.text."/LL.txt" = (lib.importTOML   ./hello.toml).msg; # msg = Hello World!
  files.text."/OD.txt" = lib.readFile      ./hello.txt;       # Hello World!
}

Options

Options are schema definition for configs values.

Example, to create a github action file, it could be done like this:

{
  config.files.yaml."/.github/workflows/ci-cd.yaml" = {
    on = "push";
    jobs.ci-cd.runs-on = "ubuntu-latest";
    jobs.ci-cd.steps   = [
      { uses = "actions/checkout@v2.4.0"; }
      { run = "npm i"; }
      { run = "npm run build"; }
      { run = "npm run test"; }
      { run = "aws s3 sync ./build s3://some-s3-bucket"; }
    ];
  };
}

This only works because this project has another module with:

{lib, ...}:
{
  options.files = submodule {
    options.yaml.type = lib.types.attrsOf lib.types.anything;
  };
}

But if we always set ci-cd.yaml like that, no complexity has been hidden, and requires copy and past it in every project.

Since most CI/CD are just: 'Pre Build', 'Build', 'Test', 'Deploy'

What most projects really need is something like:

# any module file (maybe project.nix)
{
  # our build steps
  config.gh-actions.setup  = "npm i";
  config.gh-actions.build  = "npm run build";
  config.gh-actions.test   = "npm run test";
  config.gh-actions.deploy = "aws s3 sync ./build s3://some-s3-bucket";
}

Adding this to project.nix, throws an error undefined config.gh-actions, and command fails.

It doesn't knows these options.

To make aware of it, we had to add options schema of that.

# gh-actions-options.nix
{ lib, ...}:
{
  # a property 'gh-actions.setup'
  options.gh-actions.setup = lib.mkOption {
    default     = "echo setup";
    description = "Command to run before build";
    example     = "npm i";
    type        = lib.types.str;
  };
  # a property 'gh-actions.build'
  options.gh-actions.build = lib.mkOption {
    default     = "echo build";
    description = "Command to run as build step";
    example     = "npm run build";
    type        = lib.types.str;
  };
  # a property 'gh-actions.test'
  options.gh-actions.test = lib.mkOption {
    default     = "echo test";
    description = "Command to run as test step";
    example     = "npm test";
    type        = lib.types.str;
  };
  # a property 'gh-actions.deploy'
  options.gh-actions.deploy = lib.mkOption {
    default     = "echo deploy";
    description = "Command to run as deploy step";
    example     = "aws s3 sync ./build s3://my-bucket";
    type        = lib.types.lines;
  };
}

Or using lib.types.fluent

# gh-actions-options.nix
{ lib, ...}:
lib.types.fluent {
  options.gh-actions.options = {
    # defines a property 'gh-actions.setup'
    setup.default  = "echo setup";  #default is string
    setup.mdDoc    = "Command to run before build";
    setup.example  = "npm i";
    # defines a property 'gh-actions.build'
    build.default  = "echo build";
    build.mdDoc    = "Command to run as build step";
    build.example  = "npm run build";
    # defines a property 'gh-actions.test'
    test.default   = "echo test";
    test.mdDoc     = "Command to run as test step";
    test.example   = "npm test";
    # defines a property 'gh-actions.deploy'
    deploy.default = "echo deploy";
    deploy.mdDoc   = "Command to run as deploy step";
    deploy.example = "aws s3 sync ./build s3://my-bucket";
    deploy.type    = lib.types.lines;
  };
}

Now, previous config can be used, but it does nothing, it doesn't create yaml.

It knowns what options can be accepted as config, but not what to do with it.

The following code uses parameter config that has all evaluated config values.

# gh-actions.nix
{ config, lib, ... }:
{
  imports = [ ./gh-actions-options.nix ];
  # use other module that simplify file creation to create config file
  files.yaml."/.github/workflows/ci-cd.yaml".jobs.ci-cd.steps   = [
    { uses = "actions/checkout@v2.4.0"; }

    { run  = config.gh-actions.setup;   }  # 
    { run  = config.gh-actions.build;   }  #  Read step scripts from
    { run  = config.gh-actions.test;    }  #  config.gh-actions
    { run  = config.gh-actions.deploy"; }  # 
  ];
  files.yaml."/.github/workflows/ci-cd.yaml".on = "push";
  files.yaml."/.github/workflows/ci-cd.yaml".jobs.ci-cd.runs-on = "ubuntu-latest";
}

Now it can be imported and set 'setup', 'build', 'test' and 'deploy' configs

# any other module file, maybe project.nix
{
  imports = [ ./gh-actions.nix ];
  gh-actions.setup  = "echo 'paranaue'";
  gh-actions.build  = "echo 'paranaue parana'";
  gh-actions.build  = "echo 'paranaue'";
  gh-actions.deploy = ''
    echo "paranaue 
            parana"
  '';
}

If something that is not a string is set, an error will raise, cheking it against the options schema.

There are other types that can be used (some of them):

  • lib.types.bool
  • lib.types.path
  • lib.types.package
  • lib.types.int
  • lib.types.ints.unsigned
  • lib.types.ints.positive
  • lib.types.ints.port
  • lib.types.ints.between
  • lib.types.str
  • lib.types.lines
  • lib.types.enum
  • lib.types.submodule
  • lib.types.nullOr (typed nullable)
  • lib.types.listOf (typed array)
  • lib.types.attrsOf (typed hash map)
  • lib.types.uniq (typed set)

And lib has some modules helpers functions like:

  • lib.mkIf : to only set a property if some informaiton is true
  • lib.optionals : to return an array or an empty array
  • lib.optionalString: to return an string or an empty string

Sharing our module

Now to not just copy and past it everywhere, we could create a git repository, ie. gh-actions

Then we could let nix manage it for us adding it to flake.nix file like

{
  description = "Dev Environment";

  inputs.dsf.url = "github:cruel-intentions/devshell-files";
  inputs.gha.url = "github:cruel-intentions/gh-actions";
  # for private repository use git url
  # inputs.gha.url = "git+ssh://git@github.com/cruel-intentions/gh-actions.git";

  outputs = inputs: inputs.dsf.lib.mkShell [
    "${inputs.gha}/gh-actions.nix"
    ./project.nix
  ];
}

Or manage version adding it directly to project.nix (or any other module file)

{
  imports = 
    let gh-actions = builtins.fetchGit {
      url = "git+ssh://git@github.com/cruel-intentions/gh-actions.git";
      ref = "master";
      rev = "46eead778911b5786d299ecf1a95c9ed4c130844";
    };
    in [
      "${gh-actions}/gh-actions.nix"
    ];
}

Document our module

To document our modules is simple, we just need to use config.files.docs as follow

# examples/docs.nix

{lib, pkgs, ...}:
{
  files.docs."/gh-pages/src/modules/alias.md".modules     = [ ../modules/alias.nix ../modules/alias-complete.nix ];
  files.docs."/gh-pages/src/modules/cmds.md".modules      = [ ../modules/cmds.nix        ];
  files.docs."/gh-pages/src/modules/files.md".modules     = [ ../modules/files.nix       ];
  files.docs."/gh-pages/src/modules/git.md".modules       = [ ../modules/git.nix         ];
  files.docs."/gh-pages/src/modules/on-call.md".modules   = [ ../modules/startup.nix     ];
  files.docs."/gh-pages/src/modules/gitignore.md".modules = [ ../modules/gitignore.nix   ];
  files.docs."/gh-pages/src/modules/hcl.md".modules       = [ ../modules/hcl.nix         ];
  files.docs."/gh-pages/src/modules/json.md".modules      = [ ../modules/json.nix        ];
  files.docs."/gh-pages/src/modules/mdbook.md".modules    = [ ../modules/mdbook.nix      ];
  files.docs."/gh-pages/src/modules/nim.md".modules       = [ ../modules/nim.nix         ];
  files.docs."/gh-pages/src/modules/nushell.md".modules   = [ ../modules/nushell.nix     ];
  files.docs."/gh-pages/src/modules/nush.md".modules      = [ ../modules/nush.nix ../modules/nuon.nix ];
  files.docs."/gh-pages/src/modules/rc.md".modules        = [ ../modules/services/rc-devshell.nix     ];
  files.docs."/gh-pages/src/modules/services.md".modules  = [ ../modules/services.nix    ];
  files.docs."/gh-pages/src/modules/spdx.md".modules      = [ ../modules/spdx.nix        ];
  files.docs."/gh-pages/src/modules/text.md".modules      = [ ../modules/text.nix        ];
  files.docs."/gh-pages/src/modules/toml.md".modules      = [ ../modules/toml.nix        ];
  files.docs."/gh-pages/src/modules/watch.md".modules     = [ ../modules/watch           ];
  files.docs."/gh-pages/src/modules/yaml.md".modules      = [ ../modules/yaml.nix        ];
}

We could also generate a mdbook with it
# examples/book.nix

{lib, ...}:
let
  project   = "devshell-files";
  author    = "cruel-intentions";
  org-url   = "https://github.com/${author}";
  edit-path = "${org-url}/${project}/edit/master/examples/{path}";
in
{
  files.mdbook.authors      = ["Cruel Intentions <${org-url}>"];
  files.mdbook.enable       = true;
  files.mdbook.gh-author    = author;
  files.mdbook.gh-project   = project;
  files.mdbook.language     = "en";
  files.mdbook.multilingual = false;
  files.mdbook.summary      = builtins.readFile ./summary.md;
  files.mdbook.title        = "Nix DevShell Files Maker";
  files.mdbook.output.html.edit-url-template   = edit-path;
  files.mdbook.output.html.fold.enable         = true;
  files.mdbook.output.html.git-repository-url  = "${org-url}/${project}/tree/master";
  files.mdbook.output.html.no-section-label    = true;
  files.mdbook.output.html.site-url            = "/${project}/";
  files.gitignore.pattern.gh-pages             = true;
  files.text."/gh-pages/src/introduction.md" = builtins.readFile ./readme/about.md;
  files.text."/gh-pages/src/installation.md" = builtins.readFile ./readme/installation.md;
  files.text."/gh-pages/src/examples.md"     = builtins.import   ./readme/examples.nix;
  files.text."/gh-pages/src/modules.md"      = "## Writing new modules";
  files.text."/gh-pages/src/nix-lang.md"     = builtins.readFile ./readme/modules/nix-lang.md;
  files.text."/gh-pages/src/json-nix.md"     = builtins.import   ./readme/modules/json-vs-nix.nix lib;
  files.text."/gh-pages/src/module-spec.md"  = builtins.readFile ./readme/modules/modules.md;
  files.text."/gh-pages/src/share.md"        = builtins.readFile ./readme/modules/share.md;
  files.text."/gh-pages/src/document.md"     = builtins.import   ./readme/modules/document.nix;
  files.text."/gh-pages/src/builtins.md"     = builtins.readFile ./readme/modules/builtins.md;
  files.text."/gh-pages/src/todo.md"         = builtins.readFile ./readme/todo.md;
  files.text."/gh-pages/src/issues.md"       = builtins.readFile ./readme/issues.md;
  files.text."/gh-pages/src/seeAlso.md"      = builtins.readFile ./readme/seeAlso.md;
  files.alias.publish-as-gh-pages-from-local = ''
    # same as publish-as-gh-pages but works local
    cd $PRJ_ROOT
    ORIGIN=`git remote get-url origin`
    cd gh-pages
    mdbook build
    cd book
    git init .
    git add .
    git checkout -b gh-pages
    git commit -m "docs(gh-pages): update gh-pages" .
    git remote add origin $ORIGIN
    git push -u origin gh-pages --force
  '';  
}

And publish this mdbook to github pages with book-as-gh-pages alias.

Builtin Modules

Builtin Modules are modules defined with this same package.

They are already included when we use this package.

  • files.alias, create bash script alias
  • files.cmds, install packages from nix repository
  • files.docs, convert our modules file into markdown using nmd
  • files.git, configure git with file creation
  • files.on-call, connfigure file to created only when devshell-files command is called, not on shell start
  • files.gitignore, copy .gitignore from templates
  • files.hcl, create HCL files with nix syntax
  • files.json, create JSON files with nix syntax
  • files.mdbook, convert your markdown files to HTML using mdbook
  • files.nim, similar to files.alias, but compiles Nim code
  • files.nus, similar to files.alias, but runs in Nushell
  • files.nush, similar to files.nus, but for subcommands
  • files.services, process supervisor for development services using s6
  • files.rc , WIP, process supervisor for development services using s6-rc
  • files.spdx, copy LICENSE from templates
  • files.text, create free text files with nix syntax
  • files.toml, create TOML files with nix syntax
  • files.watch, create an alias and service to run command when file changes using inotify-tools
  • files.yaml, create YAML files with nix syntax

Our documentation is generated by files.text, files.docs and files.mdbook

files

type

submodule

files.alias

bash script to create an alias

type

attribute set of Concatenated string

example

{
  files.alias = {
    hello = "echo hello";
    world = ''
      #!/usr/bin/env python
      print("world")
    '';
  };
}

default

{
  files.alias = { };
}

files.complete

Auto complete for your alias or commands

type

lazy attribute set of (submodule)

example

{
  files.complete = {
    svcCtl = {
      bash = "complete -W \"$PRJ_SVCS\" svcCtl";
      fish = "complete -c svcCtl -a \"$PRJ_SVCS\"";
    };
  };
}

default

{
  files.complete = { };
}

files.complete.<name>.bash

type

null or string

default

{
  files.complete.<name>.bash = null;
}

files.complete.<name>.fish

type

null or string

default

{
  files.complete.<name>.fish = null;
}

files.cmds

Add commands to the environment.

https://search.nixos.org for more tools

type

attribute set of boolean

example

{
  files.cmds = {
    awscli = true;
    azure-cli = true;
    cargo = true;
    conda = true;
    convco = true;
    go_1_17 = true;
    nodejs-16_x = true;
    pipenv = true;
    python39 = true;
    ruby_3_0 = true;
    rustc = true;
    terraform = true;
    yarn = true;
  };
}

default

{
  files.cmds = { };
}

file

Attribute set of files to create into the project root.

type

attribute set of (submodule)

default

{
  file = { };
}

file.<name>.executable

Set the execute bit. If null, defaults to the mode of the source file or to false for files created through the text option.

type

null or boolean

default

{
  file.<name>.executable = null;
}

file.<name>.git-add

To add this file to git repository after creation

type

null or boolean

default

{
  file.<name>.git-add = null;
}

file.<name>.interpolate

Substitute $VAR with environment var before create file

type

boolean

default

{
  file.<name>.interpolate = false;
}

file.<name>.on-enter

This file will be created on enter in development shell

type

null or boolean

default

{
  file.<name>.on-enter = true;
}

file.<name>.source

Path of the source file or directory. If

devshell.file.<name?>.text is non-null then this option will automatically point to a file containing that text.

type

path

file.<name>.target

Path to target file relative to PRJ_ROOT.

type

string

default

{
  file.<name>.target = <name>;
}

file.<name>.text

Text of the file. If this option is null then

devshel.file.<name?>.source must be set.

type

null or strings concatenated with "\n"

default

{
  file.<name>.text = null;
}

files.git.auto-add

Whether to enable auto add files to git after creation.

type

boolean

example

{
  files.git.auto-add = true;
}

default

{
  files.git.auto-add = false;
}

files.gitignore.enable

Whether to enable Auto generated .gitignore file.

type

boolean

example

{
  files.gitignore.enable = true;
}

default

{
  files.gitignore.enable = false;
}

files.gitignore.pattern

Gitignore pattern to ignore

type

attribute set of boolean

example

{
  files.gitignore.pattern = {
    "ignore-this-file.txt" = true;
  };
}

default

{
  files.gitignore.pattern = { };
}

files.gitignore.template

Append gitignore template from https://github.com/github/gitignore

type

attribute set of boolean

example

{
  files.gitignore.template = {
    Android = true;
    "community/AWS/SAM" = true;
  };
}

default

{
  files.gitignore.template = { };
}

files.hcl

Create hcl files with correponding content

type

attribute set of (JSON value)

example

{
  files.hcl = {
    "/hellaos.hcl" = {
      greeting = "hello World";
    };
    "/hellows.hcl" = {
      greetings = [
        [
          "Hello World"
        ]
        [
          "Ola Mundo"
          [
            "Holla Que Tal"
          ]
        ]
      ];
    };
  };
}

default

{
  files.hcl = { };
}

files.json

Create json files with correponding content

type

attribute set of (JSON value)

example

{
  files.json = {
    "/hellaos.json" = {
      greeting = "hello World";
    };
    "/hellows.json" = {
      greetings = [
        [
          "Hello World"
        ]
        [
          "Ola Mundo"
          [
            "Holla Que Tal"
          ]
        ]
      ];
    };
  };
}

default

{
  files.json = { };
}

files.mdbook

Helps with mdbook creation https://rust-lang.github.io/mdBook/

by default it creates gh-pages/mdbook.toml and gh-pages/src/SUMMARY.md

It also create publish-as-gh-pages helper

Full configuration example
#examples/book.nix

{lib, ...}:
let
  project   = "devshell-files";
  author    = "cruel-intentions";
  org-url   = "https://github.com/${author}";
  edit-path = "${org-url}/${project}/edit/master/examples/{path}";
in
{
  files.mdbook.authors      = ["Cruel Intentions <${org-url}>"];
  files.mdbook.enable       = true;
  files.mdbook.gh-author    = author;
  files.mdbook.gh-project   = project;
  files.mdbook.language     = "en";
  files.mdbook.multilingual = false;
  files.mdbook.summary      = builtins.readFile ./summary.md;
  files.mdbook.title        = "Nix DevShell Files Maker";
  files.mdbook.output.html.edit-url-template   = edit-path;
  files.mdbook.output.html.fold.enable         = true;
  files.mdbook.output.html.git-repository-url  = "${org-url}/${project}/tree/master";
  files.mdbook.output.html.no-section-label    = true;
  files.mdbook.output.html.site-url            = "/${project}/";
  files.gitignore.pattern.gh-pages             = true;
  files.text."/gh-pages/src/introduction.md" = builtins.readFile ./readme/about.md;
  files.text."/gh-pages/src/installation.md" = builtins.readFile ./readme/installation.md;
  files.text."/gh-pages/src/examples.md"     = builtins.import   ./readme/examples.nix;
  files.text."/gh-pages/src/modules.md"      = "## Writing new modules";
  files.text."/gh-pages/src/nix-lang.md"     = builtins.readFile ./readme/modules/nix-lang.md;
  files.text."/gh-pages/src/json-nix.md"     = builtins.import   ./readme/modules/json-vs-nix.nix lib;
  files.text."/gh-pages/src/module-spec.md"  = builtins.readFile ./readme/modules/modules.md;
  files.text."/gh-pages/src/share.md"        = builtins.readFile ./readme/modules/share.md;
  files.text."/gh-pages/src/document.md"     = builtins.import   ./readme/modules/document.nix;
  files.text."/gh-pages/src/builtins.md"     = builtins.readFile ./readme/modules/builtins.md;
  files.text."/gh-pages/src/todo.md"         = builtins.readFile ./readme/todo.md;
  files.text."/gh-pages/src/issues.md"       = builtins.readFile ./readme/issues.md;
  files.text."/gh-pages/src/seeAlso.md"      = builtins.readFile ./readme/seeAlso.md;
  files.alias.publish-as-gh-pages-from-local = ''
    # same as publish-as-gh-pages but works local
    cd $PRJ_ROOT
    ORIGIN=`git remote get-url origin`
    cd gh-pages
    mdbook build
    cd book
    git init .
    git add .
    git checkout -b gh-pages
    git commit -m "docs(gh-pages): update gh-pages" .
    git remote add origin $ORIGIN
    git push -u origin gh-pages --force
  '';  
}

type

submodule

default

{
  files.mdbook = { };
}

files.mdbook.enable

Whether to enable mdbook module.

type

boolean

example

{
  files.mdbook.enable = true;
}

default

{
  files.mdbook.enable = false;
}

files.mdbook.authors

Book author

type

null or (non-empty (list of string))

example

{
  files.mdbook.authors = [
    "Cruel Intentions"
  ];
}

default

{
  files.mdbook.authors = null;
}

files.mdbook.build

mdbook output options

type

JSON value

example

{
  files.mdbook.build = {
    build-dir = "book";
  };
}

default

{
  files.mdbook.build = { };
}

files.mdbook.description

Desciption of this book

type

null or string

example

{
  files.mdbook.description = "Modules Docummentation";
}

default

{
  files.mdbook.description = null;
}

files.mdbook.gh-author

Github Owner

type

null or string

example

{
  files.mdbook.gh-author = "cruel-intentions";
}

default

{
  files.mdbook.gh-author = null;
}

files.mdbook.gh-project

Github project

type

null or string

example

{
  files.mdbook.gh-project = "devshell-files";
}

default

{
  files.mdbook.gh-project = null;
}

files.mdbook.language

Book language

type

null or non-empty string

example

{
  files.mdbook.language = "en";
}

default

{
  files.mdbook.language = null;
}

files.mdbook.multilingual

If book has multilingual support

type

boolean

example

{
  files.mdbook.multilingual = true;
}

default

{
  files.mdbook.multilingual = false;
}

files.mdbook.output

mdbook output options

type

JSON value

example

{
  files.mdbook.output = {
    html = {
      fold = {
        enable = true;
      };
    };
  };
}

default

{
  files.mdbook.output = { };
}

files.mdbook.preprocessor

mdbook preprocessor options

type

JSON value

example

{
  files.mdbook.preprocessor = {
    mathjax = {
      renderers = [
        "html"
      ];
    };
  };
}

default

{
  files.mdbook.preprocessor = { };
}

files.mdbook.root-dir

root path of book

type

non-empty string

example

{
  files.mdbook.root-dir = "/gh-pages";
}

default

{
  files.mdbook.root-dir = "/gh-pages";
}

files.mdbook.rust

mdbook rust options

type

JSON value

example

{
  files.mdbook.rust = {
    edition = "2018";
  };
}

default

{
  files.mdbook.rust = { };
}

files.mdbook.summary

Summary of our mkdbook

type

strings concatenated with "\n"

example

{
  files.mdbook.summary = ''
    # SUMMARY
    - [Intro](./intro.md)
  '';
}

default

{
  files.mdbook.summary = ''
    # SUMMARY
  '';
}

files.mdbook.title

Book title

type

null or string

example

{
  files.mdbook.title = "Devshell Files Modules";
}

default

{
  files.mdbook.title = null;
}

files.mdbook.use-default-preprocessor

Disable the default preprocessors

type

boolean

example

{
  files.mdbook.use-default-preprocessor = false;
}

default

{
  files.mdbook.use-default-preprocessor = true;
}

files.nim

Nim code to create an command

It includes some helpers and libraries for laziness, so it better fit prototyping and simples commands/script.

Vars:

  • PRJ_ROOT : devshell PRJ_ROOT env information
  • ARGS : Arguments command arguments
  • NO_ARGS : empty arguments
  • PWD : DirPath "."
  • All devshell like: let PRJ_DATA_DIR = env "PRJ_DATA_DIR"

Procs:

  • arg : get arg n, default="", ie. 1.arg
  • env : get env name, default="", ie. "PRJ_DATA_DIR".env
  • cd : set current dir
  • exec : execute {cmd}, args=NO_ARGS, dir=".".dirPath
  • jPath: creates a path to access json ie: "foo/0/^1/0..1/0..^1/bar".jPath
    • / : concat paths, ie: myPath / "blerg" / 0 / ^1 / 0 .. 1 / 0 .. ^1/baz
    • get : get JsonNode in path of object, myPath.get myObj
    • [] : get JsonNode in pat of object, myObj[myPath]
    • set : set JsonNode in path of object, myPath.set myObj, myVal
    • []= : set JsonNode in path of object, myObj[myPath] = myVal

Using:

  • sep : string
  • dir : string
  • path: JsonPath
  • obj : JsonNode

Imports:

  • Import almost all std libraries

Flags:

  • -w:off -d:ssl --threads:on --mm:orc --hints:off --parallelBuild:4 --tlsEmulation:on

Todo:

  • Add an option to configure flags
  • Add an option for static linking

Examples:

# examples/nim.nix
{pkgs, ...}:{
  # compile nim files and them to shell
  files.nim.helloNim    = ''echo ARGS'';
  # keep an /tmp file with last result for n seconds
  files.nim.memoise     = builtins.readFile ./nim/memoise.nim;
  # clustersSTS is a good candidate to be used with services
  files.nim.clustersSTS = builtins.readFile ./nim/clustersSTS.nim;
  # our poor man jq alternative
  files.nim.jsonildo    = builtins.readFile ./nim/jsonildo.nim;
  files.nim.helloNimAgain.deps = [ pkgs.termbox pkgs.nimPackages.nimbox ];
  files.nim.helloNimAgain.src  = ''
    import nimbox
    proc main() =
      var nb = newNimbox()
      defer: nb.shutdown()
    
      nb.print(0, 0, "Hello, world!")
      nb.present()
      sleep(1000)
    
    when isMainModule:
      main()
  '';
}

```</para>

#### type

attribute set of ((submodule) or Concatenated string)

#### example

```nix
{
  files.nim = {
    dokr = ''
      // create an docker compose alias
      // cd $PRJ_ROOT/subdir; docker "compose" $@
      exec "docker", "compose" + ARGS, PRJ_ROOT / "subdir"
    '';
    hello = ''
      #compile-at-mkshell
      
      # by default nim commands were compiled at first run to reduce
      # shell activation time, add comment #compile-at-mkshell to 
      # compile at shell activation
      echo "hello"
    '';
    manage = ''
      // create an pipenv alias
      // pipenv run python ./manage.py
      exec "pipenv", args("run python ./manage.py") + ARGS
    '';
  };
}

default

{
  files.nim = { };
}

files.nus

Nushell script to create an alias.

type

attribute set of (Concatenated string or list of Concatenated string)

example

{
  files.nus = {
    hello = [
      "name"
      "{hello: $name}"
    ];
    world = ''
      # it could use previous commands
      hello world
    '';
  };
}

default

{
  files.nus = { };
}

files.nuon.enable

Enable nuon command

type

boolean

example

{
  files.nuon.enable = true;
}

default

{
  files.nuon.enable = false;
}

files.nush

Nushell script to create an alias.

type

attribute set of attribute set of (Concatenated string or list of Concatenated string)

example

{
  files.nush = {
    hello = {
      en = [
        "arg"
        "{hello: $arg}"
      ];
      pt = [
        "arg"
        "{ola: $arg}"
      ];
    };
    world = {
      hello = ''
        # it could use previous commands
        hello en "World"
        hello pt "Mundo"
      '';
    };
  };
}

default

{
  files.nush = { };
}

files.rc

Service name/command and its configuration

  • {name}.bundle for bundles
  • {name}.oneshot for one shot activations scripts
  • {name}.longrun for deamons services

All services are disabled by default.

It will fail at activation time if:

  • More then onde type (bundle, oneshot, longrun) were defined for the same {name}
  • One dependency service ins't defined but required

type

attribute set of (submodule)

example

{
  files.rc = {
    hello = {
      enable = true;
      longrun = {
        run = "mySquirelD";
      };
    };
    hola = {
      bundle = {
        contents = [
          "hello"
          "world"
        ];
      };
      enable = true;
    };
    world = {
      enable = true;
      oneshot = {
        setup = "echo Hello World > $PRJ_DATA_DIR/hello.txt";
      };
    };
  };
}

default

{
  files.rc = { };
}

files.rc.<name>.enable

Enable this service

type

boolean

example

{
  files.rc.<name>.enable = true;
}

default

{
  files.rc.<name>.enable = false;
}

files.rc.<name>.bundle

bundle is a service aggregator

For example we can bundle every system (db, http, config) by microservice.

Like:

  • Commerce Bundle:
    • commerce database service
    • commerce api service
    • commerce cache service
  • BPMN Bundle
    • BPMN database service
    • BPMN api service
    • BPMN cache service

type

null or (submodule)

example

{
  files.rc.<name>.bundle = {
    contents = [
      "myFancyServiceName"
    ];
    essential = true;
  };
}

default

{
  files.rc.<name>.bundle = null;
}

files.rc.<name>.bundle.deps

Defines what makes part of this bundle

type

non-empty (list of non-empty string)

example

{
  files.rc.<name>.bundle.deps = [
    "myDep"
    "myOtherDep"
  ];
}

files.rc.<name>.bundle.essential

Mark this bundle and all its dependencies as essential

It means some commands may work to stop these services

type

boolean

example

{
  files.rc.<name>.bundle.essential = true;
}

default

{
  files.rc.<name>.bundle.essential = false;
}

files.rc.<name>.longrun

longrun are commands that run as deamon (in a infinity loop).

Applications:

  • static site service
  • test watcher
  • some script to warn that your coffe bottle is empty

type

null or (submodule)

example

{
  files.rc.<name>.longrun = {
    agreement = {
      description = "Check to confirm";
      label = "Are you sure?";
    };
  };
}

default

{
  files.rc.<name>.longrun = null;
}

files.rc.<name>.longrun.consumer-for

Defines names of other services this services expects receive data at stdin from theirs stdout

Dependency hierarchy are calculated at activation time.

See S6 documentation for more info

type

list of non-empty string

example

{
  files.rc.<name>.longrun.consumer-for = [
    "myService"
  ];
}

default

{
  files.rc.<name>.longrun.consumer-for = [ ];
}

files.rc.<name>.longrun.data

Path that contains data for your service

type

null or path

example

{
  files.rc.<name>.longrun.data = "./.data";
}

default

{
  files.rc.<name>.longrun.data = null;
}

files.rc.<name>.longrun.deps

Defines names of other services required to be ready to this service work.

Dependency hierarchy are calculated at activation time.

See S6 documentation for more info

type

list of non-empty string

example

{
  files.rc.<name>.longrun.deps = [
    "myDependency"
  ];
}

default

{
  files.rc.<name>.longrun.deps = [ ];
}

files.rc.<name>.longrun.env

Path that contains env data for your service

type

null or path

example

{
  files.rc.<name>.longrun.env = "./.direnv";
}

default

{
  files.rc.<name>.longrun.env = null;
}

files.rc.<name>.longrun.essential

Mark this service and all its dependencies as essential

It means some commands may work to stop this service

type

boolean

example

{
  files.rc.<name>.longrun.essential = true;
}

default

{
  files.rc.<name>.longrun.essential = false;
}

files.rc.<name>.longrun.kill-after

Time in ms to give up from this service shutdown process and kill it anyway.

  • 0 means never

type

unsigned integer, meaning >=0

example

{
  files.rc.<name>.longrun.kill-after = 2005;
}

default

{
  files.rc.<name>.longrun.kill-after = 0;
}

files.rc.<name>.longrun.kill-finish-after

Time in ms to give up from this service finish script and kill it.

  • 0 means never

type

unsigned integer, meaning >=0

example

{
  files.rc.<name>.longrun.kill-finish-after = 2005;
}

default

{
  files.rc.<name>.longrun.kill-finish-after = 0;
}

files.rc.<name>.longrun.lock-descriptor

Configure a file descriptor to show that service is running

If value any value other then 0, 1 or 2, this service uses file-descriptor lock

type

null or unsigned integer, meaning >=0

example

{
  files.rc.<name>.longrun.lock-descriptor = 5;
}

default

{
  files.rc.<name>.longrun.lock-descriptor = 0;
}

files.rc.<name>.longrun.max-restarts

Times it should restart before we giveup

  • Could not be more than 4096

type

unsigned integer, meaning >=0

example

{
  files.rc.<name>.longrun.max-restarts = 2005;
}

default

{
  files.rc.<name>.longrun.max-restarts = 100;
}

files.rc.<name>.longrun.pipeline-name

Define name of this pipeline.

Dependency hierarchy are calculated at activation time.

See S6 documentation for more info

type

null or non-empty string

example

{
  files.rc.<name>.longrun.pipeline-name = "logMyService";
}

default

{
  files.rc.<name>.longrun.pipeline-name = null;
}

files.rc.<name>.longrun.producer-for

Defines a service to read this progam stdout (ie a logger)

Dependency hierarchy are calculated at activation time.

See S6 documentation for more info

type

null or non-empty string

example

{
  files.rc.<name>.longrun.producer-for = "willReadMyLog";
}

default

{
  files.rc.<name>.longrun.producer-for = null;
}

files.rc.<name>.longrun.readiness

Configure it to use pooling script as ready notification method

It make your service start script be called with s6-notifyoncheck.

See also ready-descriptor.

type

null or (submodule)

example

{
  files.rc.<name>.longrun.readiness = {
    command = "curl https://localhost";
  };
}

default

{
  files.rc.<name>.longrun.readiness = null;
}

files.rc.<name>.longrun.readiness.attempts

Times it should run before give up from this service

type

unsigned integer, meaning >=0

example

{
  files.rc.<name>.longrun.readiness.attempts = 30;
}

default

{
  files.rc.<name>.longrun.readiness.attempts = 7;
}

files.rc.<name>.longrun.readiness.command

Command to be executed as checking method

If not defined {name}-ready is assumed to be command to run

type

null or non-empty string

example

{
  files.rc.<name>.longrun.readiness.command = "curl -sSf http://localhost:8080";
}

default

{
  files.rc.<name>.longrun.readiness.command = null;
}

files.rc.<name>.longrun.readiness.fork-fork

If the check script should double fork

type

boolean

example

{
  files.rc.<name>.longrun.readiness.fork-fork = true;
}

default

{
  files.rc.<name>.longrun.readiness.fork-fork = false;
}

files.rc.<name>.longrun.readiness.initial-delay

Time in ms to await and check if its ready

type

unsigned integer, meaning >=0

example

{
  files.rc.<name>.longrun.readiness.initial-delay = 10;
}

default

{
  files.rc.<name>.longrun.readiness.initial-delay = 50;
}

files.rc.<name>.longrun.readiness.interval

Time in ms between check attempts

type

unsigned integer, meaning >=0

example

{
  files.rc.<name>.longrun.readiness.interval = 1000;
}

default

{
  files.rc.<name>.longrun.readiness.interval = 100;
}

files.rc.<name>.longrun.readiness.timeout

Time in ms to give up from this service

type

unsigned integer, meaning >=0

example

{
  files.rc.<name>.longrun.readiness.timeout = 3000;
}

default

{
  files.rc.<name>.longrun.readiness.timeout = 2000;
}

files.rc.<name>.longrun.ready-descriptor

Configure how to check if this service ready notification method

If value any value other then 0, 1 or 2, this service uses file-descriptor notification method

If attr readiness is defined, use pooling as notification method

type

null or unsigned integer, meaning >=0

example

{
  files.rc.<name>.longrun.ready-descriptor = 3;
}

default

{
  files.rc.<name>.longrun.ready-descriptor = null;
}

files.rc.<name>.longrun.signal

Signal service expects to init down process

type

unsigned integer, meaning >=0

example

{
  files.rc.<name>.longrun.signal = 3;
}

default

{
  files.rc.<name>.longrun.signal = 15;
}

files.rc.<name>.longrun.start

' execline setup script.

If not defined {name}-start is assumed to be command to run

Example for non execline scripts:

```nix
files.rc.alo.oneshot.start =
# creates a python script to run when srv start
# and save it at nix store
let start = pkgs.writeScript "mySrvStartScript" 
''
  #!/usr/bin/env -S python
  print("Alô!")  # is hello in portuguese
'';
# use the nix store address
in "${start}";
```</para>

type

null or non-empty string

example

{
  files.rc.<name>.longrun.start = "mysqld";
}

default

{
  files.rc.<name>.longrun.start = null;
}

files.rc.<name>.longrun.stop

execline teardown script.

If not defined {name}-stop is assumed to be command to run

Example for non execline scripts:

files.rc.alo.oneshot.stop = 
# creates a python script to run when srv stop
# and save it at nix store
let stop = pkgs.writeScript "mySrvStopScript"
''
  #!/usr/bin/env -S bash
  echo "Tchau!"  # is bye in portuguese
'';
# use the nix store address
in "${stop}";
```</para>

#### type

null or non-empty string

#### example

```nix
{
  files.rc.<name>.longrun.stop = "abSUrDO kill -9 1 1";
}

default

{
  files.rc.<name>.longrun.stop = null;
}

files.rc.<name>.longrun.timeout

This service should be ready in X milliseconds or it will considered a failure transition.

If null or 0 system will wait indefinitely

type

unsigned integer, meaning >=0

example

{
  files.rc.<name>.longrun.timeout = 10000;
}

default

{
  files.rc.<name>.longrun.timeout = 0;
}

files.rc.<name>.longrun.timeout-down

This service should be stoped in X milliseconds or it will considered a failure transition.

If null or 0 system will wait indefinitely

type

unsigned integer, meaning >=0

example

{
  files.rc.<name>.longrun.timeout-down = 10000;
}

default

{
  files.rc.<name>.longrun.timeout-down = 0;
}

files.rc.<name>.oneshot

oneshot is a change in the system state

Applications examples:

  • start other service that aren't controlled by this environment
  • mount files
  • sync sources

type

null or (submodule)

example

{
  files.rc.<name>.oneshot = {
    dependencies = [
      "holyMoses"
    ];
    setup = {
      example = "echo Hello World > $PRJ_DATA_DIR/hello.txt";
    };
    timeout = 2000;
  };
}

default

{
  files.rc.<name>.oneshot = null;
}

files.rc.<name>.oneshot.deps

Defines names of other services required to be ready to this service work.

Dependency hierarchy are calculated at activation time.

type

list of non-empty string

example

{
  files.rc.<name>.oneshot.deps = [
    "myDependency"
  ];
}

default

{
  files.rc.<name>.oneshot.deps = [ ];
}

files.rc.<name>.oneshot.essential

Mark this service and all its dependencies as essential

It means some commands may work to stop these services

type

boolean

example

{
  files.rc.<name>.oneshot.essential = true;
}

default

{
  files.rc.<name>.oneshot.essential = false;
}

files.rc.<name>.oneshot.start

execline setup script.

If not defined {name}-start is assumed to be command to run

Example for non execline scripts:

files.rc.alo.oneshot.start =
# creates a python script to run when srv start
# and save it at nix store
let start = pkgs.writeScript "mySrvStartScript" 
''
  #!/usr/bin/env -S python
  print("Alô!")  # is hello in portuguese
'';
# use the nix store address
in "${start}";
```</para>

#### type

null or non-empty string

#### example

```nix
{
  files.rc.<name>.oneshot.start = "echo Hello World > $PRJ_DATA_DIR/hello.txt";
}

default

{
  files.rc.<name>.oneshot.start = null;
}

files.rc.<name>.oneshot.stop

execline teardown script.

If not defined {name}-stop is assumed to be command to run

Example for non execline scripts:

files.rc.alo.oneshot.stop = 
# creates a python script to run when srv stop
# and save it at nix store
let stop = pkgs.writeScript "mySrvStopScript"
''
  #!/usr/bin/env -S bash
  echo "Tchau!"  # is bye in portuguese
'';
# use the nix store address
in "${stop}";
```</para>

#### type

null or non-empty string

#### example

```nix
{
  files.rc.<name>.oneshot.stop = "rm $PRJ_DATA_DIR/hello.txt";
}

default

{
  files.rc.<name>.oneshot.stop = null;
}

files.rc.<name>.oneshot.timeout

This service should be ready in X milliseconds or it will considered be in failure status.

If null or 0 system will wait indefinitely

type

unsigned integer, meaning >=0

example

{
  files.rc.<name>.oneshot.timeout = 10000;
}

default

{
  files.rc.<name>.oneshot.timeout = 0;
}

files.rc.<name>.oneshot.timeout-down

This service should be stop in X milliseconds or it will considered be in failure status.

If null or 0 system will wait indefinitely

type

unsigned integer, meaning >=0

example

{
  files.rc.<name>.oneshot.timeout-down = 10000;
}

default

{
  files.rc.<name>.oneshot.timeout-down = 0;
}

files.services

Service name/command to enable

{name} must be a executable command that runs forever.

Optionally could exist a {name}-finish command to stop it properly.

Optionally could exist a {name}-log command to log it properly.

Default log informations goes to $PRJ_SVCS_LOG/{name}/current .

initSvcs is a special name to auto start the process supervisor s6, it control all other services.

If we don't set initSvcs service, we can start it running initSvcs.

stopSvcsd is a special name to auto stop the process supervisor If S6 wont stop by itself, we could run stopSvcs when it's done.

It defines two env vars:

  • PRJ_SVCS_DIR: $PRJ_DATA_DIR/services
  • PRJ_SVCS_LOG: $PRJ_DATA_DIR/log

See S6 documentation.

We can use config.files.alias to help create your services scripts.

examples:

Use some program as service

Configure httplz (http static server) as service

{
  # # Make services start when you enter in shell
  # files.services.initSvcs = true;

  # static http service
  files.cmds.httplz    = true; # Install httplz
  files.services.httpd = true; # Use cmd 'httpd' as service
  files.alias.httpd    = ''
    # This is an alias of httplz
    cd $PRJ_ROOT/gh-pages
    mdbook build

    # IMPORTANT NOTE:
    # if we don't use `exec` it may not stop with stopSvcs
    exec httplz --port 8022 $PRJ_ROOT/gh-pages/book/
  '';

  # greeting service
  files.cmds.notify-desktop = true; # Install notify-desktop
  files.services.greet      = true; # Use cmd 'greet' as service
  files.alias.greet         = ''
    # Greet people
    set +e # ignore errors bacause this service isn't critical
    notify-desktop -i network-server "Welcome"

    # our stdout goes to .data/log/greet
    echo Dornröschen

    # if service ends, it will restarted
    # we sleep emulating some actual service
    exec sleep infinity 
  '';
  files.alias.greet-finish  = ''
    notify-desktop -i network-server "See you later"
  '';

  # This is a service to speedup direnv
  files.direnv.auto-build.enable = true;

  # special service to auto start
  files.services.initSvcs  = true;

  # special service to auto stopSvcs
  files.services.stopSvcsd = true;

  # files.services-opts.tini.enable = true;
  # files.services-opts.namespace.enable = true;

  # # RC (unstable) is another interface it uses s6-rc
  # # http://skarnet.org/software/s6-rc/why.html
  #
  # files.rc.hello.enable  = true;
  # files.rc.hello.oneshot.start    = ''wait 1000000 "hello world"'';
  # files.rc.hello.oneshot.stop     = ''echo "bye bye world"'';
  # files.rc.hello.oneshot.timeout  = 1200;
  #
  # files.rc.olla.enable   = true;
  # files.rc.olla.longrun.start     = ''echo "hello world"'';
  # files.rc.olla.longrun.stop      = ''echo "bye bye world"'';
  # files.rc.olla.longrun.deps      = ["hello"];
  #
  # files.rc.hellos.enable = true;
  # files.rc.hellos.bundle.deps     = ["hello" "olla"];
}

Create your own service with bash

{
  # Make all services start when you enter in shell
  files.services.initSvcs = true;

  # Use hello configured below as service
  files.services.hello    = true;

  # Creates an hello command in terminal
  files.alias.hello       = ''
    while :
    do
      echo "Hello World!"
    	sleep 60
    done
  '';
}

See also:

  • files.services-opts.tini.enable to use Tini as supervisor supervisor
  • files.services-opts.namespace.enable to use namespace

Know bugs:

If we don't use exec in our alias, some necromancer could form an army of undead process, and start a war against our system, since they both may require the most scarse resource of our lands.

You can enable namespace if it is an problem

type

attribute set of boolean

example

{
  files.services = {
    hello = true;
  };
}

default

{
  files.services = { };
}

files.services-opts.namespace.enable

Whether to enable Linux Namespaces.

type

boolean

example

{
  files.services-opts.namespace.enable = true;
}

default

{
  files.services-opts.namespace.enable = false;
}

files.services-opts.namespace.options

type

string

default

{
  files.services-opts.namespace.options = "--pid --fork --map-root-user";
}

files.services-opts.tini.enable

Whether to enable Tini Supervisor.

type

boolean

example

{
  files.services-opts.tini.enable = true;
}

default

{
  files.services-opts.tini.enable = false;
}

files.services-opts.tini.options

type

string

default

{
  files.services-opts.tini.options = "-sp SIGTERM";
}

files.license.enable

Whether to enable auto generated license file.

type

boolean

example

{
  files.license.enable = true;
}

default

{
  files.license.enable = false;
}

files.license.spdx

Use SPDX as template https://github.com/spdx/license-list-data/tree/master/text

type

submodule

example

{
  files.license.spdx = {
    name = "MIT";
    vars = {
      "copyright holders" = "Cruel Intentions";
      year = "2021";
    };
  };
}

default

{
  files.license.spdx = { };
}

files.license.spdx.name

SPDX text name without extension

type

string

example

{
  files.license.spdx.name = "MIT";
}

files.license.spdx.vars

Most SPDX templates has some placeholders like <OWNER>, it is case sensitive

type

attribute set of string

example

{
  files.license.spdx.vars = {
    OWNER = "cruel-intentions";
    "URL for Development Group/Institution" = "https://github.com/cruel-intentions";
    year = "2021";
    "yyyy, yyyy" = "2021, 2099";
  };
}

default

{
  files.license.spdx.vars = { };
}

files.text

Create text files with correponding content

type

attribute set of strings concatenated with "\n"

example

{
  files.text = {
    "/hello.txt" = ''
                         (((((            *********          ****                    
                        (((((((            *********        *******                  
                        (((((((((           .*********    *********                  
                         (((((((((            *********  *********                   
                           (((((((((           *****************                     
                            (((((((((            **************                      
                 (((((((((((((((((((((((((((((((( ***********            (           
               (((((((((((((((((((((((((((((((((((( *********          (((((         
              (((((((((((((((((((((((((((((((((((((( *********        (((((((        
                           *********                   *********    /((((((((        
                          *********                     *********  (((((((((         
                        *********                         ******* ((((((((/          
                       *********                           **** (((((((((            
       ***********************.                             ,* (((((((((((((((((((((.
      ***********************                                ((((((((((((((((((((((((
       *********************.((                             (((((((((((((((((((((((( 
                 ********* ((((((                         (((((((((                  
                ********* ((((((((                       (((((((((                   
              *********    (((((((((                   (((((((((                     
             *********      (((((((((                                                
              .*****         /((((((((**************************************         
                ***           *((((((((( **********************************          
                             (((((((((((( *******************************,           
                            (((((((((((((((           ,********                      
                          ((((((((((((((((((            *********                    
                         (((((((((   (((((((((           *********                   
                       (((((((((      (((((((((            *********                 
                         ((((((         (((((((((           ******                   
    '';
  };
}

default

{
  files.text = { };
}

files.toml

Create toml files with correponding content

type

attribute set of (JSON value)

example

{
  files.toml = {
    "/hellaos.toml" = {
      greeting = "hello World";
    };
    "/hellows.toml" = {
      greetings = [
        [
          "Hello World"
        ]
        [
          "Ola Mundo"
          [
            "Holla Que Tal"
          ]
        ]
      ];
    };
  };
}

default

{
  files.toml = { };
}

files

type

submodule

files.watch

Alias that run commands when files changes using inotify Note: It uses files.services so you should to start services with initSvcs. Or you could auto start/stop by adding files.services.initSvcs = true; files.services.stopSvcsd = true;

type

lazy attribute set of (submodule)

example

{
  files.watch = {
    FOO = {
      cmd = "echo $file $dir $time $events";
      event = {
        modify = true;
      };
      exclude = ".+\\.png$";
      files = "$PRJ_ROOT/src";
      include = ".+\\.js$";
      recursive = true;
    };
  };
}

default

{
  files.watch = { };
}

files.watch.<name>.enable

Run it when all other services start

type

boolean

default

{
  files.watch.<name>.enable = true;
}

files.watch.<name>.cmd

Command that will run with args $file $dir $time $events

type

string

files.watch.<name>.event

What kinds of events must be watched

type

submodule

default

{
  files.watch.<name>.event = { };
}

files.watch.<name>.event.access

type

boolean

default

{
  files.watch.<name>.event.access = false;
}

files.watch.<name>.event.attrib

type

boolean

default

{
  files.watch.<name>.event.attrib = false;
}

files.watch.<name>.event.close

type

boolean

default

{
  files.watch.<name>.event.close = false;
}

files.watch.<name>.event.close_nonwrite

type

boolean

default

{
  files.watch.<name>.event.close_nonwrite = false;
}

files.watch.<name>.event.close_write

type

boolean

default

{
  files.watch.<name>.event.close_write = false;
}

files.watch.<name>.event.create

type

boolean

default

{
  files.watch.<name>.event.create = false;
}

files.watch.<name>.event.delete

type

boolean

default

{
  files.watch.<name>.event.delete = false;
}

files.watch.<name>.event.modify

type

boolean

default

{
  files.watch.<name>.event.modify = true;
}

files.watch.<name>.event.move

type

boolean

default

{
  files.watch.<name>.event.move = false;
}

files.watch.<name>.event.move_from

type

boolean

default

{
  files.watch.<name>.event.move_from = false;
}

files.watch.<name>.event.move_to

type

boolean

default

{
  files.watch.<name>.event.move_to = false;
}

files.watch.<name>.event.open

type

boolean

default

{
  files.watch.<name>.event.open = false;
}

files.watch.<name>.event.unmount

type

boolean

default

{
  files.watch.<name>.event.unmount = false;
}

files.watch.<name>.exclude

Regexp to exclude matching files

type

string

default

{
  files.watch.<name>.exclude = "(.*/\\.|.+~$)";
}

files.watch.<name>.excludei

Case insesitive regexp to exclude matching files

type

string

default

{
  files.watch.<name>.excludei = "";
}

files.watch.<name>.files

Files to be watched

type

strings concatenated with "\n"

default

{
  files.watch.<name>.files = "$PRJ_ROOT";
}

files.watch.<name>.follow

Follow symbolic links

type

boolean

default

{
  files.watch.<name>.follow = false;
}

files.watch.<name>.include

Regexp to include only matching files

type

string

default

{
  files.watch.<name>.include = "";
}

files.watch.<name>.includei

Case insesitive regexp to include only matching files

type

string

default

{
  files.watch.<name>.includei = "";
}

files.watch.<name>.recursive

Recursivelly watch directories

type

boolean

default

{
  files.watch.<name>.recursive = true;
}

files.watch.<name>.timefmt

type

string

default

{
  files.watch.<name>.timefmt = "%FT%T.000%zZ";
}

files.yaml

Create yaml files with correponding content

type

attribute set of (JSON value)

example

{
  files.yaml = {
    "/hellaos.yaml" = {
      greeting = "hello World";
    };
    "/hellows.yaml" = {
      greetings = [
        [
          "Hello World"
        ]
        [
          "Ola Mundo"
          [
            "Holla Que Tal"
          ]
        ]
      ];
    };
  };
}

default

{
  files.yaml = { };
}

TODO

  • Add modules for especific cases:
    • ini files
    • most common ci/cd configuration
  • Verify if devshell could add it as default

Issues

This project uses git as version control, if your are using other version control system it may not work.

See also

Don't look at

  • *nix general definition of Unix/Linux
  • Nixos.com NSFW
  • Nixos.com.br furniture