<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Salotz's Homepage</title><link>https://salotz.info/</link><description>Salotz's Homepage</description><atom:link href="https://salotz.info/rss.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><copyright>Contents © 2022 &lt;a href="mailto:samuel.lotz@salotz.info"&gt;Samuel D. Lotz&lt;/a&gt; </copyright><lastBuildDate>Sat, 17 Dec 2022 17:21:47 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>On git-hook managers</title><link>https://salotz.info/posts/on-git-hook-managers/</link><dc:creator>Samuel D. Lotz</dc:creator><description>&lt;p&gt;Using &lt;a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks"&gt;git-hooks&lt;/a&gt; is a
common method used to enforce constraints, standards, and static quality of a
code bases. By checking for things at the point of the "pre-commit" git hook it
allows for a faster feedback loop for the developer, rather than waiting for it
to fail in CI.&lt;/p&gt;
&lt;p&gt;It is common to use a &lt;strong&gt;hook manager&lt;/strong&gt; to automate the installation of the hooks
into &lt;code&gt;.git/hooks&lt;/code&gt; and to pull them from either a remote repository of common
hook implementations or generate them from a simple kind of configuration.&lt;/p&gt;
&lt;p&gt;This page (&lt;a href="https://salotz.info/posts/on-git-hook-managers/githooks.com"&gt;githooks.com&lt;/a&gt;) provides a nice third-party overview of
git hooks along with the most commonly used hook managers.&lt;/p&gt;
&lt;p&gt;I was a little late in adopting git-hooks myself and have finally sat down and
figured out a good workflow for my tastes.&lt;/p&gt;
&lt;p&gt;At least in my little bubble the most common hook manager is
&lt;a href="https://pre-commit.com/"&gt;pre-commit&lt;/a&gt;. I've created repository templates that
use it, bootstrap it, and integrate it with CI such that it is by default
reproduced locally to avoid version drifts &lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="https://salotz.info/posts/on-git-hook-managers/#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h3 id="the-problem-with-pre-commit"&gt;The Problem with &lt;code&gt;pre-commit&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;I found &lt;code&gt;pre-commit&lt;/code&gt; to be pretty frustrating most of the time. When I imagined
a hook manager I expected that it would be a pretty simple piece of software
that simply took some commands to run, or potentially downloaded some scripts
from a git repo. &lt;code&gt;pre-commit&lt;/code&gt; on the other hand is much more complicated and
actually takes on the responsibility for downloading the hook scripts from a
special "package" format, creating individual virtual environments for them, and
a host of other options for controlling their behavior. All of which can break
in non-obvious and confusing ways &lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="https://salotz.info/posts/on-git-hook-managers/#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;I lived with the inconveniences for a while as it seemed that everyone around me
was happily using it and I must have been missing something. Unfortunately, I
don't think this to be true anymore and I'm convinced that &lt;code&gt;pre-commit&lt;/code&gt; is just
adopted in cargo cult fashion. Perhaps there is even a conflation that
&lt;code&gt;pre-commit&lt;/code&gt; is &lt;strong&gt;git hooks&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Regardless the straw that broke this camel's back was setting up Python type
checking to be run as a git hook. Because &lt;code&gt;pre-commit&lt;/code&gt; hooks are run in their
own contained virtual environment they cannot easily import either local code
you are working on (for type stubs) and if you want to inject third-party type
stubs you need to explicitly write them all down in the &lt;code&gt;pre-commit&lt;/code&gt;
configuration. The issue is that I already have all this done using other tools
or scripts which are the primary entrypoints for working on the project. I don't
want to have to maintain a duplicate listing just for my hooks to work. Managing
dependencies is hard enough without having another place to duplicate them.&lt;/p&gt;
&lt;p&gt;This is really an issue with separation of concerns and composability.
Personally, I take great care and choose my tools such that I can generate
reproducible development environments such that they are composable with many
other tools. &lt;code&gt;pre-commit&lt;/code&gt; is in opposition to this and wants to control
everything. This is useful for simple static analysis tools like &lt;code&gt;black&lt;/code&gt; which
do not need to be installed alongside your code and can act on your code as
simple text blobs. However, when you want to do more complex things that require
pulling in extra dependencies and actually importing builds of your code
&lt;code&gt;pre-commit&lt;/code&gt; just ceases to be simple and is in your way.&lt;/p&gt;
&lt;p&gt;Secondly, because &lt;code&gt;pre-commit&lt;/code&gt; thinks it runs the show it ends up infecting the
rest of your development automation. I typically give all my projects some kind
of "task runner" which gives a high-level and abstracted entrypoint to doing
repetitive tasks. This typically takes the form of a &lt;code&gt;Makefile&lt;/code&gt;, but I've also
used and can recommend &lt;a href="https://github.com/pyinvoke/invoke"&gt;invoke&lt;/a&gt; and
&lt;a href="https://pydoit.org/"&gt;pydoit&lt;/a&gt; to accomplish the same thing.&lt;/p&gt;
&lt;p&gt;Here is an example of a &lt;code&gt;Makefile&lt;/code&gt; with some common tasks without using
&lt;code&gt;pre-commit&lt;/code&gt;:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="nf"&gt;clean&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;## Clean temporary files, directories etc.&lt;/span&gt;
    hatch clean
    rm -rf dist .pytest_cache .coverage
    find . -type f -name &lt;span class="s2"&gt;"*.pyc"&lt;/span&gt; -print -delete
&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;clean&lt;/span&gt;

&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;## Run source code formatters manually.&lt;/span&gt;
    hatch run -- black src tests
    hatch run -- isort src tests
&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;docstrings&lt;/span&gt;

&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt;-&lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="n"&gt;lint&lt;/span&gt; &lt;span class="n"&gt;docstring&lt;/span&gt; &lt;span class="n"&gt;typecheck&lt;/span&gt; &lt;span class="c"&gt;## Run all linters, type checks, static analysis, etc.&lt;/span&gt;
&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;validate&lt;/span&gt;

&lt;span class="nf"&gt;format-check&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;## Run code formatting checks&lt;/span&gt;
    hatch run -- black --check src tests
    hatch run -- isort --check src tests
&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt;-&lt;span class="n"&gt;check&lt;/span&gt;

&lt;span class="nf"&gt;lint&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;## Run only the linters (non-autoformatters).&lt;/span&gt;
    hatch run -- flake8 src tests
&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;lint&lt;/span&gt;

