commit - /dev/null
commit + ddc03123beabb8cb6a06e90c1196d1f170cd309c
blob - /dev/null
blob + 4c1bcdb20ce64f9c54585b5c08a1e25808f2b25e (mode 644)
--- /dev/null
+++ .gitignore
+resources/out/
blob - /dev/null
blob + c20dbcd971d7ccfbba6def2611e03501ffaec7c4 (mode 644)
--- /dev/null
+++ build.boot
+(set-env!
+ :resource-paths #{"src" "resources"}
+ :dependencies '[[hiccup "1.0.5"]
+ [ring "1.8.0"]
+ [commonmark-hiccup "0.1.0"]])
+
+(task-options!
+ pom {:project 'blog
+ :version "0.1.0"})
+
+(require '[blog.core :refer :all])
blob - /dev/null
blob + d3db89a884777aff35a97c9f8351689076b2fe99 (mode 644)
--- /dev/null
+++ resources/css/style.css
+@charset "utf-8";
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+ box-sizing: border-box;
+}
+
+html, body {
+ line-height: 1;
+ font-size: 16px;
+}
+
+body {
+ background-color: #ffffea;
+ color: #383838;
+ font-family: monospace;
+ padding: 1rem;
+}
+
+#wrapper>header>nav>ul {
+ padding: 0;
+ list-style: none;
+}
+
+#wrapper>header>nav>ul li {
+ display: inline-block;
+ margin: 3px 20px;
+}
+
+#wrapper>header>nav>ul li:first-of-type {
+ margin-left: 0;
+}
+
+#wrapper>header>nav>ul li a {
+ text-decoration: none;
+}
+
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+ display: block;
+}
+
+#wrapper {
+ max-width: 900px;
+ margin: 0 auto;
+}
+
+body > header {
+ max-width: 960px;
+ margin: 0 auto;
+}
+
+body > header > nav ul {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+body > header > nav li {
+ display: inline;
+ margin-right: 20px;
+}
+
+[data-title~=":"] {
+ display: none;
+}
+
+.heading {
+ margin-bottom: 30px;
+}
+
+h1.main-title::after {
+ content: '-----------------------------------------------------------------';
+}
+
+p.subtitle {
+ font-style: italic;
+ font-size: 120%;
+ margin: 0;
+}
+
+
+body main {
+ margin: 0 auto;
+}
+
+body:not(.article) main {
+ max-width: 550px;
+}
+
+body.article main {
+ max-width: 960px;
+}
+
+article {
+ margin-bottom: 30px;
+}
+
+h1 {
+ position: relative;
+ display: table-cell;
+ padding: 20px 0 30px;
+ margin: 0;
+ overflow: hidden;
+
+ font-size: 200%;
+}
+
+/* a cool hack stolen from blog.soykaf.com */
+h1::after {
+ /* should be long enough */
+ content: '=================================================================';
+ position: absolute;
+ bottom: 0px;
+ left: 0;
+ white-space: nowrap;
+}
+
+h1::after, h2::before, h3::before, h4::before, h5::before, h6::before {
+ /* color: hsla(0, 0%, 36%, 1); */
+ color: #242424;
+}
+
+h1>a {
+ text-decoration: none;
+}
+
+h2, h3, h4, h5, h6 {
+ margin: 2rem 0 1rem;
+}
+
+h2 {
+ font-size: 180%;
+}
+
+h3 {
+ font-size: 160%;
+}
+
+h4 {
+ font-size: 140%;
+}
+
+h5 {
+ font-size: 120%;
+}
+
+h6 {
+ font-size: 100%;
+}
+
+h2::before {
+ content: "## ";
+}
+
+h3::before {
+ content: "### ";
+}
+
+h4::before {
+ content: "#### ";
+}
+
+h5::before {
+ content: "##### ";
+}
+
+h6::before {
+ content: "###### ";
+}
+
+ul.tags {
+ padding: 0;
+ margin: 0;
+ list-style: none;
+}
+
+ul.tags::before {
+ content: 'tagged with: ';
+}
+
+ul.tags li {
+ display: inline-block;
+ margin: 0 5px;
+}
+
+header > p {
+ margin: 0;
+}
+
+p {
+ margin: 1rem 0;
+}
+
+p, li {
+ line-height: 1.5rem;
+}
+
+a {
+ text-decoration: none;
+ color: #4b4baf;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+/* other bit stolen from blog.soykaf.com */
+a[href*="://"]::after, a[rel*="external"]::after {
+ content: " "url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20class='i-external'%20viewBox='0%200%2032%2032'%20width='14'%20height='14'%20fill='none'%20stroke='%23ff9800'%20stroke-linecap='round'%20stroke-linejoin='round'%20stroke-width='9.38%'%3E%3Cpath%20d='M14%209%20L3%209%203%2029%2023%2029%2023%2018%20M18%204%20L28%204%2028%2014%20M28%204%20L14%2018'/%3E%3C/svg%3E");
+}
+
+ul, ol {
+ padding-left: 30px;
+ list-style: disc;
+}
+
+ol {
+ list-style: decimal;
+}
+
+pre, code {
+ color: #971174;
+}
+
+pre {
+ padding: 20px;
+ overflow-x: auto;
+ border-top: 1px;
+ border-bottom: 1px;
+ border-color: #999999;
+ border-style: solid;
+}
+
+em {
+ font-style: italic;
+}
+
+strong {
+ font-weight: bold;
+}
+
+hr {
+ border: 0;
+ height: 1px;
+ background-color: hsla(0, 0%, 25%, 1);
+}
+
+blockquote, q {
+ quotes: none;
+}
+
+blockquote {
+ border-left: 3px solid hsla(113, 80%, 45%, 1);
+ padding-left: 10px;
+ font-style: italic;
+}
+
+blockquote em {
+ font-style: normal;
+}
+
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: '';
+ content: none;
+}
+
+img {
+ max-width: 100%;
+ height: auto;
+}
+
+figure figcaption {
+ padding: 5px 0;
+ text-align: center;
+}
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+nav.post-navigation {
+ margin-top: 50px;
+ display: flex;
+ flex-direction: row;
+}
+
+nav.post-navigation a.next {
+ margin-left: auto;
+}
+
+footer {
+ border-top: 1px solid #e6e0e0;
+ margin-top: 100px;
+ padding-top: 10px;
+ font-style: italic;
+ text-align: center;
+}
blob - /dev/null
blob + 650afbb15a17a0d819900b8cb7a7e53b69eb43c7 (mode 644)
Binary files /dev/null and resources/favicon.ico differ
blob - /dev/null
blob + d0ee5bbef857e58bc173363dc6f80a689241b732 (mode 644)
Binary files /dev/null and resources/img/unbound-dashboard.png differ
blob - /dev/null
blob + 9ff575e190d7b94f42b35c3ab037f8b19f0eb291 (mode 644)
--- /dev/null
+++ resources/posts/abbreviations-vi.md
+Sometimes you use a tool for years and still have something new to
+learn. I'm talking about `vi(1)`, not vim or nvim, but nvi! (the
+OpenBSD ones at least.)
+
+While I was reading [NetBSD desktop pt.6 "vi(1) editor, tmux and
+unicode
+`$TERM`"](https://www.unitedbsd.com/d/12-netbsd-desktop-pt-6-vi1-editor-tmux-and-unicode-term)
+
+I knew vim end emacs had abbreviations, but I never thought that vi
+has them too.
+
+Now my `~/.nexrc` is more rich:
+
+ ab #d #define
+ ab #i #include
blob - /dev/null
blob + 4b6bd864ce2597598ba6952c77c100302a717e31 (mode 644)
--- /dev/null
+++ resources/posts/core-naming-on-linux.md
+Let's say you're debugging something on linux. Let's say that thing
+crashed. What do you do? I will open gdb, load the executable and the
+core file and try to figure it out why the thing crashed.
+
+Well, it's exactly what happened. But there was no core file for
+me. Poor me.
+
+The defaults on OpenBSD, if I recall correctly, are to generate a core
+file named `$prgname.core` in the directory where the program was
+running before the crash. Also, but mind that I might be wrong, the
+default ulimit settings is `unlimited` for the core files.
+
+So, here's a quick description on *how to get that cores* on linux,
+primarly aimed at myself:
+
+#### ulimit
+
+A `ulimit -c unlimited` is needed since the default may be 0. This can
+cause big core dumps in the `$HOME` if something like firefox or chrome
+chrashes, but I prefer to have it in `.profile` than to have to remember
+to execute the command.
+
+#### pattern
+
+On systems with systemd there should be a systemd-*somethingsomething*
+daemon and *somethingsomething*-ctl to manage 'em. I prefer have those
+cores in the directory I'm in. Bonus points if they are called like
+I'm used.
+
+```echo %e.core | sudo tee /proc/sys/kernel/core_pattern```
+
+`%e` is replaced with the program name.
+
+However, on my machine, the core files are called `$prgname.core.$pid`. To disable the `.pid` at the end
+
+```echo 0 | sudo tee /proc/sys/kernel/core_uses_pid```
+
+And that's all!
\ No newline at end of file
blob - /dev/null
blob + 130779350554c45707c8b1f4b9daccf96c6b9069 (mode 644)
--- /dev/null
+++ resources/posts/cpp-css.md
+The first version of this website had a theme switcher. It was
+implemented with CSS variables (and a bit of javascript). Then the
+javascript switcher was eventually removed, and the theme forced to be
+dark, but I kept the CSS variables *just in case* (read: I'm lazy.)
+
+*Edit*: this is no longer the case. The current version of the website
+is *yet another one*, with a 100% rewritten (and pure) CSS.
+
+The real reason I left the CSS variables was that I didn't wanted to
+use a CSS preprocessor (such as `less` or `sass`) to manage such a
+simple file (306 line, with blanks and comments). But, at the same
+time, I didn't want to copy-paste the colors everywhere.
+
+## Introducing the C preprocessor
+
+The C preprocessor is a simple and well-known beast (sort of, at
+least), and it's included in the base system installation of most
+(pratically all, I presume) OSes.
+
+If you have never used it, here's a quick howto.
+
+You can define constants with
+```
+#define PI 3.141592653589793238462643383279502884197169
+```
+and use them whenever you like, for instance
+
+```css
+double p = PI / 4;
+```
+
+The preprocessor is more powerful, it supports `#include`s and
+function-like macro (even variadic). But `#define`s are enough to
+manage a couple of CSS variables.
+
+Now, let's see how this applies to CSS. Given a file with the
+following content
+
+ #define BASE1 #221635
+
+ body {
+ background-color: BASE1;
+ }
+
+we can *compile* it with
+
+ $ cpp -P file.css > a.css
+
+and obtain a valid CSS file `a.css`.
+
+## Conclusions
+
+It's weird. It's weird to invoke `cpp` to *build* a CSS files.
+
+But it's also *satisfying*, in some sense.
+
+As a conclusion, I would like to note that another option is to use
+m4, a general purpose macro language that should be present on every
+POSIX system. Unfortunately, I don't know the language very well, so I
+opted to `cpp`.
blob - /dev/null
blob + 491dad90e822db97e65d069277237e88142f8fc0 (mode 644)
--- /dev/null
+++ resources/posts/css-sprites-awk-imagemagick.md
+CSS sprites are a nice idea, stolen from the game developers I think,
+that consist to merge all the images you need in a single one and
+download only that. I will not prove that it's best than downloading
+*n* images, but it should clear that downloading a file, albeit a bit
+bigger, requires less TCP segments going back and forth.
+
+The other day I needed a CSS sprite. Being the lazy person I am, right
+after The GIMP was open I thought that I didn't want to do the math.
+
+After a bit of searching, I found a post where the author wrote a
+script to generate the sprites (and the relative css file) with image
+magick. I have lost that link, and the history of my browser doesn't
+help. I remember that the domain name contains "php", but nothing
+more. However, I rewritten the script, so it better fits my needs.
+
+The idea is to join all the image using `convert(1)` and then generate
+a CSS file with the offsets using `identify(1)`. Both programs should
+come along with imagemagick.
+
+Here's the script
+
+ #!/bin/sh
+
+ convert sprites/* -append img/sprites.png
+
+ for i in sprites/*; do
+ identify -format '%W %H %f\n' $i
+ done | awk '
+ function fname(path) {
+ sub (".png", "", path)
+ return path
+ }
+
+ BEGIN {
+ y = 0
+
+ print "%sprite-base {"
+ print " background-image: url(/img/sprites.png);"
+ print " background-repeat: no-repeat;"
+ print "}"
+ print ""
+ }
+
+ {
+ width = $1
+ height = $2
+ class = fname($3)
+
+ print "%sprite-" class " {"
+ print " @extend %sprite-base;"
+ if (y == 0)
+ print " background-position: 0 0;"
+ else
+ print " background-position: 0 -" y "px;"
+ print "}"
+ print ""
+
+ y += height;
+ }
+ ' > scss/_sprites.scss
+
+Assuming that the images are within `sprites/` and all of them are png
+files, this script will generate the sprite in `img/sprites.png` and a
+SASS file in `sass/_sprits.scss` with one placeholder for every image
+(the naming scheme is `%sprite-$NAMEFILE` with the `$NAMEFILE` being
+the file name without extension).
+
+It should be trivial to edit to handle pure CSS output, and eventually
+other image formats.
blob - /dev/null
blob + 9ff575e190d7b94f42b35c3ab037f8b19f0eb291 (mode 644)
--- /dev/null
+++ resources/posts/freebsd-repository-with-synth.md
+Sometimes you use a tool for years and still have something new to
+learn. I'm talking about `vi(1)`, not vim or nvim, but nvi! (the
+OpenBSD ones at least.)
+
+While I was reading [NetBSD desktop pt.6 "vi(1) editor, tmux and
+unicode
+`$TERM`"](https://www.unitedbsd.com/d/12-netbsd-desktop-pt-6-vi1-editor-tmux-and-unicode-term)
+
+I knew vim end emacs had abbreviations, but I never thought that vi
+has them too.
+
+Now my `~/.nexrc` is more rich:
+
+ ab #d #define
+ ab #i #include
blob - /dev/null
blob + 1d266c2d9083fe473982d238f40759cd8b078e15 (mode 644)
--- /dev/null
+++ resources/posts/fstar-openbsd.md
+[F*](https://fstar-lang.org) is a *general purpose functional
+programming language with effects aimed at program verification.*
+Recently I've been playing a bit with it, it's nice, and here's a
+quick guide on how to compile it on OpenBSD.
+
+-----
+
+*Edit*: This "guide" is partially incomplete. Doing some re-install
+after this guide was published, I noticed that not everything is as I
+wrote it here. Things change, I suppose. Some problems have been
+fixed, but not everything. In particular `ocamlfind` will complain
+(probably multiple times) during the build that it cannot find the
+`XXX` package, and a simple `opam install XXX` will amend.
+
+-----
+
+We'll need both `git` and GNU `make` from ports, and also ocaml (to
+build F* and run F* programs), opam (the ocaml package manager),
+ocaml-camlp4 and python 3 (to build `z3`, a theorem prover).
+
+ $ pkg_add git gmake ocaml ocaml-camlp4 opam python
+
+*Note* I've installed camlp4 from the ports instead of through opam
+because I was getting an error. I don't have much experience with
+ocaml, but I've read *somewhere* that installing through the package
+manager solved those problems.
+
+## Building z3
+
+z3 is the engine that powers the verification system of F*
+(AFAIK). It's not available through ports, so we'll need to build from
+source. Even though is a project that came from Microsoft, it seems to
+run on OpenBSD just fine. Building it is also straightforward:
+
+ $ git clone https://github.com/Z3Prover/z3
+ $ cd z3
+ $ CXX=clang++ python3 script/mk_make.py
+ $ cd build
+ $ make
+ $ cp z3 $SOMEWHERE_IN_PATH # (maybe?)
+
+*Note* by default `script/mk_make.py` will try to use base g++ and the
+build will fail. The version of g++ is too ancient and doesn't support
+C++11.
+
+## Building F*
+
+First of all, we need some ocaml dependencies, so make sure to
+initialize opam (see `opam init`) and add the suggested stuff to your
+shell init file.
+
+ $ opam install ocamlfind batteries stdint zarith ppx_deriving ppx_deriving_yojson ocaml-migrate-parsetree process
+
+We can now build the F* compiler from the ocaml output present in the
+repo. You can also build the ocaml output by yourself, but I've skip
+this step.
+
+ $ gmake -j9 -C src/ocaml-output
+
+The last step is to build the library. While the docs describes this
+as an optional step, I wasn't able to compile F* programs without it.
+
+ $ gmake -j9 -C ulib/ml
+
+This was all. Let's try the hello world now!
+
+ $ gmake -C examples/hello hello
+
+It should take a decent amount of time to compile, output a *lot* of
+text and, finally, at the end, a beautiful "Hello World".
+
+Congrats, now you have a working F* installation!
+
blob - /dev/null
blob + b411b93dc2b288fea39ed9fafcda829024062239 (mode 644)
--- /dev/null
+++ resources/posts/maildir-mutt-time.md
+Mutt is, by far, my favourite mail client. It's small, configurable,
+fast and it does not get in your way. It's a great tool.
+
+My setup involves various maildir: I have a decent amount of filters
+that I use to order the mail in different directories.
+
+One thing that I've found annoying is switching maildir: opening ones
+with ~3k mail takes too much time, at least on my machine. Now I think
+that using the `threads` sorting was the culprit, but it's only a
+guess.
+
+Anyway, while I was searching the manpage for a totally different
+issue, I've found the `header_cache` settings. Quoting the manpage:
+
+> This variable points to the header cache database. [...] By default
+> it is *unset* so no header caching will be used.
+>
+> Header caching can greatly improves speed when opening POP, IMAP HM
+> or Maildir folders, see "caching" for details.
+
+There are also other relating settings, like `header_cache_compress`
+that may be interestig.
blob - /dev/null
blob + 9aa2763075e4865de58066a111fe5ed03c8d677d (mode 644)
--- /dev/null
+++ resources/posts/poor-mans-unbound-dashboard.md
+I have a tiny i686 at home with OpenBSD where I run, amongst some other
+things, an instance of unbound.
+
+Last night I decided that I wanted a dashboard to collect some statistics
+about it.
+
+My first thought was the ELK stack. The only problem is the ram.
+The little i686 has 1GB of ram, I don't know if it's enough to run
+logstash, let alone the whole ELK.
+
+A simple solution would be to collect the logs elsewhere, but I'm not
+going to do this for various reason (lazyness being the first, followed
+by the fact that having statistics about my dns queries isn't that
+useful in my opinion, even if it's nice-to-have.)
+
+Instead, my solution involves a bit of bash (don't hate me on this),
+some fifos, tmux and ttyplot.
+
+The primarly source of inspiration is [this
+post](https://dataswamp.org/~solene/2019-07-29-ttyplot.html) that I red
+some time ago: it's about plotting various system statistics with ttyplot.
+
+The result is this
+
+![unbound dashboard screenshot](/img/unbound-dashboard.png)
+
+(note that I usually disable colors in xterm)
+
+## The flow
+
+ .-
+ / various -------> multiple
+ unbound stats ------- fifos -------> ttyplot
+ \ -------> per tmux pane
+ `-
+
+The idea is to run `unbound-control stats` every once in a while,
+multiplexing its output and draw each (interesting) stats with ttyplot
+in a tmux pane.
+
+Why the fifos? Well, if I'm not wrong, every time you call
+`unbound-control stats` it will clear the statistics, so you can't run
+it *n* times to collect *n* different stats. And since the whole setup
+requires only a couple of script, the easiest way was to use some fifos.
+
+The whole setup requires three script:
+
+ - `gen-dashboard.sh`
+ - `dashboard.sh`
+ - `mystatd.sh`
+
+### `gen-dashboard.sh`
+
+This is the startup script. I run it on my crontab as `@reboot
+/path/to/gen-dashboard.sh`. It will create the required fifos, then
+spawn a tmux session and create two windows and some panes.
+
+```sh
+#!/bin/sh
+
+# create the fifos
+for f in netstat queries hit miss time; do
+ mkfifo /tmp/my-$f
+done
+
+session=dashboard
+
+tmux new-session -d -s $session
+
+# start mystatd.sh
+tmux new-window -t $session:1 -n 'logs'
+tmux send-keys "/path/to/mystatd.sh" C-m
+
+# create the dashboard
+tmux select-window -t $session:0
+
+# setup the layout of the panes
+tmux split-window -h
+tmux select-pane -L
+tmux split-window -v
+tmux select-pane -R
+tmux split-window -v -p 66
+tmux split-window -v -p 50
+
+# load the correct ttyplot in the panes
+tmux select-pane -t 0
+tmux send-keys "/path/to/dashboard.sh netstat" C-m
+
+tmux select-pane -t 1
+tmux send-keys "/path/to/dashboard.sh queries" C-m
+
+tmux select-pane -t 2
+tmux send-keys "/path/to/dashboard.sh hit" C-m
+
+tmux select-pane -t 3
+tmux send-keys "/path/to/dashboard.sh miss" C-m
+
+tmux select-pane -t 4
+tmux send-keys "/path/to/dashboard.sh time" C-m
+```
+
+(A possible improvement may be to tell tmux which command to run when
+creating a pane instead of sending the keys to the shell, but it works
+neverthless.)
+
+There's nothing special about this script, so let's move to the next.
+
+### `dashboard.sh`
+
+This script also isn't interesting, all it does is pull the data out of
+the correct fifo and start ttyplot with the correct labels and units.
+
+```sh
+#!/bin/sh
+
+if [ -z "$1" ]; then
+ echo "missing dashboard type"
+ echo "usage: $0 <dashboard-name>"
+ exit 0
+fi
+
+case "$1" in
+ netstat)
+ (while :; do
+ cat /tmp/my-netstat
+ done) | ttyplot -t "IN Bandwidth in KB/s" \
+ -u "KB/s" -c "#"
+ ;;
+
+ queries)
+ (while :; do
+ cat /tmp/my-queries
+ done) | ttyplot -t "DNS Queries/5s" \
+ -u "q/5s" -c "#"
+ ;;
+
+ hit)
+ (while :; do
+ cat /tmp/my-hit
+ done) | ttyplot -t "DNS cache hit/5s" \
+ -u "ch/5s" -c "#"
+ ;;
+
+ miss)
+ (while :; do
+ cat /tmp/my-miss
+ done) | ttyplot -t "DNS cache miss/5s" \
+ -u "cm/5s" -c "#"
+ ;;
+
+ time)
+ (while :; do
+ cat /tmp/my-time
+ done) | ttyplot -t "DNS query time avg/5s" \
+ -c "#"
+ ;;
+
+ *)
+ printf "%s\n" "$1 is not a valid dashboard"
+ exit 1
+ ;;
+esac
+```
+
+### `mystatd.sh`
+
+This is the (only?) interesting script. It's also the only one that
+requires bash, because I'm lazy, it was already installed as dependecy of
+something, and because of the `>(cmd)` construct. Rewriting the script
+using only pure sh(1) constructs is left as an exercise to the reader
+(hint: you need some extra fifo.)
+
+```sh
+#!/usr/bin/env bash
+
+filter() {
+ grep "$1" | awk -F= '{print $2}' > /tmp/my-$2
+}
+
+# unbound stats
+( while :; do
+ unbound-control stats \
+ | grep -v thread0 \
+ | tee >(filter queries= queries) \
+ | tee >(filter hit hit) \
+ | tee >(filter miss miss) \
+ | filter time.avg time
+
+ sleep 5
+done ) &
+
+# netstat - ty solene@ for the awk
+(
+ (while :; do
+ netstat -ibn
+ sleep 1
+ done) | awk '
+ BEGIN {
+ old=-1
+ }
+ /^em0/ {
+ if(!index($4,":") && old>=0) {
+ print ($5-old)/1024
+ fflush
+ old = $5
+ }
+ if(old==-1) {
+ old = $5
+ }
+ }' | tee -a /tmp/my-netstat
+) &
+
+wait
+```
+
+The first piece collects the stat from unbound. Let's break it in pieces.
+
+ - `unbound-control stats` outputs the stats. Keep in mind that this
+ requires some privileges. I've solved this by creating a script
+ in /usr/local/bin that executes the command and allowed my user to
+ launch that script via `doas(1)`. Or you can run `mystatd.sh` as root.
+ Do as you please.
+ - `grep -v thread0` removes the per-thread stats (since my unbound
+ uses only one thread). A more solid approach like `egrep -v ^thread`
+ is probably better.
+ - `tee >(filter queries= queries) |` duplicates the stream: one copy
+ goes to the subshell with `filter` and another copy goes on the pipes.
+ - `filter` is just a small function to grep the desired entry and send
+ it to `/tmp/my-$something`
+
+The netstat bit filters the output of netstat (the awk is copied-pasted
+from the previously linked post by solene@). You may want to change the
+`^em0` to match your network device.
+
+And that's all!
+
+## Possible improvements
+
+ - if you `SIGINT` `mystatd.sh` some of its subprocess still run. Maybe a
+ `trap` is needed. Since it is the only bash running on that system,
+ `pkill bash` is, albeit a bit aggressive, a working solution.
+ - replace bash. It's not difficult, but requires more fifos.
+ - ...
+
+## Final considerations
+
+This was fun. Now I have a tmux session I remotely attach with cool
+graphs. This doesn't cover the archiviation of the statistics tho.
+I think it should be trivial to add (just one more `|tee -a` to a local
+file, maybe a cronjob to do rotation, ...) but for the moment I'm happy
+with this result.
blob - /dev/null
blob + 386074e5398d9811831020524d519f2bd8a52554 (mode 644)
--- /dev/null
+++ resources/posts/sh-multiplexing-data.md
+In my [previous article](/2019-10-16-poor-mans-unbound-dashboard.html)
+I used a bash(1) construct to send the same data to two different places.
+
+The fact that I was using a bash-specific (more or less) construct really
+bothered me this time. So I thought if there is a way in sh(1) to take
+a piece of data from a pipeline and send it to two different places.
+
+The canonical way to do this is `tee(1)`, and it's what I've used it the linked article:
+
+ something | tee >(cmd) | something-else
+
+(the `>(cmd)` construct is bash, and zsh I think, specific: it executes
+`cmd` in a subshell and execute `tee` with something like `/dev/fd/63`
+-- at least on linux and OpenBSD.)
+
+Now I'll try to describe three different approaches to emulate the same
+behavior of `>(...)` in pure sh(1), trying to outline both the advantages
+and the cons.
+
+## temporary files
+
+That is:
+
+ something | tee a-file | something-else
+ cmd < a-file
+
+ # and then, probably
+ rm a-file
+
+(or even better with `mktemp(1)`)
+
+This is the most simple approach I can think of. The obvious disadvantage
+is the need to create and delete files quickly. This shouldn't be a huge
+problem in most cases, but for script do this constantly and (possibly)
+quickly it can be. Plus, at least for me, it doesn't seem very elegant,
+but it's subjective.
+
+## fifo to the rescue
+
+An almost similar approach is to use fifo. A fifo, aka a named pipe,
+is a special file that you can write to and read from, in a FIFO
+fashion.
+
+ mkfifo a-fifo
+ something | tee a-fifo | something-else
+ while read line < a-fifo; do echo $line; done | cmd
+
+This is especially useful if you need to have a long-running script that
+needs to duplicate a stream from one source. You create the fifos at the
+startup and then you're done, instead of constantly creating temporary
+files over and over again. An annoying thing is that you need to wrap
+the read from the fifo in some sort of loop: AFAIK to read the next item
+in a fifo you need to close and re-open it.
+
+## An unexpected sed
+
+Let's add another requisite: other than dumbly copy the stream, suppose
+that you want also to apply some sort of transformation to one branch
+of the duplication, like filter out something.
+
+Well, you can adapt the last two examples by adding a `| sed '...'`.
+Or you can use the `w` command of sed.
+
+I'll be a bit more specific: let's say that you need to *dispatch*
+something taken from a single stream and feed *n* subprocess. It's
+exactly what I've tried to achieve in my last post: I've used `>(cmd)`
+to dispatch the unbound statistics to different fifo, and then I had
+different processes pulling out those stats from the fifo to draw graphs.
+
+The code was like
+
+ unbound-control stats \
+ | tee >(grep something > a-fifo) \
+ | tee >(grep something-else > b-fifo) \
+ | ...
+
+Well, that can be rewritten as
+
+ unbound-control stats \
+ | sed 's/something/&/w a-fifo' \
+ | sed 's/something-else/&/w b-fifo \
+ | ...
+
+by leveraging some sed behaviours:
+
+1. every line read is printed to stdout (eventually transformed)
+2. after the `s` command you can use `w` to write the transformed lines
+ to a local file
+
+I've got this *illumination* by reading [Sed - An introduction and
+Tutorial by Bruce Barnett](http://www.grymoire.com/Unix/Sed.html).
+
+I tried to use `sed 'g/something/w a-fifo'` but `sed` complained
+about "extra characters after command", so I assume that the no-op
+`s/something/&/` is needed.
+
+-----
+
+Edit: I tried with `g/...` because I thought that `sed` had a `g` command
+that behaves like the `vi(1)` or `sam(1)` `g` command. `sed` has a `g`
+command, but does something different. The correct form would be `sed
+'/something/w a-fifo'` because `sed`, like `awk(1)`, has the ability to
+perform commands only on lines that match a regular expression.
+
+-----
+
+
+I find this last technique pretty, even prettier than the `>(grep ... >)`
+idiom I used previously since it avoids the subshell to do the filtering
+and because it does not make assumption on the shell used -- it only
+requires a POSIX shell.
+
+I hope you found this interesting. At least to me it really is.
blob - /dev/null
blob + 5563d222ae0d6a900b7d46a4a30e39f45ce3b35a (mode 644)
--- /dev/null
+++ resources/posts/spell-checking-vi.md
+UNIX is all about files and programs that do one thing and to it well,
+right? `vi(1)` is one of my favorite text editors. However it lacks
+some feature: but here's where the *composition* shines.
+
+It's stupid and dead-simple actually, but I haven't thought
+about it until some weeks ago. With a simple
+
+ map « :w^M:!aspell -c %^M:e!^M^M
+
+in your `~/.nexrc` it's simple to do spell checking in `vi`.
+
+**Friendly remainder**: the `^M` is literally the *enter* key inserted
+with `C-v ENTER` or `C-v C-m`.
+
+I've also the following binding in my `~/.nexrc` to spell check
+Italian text:
+
+ map » :w^M:!aspell --lang=it -c %^M:e!^M^M
+
+
+### What's that gibberish?
+
+OK, it may be non-obvious what that that mapping does, so let's split
+it into pieces:
+
+ - `map «` starts a mapping on the `«` key
+ - `:w^M` writes the current file (the return is necessary to *enter* the command)
+ - `:!aspell -c %^M` run aspell over the file (`%` is replaced with the current file name)
+ - `:e!^M` force vi to re-read the file
+ - `^M` Tell vi to render the editor. After a command execution vi doesn't render its interface. Rather, it wait (a bit like `ex`) for a command.
+
+### Why the `«` and `»` characters?
+
+Those keys aren't bind to anything and are simple to type with my keyboard layout.
blob - /dev/null
blob + 4c3247e50b2a55910349f5e53c0c5db2d59f8d3a (mode 644)
--- /dev/null
+++ src/blog/core.clj
+(ns blog.core
+ (:require [blog.time :as time]
+ [blog.templates :as templates]
+ [boot.core :as core]
+ [boot.task.built-in :as task]
+ [ring.adapter.jetty :as jetty]
+ [ring.middleware.resource :refer [wrap-resource]]
+ [ring.middleware.content-type :refer [wrap-content-type]]
+ [clojure.java.io :as io]
+ [clojure.java.shell :refer [sh]])
+ (:import (java.io File)))
+
+(defn copy-file [src dst]
+ (with-open [in (io/input-stream (io/file src))
+ out (io/output-stream (io/file dst))]
+ (io/copy in out)))
+
+(defn post [{:keys [slug title short date tags]}]
+ (let [f (io/resource (str "posts/" slug ".md"))]
+ {:slug slug
+ :title title
+ :short short
+ :date (time/parse date)
+ :body (slurp f)
+ :tags tags}))
+
+(def per-tag (atom {}))
+(def posts (atom []))
+
+(defn add-post! [m]
+ (let [p (post m)]
+ (swap! posts conj p)
+ (doseq [t (:tags m)]
+ (swap! per-tag update t conj p))))
+
+(load "posts")
+
+(defn create-dirs! []
+ (doseq [d ["resources/out"
+ "resources/out/css"
+ "resources/out/post"
+ "resources/out/tag"
+ "resources/out/img"]]
+ (.. (File. d) mkdirs)))
+
+(defn post-pages []
+ (let [tags (keys @per-tag)]
+ (map-indexed (fn [i posts]
+ {:filename (if (= i 0)
+ "index.html"
+ (str (inc i) ".html"))
+ :tags tags
+ :nth (inc i)
+ :posts posts
+ :has-next true
+ :has-prev true})
+ (partition-all 6 @posts))))
+
+(defn fix-next-last
+ "Fix the :has-prev/:has-next for the post pages. This assumes
+ that `(not (empty? post-pages))`"
+ [post-pages]
+ (-> post-pages
+ (->> (into []))
+ (update 0 assoc :has-prev false)
+ (update (dec (count post-pages)) assoc :has-next false)))
+
+(defn render-post-list []
+ (doseq [p (fix-next-last (post-pages))
+ :let [{:keys [filename]} p]]
+ (spit (str "resources/out/" filename)
+ (templates/home-page p))))
+
+(defn render-post [{s :slug, :as post}]
+ (spit (str "resources/out/post/" s ".html")
+ (templates/post-page post)))
+
+(defn render-tags [tags]
+ (spit (str "resources/out/tags.html")
+ (templates/tags-page tags)))
+
+(defn render-tag [tag posts]
+ (spit (str "resources/out/tag/" tag ".html")
+ (templates/tag-page tag posts)))
+
+(defn copy-dir
+ "Copy the content of resources/`dir` to resources/out/`dir`, assuming
+ these two directories exists. It does not copy recursively."
+ [dir]
+ (let [in (io/file (str "resources/" dir "/"))
+ out (str "resources/out/" dir "/")]
+ (doseq [f (->> in file-seq (filter #(.isFile %)))]
+ (println "copying" (.getName f) "to" (str out (.getName f)))
+ (io/copy f (io/file (str out (.getName f)))))))
+
+(comment
+ (copy-dir "img")
+ (io/copy (io/file "resources/img/unbound-dashboard.png")
+ (io/file "resources/out/img/unbound-dashboard.png"))
+)
+
+(defn copy-assets
+ "Copy css and images to their places"
+ []
+ (copy-dir "img")
+ (copy-file "resources/favicon.ico" "resources/out/favicon.ico")
+ (copy-file "resources/css/style.css" "resources/out/css/style.css"))
+
+(core/deftask build
+ "Build the blog"
+ []
+ (create-dirs!)
+ (copy-assets)
+ (render-post-list)
+ (doseq [p @posts]
+ (render-post p))
+ (render-tags (keys @per-tag))
+ (doseq [t @per-tag
+ :let [[tag posts] t]]
+ (render-tag (name tag) posts)))
+
+(def j (atom nil))
+
+(core/deftask serve
+ "Serve a preview"
+ []
+ (reset!
+ j
+ (jetty/run-jetty (-> (fn [_] {:status 404, :body "not found"})
+ (wrap-resource "out")
+ (wrap-content-type))
+ {:port 3000
+ :join? false})))
+
+(core/deftask deploy
+ "Copy the files to the server"
+ []
+ (sh "openrsync" "-r" "--delete" "resources/out/" "op:sites/www.omarpolo.com/"))
+
+(defn stop-jetty []
+ (.stop @j)
+ (reset! j nil))
+
+(comment
+ (build)
+ (serve)
+ (stop-jetty)
+)
blob - /dev/null
blob + 32b6f52a0584e165bf432318cfab24456fe98d3e (mode 644)
--- /dev/null
+++ src/blog/posts.clj
+(comment
+ (add-post! {:title "Blogging with clojure"
+ :slug "blogging-with-clojure"
+ :date "2020/03/27"
+ :tags #{:clojure}
+ :short "Build static blogs with clojure & boot"}))
+
+(comment
+ (add-post! {:title "Build a personal FreeBSD repository"
+ :slug "freebsd-repository-with-synth"
+ :date "2020/03/24"
+ :tags #{:FreeBSD}
+ :short "Build with ease. Scripts included!"}))
+
+(comment
+ (add-post! {:title "A tale of three editors"
+ :slug "tale-of-three-editors"
+ :date "2019/10/04"
+ :tags #{:emacs :vi :acme}
+ :short "The gigantic, the fake minimal and the glorious. Or escaping the emacs-verse if you prefer."}))
+
+(add-post! {:title "Multiplexing data in sh(1)"
+ :slug "sh-multiplexing-data"
+ :date "2019/10/17"
+ :tags #{:sh}
+ :short "please put something clever here"})
+
+(add-post! {:title "Poor man's unbound dashboard"
+ :slug "poor-mans-unbound-dashboard"
+ :date "2019/10/16"
+ :tags #{:OpenBSD :tmux}
+ :short "A little bit of bash, fifos, tmux & ttyplot"})
+
+(add-post! {:title "Core naming in linux"
+ :slug "core-naming-on-linux"
+ :date "2019/10/15"
+ :tags #{:OpenBSD :linux}
+ :short "Porting BSD behaviours to linux"})
+
+(add-post! {:title "CSS splites with awk and ImageMagick"
+ :slug "css-sprites-awk-imagemagick"
+ :date "2019/08/08"
+ :tags #{:css :awk}
+ :short "I like to automatize things"})
+
+(add-post! {:title "Using the C preprocessors for CSS generation"
+ :slug "cpp-css"
+ :date "2019/03/30"
+ :tags #{:css}
+ :short "old tools for new websites"})
+
+(add-post! {:title "F* on OpenBSD"
+ :slug "fstar-openbsd"
+ :date "2019/03/25"
+ :tags #{:fstar :OpenBSD}
+ :short "It just works"})
+
+(add-post! {:title "Abbreviations in vi(1)"
+ :slug "abbreviations-vi"
+ :date "2018/09/14"
+ :tags #{:vi}
+ :short "type even less"})
+
+(add-post! {:title "Improving maildir opening time in mutt"
+ :slug "maildir-mutt-time"
+ :date "2018/09/14"
+ :tags #{:mutt}
+ :short "<code>header_cache</code> is just awesome"})
+
+(add-post! {:title "Spell checking in vi(1)"
+ :slug "spell-checking-vi"
+ :date "2018/09/08"
+ :tags #{:vi}
+ :short "Aspell plays nice with vi."})
blob - /dev/null
blob + e08fa1f4cd7306a5db7b149446d64b3fe1e54f5b (mode 644)
--- /dev/null
+++ src/blog/templates.clj
+(ns blog.templates
+ (:require [blog.time :as time]
+ [hiccup.page :refer [html5 include-css]]
+ [commonmark-hiccup.core :refer [markdown->hiccup default-config]]))
+
+(defn link-item [{:keys [url text]}]
+ [:li [:a {:href url} text]])
+
+(defn header [{:keys [tags]}]
+ (list
+ [:header
+ [:nav
+ [:ul
+ (link-item {:url "/", :text "Home"})
+ (link-item {:url "/tags.html", :text "All Tags"})]]
+ [:div
+ [:h1 [:a {:href "/"} "yumh"]]
+ [:p "writing about things, sometimes."]]]))
+
+(defn with-page
+ [{:keys [title class], :as d} & body]
+ (html5 {:lang "en"}
+ [:head
+ [:meta {:charset "utf8"}]
+ [:meta {:name "viewport", :content "width=device-width, initial-scale=1"}]
+ [:link {:rel "shortcut icon", :href "/favicon.ico"}]
+ [:title title]
+ (include-css "/css/style.css")]
+ [:body {:class (or class "")}
+ (header d)
+ [:main body]
+ [:footer
+ [:p "Blog powered by "
+ [:code "(clojure)"]]]]))
+
+(defn post-fragment
+ [{:keys [full? title-with-link?]}
+ {:keys [title date slug tags short body], :as post}]
+ [:article
+ [:header
+ [:h1 (if title-with-link?
+ [:a {:href (str "post/" slug ".html")} title]
+ title)]
+ [:p.author "Written by " [:em "Omar Polo"] " on " (time/fmt-loc date)]
+ [:ul.tags (map #(vector :li [:a {:href (str "/tag/" (name %) ".html")}
+ (str "#" (name %))])
+ tags)]]
+ [:section
+ (if full?
+ (markdown->hiccup default-config body)
+ [:p short])]])
+
+(defn home-page
+ [{:keys [posts has-next has-prev nth]}]
+ (with-page {:title "Home"}
+ (map (partial post-fragment {:title-with-link? true})
+ posts)
+ [:nav.post-navigation
+ (if has-prev
+ [:a.prev {:href (str "/" (if (= (dec nth) 1)
+ "index"
+ (dec nth)) ".html")}
+ "« Newer Posts"])
+ (if has-next
+ [:a.next {:href (str "/" (inc nth) ".html")}
+ "Older Posts »"])]))
+
+(defn post-page
+ [{:keys [title], :as post}]
+ (with-page {:title title
+ :class "article"}
+ (post-fragment {:full? true}
+ post)))
+
+(defn tags-page
+ [tags]
+ (with-page {:title "All tags"
+ :class "tags"}
+ [:h2 "All tags"]
+ [:nav
+ [:ul
+ (map #(vector :li [:a {:href (str "/tag/" (name %) ".html")} (str "#" (name %))])
+ (sort (fn [a b]
+ (compare (.toLowerCase (name a))
+ (.toLowerCase (name b)))) tags))]]))
+
+(defn tag-page
+ [tag posts]
+ (with-page {:title (str "Posts tagged with #" tag)
+ :class "tag"}
+ [:h2 "Post tagged with " [:code "#" tag]]
+ (map (partial post-fragment {:title-with-link? true})
+ (->> posts
+ (sort-by :date)
+ (reverse)))))
blob - /dev/null
blob + e64701e8994a7f3c198b9b5ae5066c373b80786f (mode 644)
--- /dev/null
+++ src/blog/time.clj
+(ns blog.time
+ (:import (java.time.format DateTimeFormatter FormatStyle)
+ (java.time LocalDate)
+ (java.util Locale)))
+
+(def pattern
+ (DateTimeFormatter/ofPattern "yyyy/MM/dd"))
+
+(def loc (Locale/forLanguageTag "en"))
+
+(def pattern-loc
+ (DateTimeFormatter/ofPattern "dd MMMM yyy" loc))
+
+(defn fmt [d]
+ (.format d pattern))
+
+(defn fmt-loc [d]
+ (.format d pattern-loc))
+
+(defn parse [s]
+ (LocalDate/parse s pattern))
+
+(comment
+ (parse "2020/03/24")
+ (fmt (LocalDate/now))
+ (fmt-loc (LocalDate/now))
+ )