[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
02/02: website: Add draft post about Guix Home.
From: |
Ludovic Courtès |
Subject: |
02/02: website: Add draft post about Guix Home. |
Date: |
Sun, 20 Mar 2022 11:23:22 -0400 (EDT) |
civodul pushed a commit to branch master
in repository guix-artwork.
commit 7c82c1234e81fc8e934be559c0a10d0b11abe47c
Author: Ludovic Courtès <ludo@gnu.org>
AuthorDate: Sun Mar 20 16:21:45 2022 +0100
website: Add draft post about Guix Home.
* website/drafts/home.md,
website/static/blog/img/home-extension-graph.dot,
website/static/blog/img/home-extension-graph.svg: New files.
---
website/drafts/home.md | 318 +++++++++++++++++++++++
website/static/blog/img/home-extension-graph.dot | 43 +++
website/static/blog/img/home-extension-graph.svg | 253 ++++++++++++++++++
3 files changed, 614 insertions(+)
diff --git a/website/drafts/home.md b/website/drafts/home.md
new file mode 100644
index 0000000..cb6f672
--- /dev/null
+++ b/website/drafts/home.md
@@ -0,0 +1,318 @@
+title: Keeping one’s home tidy
+date: 2022-03-21 15:00:00
+author: Ludovic Courtès
+tags: Home, Scheme API, Programming interfaces
+---
+
+How much effort to recreate your work environment when you switch to a
+new machine? What would it take to roll back to your previous
+environment once you’ve noticed a program no longer behaves as expected?
+What about sharing your environment with friends of yours? These are
+some of the things that [Guix
+Home](https://guix.gnu.org/manual/devel/en/html_node/Home-Configuration.html),
+which landed in Guix as a “technology preview” in September 2021, aims
+to make effortless, reliable, and fun.
+
+In a nutshell, Guix Home brings the [fully declarative configuration of
+Guix
+System](https://guix.gnu.org/manual/devel/en/html_node/Using-the-Configuration-System.html)
+to home directories. With Guix System, users and administrators provide
+a configuration file that defines the operating system configuration;
+with Guix Home, users provide a configuration file that defines the
+configuration of their work environment in their home directory. That
+configuration is meant to *stand alone*, to describe all the relevant
+aspects of your work environment. But what exactly goes in a _home
+environment_?
+
+# “Dot files” don’t live in a vacuum
+
+Among seasoned Unix-style users, we often equate “home environment” with
+“dot files”—configuration files in our home directory, from `~/.bashrc`
+and `~/.ssh/config` to `~/.emacs` and everything under `~/.config`.
+These files are precious and many store them under version control, to
+keep track of changes made to your configuration. That’s a good idea,
+but is that all it takes to describe your home environment? To roll
+back to a previous version?
+
+Of course not. Dot files don’t exist in a vacuum; at the very least,
+your home environment is not just a set of dot files, but also a set of
+installed packages. They work together: if you upgrade a package, the
+corresponding dot file might need to be adjusted; if a package is
+missing, its dot file is not of any use. Sometimes a home environment
+contains additional things: daemons (program that run in the
+background), or periodically executed jobs.
+
+Guix Home thus goes beyond dot files: it lets you declare and
+instantiate all these aspects that make up your home environment.
+
+# Genesis
+
+Guix Home was initially developed by Andrew Tropin as part of the [rde
+project](https://git.sr.ht/~abcdw/rde); it was integrated in Guix proper
+six months ago. I am writing this as an adopter and contributor, but
+there were a number of earlier adopters and earlier contributors. In
+fact, despite being still very much under development, the tool has
+already attracted a number of excited users eager to find a way to keep
+their home tidy!
+
+The idea of writing down a declaration of your home environment that you
+can reproduce anytime is a natural followup to everything Guix does—you
+could already declare a package set in a
+[manifest](https://guix.gnu.org/manual/devel/en/html_node/Invoking-guix-package.html#index-profile-manifest)
+or even a [complete operating
+system](https://guix.gnu.org/manual/devel/en/html_node/Using-the-Configuration-System.html).
+It had been floating in the air, in Nix land with [Home
+Manager](https://github.com/nix-community/home-manager) and in Guix land
+with the now-defunct [Guix Home
+Manager](https://framagit.org/tyreunom/guix-home-manager/) by Julien
+Lepiller. The latter was similar to today’s Guix Home, but went one
+step further by making your home directory read-only—yes, *read-only*!
+The main advantage is that it would ensure statelessness—you’d be sure
+that absolutely *all* your home configuration is under Guix Home
+Manager’s control; sub-directories containing mutable data would have to
+be explicitly declared. The downside is that it raised the barrier to
+entry: you’d have to either switch entirely, or not use it at all. Guix
+Home takes a more pragmatic approach and happily coexists with
+configuration managed “the old way”.
+
+# Getting started
+
+To get started, you need a Home configuration file. There’s
+[documentation](https://guix.gnu.org/manual/devel/en/html_node/Declaring-the-Home-Environment.html),
+but as always, starting from a blank page is a bit intimidating. So
+instead of starting from a blank page, you can let `guix home import`
+generate an initial config for you:
+
+```
+guix home import ~/src/guix-config
+```
+
+This will create the `~/src/config` directory and populate it with a
+bunch of files among which `home-configuration.scm` along these lines:
+
+```scheme
+(use-modules (gnu home)
+ (gnu packages)
+ (gnu services)
+ (guix gexp)
+ (gnu home services shells))
+
+(home-environment
+ (packages
+ (map (compose list specification->package+output)
+ (list "emacs-geiser-guile"
+ "emacs-geiser"
+ "pinentry-emacs"
+ "emacs-exwm"
+ "gnome-maps"
+ "pipe-viewer"
+ "emacs"
+ "pavucontrol"
+ "git"
+ "xterm"
+ "qemu"
+ "openssh")))
+ (services
+ (list (service home-bash-service-type
+ (home-bash-configuration
+ (aliases
+ '(("grep" . "grep --color=auto")
+ ("ll" . "ls -l")
+ ("ls" . "ls -p --color=auto")
+ ("qemu" . "qemu-system-x86_64 -enable-kvm -m 512")
+ ("rm" . "rm --one-file-system")))
+ (bashrc
+ (list (local-file "/home/charlie/src/guix-config/.bashrc"
+ "bashrc")))
+ (bash-profile
+ (list (local-file
+ "/home/charlie/src/guix-config/.bash_profile"
+ "bash_profile"))))))))
+```
+
+`guix home import` automatically added the packages of `~/.guix-profile`
+to the `packages` field. Because I’m using
+[Bash](https://www.gnu.org/software/bash), it also added an instance of
+[`home-bash-service-type`](https://guix.gnu.org/manual/devel/en/html_node/Shells-Home-Services.html)
+with aliases extracted from my `~/.bashrc`; it also made copies of
+`~/.bashrc` and `~/.bash_profile` and refers to them.
+
+Now that I have an initial configuration, I can first test it in an
+_isolated container_:
+
+```
+guix home container ~/src/guix-config/home-configuration.scm
+```
+
+This command gives an interactive shell in a container where my home
+environment, as declared in `home-configuration.scm`, is deployed.
+There I can see my home directory as it would look like if I deploy my
+home environment “for real”: I can see my `~/.bashrc` and co., I can
+check that all the packages declared are in `$PATH` and visible in
+`~/.guix-home`, and so on. And all this is safe: my actual home
+directory has been left unchanged!
+
+Once satisfied with my configuration, I can instantiate it:
+
+```
+guix home reconfigure ~/src/guix-config/home-configuration.scm
+```
+
+At that point, my actual home directory corresponds to that
+configuration. Some of my dot files are now provided by Guix Home, and
+thus they’re symbolic links (“symlinks”) to their read-only copy in
+`/gnu/store`:
+
+```
+$ ls -l ~/.bashrc ~/.bash_profile
+lrwxrwxrwx 1 charlie users 56 Mar 7 15:46 /home/charlie/.bash_profile ->
/gnu/store/lpdydssyyxx9n0xvp2jmv7yqgyr2pcg3-bash_profile
+lrwxrwxrwx 1 charlie users 50 Mar 7 15:46 /home/charlie/.bashrc ->
/gnu/store/kxc0j4i05sib04vf92nr8xxkb8isdfn7-bashrc
+```
+
+But don’t worry: before creating those symlinks, `guix home reconfigure`
+created backups of existing files under
+`~/TIMESTAMP-guix-home-legacy-configs-backup`, where `TIMESTAMP` is a
+Unix-style timestamp.
+
+And voilà, I have my first Guix Home generation!
+
+```
+$ guix home describe
+Generation 1 Mar 07 2022 15:46:20 (current)
+ file name: /var/guix/profiles/per-user/charlie/guix-home-1-link
+ canonical file name: /gnu/store/qr1c5jpfrj815ncv6yr2lfdgs8nq8kkn-home
+ channels:
+ guix:
+ repository URL: https://git.savannah.gnu.org/git/guix.git
+ branch: master
+ commit: 3ac1366648f933f7244c2d0b9926f7ba5d92a113
+ configuration file:
/gnu/store/xfgasfms9rhhigyj7i8za77zpqx6zbhn-configuration.scm
+```
+
+`guix home describe` shows provenance tracking we know and love from
+Guix System: all the info we need to redeploy the same home environment
+elsewhere, or at a different point in time.
+
+# Going further
+
+Alright, at this point, you might be thinking that it’s a lot of fuss
+but the only benefit over dot files under version control is that `guix
+home` also takes care of installing packages. Guix Home really shines
+once you use higher-level services, and when you start composing
+services together.
+
+To the example above, in the `services` field, we can add a service
+declaration that runs
+[Redshift](https://guix.gnu.org/manual/devel/en/html_node/Desktop-Home-Services.html#index-home_002dredshift_002dservice_002dtype),
+a program that adjusts the display color temperature according to the
+time of day:
+
+```scheme
+(service home-redshift-service-type
+ (home-redshift-configuration
+ (location-provider 'manual)
+ (latitude 35.81) ;northern hemisphere
+ (longitude -0.80))) ;west of Greenwich
+```
+
+The effect is that, as soon as we log in, under Xorg, Redshift will be
+started in the background as a [Shepherd
+service](https://guix.gnu.org/manual/devel/en/html_node/Shepherd-Home-Service.html).
+The Home-generated `~/.profile` takes care of spawning `shepherd`, which
+in turn spawns the `redshift` service:
+
+```
+$ herd status
+Started:
+ + root
+ + redshift
+```
+
+We gained another thing here: a consistent, unified configuration
+language. Instead of learning Redshift’s configuration file format, we
+define a `home-redshift-configuration` record, right in Scheme. Under
+the hood, that configuration is converted into Redshift’s file format;
+any error is caught at configuration time, when running `guix home
+reconfigure`, and we can be sure that Redshift is passed a valid
+configuration file.
+
+We can similarly define a [periodic mcron
+job](https://guix.gnu.org/manual/devel/en/html_node/Mcron-Home-Service.html#index-home_002dmcron_002dservice_002dtype),
+for example one that updates a
+[GNU Idutils](https://www.gnu.org/software/idutils) search database
+(that’s a pretty convenient and speedy way to look for code or
+documents!):
+
+```scheme
+(simple-service 'idutils home-mcron-service-type
+ ;; Every day at 12:15 and 19:15.
+ (list #~(job '(next-minute-from (next-hour '(12
19)) '(15))
+ (string-append
#$idutils "/bin/mkid \
+-o $HOME/.idutils/src.db $HOME/src"))))
+```
+
+Again, `guix home` creates a Shepherd service that start mcron with a
+configuration file containing definitions for periodic jobs, which we
+can inspect *via* `herd`:
+
+```
+$ herd schedule mcron | head -5
+Sun Mar 20 19:15:00 2022 +0000
+/gnu/store/2d026nan309qkci968k8gpa8fcv9q4mv-idutils-4.6/bin/mkid -m
$HOME/.id-lang.map -o $HOME/.idutils/doc $HOME/doc/ $HOME/.erc
+
+Mon Mar 21 12:15:00 2022 +0000
+/gnu/store/2d026nan309qkci968k8gpa8fcv9q4mv-idutils-4.6/bin/mkid -m
$HOME/.id-lang.map -o $HOME/.idutils/doc $HOME/doc/ $HOME/.erc
+```
+
+# Services, composed
+
+If you already use Guix System, all the above certainly looks familiar:
+Guix Home builds upon the [service
+framework](https://guix.gnu.org/manual/devel/en/html_node/Defining-Services.html)
+that powers Guix System. That framework lets us define relations among
+“services”, in a broad sense, and how services _extend_ each other—in
+the example above, `redshift` and `mcron` both extend `shepherd` by
+giving it a daemon to take care of. We can see those relations at play
+by running:
+
+```
+guix home extension-graph home-configuration.scm
+```
+
+… which, for the configuration described above, gives a graph that looks
+like this:
+
+![Extension graph for home
services.](/static/blog/img/home-extension-graph.svg)
+
+We see `redshift`, `mcron`, and `shepherd`, but we also see lower-level
+services that `guix home` instantiates for us, such as the `profile`
+service which takes care of deploying packages listed in the `packages`
+field under `~/.guix-home/profile`. Each arrow denotes a _service
+extension_. You can [read
+more](https://guix.gnu.org/manual/devel/en/html_node/Service-Composition.html)
+(and [view
+more!](https://archive.fosdem.org/2017/schedule/event/composingsystemservicesinguixsd/))
+about service composition. To satisfy our math and
+functional-programming geek audience, we should mention that service
+types and their extension operation form a
+[monoid](https://en.wikipedia.org/wiki/Monoid).
+
+# What’s next?
+
+Let’s be clear: Guix Home is pretty new and chances are that `guix home
+search` won’t give you the service you’re looking for. There’s also a
+bunch of open questions left, such as how to reuse services initially
+defined for Guix System in cases where they could be equally useful in
+Guix
+Home—[Syncthing](https://guix.gnu.org/manual/devel/en/html_node/Networking-Services.html#index-syncthing_002dservice_002dtype),
+for example.
+
+But while it’s still a “technology preview”, it’s already a tool that
+tinkerers can play with and benefit from. Patches adding new services
+have already been proposed; maybe you favorite service is next?
+Consider
+[contributing](https://guix.gnu.org/manual/devel/en/html_node/Contributing.html).
+
+With a new release coming up and its ten-year birthday, we’re happy to
+celebrate with a new tool extending the reach of declarative and
+reproducible deployment!
diff --git a/website/static/blog/img/home-extension-graph.dot
b/website/static/blog/img/home-extension-graph.dot
new file mode 100644
index 0000000..4254ba2
--- /dev/null
+++ b/website/static/blog/img/home-extension-graph.dot
@@ -0,0 +1,43 @@
+digraph "Guix service" {
+ "140222976182944" [label = "home", shape = box, fontname = sans];
+ "140222976182912" -> "140222976182944" [color = red];
+ "140222976183104" -> "140222976182944" [color = cyan3];
+ "140222976183136" -> "140222976182944" [color = darkgoldenrod];
+ "140222976183168" -> "140222976182944" [color = darkviolet];
+ "140223216368000" -> "140222976182944" [color = darkseagreen];
+ "140222976180704" -> "140222976182944" [color = darkgoldenrod];
+ "140222976182912" [label = "home-profile", shape = box, fontname = sans];
+ "140222976183040" -> "140222976182912" [color = darkgoldenrod];
+ "140222879378240" -> "140222976182912" [color = darkgoldenrod];
+ "140222976180640" -> "140222976182912" [color = magenta];
+ "140222976180576" -> "140222976182912" [color = cyan3];
+ "140222976183040" [label = "home-fontconfig", shape = box, fontname = sans];
+ "140222879378240" [label = "home-bash", shape = box, fontname = sans];
+ "140222976180640" [label = "home-mcron", shape = box, fontname = sans];
+ "140223216369280" -> "140222976180640" [color = blue];
+ "140223216369280" [label = "idutils", shape = box, fontname = sans];
+ "140222976180576" [label = "home-shepherd", shape = box, fontname = sans];
+ "140222879378176" -> "140222976180576" [color = blue];
+ "140222976180640" -> "140222976180576" [color = magenta];
+ "140222879378176" [label = "home-redshift", shape = box, fontname = sans];
+ "140222976183104" [label = "home-environment-variables", shape = box,
fontname = sans];
+ "140222976183008" -> "140222976183104" [color = dimgrey];
+ "140222976183008" [label = "home-xdg-base-directories", shape = box,
fontname = sans];
+ "140222976183136" [label = "home-activation", shape = box, fontname = sans];
+ "140222976183008" -> "140222976183136" [color = dimgrey];
+ "140222976183072" -> "140222976183136" [color = blue];
+ "140222976180576" -> "140222976183136" [color = cyan3];
+ "140222976180512" -> "140222976183136" [color = darkgoldenrod];
+ "140222976183072" [label = "home-symlink-manager", shape = box, fontname =
sans];
+ "140222976180512" [label = "home-run-on-change", shape = box, fontname =
sans];
+ "140222976183040" -> "140222976180512" [color = darkgoldenrod];
+ "140222976183168" [label = "home-run-on-first-login", shape = box, fontname
= sans];
+ "140222976180576" -> "140222976183168" [color = cyan3];
+ "140223216368000" [label = "home-provenance", shape = box, fontname = sans];
+ "140222976180704" [label = "home-files", shape = box, fontname = sans];
+ "140222976182976" -> "140222976180704" [color = darkviolet];
+ "140222976183040" -> "140222976180704" [color = darkgoldenrod];
+ "140222879378240" -> "140222976180704" [color = darkgoldenrod];
+ "140222976182976" [label = "home-shell-profile", shape = box, fontname =
sans];
+
+}
diff --git a/website/static/blog/img/home-extension-graph.svg
b/website/static/blog/img/home-extension-graph.svg
new file mode 100644
index 0000000..2366ce6
--- /dev/null
+++ b/website/static/blog/img/home-extension-graph.svg
@@ -0,0 +1,253 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.49.0 (20210828.1703)
+ -->
+<!-- Title: Guix service Pages: 1 -->
+<svg width="1195pt" height="662pt"
+ viewBox="0.00 0.00 1194.58 662.08" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4
658.08)">
+<title>Guix service</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-658.08
1190.58,-658.08 1190.58,4 -4,4"/>
+<!-- 140222976182944 -->
+<g id="node1" class="node">
+<title>140222976182944</title>
+<polygon fill="none" stroke="black" points="670.58,-54 614.58,-54 614.58,-18
670.58,-18 670.58,-54"/>
+<text text-anchor="middle" x="642.58" y="-32.3" font-family="sans"
font-size="14.00">home</text>
+</g>
+<!-- 140222976182912 -->
+<g id="node2" class="node">
+<title>140222976182912</title>
+<polygon fill="none" stroke="black" points="392.58,-200 288.58,-200
288.58,-164 392.58,-164 392.58,-200"/>
+<text text-anchor="middle" x="340.58" y="-178.3" font-family="sans"
font-size="14.00">home-profile</text>
+</g>
+<!-- 140222976182912->140222976182944 -->
+<g id="edge1" class="edge">
+<title>140222976182912->140222976182944</title>
+<path fill="none" stroke="red" d="M376.41,-163.92C433.98,-136.46 546.55,-82.79
605.14,-54.85"/>
+<polygon fill="red" stroke="red" points="606.88,-57.9 614.4,-50.44
603.87,-51.58 606.88,-57.9"/>
+</g>
+<!-- 140222976183104 -->
+<g id="node3" class="node">
+<title>140222976183104</title>
+<polygon fill="none" stroke="black" points="1026.58,-200 810.58,-200
810.58,-164 1026.58,-164 1026.58,-200"/>
+<text text-anchor="middle" x="918.58" y="-178.3" font-family="sans"
font-size="14.00">home-environment-variables</text>
+</g>
+<!-- 140222976183104->140222976182944 -->
+<g id="edge2" class="edge">
+<title>140222976183104->140222976182944</title>
+<path fill="none" stroke="#00cdcd" d="M885.84,-163.92C834.14,-136.94
733.92,-84.65 679.65,-56.34"/>
+<polygon fill="#00cdcd" stroke="#00cdcd" points="681.2,-53.2 670.71,-51.68
677.96,-59.4 681.2,-53.2"/>
+</g>
+<!-- 140222976183136 -->
+<g id="node4" class="node">
+<title>140222976183136</title>
+<polygon fill="none" stroke="black" points="792.58,-200 664.58,-200
664.58,-164 792.58,-164 792.58,-200"/>
+<text text-anchor="middle" x="728.58" y="-178.3" font-family="sans"
font-size="14.00">home-activation</text>
+</g>
+<!-- 140222976183136->140222976182944 -->
+<g id="edge3" class="edge">
+<title>140222976183136->140222976182944</title>
+<path fill="none" stroke="darkgoldenrod" d="M718.38,-163.92C703.44,-138.91
675.51,-92.14 658.04,-62.89"/>
+<polygon fill="darkgoldenrod" stroke="darkgoldenrod" points="660.9,-60.85
652.77,-54.06 654.89,-64.44 660.9,-60.85"/>
+</g>
+<!-- 140222976183168 -->
+<g id="node5" class="node">
+<title>140222976183168</title>
+<polygon fill="none" stroke="black" points="646.08,-200 467.08,-200
467.08,-164 646.08,-164 646.08,-200"/>
+<text text-anchor="middle" x="556.58" y="-178.3" font-family="sans"
font-size="14.00">home-run-on-first-login</text>
+</g>
+<!-- 140222976183168->140222976182944 -->
+<g id="edge4" class="edge">
+<title>140222976183168->140222976182944</title>
+<path fill="none" stroke="darkviolet" d="M566.78,-163.92C581.72,-138.91
609.65,-92.14 627.12,-62.89"/>
+<polygon fill="darkviolet" stroke="darkviolet" points="630.27,-64.44
632.39,-54.06 624.26,-60.85 630.27,-64.44"/>
+</g>
+<!-- 140223216368000 -->
+<g id="node6" class="node">
+<title>140223216368000</title>
+<polygon fill="none" stroke="black" points="1186.58,-200 1044.58,-200
1044.58,-164 1186.58,-164 1186.58,-200"/>
+<text text-anchor="middle" x="1115.58" y="-178.3" font-family="sans"
font-size="14.00">home-provenance</text>
+</g>
+<!-- 140223216368000->140222976182944 -->
+<g id="edge5" class="edge">
+<title>140223216368000->140222976182944</title>
+<path fill="none" stroke="darkseagreen" d="M1059.46,-163.92C961.86,-134.2
763.33,-73.76 680.35,-48.5"/>
+<polygon fill="darkseagreen" stroke="darkseagreen" points="681.18,-45.09
670.59,-45.53 679.14,-51.79 681.18,-45.09"/>
+</g>
+<!-- 140222976180704 -->
+<g id="node7" class="node">
+<title>140222976180704</title>
+<polygon fill="none" stroke="black" points="202.08,-200 113.08,-200
113.08,-164 202.08,-164 202.08,-200"/>
+<text text-anchor="middle" x="157.58" y="-178.3" font-family="sans"
font-size="14.00">home-files</text>
+</g>
+<!-- 140222976180704->140222976182944 -->
+<g id="edge6" class="edge">
+<title>140222976180704->140222976182944</title>
+<path fill="none" stroke="darkgoldenrod" d="M202.34,-167.71C297.06,-139.59
516.34,-74.48 604.68,-48.25"/>
+<polygon fill="darkgoldenrod" stroke="darkgoldenrod" points="605.71,-51.6
614.3,-45.4 603.72,-44.89 605.71,-51.6"/>
+</g>
+<!-- 140222976183040 -->
+<g id="node8" class="node">
+<title>140222976183040</title>
+<polygon fill="none" stroke="black" points="368.08,-490 237.08,-490
237.08,-454 368.08,-454 368.08,-490"/>
+<text text-anchor="middle" x="302.58" y="-468.3" font-family="sans"
font-size="14.00">home-fontconfig</text>
+</g>
+<!-- 140222976183040->140222976182912 -->
+<g id="edge7" class="edge">
+<title>140222976183040->140222976182912</title>
+<path fill="none" stroke="darkgoldenrod" d="M304.84,-453.85C311.21,-405.62
329.29,-268.57 337.01,-210.04"/>
+<polygon fill="darkgoldenrod" stroke="darkgoldenrod" points="340.49,-210.44
338.33,-200.06 333.55,-209.52 340.49,-210.44"/>
+</g>
+<!-- 140222976183040->140222976180704 -->
+<g id="edge22" class="edge">
+<title>140222976183040->140222976180704</title>
+<path fill="none" stroke="darkgoldenrod" d="M236.88,-463.48C167.02,-451.59
59.83,-421.31 9.58,-345 -27.04,-289.39 55.32,-234.21 111.28,-204.79"/>
+<polygon fill="darkgoldenrod" stroke="darkgoldenrod" points="113.06,-207.8
120.35,-200.12 109.86,-201.58 113.06,-207.8"/>
+</g>
+<!-- 140222976180512 -->
+<g id="node16" class="node">
+<title>140222976180512</title>
+<polygon fill="none" stroke="black" points="532.58,-345 368.58,-345
368.58,-309 532.58,-309 532.58,-345"/>
+<text text-anchor="middle" x="450.58" y="-323.3" font-family="sans"
font-size="14.00">home-run-on-change</text>
+</g>
+<!-- 140222976183040->140222976180512 -->
+<g id="edge19" class="edge">
+<title>140222976183040->140222976180512</title>
+<path fill="none" stroke="darkgoldenrod" d="M320.43,-453.75C346.55,-428.52
395.29,-381.43 425.14,-352.58"/>
+<polygon fill="darkgoldenrod" stroke="darkgoldenrod" points="427.91,-354.78
432.67,-345.31 423.04,-349.74 427.91,-354.78"/>
+</g>
+<!-- 140222879378240 -->
+<g id="node9" class="node">
+<title>140222879378240</title>
+<polygon fill="none" stroke="black" points="274.08,-345 179.08,-345
179.08,-309 274.08,-309 274.08,-345"/>
+<text text-anchor="middle" x="226.58" y="-323.3" font-family="sans"
font-size="14.00">home-bash</text>
+</g>
+<!-- 140222879378240->140222976182912 -->
+<g id="edge8" class="edge">
+<title>140222879378240->140222976182912</title>
+<path fill="none" stroke="darkgoldenrod" d="M240.33,-308.75C260.28,-283.73
297.35,-237.23 320.4,-208.32"/>
+<polygon fill="darkgoldenrod" stroke="darkgoldenrod" points="323.28,-210.31
326.78,-200.31 317.81,-205.95 323.28,-210.31"/>
+</g>
+<!-- 140222879378240->140222976180704 -->
+<g id="edge23" class="edge">
+<title>140222879378240->140222976180704</title>
+<path fill="none" stroke="darkgoldenrod" d="M218.26,-308.75C206.34,-284.06
184.32,-238.42 170.34,-209.44"/>
+<polygon fill="darkgoldenrod" stroke="darkgoldenrod" points="173.43,-207.8
165.93,-200.31 167.13,-210.84 173.43,-207.8"/>
+</g>
+<!-- 140222976180640 -->
+<g id="node10" class="node">
+<title>140222976180640</title>
+<polygon fill="none" stroke="black" points="514.58,-490 410.58,-490
410.58,-454 514.58,-454 514.58,-490"/>
+<text text-anchor="middle" x="462.58" y="-468.3" font-family="sans"
font-size="14.00">home-mcron</text>
+</g>
+<!-- 140222976180640->140222976182912 -->
+<g id="edge9" class="edge">
+<title>140222976180640->140222976182912</title>
+<path fill="none" stroke="magenta" d="M442.72,-453.66C418.53,-431.24
378.55,-389.74 359.58,-345 340.81,-300.73 338.58,-243.91 339.21,-210.67"/>
+<polygon fill="magenta" stroke="magenta" points="342.72,-210.45 339.51,-200.36
335.72,-210.25 342.72,-210.45"/>
+</g>
+<!-- 140222976180576 -->
+<g id="node11" class="node">
+<title>140222976180576</title>
+<polygon fill="none" stroke="black" points="676.58,-345 550.58,-345
550.58,-309 676.58,-309 676.58,-345"/>
+<text text-anchor="middle" x="613.58" y="-323.3" font-family="sans"
font-size="14.00">home-shepherd</text>
+</g>
+<!-- 140222976180640->140222976180576 -->
+<g id="edge13" class="edge">
+<title>140222976180640->140222976180576</title>
+<path fill="none" stroke="magenta" d="M480.8,-453.75C507.55,-428.41
557.59,-381.02 588.02,-352.21"/>
+<polygon fill="magenta" stroke="magenta" points="590.45,-354.73 595.3,-345.31
585.64,-349.65 590.45,-354.73"/>
+</g>
+<!-- 140222976180576->140222976182912 -->
+<g id="edge10" class="edge">
+<title>140222976180576->140222976182912</title>
+<path fill="none" stroke="#00cdcd" d="M580.92,-308.89C531.29,-282.89
436.88,-233.44 382.33,-204.87"/>
+<polygon fill="#00cdcd" stroke="#00cdcd" points="383.76,-201.67 373.28,-200.13
380.51,-207.87 383.76,-201.67"/>
+</g>
+<!-- 140222976180576->140222976183136 -->
+<g id="edge17" class="edge">
+<title>140222976180576->140222976183136</title>
+<path fill="none" stroke="#00cdcd" d="M627.45,-308.75C647.57,-283.73
684.97,-237.23 708.22,-208.32"/>
+<polygon fill="#00cdcd" stroke="#00cdcd" points="711.12,-210.3 714.66,-200.31
705.67,-205.91 711.12,-210.3"/>
+</g>
+<!-- 140222976180576->140222976183168 -->
+<g id="edge20" class="edge">
+<title>140222976180576->140222976183168</title>
+<path fill="none" stroke="#00cdcd" d="M606.71,-308.75C596.91,-284.16
578.83,-238.82 567.27,-209.82"/>
+<polygon fill="#00cdcd" stroke="#00cdcd" points="570.44,-208.3 563.48,-200.31
563.93,-210.9 570.44,-208.3"/>
+</g>
+<!-- 140223216369280 -->
+<g id="node12" class="node">
+<title>140223216369280</title>
+<polygon fill="none" stroke="black" points="492.08,-636 433.08,-636
433.08,-600 492.08,-600 492.08,-636"/>
+<text text-anchor="middle" x="462.58" y="-614.3" font-family="sans"
font-size="14.00">idutils</text>
+</g>
+<!-- 140223216369280->140222976180640 -->
+<g id="edge11" class="edge">
+<title>140223216369280->140222976180640</title>
+<path fill="none" stroke="blue" d="M462.58,-599.92C462.58,-575.34
462.58,-529.75 462.58,-500.43"/>
+<polygon fill="blue" stroke="blue" points="466.08,-500.06 462.58,-490.06
459.08,-500.06 466.08,-500.06"/>
+</g>
+<!-- 140222879378176 -->
+<g id="node13" class="node">
+<title>140222879378176</title>
+<polygon fill="none" stroke="black" points="670.08,-490 557.08,-490
557.08,-454 670.08,-454 670.08,-490"/>
+<text text-anchor="middle" x="613.58" y="-468.3" font-family="sans"
font-size="14.00">home-redshift</text>
+</g>
+<!-- 140222879378176->140222976180576 -->
+<g id="edge12" class="edge">
+<title>140222879378176->140222976180576</title>
+<path fill="none" stroke="blue" d="M613.58,-453.75C613.58,-429.38
613.58,-384.61 613.58,-355.59"/>
+<polygon fill="blue" stroke="blue" points="617.08,-355.31 613.58,-345.31
610.08,-355.31 617.08,-355.31"/>
+</g>
+<!-- 140222976183008 -->
+<g id="node14" class="node">
+<title>140222976183008</title>
+<polygon fill="none" stroke="black" points="1098.08,-345 895.08,-345
895.08,-309 1098.08,-309 1098.08,-345"/>
+<text text-anchor="middle" x="996.58" y="-323.3" font-family="sans"
font-size="14.00">home-xdg-base-directories</text>
+</g>
+<!-- 140222976183008->140222976183104 -->
+<g id="edge14" class="edge">
+<title>140222976183008->140222976183104</title>
+<path fill="none" stroke="dimgrey" d="M987.17,-308.75C973.7,-284.06
948.81,-238.42 933.01,-209.44"/>
+<polygon fill="dimgrey" stroke="dimgrey" points="935.88,-207.41 928.02,-200.31
929.74,-210.77 935.88,-207.41"/>
+</g>
+<!-- 140222976183008->140222976183136 -->
+<g id="edge15" class="edge">
+<title>140222976183008->140222976183136</title>
+<path fill="none" stroke="dimgrey" d="M964.52,-308.89C915.79,-282.89
823.12,-233.44 769.56,-204.87"/>
+<polygon fill="dimgrey" stroke="dimgrey" points="771.15,-201.75 760.68,-200.13
767.85,-207.92 771.15,-201.75"/>
+</g>
+<!-- 140222976183072 -->
+<g id="node15" class="node">
+<title>140222976183072</title>
+<polygon fill="none" stroke="black" points="876.58,-345 694.58,-345
694.58,-309 876.58,-309 876.58,-345"/>
+<text text-anchor="middle" x="785.58" y="-323.3" font-family="sans"
font-size="14.00">home-symlink-manager</text>
+</g>
+<!-- 140222976183072->140222976183136 -->
+<g id="edge16" class="edge">
+<title>140222976183072->140222976183136</title>
+<path fill="none" stroke="blue" d="M778.71,-308.75C768.91,-284.16
750.83,-238.82 739.27,-209.82"/>
+<polygon fill="blue" stroke="blue" points="742.44,-208.3 735.48,-200.31
735.93,-210.9 742.44,-208.3"/>
+</g>
+<!-- 140222976180512->140222976183136 -->
+<g id="edge18" class="edge">
+<title>140222976180512->140222976183136</title>
+<path fill="none" stroke="darkgoldenrod" d="M483.84,-308.89C534.38,-282.89
630.52,-233.44 686.07,-204.87"/>
+<polygon fill="darkgoldenrod" stroke="darkgoldenrod" points="687.99,-207.81
695.29,-200.13 684.79,-201.59 687.99,-207.81"/>
+</g>
+<!-- 140222976182976 -->
+<g id="node17" class="node">
+<title>140222976182976</title>
+<polygon fill="none" stroke="black" points="160.58,-345 18.58,-345 18.58,-309
160.58,-309 160.58,-345"/>
+<text text-anchor="middle" x="89.58" y="-323.3" font-family="sans"
font-size="14.00">home-shell-profile</text>
+</g>
+<!-- 140222976182976->140222976180704 -->
+<g id="edge21" class="edge">
+<title>140222976182976->140222976180704</title>
+<path fill="none" stroke="darkviolet" d="M97.78,-308.75C109.53,-284.06
131.23,-238.42 145.01,-209.44"/>
+<polygon fill="darkviolet" stroke="darkviolet" points="148.22,-210.84
149.35,-200.31 141.89,-207.84 148.22,-210.84"/>
+</g>
+</g>
+</svg>