&lt;span class="nf"&gt;docstring-check&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;## Run docstring coverage only.&lt;/span&gt;
    hatch run -- interrogate src tests
&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;docstring&lt;/span&gt;

&lt;span class="nf"&gt;typecheck&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;## Run only the type checker (requires mypy)&lt;/span&gt;
    hatch run -- mypy --strict src tests/utils
&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;typecheck&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In this scenario &lt;code&gt;make validate&lt;/code&gt; is the canonical way to run all the various
linters. This should be the only entrypoint to these tasks so that we don't get
duplication and drift in different environments, e.g. locally, CI, and
git-hooks.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;pre-commit&lt;/code&gt; however I cannot call these &lt;code&gt;Makefile&lt;/code&gt; tasks in any practical
means. So what ends up happening is that you need to replace commands in the
tasks with explicit calls to &lt;code&gt;pre-commit&lt;/code&gt; to run things for you. E.g.:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.pre-commit-config.yaml&lt;/code&gt;&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="nt"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;https://github.com/psf/black&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;rev&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;22.6.0&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# run the formatting&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;black&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;black-format&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;^(src|tests)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Format:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;black"&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# just check&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;black&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;black-check&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;--check&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;^(src|tests)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Check:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;black"&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# don't run unless done manually&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;manual&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notice the rigamarole you have to go through to run a single hook type in
multiple ways.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Makefile&lt;/code&gt;&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;## Run source code formatters manually.&lt;/span&gt;
    pre-commit run --all-files black-format
    pre-commit run --all-files isort-format
&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;docstrings&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So now &lt;code&gt;pre-commit&lt;/code&gt; is not just the thing that runs things on your behalf at
specific time (the essence of what git hooks are) it is now an integral part of
how you manage dependencies for your project.&lt;/p&gt;
&lt;h3 id="the-solutions"&gt;The Solutions&lt;/h3&gt;
&lt;p&gt;At this point I decided I needed something better. I want a hook manager to be:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Very easy to install and bootstrap for newcomers to a project.&lt;/li&gt;
&lt;li&gt;Ability to run arbitrary commands and integrate with any virtual environment
   manager and task runner I'm already using.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For 1 this is very important because you do not want to frustrate people before
they actually start working on your code. At this stage it should be so easy
that you should be able to write a task or shell script that can bootstrap the
manager. So ideally that means a single executable file.&lt;/p&gt;
&lt;p&gt;I narrowed it down to two hook managers that seemed to accomplish this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/evilmartians/lefthook"&gt;lefthook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Autohook/Autohook"&gt;Autohook&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I ultimately decided on &lt;code&gt;lefthook&lt;/code&gt; but &lt;code&gt;Autohook&lt;/code&gt; was very tempting as well.
&lt;code&gt;Autohook&lt;/code&gt; is a single &lt;code&gt;bash&lt;/code&gt; script that you can just download. You operate it
by maintaining a &lt;code&gt;hooks&lt;/code&gt; directory that you dump executables into, and control
which hook stage and order they run in with symlinks. All &lt;code&gt;Autohook&lt;/code&gt; does is
place these into &lt;code&gt;.git/hooks&lt;/code&gt;. I don't think it gets any easier than that.
Looking at the code as well its just a bit more robust version of the shell
script you would have written yourself without a plethora of hook managers to
choose from.&lt;/p&gt;
&lt;p&gt;What I liked about &lt;code&gt;lefthook&lt;/code&gt; was that its a Go project and comes as a single
binary executable which is easy to download and immediately use. It should also
be easier to support on platforms like Windows since there is no reliance on a
POSIX-like shell. It also provides packages for just about every package
manager, and even for language specific ones like &lt;code&gt;pip&lt;/code&gt; and &lt;code&gt;npm&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Second, its dead simple in its operation. You write a &lt;code&gt;lefthook.yml&lt;/code&gt; file which
specifies the hooks you want to use. Here is mine:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;lefthook.yml&lt;/code&gt;&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="nt"&gt;pre-commit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;parallel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;format-check&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;make format-check&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;lint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;make lint&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;docstrings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;make docstring-check&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;typecheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;make typecheck&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The top-level groups are for each hook stage and here I am only configuring the
"pre-commit" stage. Under the &lt;code&gt;commands&lt;/code&gt; section I list out the name of each
hook and how to run it. Since I already have the business logic for these in my
task runner its as simple as wiring them up!&lt;/p&gt;
&lt;p&gt;This is great, its a hook manager thats doing only one thing, managing hooks,
and doing it well. It didn't require any refactoring of anything else in my
project, it runs the hooks in parallel and provides a bunch of other useful
options to control its behavior.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Thats it! I've not even used
&lt;a href="https://github.com/evilmartians/lefthook"&gt;lefthook&lt;/a&gt; for a full day and its
already much improved my life:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I now can easily do complex typechecking with a git pre-commit hook,&lt;/li&gt;
&lt;li&gt;I don't need to run my hook manager in CI, just my task runner,&lt;/li&gt;
&lt;li&gt;I don't need to worry about yet-another system with caches and virtualenvs,&lt;/li&gt;
&lt;li&gt;I can run hooks more quickly and in parallel&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I highly recommend it and encourage you to question cargo cult adoption of
sub-par tooling like &lt;code&gt;pre-commit&lt;/code&gt; in other areas.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;A lot of times I see impatient developer's just ignore these kinds of
issues and get by just fine for a long while. I have the unique (mis)fortune
of typically running into these kinds of issues very quickly if not
immediately and this was the same for using &lt;code&gt;pre-commit&lt;/code&gt; in my CI pipelines.
So thats just to say that, yes you really do need to worry about this as it
will bite you sooner or later. &lt;a class="footnote-backref" href="https://salotz.info/posts/on-git-hook-managers/#fnref:1" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;For &lt;a href="https://github.com/econchick/interrogate/issues/60"&gt;example&lt;/a&gt; &lt;a class="footnote-backref" href="https://salotz.info/posts/on-git-hook-managers/#fnref:2" title="Jump back to footnote 2 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><category>git</category><category>git-hooks</category><category>lefthook</category><category>pre-commit</category><guid>https://salotz.info/posts/on-git-hook-managers/</guid><pubDate>Sat, 17 Dec 2022 16:11:58 GMT</pubDate></item><item><title>Demos</title><link>https://salotz.info/posts/demos/</link><dc:creator>Samuel D. Lotz</dc:creator><description>&lt;p&gt;There is a new section on my webpage dedicated to &lt;a href="https://salotz.info/pages/portfolio/"&gt;demos&lt;/a&gt; that can be run in the
browser. Check out the first one: &lt;a href="/demos/godot-2d-creeps"&gt;Godot 2D Creeps&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Its not very interesting as it is just the 2D game from the Godot documentation,
but I am posting here as a proof of concept.&lt;/p&gt;</description><category>demos</category><category>games</category><category>godot</category><category>wasm</category><guid>https://salotz.info/posts/demos/</guid><pubDate>Sun, 27 Nov 2022 22:19:06 GMT</pubDate></item><item><title>Presentation at Silicon Therapeutics</title><link>https://salotz.info/posts/sitx_wepy_talk/</link><dc:creator>Samuel D. Lotz</dc:creator><description>&lt;p&gt;
I was lucky enough to be able to present my work this week at Silicon
Therapeutics, thanks for having me!
&lt;/p&gt;

