From 1d92f4686c4803c802dce66804eba9ebe61efc1a Mon Sep 17 00:00:00 2001
From: Ben Sima <ben@bsima.me>
Date: Tue, 13 Oct 2020 11:57:29 -0400
Subject: Move nix files into Biz.Bild namespace

---
 Biz/Bild/Deps.nix     |  60 ++++++++++++++++++++
 Biz/Bild/Nixpkgs.nix  |  13 +++++
 Biz/Bild/Overlay.nix  |  56 +++++++++++++++++++
 Biz/Bild/Repl.nix     |   1 +
 Biz/Bild/Rules.nix    | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++
 Biz/Bild/ShellHook.sh |  98 +++++++++++++++++++++++++++++++++
 Biz/Bild/Sources.json | 103 ++++++++++++++++++++++++++++++++++
 Biz/Bild/Sources.nix  | 138 ++++++++++++++++++++++++++++++++++++++++++++++
 Biz/Bild/Wemux.nix    |  12 ++++
 9 files changed, 630 insertions(+)
 create mode 100644 Biz/Bild/Deps.nix
 create mode 100644 Biz/Bild/Nixpkgs.nix
 create mode 100644 Biz/Bild/Overlay.nix
 create mode 100644 Biz/Bild/Repl.nix
 create mode 100644 Biz/Bild/Rules.nix
 create mode 100644 Biz/Bild/ShellHook.sh
 create mode 100644 Biz/Bild/Sources.json
 create mode 100644 Biz/Bild/Sources.nix
 create mode 100644 Biz/Bild/Wemux.nix

(limited to 'Biz')

