|
| 1 | +Title: Habits in the Shell, shared |
| 2 | +Slug: habits-in-the-shell-shared |
| 3 | +Date: 2025-07-25T09:26:31.883943 |
| 4 | +Tags: shell, chezmoi, habits, til |
| 5 | +Category: tools |
| 6 | +Author: Chris Rose |
| 7 | +Email: offline@offby1.net |
| 8 | +Status: published |
| 9 | +Summary: Summarize this |
| 10 | + |
| 11 | +[A post I saw today](https://www.judy.co.uk/blog/using-fortune-to-reinforce-habits/) about "using `fortune` to remind me of the shell tools I have installed, instead of the old ones I use by reflex" got me really excited by the idea of the reminders. |
| 12 | + |
| 13 | +I adopted the basic idea immediately -- putting the habits in `~/.local/share/habits/` and adding the fortune to my [fish shell](https://fishshell.com/) greeting. It was great! |
| 14 | + |
| 15 | +But... |
| 16 | + |
| 17 | +I wanted it to be shared between my machines, instead of being per-machine. That meant I needed to do some more work to tie it into [chezmoi](https://chezmoi.io/). This post covers that. I'll be using chezmoi's `run_onchange_` script support here, to adapt Judy's idea. |
| 18 | + |
| 19 | +Here's what I have in chezmoi's `dot_local/share/habits/` directory: |
| 20 | + |
| 21 | +``` |
| 22 | +$ ll |
| 23 | +.rw-r--r--@ 670 offby1 25 Jul 08:37 0_habits |
| 24 | +.rw-r--r--@ 68 offby1 25 Jul 08:37 README.md |
| 25 | +.rw-r--r--@ 230 offby1 25 Jul 08:48 run_onchange_compile_habits.sh.tmpl |
| 26 | +``` |
| 27 | + |
| 28 | +The only interesting thing in there is the onchange script: |
| 29 | + |
| 30 | +```{ .shell } |
| 31 | +#!/bin/bash |
| 32 | +set -eu -o pipefail |
| 33 | +
|
| 34 | +# Habits hash: {{ include "private_dot_local/share/habits/0_habits" | sha256sum }} |
| 35 | +
|
| 36 | +strfile 0_habits habits.dat |
| 37 | +# the habits file must be named "habits" for fortune to |
| 38 | +# find it |
| 39 | +mv 0_habits habits |
| 40 | +``` |
| 41 | + |
| 42 | +I had to work around chezmoi's ordering, which wouldn't write the `habits` file until after the on-change script was run. That meant that habits didn't update on change, only after the change. That's why `0_habits` instead of `habits`. Second, The file has to be moved into `habits` because the fortune index file needs the name to match. |
| 43 | + |
| 44 | +I also added a script to add new habits, so I could do so easily; it renders as `,add-habit`[ref]Naturally, you also [start all of your commands with a comma](https://rhodesmill.org/brandon/2009/commands-with-comma/), right?[/ref] on my `PATH`: |
| 45 | + |
| 46 | +```{ .shell } |
| 47 | +#!/bin/bash |
| 48 | +
|
| 49 | +HABIT_DIR=$HOME/.local/share/habits |
| 50 | +HABIT_FILE=$HABIT_DIR/0_habits |
| 51 | +
|
| 52 | +# we assume a habit file exists. |
| 53 | +
|
| 54 | +echo "%" >>$HABIT_FILE |
| 55 | +echo "$@" >>$HABIT_FILE |
| 56 | +
|
| 57 | +cd $HABIT_DIR |
| 58 | +just compile-habits |
| 59 | +``` |
| 60 | + |
| 61 | +<details> |
| 62 | +<summary>If you're curious, this is the full diff I applied to my dotfiles to enable this functionality, including the shell integrations[ref]and a bootstrap set of habits from Hynek. Thanks![/ref]:</summary> |
| 63 | +```{ .patch } |
| 64 | +diff --git a/dot_bashrc.tmpl b/dot_bashrc.tmpl |
| 65 | +index f6e9b806a2..c3d545c02d 100644 |
| 66 | +--- a/dot_bashrc.tmpl |
| 67 | ++++ b/dot_bashrc.tmpl |
| 68 | +@@ -19,4 +19,7 @@ |
| 69 | + [[ $(type -P atuin) ]] && eval "$(atuin init --disable-up-arrow bash)" |
| 70 | + |
| 71 | + [[ $(type -P zoxide) ]] && eval "$(zoxide init bash)" |
| 72 | ++ |
| 73 | ++fortune ~/.local/share/habits | lolcrab | boxes -d parchment |
| 74 | ++ |
| 75 | + {{- end }} |
| 76 | +diff --git a/dot_config/private_fish/functions/fish_greeting.fish b/dot_config/private_fish/functions/fish_greeting.fish |
| 77 | +new file mode 100644 |
| 78 | +index 0000000000..d536eff984 |
| 79 | +--- /dev/null |
| 80 | ++++ b/dot_config/private_fish/functions/fish_greeting.fish |
| 81 | +@@ -0,0 +1,3 @@ |
| 82 | ++function fish_greeting |
| 83 | ++ fortune ~/.local/share/habits | lolcrab | boxes -d parchment |
| 84 | ++end |
| 85 | +diff --git a/dot_zshrc.tmpl b/dot_zshrc.tmpl |
| 86 | +index 376afb6368..018de28db1 100644 |
| 87 | +--- a/dot_zshrc.tmpl |
| 88 | ++++ b/dot_zshrc.tmpl |
| 89 | +@@ -50,3 +50,5 @@ |
| 90 | + type -p atuin &>/dev/null && eval "$(atuin init --disable-up-arrow zsh)" |
| 91 | + |
| 92 | + type -p zoxide &>/dev/null && eval "$(zoxide init zsh)" |
| 93 | ++ |
| 94 | ++fortune ~/.local/share/habits | lolcrab | boxes -d parchment |
| 95 | +diff --git a/private_dot_local/bin/executable_,add-habit b/private_dot_local/bin/executable_,add-habit |
| 96 | +new file mode 100644 |
| 97 | +index 0000000000..60c6165545 |
| 98 | +--- /dev/null |
| 99 | ++++ b/private_dot_local/bin/executable_,add-habit |
| 100 | +@@ -0,0 +1,16 @@ |
| 101 | ++#!/bin/bash |
| 102 | ++ |
| 103 | ++HABIT_DIR=$HOME/.local/share/habits |
| 104 | ++HABIT_FILE=$HABIT_DIR/0_habits |
| 105 | ++ |
| 106 | ++# we assume a habit file exists. |
| 107 | ++ |
| 108 | ++echo "%" >>$HABIT_FILE |
| 109 | ++echo "$@" >>$HABIT_FILE |
| 110 | ++ |
| 111 | ++cd $HABIT_DIR |
| 112 | ++ |
| 113 | ++strfile 0_habits habits.dat |
| 114 | ++# the habits file must be named "habits" for fortune to |
| 115 | ++# find it |
| 116 | ++mv 0_habits habits |
| 117 | +diff --git a/private_dot_local/share/habits/0_habits b/private_dot_local/share/habits/0_habits |
| 118 | +new file mode 100644 |
| 119 | +index 0000000000..7bb7c7b2b0 |
| 120 | +--- /dev/null |
| 121 | ++++ b/private_dot_local/share/habits/0_habits |
| 122 | +@@ -0,0 +1,27 @@ |
| 123 | ++make sure to add habits you like to ~/.local/share/chezmoi/private_dot_local/share/habits/habits. |
| 124 | ++% |
| 125 | ++Still using man? Try using `tldr` first! |
| 126 | ++% |
| 127 | ++Don't forget about `hexyl`! A handy binary file viewer. |
| 128 | ++% |
| 129 | ++`trippy` (started as `trip`) is the new mtr/traceroute. |
| 130 | ++% |
| 131 | ++`bandwhich` shows network usage. |
| 132 | ++% |
| 133 | ++`rlwrap` adds readline superpowers to commands that lack them. |
| 134 | ++% |
| 135 | ++`tre` is a nicer tree. |
| 136 | ++% |
| 137 | ++`dust` and `gdu-go` are nicer du replacements. |
| 138 | ++% |
| 139 | ++`hyperfine` for CLI benchmarking, `oha` for http. |
| 140 | ++% |
| 141 | ++`duf` instead of df (disk full, NOT disk usage). |
| 142 | ++% |
| 143 | ++`hurl` to stress/test HTTP. |
| 144 | ++% |
| 145 | ++`hwatch` to repeatedly run commands and compare output. |
| 146 | ++% |
| 147 | ++`dog` for better DNS. |
| 148 | ++% |
| 149 | ++`tokei` to count LoC. |
| 150 | +diff --git a/private_dot_local/share/habits/README.md b/private_dot_local/share/habits/README.md |
| 151 | +new file mode 100644 |
| 152 | +index 0000000000..b06f2fcc8d |
| 153 | +--- /dev/null |
| 154 | ++++ b/private_dot_local/share/habits/README.md |
| 155 | +@@ -0,0 +1,1 @@ |
| 156 | ++We use `0_habits` because chezmoi is ordering sensitive, damn it... |
| 157 | +diff --git a/private_dot_local/share/habits/run_onchange_compile_habits.sh.tmpl b/private_dot_local/share/habits/run_onchange_compile_habits.sh.tmpl |
| 158 | +new file mode 100644 |
| 159 | +index 0000000000..4ad17ea927 |
| 160 | +--- /dev/null |
| 161 | ++++ b/private_dot_local/share/habits/run_onchange_compile_habits.sh.tmpl |
| 162 | +@@ -0,0 +1,9 @@ |
| 163 | ++#!/bin/bash |
| 164 | ++set -eu -o pipefail |
| 165 | ++ |
| 166 | ++# Habits hash: {{ include "private_dot_local/share/habits/0_habits" | sha256sum }} |
| 167 | ++ |
| 168 | ++strfile 0_habits habits.dat |
| 169 | ++# the habits file must be named "habits" for fortune to |
| 170 | ++# find it |
| 171 | ++mv 0_habits habits |
| 172 | +``` |
| 173 | +</details> |
0 commit comments