&lt;p&gt;
I've made the slides available in this repository on gitlab
&lt;a href="https://gitlab.com/salotz/presentation.sitx_interview_2020-12/-/raw/master/presentation.pdf"&gt;at this location as a PDF&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt;
Presentation roughly covers these topics:
&lt;/p&gt;

&lt;ul class="org-ul"&gt;
&lt;li&gt;Kinetics oriented drug design&lt;/li&gt;
&lt;li&gt;Description of the Weighted Ensemble (WE) class of enhanced sampling
algorithms&lt;/li&gt;
&lt;li&gt;Motivation and design of the &lt;a href="https://github.com/ADicksonLab/wepy"&gt;wepy&lt;/a&gt; framework for performing and
analyzing WE simulations as well as prototyping new methods.&lt;/li&gt;
&lt;li&gt;Results for my PhD research into ligand unbinding transition state
structure for clinically relevant target soluble epoxide hydrolase.&lt;/li&gt;
&lt;/ul&gt;

&lt;div id="outline-container-org129cd8d" class="outline-2"&gt;
&lt;h2 id="org129cd8d"&gt;Comments&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-org129cd8d"&gt;
&lt;p&gt;
Comments are implemented as a mailing list hosted on sourcehut:
&lt;a href="https://lists.sr.ht/~salotz/salotz.info_comments-section"&gt;https://lists.sr.ht/~salotz/salotz.info_comments-section&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
Comments are for the whole website so start your subject line with:
&lt;code&gt;[sitx_wepy_talk]&lt;/code&gt; for this post.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description><category>enhanced-sampling</category><category>molecular-dynamics</category><category>presentation</category><category>research</category><category>talk</category><category>weighted-ensemble</category><category>wepy</category><guid>https://salotz.info/posts/sitx_wepy_talk/</guid><pubDate>Wed, 09 Dec 2020 18:17:06 GMT</pubDate></item><item><title>Cron alternative with runit and snooze</title><link>https://salotz.info/posts/runit_snooze/</link><dc:creator>Samuel D. Lotz</dc:creator><description>&lt;p&gt;
I have tried many times to use &lt;code&gt;cron&lt;/code&gt; effectively, but ultimately this
ends in failure each time. Lets look at why I find cron onerous and
a solution I came up with&lt;sup&gt;&lt;a id="fnr.1" class="footref" href="https://salotz.info/posts/runit_snooze/#fn.1" role="doc-backlink"&gt;1&lt;/a&gt;&lt;/sup&gt;.
&lt;/p&gt;

&lt;p&gt;
Skip to this &lt;a href="https://salotz.info/posts/runit_snooze/#solution"&gt;section&lt;/a&gt; if you just want to see the solution.
&lt;/p&gt;

&lt;div id="outline-container-org8b64ff5" class="outline-2"&gt;
&lt;h2 id="org8b64ff5"&gt;Non-Issues&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-org8b64ff5"&gt;
&lt;p&gt;
Conceptually specifying when to run things with a special syntax
is not a bad idea and not why I find cron hard to use.
&lt;/p&gt;

&lt;p&gt;
Just as a reminder this is what a cron job I found in my system for
the &lt;code&gt;dma&lt;/code&gt; mail agent:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;*/5 *	* * *	root	[ -x /usr/sbin/dma ] &amp;amp;&amp;amp; /usr/sbin/dma -q1
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
The meaning and examples for the timing specs are well known and not
really an issue here. There are lots of alternatives that try to
improve on this, this is not one of them (although it is different in
this regard).
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org6f13b1f" class="outline-2"&gt;
&lt;h2 id="org6f13b1f"&gt;Sys-Admin Orientation&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-org6f13b1f"&gt;
&lt;p&gt;
My first issue with &lt;code&gt;cron&lt;/code&gt; is that it is inseparable (or at least
practically so) from the base *nix system. That means you can't run it
as a user as it is meant to be the scheduled thing runner for a
multi-user system. So while you can have the system run things on
behalf of you as a user you can't really control it.
&lt;/p&gt;



&lt;p&gt;
In this day and age, multi-user systems are becoming pretty rare
outside HPCC systems in academia and gov't labs. For most people they
want to run simple recurring tasks on their desktop, laptop, VPS
server, or maybe even a homelab server. In all these cases user's are
usually simply mechanisms for implementing access control and not for
distinct human entities.
&lt;/p&gt;

&lt;p&gt;
This kind of sysadmin oriented baggage is pretty pervasive in *nix
systems and cron is not alone here.
&lt;/p&gt;

&lt;p&gt;
Because cron is so deeply embedded into the lizard brain of *nix it is
necessary to have a properly running and configured cron for the
stability of your system.
&lt;/p&gt;

&lt;p&gt;
As a reasonably competent user of linux desktops, the thought of
inadvertently messing up &lt;code&gt;/etc/crontab&lt;/code&gt; and its ilk is enough to keep
me away from it.
&lt;/p&gt;

&lt;p&gt;
For instance where exactly am I supposed to put a cron job? On Ubuntu
20.04 (KDE Neon actually) I see this in my &lt;code&gt;/etc&lt;/code&gt; dir:
&lt;/p&gt;

&lt;pre class="example" id="org7ca452f"&gt;
--&amp;gt; ls /etc/ | grep cron
anacrontab
cron.d
cron.daily
cron.hourly
cron.monthly
crontab
cron.weekly
&lt;/pre&gt;