diff --git a/Biz/Bild/Deps.nix b/Biz/Bild/Deps.nix
new file mode 100644
index 0000000..dccbd81
--- /dev/null
+++ b/Biz/Bild/Deps.nix
@@ -0,0 +1,60 @@
+[
+  "MonadRandom"
+  "QuickCheck"
+  "acid-state"
+  "aeson"
+  "async"
+  "bytestring"
+  "capability"
+  "clay"
+  "config-ini"
+  "containers"
+  "directory"
+  "ekg"
+  "envy"
+  "fast-logger"
+  "filepath"
+  "ghcjs-base"
+  "haskeline"
+  "http-types"
+  "ixset"
+  "katip"
+  "lucid"
+  "miso"
+  "monad-logger"
+  "monad-metrics"
+  "mtl"
+  "network-uri"
+  "optparse-simple"
+  "parsec"
+  "process"
+  "protolude"
+  "quickcheck-instances"
+  "random"
+  "regex-applicative"
+  "req"
+  "safecopy"
+  "scotty"
+  "servant"
+  "servant-auth"
+  "servant-auth-server"
+  "servant-lucid"
+  "servant-server"
+  "split"
+  "stm"
+  "string-quote"
+  "tasty"
+  "text"
+  "time"
+  "transformers"
+  "unagi-chan"
+  "unix"
+  "unordered-containers"
+  "vector"
+  "wai"
+  "wai-app-static"
+  "wai-extra"
+  "wai-middleware-metrics"
+  "warp"
+  "x509"
+]
diff --git a/Biz/Bild/Nixpkgs.nix b/Biz/Bild/Nixpkgs.nix
new file mode 100644
index 0000000..21be382
--- /dev/null
+++ b/Biz/Bild/Nixpkgs.nix
@@ -0,0 +1,13 @@
+let
+  sources = import ./Sources.nix;
+  nixpkgs = import sources.nixpkgs {
+    system = __currentSystem;
+    overlays = [
+      (_: _: { inherit sources; })
+      (import ./Overlay.nix)
+      (_: pkgs: pkgs.overridePinnedDeps pkgs.overrideSource)
+      (_: _: { niv = import sources.niv {}; })
+      (_: pkgs: { wemux = pkgs.callPackage ./Wemux.nix {}; })
+    ];
+  };
+in nixpkgs
diff --git a/Biz/Bild/Overlay.nix b/Biz/Bild/Overlay.nix
new file mode 100644
index 0000000..b8fe269
--- /dev/null
+++ b/Biz/Bild/Overlay.nix
@@ -0,0 +1,56 @@
+_: pkgs:
+
+let
+  simpleCabalBuilder = self: name:
+      self.callCabal2nix name pkgs.sources.${name} {};
+  buildCabal = self: name: subdir:
+    if isNull subdir then
+      self.callCabal2nix name pkgs.sources.${name} {}
+    else
+      self.callCabal2nix name (pkgs.sources.${name} + "/${subdir}") {};
+in rec
+{
+  pinnedDeps = builtins.attrNames
+    (builtins.removeAttrs pkgs.sources ["__functor"]);
+  overridePinnedDeps = builder: pkgs.lib.genAttrs pinnedDeps builder;
+
+  # Modifies a derivation with our source and version, keeping old build
+  # rules. This will fail if build steps have changed, or if no build
+  # rules are available upstream..
+  overrideSource = name: pkgs.${name}.overrideAttrs (old: old // rec {
+    name = "${name}-${version}";
+    version = pkgs.sources.${name}.version or pkgs.sources.${name}.rev;
+    src = pkgs.sources.${name};
+  });
+
+  haskell = pkgs.haskell // {
+    packages = pkgs.haskell.packages // {
+      ghc865 = pkgs.haskell.packages.ghc865.override (old: {
+        overrides = with pkgs.pkgs.haskell.lib; self: super:
+          overridePinnedDeps (simpleCabalBuilder self) // {
+          acid-state = dontCheck super.acid-state; # mac: "too many open files"
+          servant-auth = buildCabal self "servant-auth" "servant-auth";
+          wai-middleware-metrics = dontCheck super.wai-middleware-metrics;
+        };
+      });
+      ghcjs = pkgs.haskell.packages.ghcjs.override (old: {
+        overrides = with pkgs.haskell.lib; self: super:
+          overridePinnedDeps (simpleCabalBuilder self) // {
+          Glob = dontCheck super.Glob;
+          QuickCheck = dontCheck super.QuickCheck;
+          base-compat-batteries = dontCheck super.http-types;
+          clay = dontCheck super.clay;
+          comonad = dontCheck super.comonad;
+          jsaddle-warp = dontCheck (self.callCabal2nix "jsaddle-warp" "${pkgs.sources.jsaddle}/jsaddle-warp" {});
+          http-types = dontCheck super.http-types;
+          network-uri= dontCheck super.network-uri;
+          scientific = dontCheck super.scientific; # takes forever
+          servant = dontCheck super.servant;
+          servant-auth = buildCabal self "servant-auth" "servant-auth";
+          tasty-quickcheck = dontCheck super.tasty-quickcheck;
+          time-compat = dontCheck super.time-compat;
+        };
+      });
+    };
+  };
+}
diff --git a/Biz/Bild/Repl.nix b/Biz/Bild/Repl.nix
new file mode 100644
index 0000000..94edf3a
--- /dev/null
+++ b/Biz/Bild/Repl.nix
@@ -0,0 +1 @@
+{ nixpkgs = import ./.; }
diff --git a/Biz/Bild/Rules.nix b/Biz/Bild/Rules.nix
new file mode 100644
index 0000000..6afe9a0
--- /dev/null
+++ b/Biz/Bild/Rules.nix
@@ -0,0 +1,149 @@
+{ nixpkgs }:
+
+with nixpkgs;
+
+let
+  # provided by .envrc
+  root = builtins.getEnv "BIZ_ROOT";
+
+  # general functions to put in a lib
+  lines = s: lib.strings.splitString "\n" s;
+  removeNull = ls: builtins.filter (x: x != null) ls;
+
+  depsToPackageSet = packageSet: deps:
+    lib.attrsets.attrVals deps packageSet;
+
+  # returns true if a is a subset of b, where a and b are attrsets
+  subset = a: b: builtins.all
+    (x: builtins.elem x b) a;
+
+  allDeps = import ./Deps.nix;
+
+  # gather data needed for compiling by analyzing the main module
+  analyze = main: rec {
+    # path to the module relative to the git root
+    relpath = builtins.replaceStrings ["${root}/"] [""]
+        (builtins.toString main);
+    # Haskell-appropriate name of the module
+    module = builtins.replaceStrings ["/" ".hs"] ["." ""] relpath;
+    # file contents
+    content = builtins.readFile main;
+    # search for the ': exe' declaration
+    exe = builtins.head (lib.lists.flatten (removeNull
+      (map (builtins.match "^-- : exe ([[:alnum:]._-]*)$")
+        (lines content))));
+    # collect all of the ': dep' declarations
+    deps = lib.lists.flatten (removeNull
+      (map (builtins.match "^-- : dep ([[:alnum:]._-]*)$")
+        (lines content)));
+  };
+
+  mkGhc = compiler: (deps: compiler (hp:
+    if (subset deps allDeps)
+    then depsToPackageSet hp deps
+    else throw ''
+      missing from nix/haskell-deps.nix:
+      ${toString (lib.lists.subtractLists allDeps deps)}
+    ''));
+
+  ghc_ = mkGhc pkgs.haskell.packages.ghc865.ghcWithHoogle;
+  ghcjs_ = mkGhc pkgs.haskell.packages.ghcjs.ghcWithPackages;
+in {
+  ghc = main:
+    let
+      data = analyze main;
+      ghc = ghc_ data.deps;
+    in stdenv.mkDerivation {
+      name = data.module;
+      src = ../.;
+      nativeBuildInputs = [ ghc ];
+      strictDeps = true;
+      buildPhase = ''
+        mkdir -p $out/bin
+        # compile with ghc
+        ${ghc}/bin/ghc -Werror -Weverything -i. \
+            --make ${main} \
+            -main-is ${data.module} \
+            -o $out/bin/${data.exe}
+      '';
+      # the install process was handled above
+      installPhase = "exit 0";
+    } // { env = ghc; };
+
+  ghcjs = main:
+    let
+      data = analyze main;
+      ghcjs = ghcjs_ data.deps;
+    in stdenv.mkDerivation {
+      name = data.module;
+      src = ../.;
+      nativeBuildInputs = [ ghcjs ];
+      strictDeps = true;
+      buildPhase = ''
+        mkdir -p $out/static
+        # compile with ghcjs
+        ${ghcjs}/bin/ghcjs -Werror -Weverything -i. \
+            --make ${main} \
+            -main-is ${data.module} \
+            -o ${data.exe}
+        # optimize js output
+        ${pkgs.closurecompiler}/bin/closure-compiler \
+          ${data.exe}/all.js > $out/static/${data.exe}
+      '';
+      installPhase = "exit 0";
+    } // { env = ghcjs; };
+
+  env = mkShell {
+    name = "bizdev";
+    buildInputs = [
+      (ghc_ allDeps)
+      # ghcjs doesn't need everything, and many things fail to build
+      (ghcjs_ [
+        "aeson"
+        "clay"
+        "containers"
+        "miso"
+        "protolude"
+        "servant"
+        "split"
+        "string-quote"
+        "text"
+        "ghcjs-base"
+      ])
+
+      nixpkgs.figlet
+      nixpkgs.hlint
+      nixpkgs.lolcat
+      nixpkgs.niv.niv
+      nixpkgs.ormolu
+      nixpkgs.python37Packages.black
+      nixpkgs.python37Packages.pylint
+      nixpkgs.wemux
+    ];
+    shellHook = ". ${./ShellHook.sh}";
+  };
+
+  os = cfg: (nixos (args: lib.attrsets.recursiveUpdate cfg {
+    boot.cleanTmpDir = true;
+    networking.firewall.allowPing = true;
+    nix.binaryCaches = [ "https://cache.nixos.org" ];
+    nix.gc.automatic = true;
+    nix.gc.dates = "Sunday 02:15";
+    nix.optimise.automatic = true;
+    nix.optimise.dates = [ "Sunday 02:30" ];
+    nixpkgs.overlays = overlays;
+    programs.mosh.enable = true;
+    programs.mosh.withUtempter = true;
+    security.acme.email = "ben@bsima.me";
+    security.acme.acceptTerms = true;
+    security.sudo.wheelNeedsPassword = false;
+    services.clamav.daemon.enable = true; # security
+    services.clamav.updater.enable = true; # security
+    services.fail2ban.enable = true; # security
+    services.openssh.enable = true;
+    services.openssh.openFirewall = true;
+    services.openssh.forwardX11 = true;
+    services.openssh.passwordAuthentication = false;
+    system.autoUpgrade.enable = false; # 'true' breaks our nixpkgs pin
+  })).toplevel;
+}
diff --git a/Biz/Bild/ShellHook.sh b/Biz/Bild/ShellHook.sh
new file mode 100644
index 0000000..75a0842
--- /dev/null
+++ b/Biz/Bild/ShellHook.sh
@@ -0,0 +1,98 @@
+function help() {
+  echo ""
+  echo "bizdev" | figlet | lolcat
+  echo ""
+  echo "   bild   compile code"
+  echo "   deps   manage dependencies with niv"
+  echo "   ghci   start ghci with correct options"
+  echo "   help   show this message"
+  echo "   hero   compile and start a dev server for herocomics.app"
+  echo "   lint   auto-lint all changed files"
+  echo "   pie    product improvement engine"
+  echo "   push   send a namespace to the cloud"
+  echo "   ship   lint, bild, and push one (or all) namespace(s)"
+}
+
+function bild() {
+  runghc Biz.Bild $@
+}
+
+function deps() {
+  niv --sources-file $BIZ_ROOT/Biz/Bild/Sources.json $@
+}
+
+alias ghci="ghci -i$BIZ_ROOT -ghci-script $BIZ_ROOT/.ghci"
+
+function hero() {
+  export HERO_PORT=3000
+  export HERO_KEEP=$BIZ_ROOT/_/keep
+  export HERO_SKEY=$BIZ_ROOT/_/skey
+  bild="runghc Biz.Bild"
+  if [[ ! -z "${IN_NIX_SHELL}" ]]
+  then
+    out="_/bild/dev"
+    # in dev mode, mmc.js is a directory of js assets
+    export HERO_NODE=$BIZ_ROOT/$out/static/mmc.js
+    rg --files \
+      | entr -rcs \
+        "$bild Hero.Host.hs && $bild Hero.Node.hs && $out/bin/mmc"
+  else
+    out="_/bild/nix"
+    export HERO_NODE=$BIZ_ROOT/$out/Hero.Node/static
+    rg --files \
+      | entr -rcs \
+        "$bild Hero.Host && $bild Hero.Node && $out/Hero.Host/bin/mmc"
+  fi
+}
+
+function lint() {
+  alias lint=$BIZ_ROOT/Biz/lint.py
+}
+
+function pie() {
+  runghc Biz.Pie $@
+}
+
+# TODO: convert to haskell
+function push() {
+  prefix=$(echo $PWD | sed -e "s|^$BIZ_ROOT/*||g" -e "s|/|.|g")
+  if [[ "$prefix" == "" ]]
+  then
+    target="$1"
+  else
+    target="$prefix.$1"
+  fi
+  what=$(realpath "$BIZ_ROOT/_/bild/$target")
+  # hack: get the domain from the activation script. there does not seem
+  # to be a way to get it from nix-instantiate
+  where=$(rg -r '$2' -e '(domainname ")(.*)(")' "$what/activate")
+  nix copy --to ssh://root@$where $what
+  ssh root@$where $what/bin/switch-to-configuration switch
+  ssh root@$where nix-env --profile /nix/var/nix/profiles/system --set $what
+}
+
+# TODO: convert to haskell
+function ship() {
+  set -ex
+  $BIZ_ROOT/Biz/lint.py
+  stuff=(${1})
+  if [[ ${#stuff[@]} -eq 0 ]]
+  then
+    stuff=(
+      Biz.Cloud
+      Biz.Dev
+      Que.Prod
+      Hero.Prod
+    )
+  fi
+  for thing in ${stuff[@]}
+  do
+    bild $thing
+  done
+  for thing in ${stuff[@]}
+  do
+    push $thing
+  done
+}
+
+help
diff --git a/Biz/Bild/Sources.json b/Biz/Bild/Sources.json
new file mode 100644
index 0000000..d2565a7
--- /dev/null
+++ b/Biz/Bild/Sources.json
@@ -0,0 +1,103 @@
+{
+    "clay": {
+        "branch": "master",
+        "description": "A CSS preprocessor as embedded Haskell.",
+        "homepage": "",
+        "owner": "sebastiaanvisser",
+        "repo": "clay",
+        "rev": "dcc4fc6d8b55af4814bd3f9bbb6d32e2fa2751a8",
+        "sha256": "1dm71z1q7yaq0kl2yb0vr0lsbd8byq5qkdb2kvr26jq48nfq2xdc",
+        "type": "tarball",
+        "url": "https://github.com/sebastiaanvisser/clay/archive/dcc4fc6d8b55af4814bd3f9bbb6d32e2fa2751a8.tar.gz",
+        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz",
+        "version": "cc7729b1b42a79e261091ff7835f7fc2a7ae3cee"
+    },
+    "jsaddle": {
+        "branch": "master",
+        "description": "JavaScript interface that works with GHCJS or GHC",
+        "homepage": "",
+        "owner": "ghcjs",
+        "repo": "jsaddle",
+        "rev": "d569be43f92b9b8c01dc3ee4c41401ab406a2076",
+        "sha256": "1m1xxy4l9ii91k1k504qkxh9k1ybprm1m66mkb9dqlwcpyhcccmv",
+        "type": "tarball",
+        "url": "https://github.com/ghcjs/jsaddle/archive/d569be43f92b9b8c01dc3ee4c41401ab406a2076.tar.gz",
+        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz",
+        "version": "1e39844"
+    },
+    "miso": {
+        "branch": "master",
+        "description": ":ramen: A tasty Haskell front-end framework",
+        "homepage": "https://haskell-miso.org",
+        "owner": "dmjio",
+        "repo": "miso",
+        "rev": "41234e419d6177fe05913a1bd885f811afe5cc9f",
+        "sha256": "1nby1y8yixv0a47h1bzdfjcwzah3km7bfd0phdb520ci4dgs30w1",
+        "type": "tarball",
+        "url": "https://github.com/dmjio/miso/archive/41234e419d6177fe05913a1bd885f811afe5cc9f.tar.gz",
+        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz",
+        "version": "1.5"
+    },
+    "niv": {
+        "branch": "master",
+        "description": "Easy dependency management for Nix projects",
+        "homepage": "https://github.com/nmattia/niv",
+        "owner": "nmattia",
+        "repo": "niv",
+        "rev": "f73bf8d584148677b01859677a63191c31911eae",
+        "sha256": "0jlmrx633jvqrqlyhlzpvdrnim128gc81q5psz2lpp2af8p8q9qs",
+        "type": "tarball",
+        "url": "https://github.com/nmattia/niv/archive/f73bf8d584148677b01859677a63191c31911eae.tar.gz",
+        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
+    },
+    "nixpkgs": {
+        "branch": "nixos-19.09",
+        "description": "Nix Packages collection",
+        "homepage": "https://github.com/NixOS/nixpkgs",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "b0c285807d6a9f1b7562ec417c24fa1a30ecc31a",
+        "sha256": "0waapr7aqz0h1fy1fqlx981ygllh91qx9sz1l2j2h59s46cdircl",
+        "type": "tarball",
+        "url": "https://github.com/NixOS/nixpkgs/archive/b0c285807d6a9f1b7562ec417c24fa1a30ecc31a.tar.gz",
+        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
+    },
+    "regex-applicative": {
+        "branch": "master",
+        "description": "Regex-based parsing with applicative interface",
+        "homepage": "",
+        "owner": "feuerbach",
+        "repo": "regex-applicative",
+        "rev": "449519c38e65753345e9a008362c011cb7a0a4d9",
+        "revision": "449519c38e65753345e9a008362c011cb7a0a4d9",
+        "sha256": "1vdrhsjzij5dm7rn10sic5dv9574yb0lyhzfv9psh7b08dsj8g1k",
+        "type": "tarball",
+        "url": "https://github.com/feuerbach/regex-applicative/archive/449519c38e65753345e9a008362c011cb7a0a4d9.tar.gz",
+        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz",
+        "version": "0.3.4"
+    },
+    "servant-auth": {
+        "branch": "master",
+        "description": null,
+        "homepage": null,
+        "owner": "haskell-servant",
+        "repo": "servant-auth",
+        "rev": "696fab268e21f3d757b231f0987201b539c52621",
+        "sha256": "1had0xyh511q7ggw2mlfhhk7pfbc30gqm2c9gj1y7pbflmsjgjda",
+        "type": "tarball",
+        "url": "https://github.com/haskell-servant/servant-auth/archive/696fab268e21f3d757b231f0987201b539c52621.tar.gz",
+        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
+    },
+    "wemux": {
+        "branch": "master",
+        "description": "Multi-User Tmux Made Easy",
+        "homepage": "",
+        "owner": "zolrath",
+        "repo": "wemux",
+        "rev": "01c6541f8deceff372711241db2a13f21c4b210c",
+        "sha256": "1y962nzvs7sf720pl3wa582l6irxc8vavd0gp4ag4243b2gs4qvm",
+        "type": "tarball",
+        "url": "https://github.com/zolrath/wemux/archive/01c6541f8deceff372711241db2a13f21c4b210c.tar.gz",
+        "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
+    }
+}
diff --git a/Biz/Bild/Sources.nix b/Biz/Bild/Sources.nix
new file mode 100644
index 0000000..8da4974
--- /dev/null
+++ b/Biz/Bild/Sources.nix
@@ -0,0 +1,138 @@
+# This file has been generated by Niv.
+
+let
+
+  #
+  # The fetchers. fetch_<type> fetches specs of type <type>.
+  #
+
+  fetch_file = pkgs: spec:
+    if spec.builtin or true then
+      builtins_fetchurl { inherit (spec) url sha256; }
+    else
+      pkgs.fetchurl { inherit (spec) url sha256; };
+
+  fetch_tarball = pkgs: name: spec:
+    let
+      ok = str: ! builtins.isNull (builtins.match "[a-zA-Z0-9+-._?=]" str);
+      # sanitize the name, though nix will still fail if name starts with period
+      name' = stringAsChars (x: if ! ok x then "-" else x) "${name}-src";
+    in
+      if spec.builtin or true then
+        builtins_fetchTarball { name = name'; inherit (spec) url sha256; }
+      else
+        pkgs.fetchzip { name = name'; inherit (spec) url sha256; };
+
+  fetch_git = spec:
+    builtins.fetchGit { url = spec.repo; inherit (spec) rev ref; };
+
+  fetch_local = spec: spec.path;
+
+  fetch_builtin-tarball = name: throw
+    ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`.
+        $ niv modify ${name} -a type=tarball -a builtin=true'';
+
+  fetch_builtin-url = name: throw
+    ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`.
+        $ niv modify ${name} -a type=file -a builtin=true'';
+
+  #
+  # Various helpers
+  #
+
+  # The set of packages used when specs are fetched using non-builtins.
+  mkPkgs = sources:
+    let
+      sourcesNixpkgs =
+        import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) {};
+      hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath;
+      hasThisAsNixpkgsPath = <nixpkgs> == ./.;
+    in
+      if builtins.hasAttr "nixpkgs" sources
+      then sourcesNixpkgs
+      else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then
+        import <nixpkgs> {}
+      else
+        abort
+          ''
+            Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or
+            add a package called "nixpkgs" to your sources.json.
+          '';
+
+  # The actual fetching function.
+  fetch = pkgs: name: spec:
+
+    if ! builtins.hasAttr "type" spec then
+      abort "ERROR: niv spec ${name} does not have a 'type' attribute"
+    else if spec.type == "file" then fetch_file pkgs spec
+    else if spec.type == "tarball" then fetch_tarball pkgs name spec
+    else if spec.type == "git" then fetch_git spec
+    else if spec.type == "local" then fetch_local spec
+    else if spec.type == "builtin-tarball" then fetch_builtin-tarball name
+    else if spec.type == "builtin-url" then fetch_builtin-url name
+    else
+      abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}";
+
+  # Ports of functions for older nix versions
+
+  # a Nix version of mapAttrs if the built-in doesn't exist
+  mapAttrs = builtins.mapAttrs or (
+    f: set: with builtins;
+    listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set))
+  );
+
+  # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
+  range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1);
+
+  # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
+  stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
+
+  # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
+  stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
+  concatStrings = builtins.concatStringsSep "";
+
+  # fetchTarball version that is compatible between all the versions of Nix
+  builtins_fetchTarball = { url, name, sha256 }@attrs:
+    let
+      inherit (builtins) lessThan nixVersion fetchTarball;
+    in
+      if lessThan nixVersion "1.12" then
+        fetchTarball { inherit name url; }
+      else
+        fetchTarball attrs;
+
+  # fetchurl version that is compatible between all the versions of Nix
+  builtins_fetchurl = { url, sha256 }@attrs:
+    let
+      inherit (builtins) lessThan nixVersion fetchurl;
+    in
+      if lessThan nixVersion "1.12" then
+        fetchurl { inherit url; }
+      else
+        fetchurl attrs;
+
+  # Create the final "sources" from the config
+  mkSources = config:
+    mapAttrs (
+      name: spec:
+        if builtins.hasAttr "outPath" spec
+        then abort
+          "The values in sources.json should not have an 'outPath' attribute"
+        else
+          spec // { outPath = fetch config.pkgs name spec; }
+    ) config.sources;
+
+  # The "config" used by the fetchers
+  mkConfig =
+    { sourcesFile ? ./Sources.json
+    , sources ? builtins.fromJSON (builtins.readFile sourcesFile)
+    , pkgs ? mkPkgs sources
+    }: rec {
+      # The sources, i.e. the attribute set of spec name to spec
+      inherit sources;
+
+      # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers
+      inherit pkgs;
+    };
+in
+mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); }
diff --git a/Biz/Bild/Wemux.nix b/Biz/Bild/Wemux.nix
new file mode 100644
index 0000000..365853f
--- /dev/null
+++ b/Biz/Bild/Wemux.nix
@@ -0,0 +1,12 @@
+{ sources, stdenv }:
+
+stdenv.mkDerivation rec {
+  name = "wemux-${version}";
+  version = "2020.04.03";
+  src = sources.wemux;
+  installPhase = ''
+    mkdir -p $out/bin
+    cp ${src}/wemux $out/bin
+    chmod +x $out/bin/wemux
+  '';
+}
-- 
cgit v1.2.3