&lt;p&gt;
Additionally this is highly variable distro to distro.
&lt;/p&gt;

&lt;p&gt;
For instance here is part of the &lt;code&gt;man&lt;/code&gt; page for cron on my system:
&lt;/p&gt;


&lt;pre class="example" id="org0885608"&gt;
DEBIAN SPECIFIC
       Debian introduces some changes to cron that were not originally available  upstream.   The  most
       significant changes introduced are:

       —      Support for /etc/cron.{hourly,daily,weekly,monthly} via /etc/crontab,

       —      Support for /etc/cron.d (drop-in dir for package crontabs),

       —      PAM support,

       —      SELinux support,

       —      auditlog support,

       —      DST and other time-related changes/fixes,

       —      SGID crontab(1) instead of SUID root,

       —      Debian-specific file locations and commands,

       —      Debian-specific configuration (/etc/default/cron),

       —      numerous other smaller features and fixes.

       Support  for  /etc/cron.hourly,  /etc/cron.daily, /etc/cron.weekly and /etc/cron.monthly is pro‐
       vided in Debian through the default setting of the /etc/crontab file (see the system-wide  exam‐
       ple  in crontab(5)).  The default system-wide crontab contains four tasks: run every hour, every
       day, every week and every month.  Each of these tasks will execute run-parts providing each  one
       of the directories as an argument.  These tasks are disabled if anacron is installed (except for
       the hourly task) to prevent conflicts between both daemons.
&lt;/pre&gt;

&lt;p&gt;
This goes on for several more paragraphs of neckbeard-speak that I
don't pretend to understand. Am I running &lt;code&gt;anacron&lt;/code&gt;? Why? What is
&lt;code&gt;run-parts&lt;/code&gt;? Its obvious a lot of this is for security purposes that
are important in enterprise environments that a professional sysadmin
is paid a hefty salary to comprehend.  So there is probably a simple
answer to this mess, except I don't have direct access to the people
who made this mess to ask them.
&lt;/p&gt;

&lt;p&gt;
This is a scenario where reading the manual leaves me more confused
than I started. I'll probably get chastised for overcomplicating
things. In pre-emptive response I will leave this quote from James
Clear's mailing list I got today:
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;
To simplify before you understand the details is ignorance.
&lt;/p&gt;

&lt;p&gt;
To simplify after you understand the details is genius.
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
I'm in the former category.
&lt;/p&gt;

&lt;p&gt;
I would &lt;i&gt;like&lt;/i&gt; to schedule backups to happen everynight, but I would
rather not upset the fragile balance distro maintainers have made to
keep your desktop running "smoothly".
&lt;/p&gt;

&lt;p&gt;
Cron is simply not meant for a simple user like me.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;


&lt;div id="outline-container-orgbffb771" class="outline-2"&gt;
&lt;h2 id="orgbffb771"&gt;Control &amp;amp; Supervision&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-orgbffb771"&gt;
&lt;p&gt;
The second major problem I had with cron was the real lack of control
you have over it. You write text into the file and cron goes off and
does its thing, just like Ritchie and Thompson intended.
&lt;/p&gt;

&lt;p&gt;
Everyone with a modicum of understanding of why databases exist sees
the obvious issues with inconsistency that this has. So no longer can
you just edit &lt;code&gt;/etc/crontab&lt;/code&gt; but rather you are supposed to go through
helper tools (like &lt;code&gt;crontab&lt;/code&gt;) which does all manner of locking and
gatekeeping to desparately pretend its a database.
&lt;/p&gt;

&lt;p&gt;
Using tools that automatically open up editors is another pet-peeve of
mine since you now are bringing in a lot of other assumptions about
the configuration of your system. I hope you know how to safely close
files in &lt;code&gt;nano&lt;/code&gt; and &lt;code&gt;vi&lt;/code&gt;!  Further, I like to keep all my
configurations in a central configuration directory that I can use
normal version control etc. How am I supposed to "load" these into the
cron jobs without manually copy-pasting, at least if your going by the
"manual". Aren't these systems supposed to be scriptable? &lt;sup&gt;&lt;a id="fnr.2" class="footref" href="https://salotz.info/posts/runit_snooze/#fn.2" role="doc-backlink"&gt;2&lt;/a&gt;&lt;/sup&gt;
&lt;/p&gt;

&lt;p&gt;
As we'll get to in the actual solution I'm presenting here, the
loading, unloading, restarting, pausing etc. of jobs is eerily similar
to the feature sets used in PID 0 programs like &lt;code&gt;systemd&lt;/code&gt;.
&lt;/p&gt;

&lt;p&gt;
Indeed &lt;code&gt;systemd&lt;/code&gt; has a similar timer system, which has much better
control, but again suffers from being sysadmin oriented. Also writing
unit files blows. I do it, but I try not to.
&lt;/p&gt;

&lt;p&gt;
Back to cron though, the only solution to this I have found is to
install a program called &lt;a href="https://cronitor.io/"&gt;cronitor&lt;/a&gt;, which is like a freemium tool that
really isn't that scriptable either due to the (very nice) terminal
UI. This is useful, but I don't really see this as something I can
expect to have going into the future on all my systems.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-orge77d3a3" class="outline-2"&gt;
&lt;h2 id="orge77d3a3"&gt;Observability &amp;amp; Logging Utilities&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-orge77d3a3"&gt;
&lt;p&gt;
I was able to get through all the issues above through sheer force of
will, but ultimately I was done in by the necessary interaction of
cron to two even more obtuse systems in *nix: email and logging.
&lt;/p&gt;

&lt;p&gt;
Email is sort of baked into cron and from what I can tell is the main
way of getting notifications that running things had issues.
&lt;/p&gt;

&lt;p&gt;
For instance if your trying to run a script and you got the path wrong
you'll need a working email server for cron to send a message. And if
you give a mouse a cookie…
&lt;/p&gt;

&lt;p&gt;
Then you'll need to make sure your mail boxes for the users on your
system are working, you know where they are and you have a client for
reading them.
&lt;/p&gt;

&lt;p&gt;
This seems like a huge dependency to have to just get a message about
a job having an error in it?
&lt;/p&gt;

&lt;p&gt;
Shouldn't there just be a log file I can pop open in a text editor
(like Ritchie and Thompson intended goddamit)?
&lt;/p&gt;

&lt;p&gt;
For me and I'm sure many others, I've never had a need to interact
with intra-system email. Even on the HPCC systems I worked in for my
PhD this is basically an unused feature.
&lt;/p&gt;


&lt;p&gt;
When you do get jobs running without error (and thus no reason to
email you), you'll want to see their logs too.
&lt;/p&gt;

&lt;p&gt;
In cron, its completely up to you to create and manage the lifecycle
of logs in your system. While in theory this is a good thing since it
allows you to not be locked into something you hate, in practice for
non-neckbeards it involves you having detailed knowledge of yet
another complex subsystem.
&lt;/p&gt;

&lt;p&gt;
I simply don't know where to even start here. The cron docs say to use
&lt;code&gt;rsyslogd&lt;/code&gt;, which I don't know how to use. Furthermore, my system is
using systemd which has nice commands that show you the latest in the
logs. Is this subsystem disjoint? It wouldn't suprise me that they
would jump through different calcified hoops to keep 40 year old
things running.
&lt;/p&gt;

&lt;p&gt;
Again there is probably a simple answer to all this, but its one that
reading the man page can't get you. I don't log a lot of stuff, but
mistakes happen and logs can fill up a &lt;code&gt;/&lt;/code&gt; partition scarily easy.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;


&lt;div id="outline-container-orgc2cde1b" class="outline-2"&gt;
&lt;h2 id="orgc2cde1b"&gt;Runit tutorial&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-orgc2cde1b"&gt;
&lt;p&gt;
I've contemplated and even tried a few alternatives to cron. This one
was discovered while throwing stones at other birds. I was pleasantly
surprised.
&lt;/p&gt;

&lt;p&gt;
Basically it boils down to using a process supervisor called &lt;a href="http://smarden.org/runit/"&gt;runit&lt;/a&gt;
and a fancy big brother to the unix &lt;code&gt;sleep&lt;/code&gt; command called &lt;a href="https://github.com/leahneukirchen/snooze"&gt;snooze&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt;
Runit was originally meant to be a replacement for older init systems
like &lt;code&gt;sysvinit&lt;/code&gt; that is cross-platform as well as very simple and not
requiring many libraries. This was to suite it as part of the core
system to bootstrap everything else (think cron and &lt;code&gt;sshd&lt;/code&gt; etc.).
&lt;/p&gt;

&lt;p&gt;
While these are nice characteristics they aren't killer for us
here. However, nowadays its pretty popular with the anti-systemd
crowd. Its an option for init systems in Gentoo and others and is the
default in Void linux. Besides its simplicity it is pretty easy to get
up and configured and you just run a few shell scripts to get
everything going.
&lt;/p&gt;

&lt;p&gt;
I have a few complaints
&lt;/p&gt;

&lt;ol class="org-ol"&gt;
&lt;li&gt;the documentation is kind of nonlinear and doesn't give you a
walkthrough of how to use the whole system &lt;sup&gt;&lt;a id="fnr.3" class="footref" href="https://salotz.info/posts/runit_snooze/#fn.3" role="doc-backlink"&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;li&gt;commands are disjointed and spread between a number of executables
and use of standard unix commands like &lt;code&gt;ln&lt;/code&gt; and &lt;code&gt;rm&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;
The second point is actually a feature where each of the little
components can be used standalone. However, this makes it a little
more confusing to wrap your head around and I found myself constantly
reviewing my notes to know which command to use.
&lt;/p&gt;

&lt;p&gt;
I solved this with a few shell functions, but I would like to see a
wrapper CLI to make it a little centralized conceptually (and to add a
few convenience features) for those that would want it.
&lt;/p&gt;

&lt;p&gt;
Reading these articles also helped in understanding it:
&lt;/p&gt;

&lt;ul class="org-ul"&gt;
&lt;li&gt;&lt;a href="https://www.mikeperham.com/2014/07/07/use-runit/"&gt;https://www.mikeperham.com/2014/07/07/use-runit/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rubyists.github.io/2011/05/02/runit-for-ruby-and-everything-else.html"&gt;https://rubyists.github.io/2011/05/02/runit-for-ruby-and-everything-else.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
Even after reading these I had to muck around and figure a bunch of
little details out so I thought I would throw my own little tutorial
on the pile to hopefully save some people's time and make runit a
little more approachable.
&lt;/p&gt;

&lt;p&gt;
Luckily on Ubuntu 20.04 its really easy to get runit installed and
running as a systemd service. Just install using apt:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo apt install runit
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
This even starts the service:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo systemctl status runit
sudo journalctl -u runit
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
Normally there are 3 &lt;i&gt;stages&lt;/i&gt; (i.e. states) runit has:
&lt;/p&gt;

&lt;ol class="org-ol"&gt;
&lt;li&gt;Single run tasks on startup&lt;/li&gt;
&lt;li&gt;Process supervision: starting, stopping, restarting services&lt;/li&gt;
&lt;li&gt;Shutting services down as the system goes down&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;
Because we aren't using runit as a PID 0 init system, we only care
about stage 2 &amp;amp; 3. The &lt;code&gt;apt&lt;/code&gt; installation takes care of this for us
thankfully.
&lt;/p&gt;

&lt;p&gt;
So you should see the following directories appear:
&lt;/p&gt;

&lt;dl class="org-dl"&gt;
&lt;dt&gt;&lt;code&gt;/etc/runit&lt;/code&gt;&lt;/dt&gt;&lt;dd&gt;stages and runlevel stuff, ignore this (for now).&lt;/dd&gt;
&lt;dt&gt;&lt;code&gt;/etc/sv&lt;/code&gt;&lt;/dt&gt;&lt;dd&gt;The services directory, this is where you author things.&lt;/dd&gt;
&lt;dt&gt;&lt;code&gt;/etc/service&lt;/code&gt;&lt;/dt&gt;&lt;dd&gt;This is where "enabled" services are put.&lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;
I'll call these different directories by the environment variable I
refer to them as. I put this in my &lt;code&gt;~/.profile&lt;/code&gt;:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;## runit known env variables&lt;/span&gt;

&lt;span class="c1"&gt;# the active service directory to query for state, this is what it is&lt;/span&gt;
&lt;span class="c1"&gt;# default, but I like to set so its easier for me to disable services&lt;/span&gt;
&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;SVDIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/etc/service"&lt;/span&gt;

&lt;span class="c1"&gt;# SNIPPET: the normal wait time&lt;/span&gt;
&lt;span class="c1"&gt;# export SVWAIT=7&lt;/span&gt;

&lt;span class="c1"&gt;## my vars, not recognized by any runit tools&lt;/span&gt;

&lt;span class="c1"&gt;# this is the standard directory of where services are put for the&lt;/span&gt;
&lt;span class="c1"&gt;# system. The SerVice LIBrary&lt;/span&gt;
&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;SVLIB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/etc/sv"&lt;/span&gt;

&lt;span class="c1"&gt;# log dir&lt;/span&gt;
&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;SVLOG_SYS_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/local/log/runit"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
To define a &lt;b&gt;service&lt;/b&gt; you make a directory in &lt;code&gt;SVLIB&lt;/code&gt; with some
specially named shell scripts. Mine has these directories in it:
&lt;/p&gt;

&lt;pre class="example" id="org1b6258a"&gt;
--&amp;gt; ls $SVLIB
backup  hello  printer_live  recollindex  test_env
&lt;/pre&gt;

&lt;p&gt;
Each one is a specific service. Lets first look at &lt;code&gt;hello&lt;/code&gt; to get a
simple picture of what these are:
&lt;/p&gt;

&lt;pre class="example" id="org6240e9e"&gt;
--&amp;gt; ls $SVLIB/hello
finish  log  run  supervise
&lt;/pre&gt;

&lt;p&gt;
The most important one is &lt;code&gt;run&lt;/code&gt; which is a shell script:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="c1"&gt;# run the service&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; :
&lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Hello"&lt;/span&gt;
    sleep &lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
This just prints "Hello" to stdout and then waits 2 seconds.
&lt;/p&gt;

&lt;p&gt;
This service isn't being run yet. For that you need to put it into the
&lt;code&gt;SVDIR&lt;/code&gt;:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo ln -s -f &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SVLIB&lt;/span&gt;&lt;span class="s2"&gt;/hello"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SVDIR&lt;/span&gt;&lt;span class="s2"&gt;/"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
You can check the status of the service with the &lt;code&gt;sv&lt;/code&gt; command:
&lt;/p&gt;

&lt;pre class="example" id="org5defdca"&gt;
--&amp;gt; sudo sv status $SVDIR/hello
run: /etc/service/hello: (pid 664634) 193s
&lt;/pre&gt;

&lt;p&gt;
You can check the status of all services similarly:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo sv status &lt;span class="nv"&gt;$SVDIR&lt;/span&gt;/*
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
If you see this:
&lt;/p&gt;

&lt;pre class="example" id="org88d6022"&gt;
--&amp;gt; sudo sv status $SVDIR/hello
down: /etc/service/hello: 1s, normally up, want up
&lt;/pre&gt;

&lt;p&gt;
There is something wrong with your run script.
&lt;/p&gt;

&lt;p&gt;
Looking at &lt;code&gt;sudo systemctl status runit&lt;/code&gt; and &lt;code&gt;sudo journalctl -u
runit&lt;/code&gt; could usually help me figure the issue out (no email!!!).
&lt;/p&gt;

&lt;p&gt;
Once its working you should see the "Hello"s on the log for runit if
you aren't logging this service:
&lt;/p&gt;

&lt;pre class="example" id="orgf1ea7ec"&gt;
--&amp;gt; sudo journalctl -u runit | tail
Oct 23 16:49:30 ostrich 2[664634]: Hello
Oct 23 16:49:32 ostrich 2[664634]: Hello
Oct 23 16:49:34 ostrich 2[664634]: Hello
Oct 23 16:49:36 ostrich 2[664634]: Hello
Oct 23 16:49:38 ostrich 2[664634]: Hello
Oct 23 16:49:40 ostrich 2[664634]: Hello
Oct 23 16:49:42 ostrich 2[664634]: Hello
Oct 23 16:49:44 ostrich 2[664634]: Hello
Oct 23 16:49:46 ostrich 2[664634]: Hello
Oct 23 16:49:48 ostrich 2[664634]: Hello
&lt;/pre&gt;

&lt;p&gt;
Next you'll have the &lt;code&gt;finish&lt;/code&gt; script which is just what should be run
at the end of the script:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Shutting Down"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
We don't have anything to do really so we just write a message. But
you could do cleanup stuff here if you want.
&lt;/p&gt;

&lt;p&gt;
Last the logging spec. This is a subdirectory called &lt;code&gt;log&lt;/code&gt;:
&lt;/p&gt;

&lt;pre class="example" id="org8698266"&gt;
--&amp;gt; tree $SVLIB/hello/log
/etc/sv/hello/log
├── run
└── supervise [error opening dir]

1 directory, 1 file
&lt;/pre&gt;

&lt;p&gt;
Where again the &lt;code&gt;run&lt;/code&gt; is a shell script:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="nb"&gt;exec&lt;/span&gt; svlogd -tt /var/local/log/runit/hello
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
To keep things simple this is what you want. In general you could swap
out different logging daemons other than &lt;code&gt;svlogd&lt;/code&gt; (which comes with
runit), but I don't see a reason to and this Just Works™. Basically
runit will create this as a separate service, but just knows how to
pipe around outputs now.
&lt;/p&gt;

&lt;p&gt;
If you add these and then reload the services:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo sv reload &lt;span class="nv"&gt;$SVDIR&lt;/span&gt;/hello
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
You'll stop seeing "Hello" in the runit system log, and start seeing
it in the log file we configured:
&lt;/p&gt;

&lt;pre class="example" id="org97a1f42"&gt;
sudo less +F "$SVLOG_SYS_DIR/hello/current"
# or
sudo tail -f "$SVLOG_SYS_DIR/hello/current"
&lt;/pre&gt;

&lt;p&gt;
Before we go over configuring the logging daemon (that &lt;code&gt;sysvlogd&lt;/code&gt;
thing we ran in &lt;code&gt;log/run&lt;/code&gt;) I should mention all those &lt;code&gt;supervise&lt;/code&gt; dirs
that were laying around.
&lt;/p&gt;

&lt;p&gt;
These basically are the locks and other control data that runit uses
to manage the services. Don't mess with them. They are owned by root
anyways. One thing you can do if you think you messed things up is to
disable the service and remove them all to start fresh:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo rm -rf &lt;span class="nv"&gt;$SVDIR&lt;/span&gt;/hello
rm -rf &lt;span class="nv"&gt;$SVLIB&lt;/span&gt;/hello/supervise
rm -rf &lt;span class="nv"&gt;$SVLIB&lt;/span&gt;/hello/log/supervise
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
Now one last thing is to configure the log. This file doesn't go in
the service directory (&lt;code&gt;SVLIB&lt;/code&gt;) but the directory where the logs
are. So make this file &lt;code&gt;SVLOG_SYS_DIR/hello/config&lt;/code&gt; and it should have
something like this:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# max size in bytes
s100000

# keep max of 10 files
n10

# minimum of 5 files
N5

# rotate every number of seconds
t86400

# prepend each log message with the characters
pHELLO::
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
This lets you rotate logs and control file sizes. Its a really not
nice file format but I will forgive them considering they aren't using
any libraries for TOML or YAML parsing or such things. Again something
I would improve on for non PID 0 usage.
&lt;/p&gt;

&lt;p&gt;
With this all in place you'll see something like this in
&lt;code&gt;SVLOG_SYS_DIR/hello&lt;/code&gt;:
&lt;/p&gt;

&lt;pre class="example" id="org8da4c44"&gt;
--&amp;gt; sudo tree $SVLOG_SYS_DIR/hello
/var/local/log/runit/hello
├── @400000005f929f2c12d2041c.s
├── @400000005f92b3043a84c42c.s
├── @400000005f92c6dd22d71b04.s
├── @400000005f92dab60d0ce77c.s
├── @400000005f92ee8f37dec1dc.s
├── @400000005f93026921e78ae4.s
├── @400000005f931643127f5bf4.s
├── @400000005f932a1d27cdd3b4.s
├── @400000005f933df70f77ffc4.s
├── @400000005f933e0a239542b4.s
├── config
├── current
└── lock
&lt;/pre&gt;

&lt;p&gt;
Where those ID named files are the rotated logs.
&lt;/p&gt;


&lt;p&gt;
Now that we're done with the runit tutorial lets show you how to make
a timer service that acts like a cron job.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-solution" class="outline-2"&gt;
&lt;h2 id="solution"&gt;Timer Services With Runit and Snooze&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-solution"&gt;
&lt;p&gt;
&lt;code&gt;snooze&lt;/code&gt; was also available in the Ubuntu package index so we just
install with apt:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo apt install snooze
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
Otherwise its very basic C so should be trivial to compile and
install.
&lt;/p&gt;

&lt;p&gt;
First lets just right a timer script that runs a command every 40
seconds seconds with &lt;code&gt;sleep&lt;/code&gt; and then we can just replace &lt;code&gt;sleep&lt;/code&gt; with
&lt;code&gt;snooze&lt;/code&gt;. Our &lt;code&gt;run&lt;/code&gt; script is then:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Sleeping until time to run"&lt;/span&gt;
sleep &lt;span class="m"&gt;40&lt;/span&gt;

/home/user/.local/bin/run_thing

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Finished with task"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
Because we are running this script as a service and the job of a
service manager is to restart failing services, this script will just
run over and over. There really wasn't a need for the while loop in
our &lt;code&gt;hello&lt;/code&gt; example.
&lt;/p&gt;

&lt;p&gt;
Now to make this "cron-like" we replace &lt;code&gt;sleep&lt;/code&gt; with &lt;code&gt;snooze&lt;/code&gt;:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Sleeping until time to run"&lt;/span&gt;
snooze

/home/user/.local/bin/run_thing

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Finished with task"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
Where the default of &lt;code&gt;snooze&lt;/code&gt; is just to block until midnight every
night (that is hour zero). So basically its just calculating how long
it is until midnight and sleeping until then. Pretty simple right?
Once snoozing is over the command will run the task will terminate,
get restarted and then will snooze again until next time.
&lt;/p&gt;

&lt;p&gt;
You can see all the options for configuring when &lt;code&gt;snooze&lt;/code&gt; will sleep
until in its docs and man page (this one is actually readable). But
for instance you can set it sleep until 3 AM on Mondays and Thursdays:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;snooze -w1,4 -H3 
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
Something else I like to do is have it run a number of jobs in serial
as a cycle. Where you would have to have multiple cron jobs to achieve
this you can do it in one script with this method.
&lt;/p&gt;

&lt;p&gt;
I have a two print jobs every week to keep the heads from drying
out. Black and white on Monday and color on Thursday at noon.
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Snoozing until time to print"&lt;/span&gt;
snooze -w1 -H12

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Printing black and white test page"&lt;/span&gt;

&lt;span class="c1"&gt;# black and white&lt;/span&gt;
lp /home/salotz/testpage_bw.pdf


&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Snoozing until next print job"&lt;/span&gt;
snooze -w4 -H12

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Printing RBG color test page"&lt;/span&gt;

&lt;span class="c1"&gt;# color: RBG&lt;/span&gt;
lp /home/salotz/testpage_color_rbg.pdf

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"End of run script job cycle"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
With cron you'd have something like &lt;sup&gt;&lt;a id="fnr.4" class="footref" href="https://salotz.info/posts/runit_snooze/#fn.4" role="doc-backlink"&gt;4&lt;/a&gt;&lt;/sup&gt;:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;0 12 * * 1 salotz lp /home/salotz/testpage_bw.pdf
0 12 * * 4 salotz lp /home/salotz/testpage_color_rbg.pdf
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;
So the syntax for snooze is actually pretty similar and perhaps a
little cleaner and semantic.
&lt;/p&gt;

&lt;p&gt;
So just to review: By avoiding cron we've avoided some of the most
difficult and finnicky sysadmin tasks in linux, namely email and
logging, and traded them for runit. I think its a fair trade and as
we've seen setting up runit is trivial in Ubuntu (and likely other
distros).
&lt;/p&gt;

&lt;p&gt;
I can definitely see now why runit has fierce supporters. I'll
definitely be using it for situations I would normally use systemd as
well. While I'm not going to be running Void linux on my desktop any
time soon its a good candidate for inside of containers.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org0152643" class="outline-2"&gt;
&lt;h2 id="org0152643"&gt;Final Notes&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-org0152643"&gt;
&lt;p&gt;
As I mentioned I am not a grey-neckbeard and so if I've made any gross
oversights in my claims, please let me know in the comments. I would
love to like cron more.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org2caa482" class="outline-2"&gt;
&lt;h2 id="org2caa482"&gt;Comments&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-org2caa482"&gt;
&lt;p&gt;
Comments are implemented as a mailing list hosted on sourcehut:
&lt;a href="https://lists.sr.ht/~salotz/salotz.info_comments-section"&gt;https://lists.sr.ht/~salotz/salotz.info_comments-section&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
Comments are for the whole website so start your subject line with:
&lt;code&gt;[runit_snooze]&lt;/code&gt; for this post.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id="footnotes"&gt;
&lt;h2 class="footnotes"&gt;Footnotes: &lt;/h2&gt;
&lt;div id="text-footnotes"&gt;

&lt;div class="footdef"&gt;&lt;sup&gt;&lt;a id="fn.1" class="footnum" href="https://salotz.info/posts/runit_snooze/#fnr.1" role="doc-backlink"&gt;1&lt;/a&gt;&lt;/sup&gt; &lt;div class="footpara" role="doc-footnote"&gt;&lt;p class="footpara"&gt;
Well, the idea was hinted at in the &lt;code&gt;snooze&lt;/code&gt; README but I put
it together.
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class="footdef"&gt;&lt;sup&gt;&lt;a id="fn.2" class="footnum" href="https://salotz.info/posts/runit_snooze/#fnr.2" role="doc-backlink"&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;div class="footpara" role="doc-footnote"&gt;&lt;p class="footpara"&gt;
Every shell script is a scar:
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;master_tab&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;cat &lt;span class="s"&gt;&amp;lt;&amp;lt; EOF&lt;/span&gt;
&lt;span class="s"&gt;$(cat $CONFIGS_LIB_DIR/cron/header.crontab)&lt;/span&gt;
&lt;span class="s"&gt;$(cat $CONFIGS_LIB_DIR/cron/misc.crontab)&lt;/span&gt;
&lt;span class="s"&gt;$(cat $CONFIGS_LIB_DIR/cron/my.crontab)&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# then write to the crontab file after cleaning it out&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;crontab -r &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; crontab -l &lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$master_tab&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; crontab -
&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class="footdef"&gt;&lt;sup&gt;&lt;a id="fn.3" class="footnum" href="https://salotz.info/posts/runit_snooze/#fnr.3" role="doc-backlink"&gt;3&lt;/a&gt;&lt;/sup&gt; &lt;div class="footpara" role="doc-footnote"&gt;&lt;p class="footpara"&gt;
It only has How-To Guides and Reference according to this classification: &lt;a href="https://documentation.divio.com/"&gt;https://documentation.divio.com/&lt;/a&gt;
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class="footdef"&gt;&lt;sup&gt;&lt;a id="fn.4" class="footnum" href="https://salotz.info/posts/runit_snooze/#fnr.4" role="doc-backlink"&gt;4&lt;/a&gt;&lt;/sup&gt; &lt;div class="footpara" role="doc-footnote"&gt;&lt;p class="footpara"&gt;
No I didn't do that myself, of course I used this &lt;a href="https://crontab.guru/"&gt;https://crontab.guru/&lt;/a&gt;. 
&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;/div&gt;</description><category>*nix</category><category>cron</category><category>linux</category><category>runit</category><category>scheduling</category><category>snooze</category><category>systemd</category><category>tips</category><category>tricks</category><category>unix</category><category>utilities</category><guid>https://salotz.info/posts/runit_snooze/</guid><pubDate>Fri, 23 Oct 2020 15:16:36 GMT</pubDate></item><item><title>Blog Launch</title><link>https://salotz.info/posts/blog_launch/</link><dc:creator>Samuel D. Lotz</dc:creator><description>&lt;p&gt;
Here is my obligatory blog launch post where I tell you about how I
created my blog and so that there is at least one post here ;)
&lt;/p&gt;

&lt;div id="outline-container-org443bdd5" class="outline-2"&gt;
&lt;h2 id="org443bdd5"&gt;This Blog&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-org443bdd5"&gt;
&lt;p&gt;
I ended up choosing &lt;a href="https://getnikola.com/"&gt;Nikola&lt;/a&gt; mainly because:
&lt;/p&gt;

&lt;ul class="org-ul"&gt;
&lt;li&gt;It's written and extended in Python (my default language)&lt;/li&gt;
&lt;li&gt;It has pretty good support for Emacs Org-Mode markup via a plugin&lt;/li&gt;
&lt;li&gt;Simple while still supporting basic blog &amp;amp; general purpose web pages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
Nikola is basically a wrapper around &lt;a href="https://pydoit.org/"&gt;&lt;code&gt;doit&lt;/code&gt;&lt;/a&gt; with special functions for
web pages.
&lt;/p&gt;

&lt;p&gt;
&lt;code&gt;doit&lt;/code&gt; is essentially a python programmable replacement for &lt;code&gt;make&lt;/code&gt;,
and just about every experience report with static site generators is
that they are just fancy makefiles. QED
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-org172cc49" class="outline-2"&gt;
&lt;h2 id="org172cc49"&gt;Future Posts&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-org172cc49"&gt;
&lt;p&gt;
In future posts I hope to discuss some practical issues around:
&lt;/p&gt;

&lt;ul class="org-ul"&gt;
&lt;li&gt;Tools and tips for data science projects as applications and
pipelines.&lt;/li&gt;
&lt;li&gt;Managing dependencies, virtualenvs, repository structure, and
release management for python projects.&lt;/li&gt;
&lt;li&gt;Setting up and managing personal configuration and infrastructure in
a linux environment.&lt;/li&gt;
&lt;li&gt;Software architecture and design.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
The goal of my posts will not be tutorials around how to use specific
tools but rather how they all can fit together and integrate.
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="outline-container-orgc5c40de" class="outline-2"&gt;
&lt;h2 id="orgc5c40de"&gt;Potential Collaborations&lt;/h2&gt;
&lt;div class="outline-text-2" id="text-orgc5c40de"&gt;
&lt;p&gt;
Check out my list of &lt;a href="https://salotz.info/pages/projects/"&gt;projects&lt;/a&gt;. I am looking for others to test out and
critique their design.
&lt;/p&gt;

&lt;p&gt;
I also maintain a personal listing of: Request For Comments (RFCs) and
Proposals For Software (PFS) in this &lt;a href="https://github.com/salotz/rfcs"&gt;repository&lt;/a&gt; (web page to come).
&lt;/p&gt;

&lt;p&gt;
Thats it!
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description><category>blog</category><category>introduction</category><category>nikola</category><category>static sites</category><guid>https://salotz.info/posts/blog_launch/</guid><pubDate>Fri, 10 Apr 2020 14:19:54 GMT</pubDate></item></channel></rss>