init blog

This commit is contained in:
Viktor Varland 2023-08-15 23:02:17 +02:00
commit 0caaae801e
Signed by: varl
GPG key ID: 7459F0B410115EE8
25 changed files with 3616 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
build/
node_modules/

16
lib/css/main.css Normal file
View file

@ -0,0 +1,16 @@
@import 'highlight.js/styles/dark.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind variants;
@layer base {
html {
font-family: IBM Plex Mono, ui-monospace, monospace;
}
}
.flow > * + * {
margin-block-start: var(--flow-space, 1em);
}

23
lib/layouts/default.njk Normal file
View file

@ -0,0 +1,23 @@
<!doctype html>
<html>
<head>
<title>{{ title }}</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="/main.css" rel="stylesheet">
</head>
<body class="bg-white text-black dark:bg-black dark:text-white">
<section class="flow md:container md:mx-auto p-4">
{% block nav %}
<p>
<a href="/">{{ sitename }}</a>
</p>
{% endblock %}
{% block main %}
{{ contents | safe }}
{% endblock %}
</section>
</body>
</html>

22
lib/layouts/index.njk Normal file
View file

@ -0,0 +1,22 @@
{% extends "default.njk" %}
{% block main %}
<p>
{{ contents | safe }}
</p>
{% for date, posts in collections.posts | groupby('pub.year') | dictsort | reverse %}
<h2 class="{{classes.h2}}">{{ date }}</h2>
<hr class="{{ classes.hr }}"/>
{% for post in posts %}
<div>
<h4 class="{{ classes.h4 }}"><a href="{{ post.permalink }}">{{ post.title | safe }}</a></h4>
<h5>{{ post.description | safe }}</h5>
</div>
{% else %}
No posts
{% endfor %}
{% endfor %}
{% endblock %}

21
lib/layouts/post.njk Normal file
View file

@ -0,0 +1,21 @@
{% extends "default.njk" %}
{% block main %}
{{ contents | safe }}
{% if previous %}
<p>
Previous:
<a href="/{{ previous.path }}">{{ previous.title }}</a>
</p>
{% endif %}
{% if next %}
<p>
Next:
<a href="/{{ next.path }}">{{ next.title }}</a>
</p>
{% endif %}
{% endblock %}

69
lib/tailwind-renderer.js Normal file
View file

@ -0,0 +1,69 @@
export const classes = {
blockquote: 'flow px-4 border-l-8 border-purple-900',
footnote: 'list-disc flow',
footnote_section: 'mx-8 my-4',
bullet_list: 'list-disc mx-4',
h1: 'text-6xl',
h2: 'text-4xl',
h3: 'text-2xl',
h4: 'text-xl',
h5: 'text-lg',
h6: 'text-base',
hljs: 'hljs p-4 overflow-x-scroll',
hr: 'border-red-900',
code: 'text-slate-400',
image: 'grayscale',
}
export const rules = (md) => {
const proxy = (tokens, idx, options, env, self) => self.renderToken(tokens, idx, options);
const defaultRenderer = (name) => md.renderer.rules[name] || proxy;
const defaultBulletListOpenRenderer = defaultRenderer('bullet_list_open')
const defaultBlockquoteOpenRenderer = defaultRenderer('blockquote_open');
const defaultHeadingOpenRenderer = defaultRenderer('heading_open');
const defaultCodeOpenRenderer = defaultRenderer('code_inline');
const defaultHrRenderer = defaultRenderer('hr');
const defaultImageRenderer = defaultRenderer('image');
return {
footnote_block_open: () => (
`<hr class="${classes.hr}">\n` +
`<section class="${classes.footnote_section}">\n` +
`<ol class="${classes.footnote}">\n`
),
bullet_list_open: (tokens, idx, options, env, self) => {
tokens[idx].attrJoin("class", classes.bullet_list);
return defaultBulletListOpenRenderer(tokens, idx, options, env, self);
},
blockquote_open: (tokens, idx, options, env, self) => {
tokens[idx].attrJoin("class", classes.blockquote);
return defaultBlockquoteOpenRenderer(tokens, idx, options, env, self);
},
heading_open: (tokens, idx, options, env, self) => {
tokens[idx].attrJoin("class", classes[tokens[idx].tag]);
return defaultHeadingOpenRenderer(tokens, idx, options, env, self);
},
code_inline: (tokens, idx, options, env, self) => {
tokens[idx].attrJoin("class", classes.code);
console.log(tokens[idx])
return defaultCodeOpenRenderer(tokens, idx, options, env, self);
},
hr: (tokens, idx, options, env, self) => {
tokens[idx].attrJoin("class", classes.hr);
return defaultHrRenderer(tokens, idx, options, env, self);
},
image: (tokens, idx, options, env, self) => {
tokens[idx].attrJoin("class", classes.image);
return defaultImageRenderer(tokens, idx, options, env, self);
},
}
}
;

140
metalsmith.js Normal file
View file

@ -0,0 +1,140 @@
import fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname } from 'path';
import collections from '@metalsmith/collections';
import drafts from '@metalsmith/drafts';
import layouts from '@metalsmith/layouts';
import markdown from '@metalsmith/markdown';
import permalinks from '@metalsmith/permalinks';
import inplace from '@metalsmith/in-place';
import Metalsmith from 'metalsmith';
import hljs from 'highlight.js';
import MarkdownIt from 'markdown-it';
import MarkdownItFootnote from 'markdown-it-footnote';
import postcss from 'postcss';
import autoprefixer from 'autoprefixer';
import tailwindcss from 'tailwindcss';
import atImport from 'postcss-import';
import { rules, classes } from './lib/tailwind-renderer.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
const t1 = performance.now();
let md
Metalsmith(__dirname)
.source('./src')
.destination('./build')
.clean(true)
.env({
DEBUG: process.env.DEBUG,
NODE_ENV: process.env.NODE_ENV,
})
.metadata({
sitename: 'vlv',
siteurl: 'https://vlv.io',
description: 'vlv - a scatterbraindump',
generatorname: 'Metalsmith',
generatorurl: 'https://metalsmith.io/',
classes,
})
.use(drafts({
default: false,
include: false,
}))
.use(
markdown({
render: function (source, options, context) {
if (!md) {
md = new MarkdownIt(options);
md.use(MarkdownItFootnote);
md.renderer.rules = {
...md.renderer.rules,
...rules(md),
};
console.log(md.renderer.rules)
}
if (context.key == 'contents') {
return md.render(source)
} else {
return md.renderInline(source)
}
},
engineOptions: {
linkify: true,
highlight: function (str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return `<pre class="${classes.hljs}"><code>${hljs.highlight(str, { language: lang, ignoreIllegals: true }).value}</code></pre>`;
} catch (__) {}
}
return `<pre class="${classes.hljs}"><code>${md.utils.escapeHtml(str)}</code></pre>`;
}
},
}),
)
.use(
collections({
posts: {
pattern: 'posts/**/*.html',
sortBy: 'date',
reverse: false,
}
}),
)
.use(
permalinks({
pattern: ':date/:title',
date: 'YYYY',
relative: false,
slug: {
remove: /[^a-z0-9- ]+/gi,
lower: true,
extend: {
"'": '-'
}
},
}),
)
.use(
inplace({
transform: 'nunjucks'
})
)
.use(
layouts({
directory: 'lib/layouts',
default: 'post.njk',
engineOptions: {},
}),
)
.build((err) => {
if (err) throw err;
// run postcss with tailwind at the end
fs.readFile('./lib/css/main.css', (err, css) => {
console.log('running postcss ...');
postcss([tailwindcss, autoprefixer, atImport])
.process(css, { from: './lib/css/main.css', to: './build/main.css' })
.then((result) => {
fs.writeFile('./build/main.css', result.css, () => true);
if (result.map) {
fs.writeFile(
'./build/main.css.map',
result.map.toString(),
() => true,
);
}
console.log('completed postcss ...');
});
});
console.log(
`Build success in ${((performance.now() - t1) / 1000).toFixed(1)}s`,
);
});

1570
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

26
package.json Normal file
View file

@ -0,0 +1,26 @@
{
"scripts": {
"build": "node metalsmith.js",
"format": "prettier --write ."
},
"type": "module",
"dependencies": {
"@metalsmith/collections": "1.3.0",
"@metalsmith/drafts": "1.3.0",
"@metalsmith/in-place": "5.0.0",
"@metalsmith/layouts": "2.7.0",
"@metalsmith/markdown": "1.10.0",
"@metalsmith/permalinks": "2.5.1",
"@metalsmith/postcss": "5.4.1",
"autoprefixer": "10.4.14",
"highlight.js": "11.8.0",
"jstransformer-nunjucks": "1.2.0",
"markdown-it": "13.0.1",
"markdown-it-footnote": "3.0.3",
"metalsmith": "2.6.1",
"postcss": "8.4.27",
"postcss-import": "15.1.0",
"prettier": "3.0.1",
"tailwindcss": "3.3.3"
}
}

3
prettier.config.js Normal file
View file

@ -0,0 +1,3 @@
export default {
singleQuote: true,
};

BIN
src/assets/P7240102.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 KiB

6
src/index.md Normal file
View file

@ -0,0 +1,6 @@
---
title: index
layout: index.njk
---
asdf

View file

@ -0,0 +1,453 @@
---
title: Bash powered flat-file blog
date: 2019-08-01
pub:
year: 2019
---
# Bash powered flat-file blog
I've been meaning to put something on this space for a long time, yet I
couldn't bring myself to commit to a weblog software. I just want to
serve up flat markdown files and I am aware of that there are
[hundreds](https://github.com/myles/awesome-static-generators)
upon [hundreds](https://staticsitegenerators.net/) of potential
candidates to do this. Why reinvent the wheel?
Guess what, I did reinvent the wheel.
Multiple times: [1](https://github.com/varl/blorgh),
[2](https://github.com/varl/spine),
[3](https://github.com/varl/blog-elm),
[4](https://github.com/varl/galgen). _Spine_ actually works, and is
kinda neat. It's what powered this site four years ago, and I've used it
to deploy static sites for clients. _Blorgh_ was meant to replace
_Spine_ ... You know how it goes. In the meantime my [podcast
downloader](https://github.com/varl/pyphoid) broke and I decided to
rewrite that in a new language (Go) as an exercise in learning a new
language. Obviously that has priority over trying to fix the parser to
accept some changes that the feed provider did to their feeds. I thought
RSS was widely accepted, but I guess everyone has to add their own
extensions to it.
It is Java however and since I've reduced the specs for this virtual
server (cost optimization) and tried to outsource some of the tasks it
previously did, so Java had to go. It was also a chore updating and
recompiling the thing. *cough* Made up excuses. *cough*
It was hard enough to find a Wiki software that used Git as a backing
storage system. Eventually I settled on [Gitit]() that gives me the
Wiki data in a [nice and flat
format](https://git.vardevs.se/varl/wikidata.git/tree/).
I remembered that a former colleague of mine experimented with [Bash
CGI](https://github.com/ruudud/cgi), and hey, I have
[Pandoc](https://pandoc.org/) already since it was required for Gitit.
Let's begin to connect the pipes.
## Server setup
Primarily we need two things to do our thing: **nginx** and
**fcgiwrap**. We are going to use FastCGI (shipped with **nginx**) and
FCGI Wrap to make it easy for us to run our Bash scripts through the
Common Gateway Interface.
```
apt install nginx fcgiwrap
```
### File-system
We need to serve our CGI scripts from somewhere, as well as have a root
for the public files. Throw in a log file directory for access logs and
what not.
```
/srv/www/vlv.io$ tree -L 1
.
├ cgi
├ logs
└ public
3 directories, 0 files
```
### NGINX
- `/etc/nginx/sites-available/vlv.io.vhost`:
```
server {
server_name .vlv.io;
charset utf-8;
access_log /srv/www/vlv.io/logs/access.log;
error_log /srv/www/vlv.io/logs/error_log;
location / {
root /srv/www/vlv.io/public/;
autoindex on;
index index.html index.htm;
try_files $uri $uri/ /$args;
}
location ~ ^/cgi {
root /srv/www/vlv.io/cgi/;
rewrite ^/cgi/(.*) /$1 break;
include fastcgi_params;
fastcgi_pass unix:/var/run/fcgiwrap.socket;
fastcgi_param SCRIPT_FILENAME /srv/www/vlv.io/cgi$fastcgi_script_name;
}
}
```
### CGI Bash
The
[`httputils`](https://github.com/ruudud/cgi/blob/master/cgi-bin/httputils)
makes writing end-points easier, so let's use that for the foundation of
our scripts.
Additionally, we use [`jq`](https://stedolan.github.io/jq/) to generate
JSON, and as mentioned, [`Pandoc`](https://pandoc.org) to generate HTML
reponses:
```
apt install jq pandoc
```
Let's explore the scripts, shall we?
- The first script is to return a list of the articles, `/srv/www/vlv.io/cgi/ls`:
```
#!/bin/bash
if [[ "$SCRIPT_FILENAME" ]]; then
. "$(dirname $SCRIPT_FILENAME)/httputils"
else
. "$(dirname $(pwd)$SCRIPT_NAME)/httputils"
fi
METHOD_NOT_ALLOWED="405 Method Not Allowed"
METHOD_OK="200 OK"
PLAN_PATH="/srv/www/vlv.io/public/plan"
PLAN_PATH_LENGTH=${#PLAN_PATH}
do_GET() {
shopt -s nullglob globstar
local array=($PLAN_PATH/**/*)
local length=${#array[@]}
shopt -u nullglob globstar
echo "Status: ${METHOD_OK}"
echo ""
if [[ $length == 0 ]]; then
cat <<JSON
{
"files": []
}
JSON
else
local json_array="["
local count=1
for i in "${array[@]}"; do
if [ -f "${i}" ]; then
local file=${i:PLAN_PATH_LENGTH}
local f="${file%.*}"
json_array="${json_array}\"${f}\""
if [[ $count -lt $length ]]; then
json_array="${json_array},"
fi
fi
(( count += 1 ))
done
json_array="${json_array}]"
cat <<JSON
{
"files": $json_array
}
JSON
fi
}
echo "Content-Type: application/json"
case $REQUEST_METHOD in
GET)
do_GET
;;
*)
echo "No handle for $REQUEST_METHOD"
exit 0
;;
esac
```
- We pass in the file to load to the `/srv/www/vlv.io/cgi/view` script,
which loads and transforms the markdown to html:
```
#!/bin/bash
if [[ "$SCRIPT_FILENAME" ]]; then
. "$(dirname $SCRIPT_FILENAME)/httputils"
else
. "$(dirname $(pwd)$SCRIPT_NAME)/httputils"
fi
METHOD_NOT_ALLOWED="405 Method Not Allowed"
METHOD_OK="200 OK"
do_GET() {
echo "Status: ${METHOD_OK}"
echo ""
local FILE_CONTENTS=$(pandoc --html-q-tags --from=markdown --to=html "/srv/www/vlv.io/public/plan/${QUERY_STRING}.md")
local ENCODED_CONTENTS=$(jq -n --arg q "$QUERY_STRING" --arg f "$FILE_CONTENTS" '{"filename": $q, "contents": $f}')
cat <<JSON
$ENCODED_CONTENTS
JSON
}
echo "Content-Type: application/json"
case $REQUEST_METHOD in
GET)
do_GET
;;
*)
echo "No handle for $REQUEST_METHOD"
exit 0
;;
esac
```
That's all we need server-side to return the list of articles and to
view an article's content:
```
$ curl -i https://vlv.io/cgi/ls
HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Mon, 19 Aug 2019 07:41:34 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
{
"files":
["/2019/07/we-did-a-thing","/2019/08/bash-powered-flat-file-blog"]
}
$ curl -i https://vlv.io/cgi/view?/2019/07/we-did-a-thing
HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Mon, 19 Aug 2019 07:42:57 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
{
"filename": "/2019/07/we-did-a-thing",
"contents": "<figure>\n<img
src=\"../../../share/2019/baby-online/P7240102.jpg\"
alt=\"Welcome to the world, little one.\" /><figcaption>Welcome
to the world, little one.</figcaption>\n</figure>"
}
```
This gives us our API, but we still want to consume it.
## Client setup
### HTML/CSS
In `/srv/www/vlv.io/public/index.html` we keep a simple template:
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>vlv.io</title>
<script type="module" src="/static/vlv.io.js"></script>
<link rel="stylesheet" type="text/css" href="/static/vlv.io.css">
</head>
<body>
<viva-log></viva-log>
</body>
</html>
```
The `vlv.io.css` CSS file is a [Normalize
CSS](https://github.com/necolas/normalize.css) file + some very basic
styles:
```
body {
background-color: #181818;
color: #aeaeae;
height: 100%;
width: 80%;
max-width: 1280px;
margin: 0 auto;
}
```
### JavaScript
The logger itself is a self-contained custom element, and we can split
the `vlv.io.js` script into two parts. The `template` element and the
`viva-log` element.
The slotted template:
```
const template = document.createElement('template')
template.innerHTML = `
<style>
:host {
color: inherit;
background-color: inherit;
font-size: 24px;
font-family: Consolas, monospace;
}
img {
max-width: 100%;
}
a {
color: inherit;
cursor: pointer;
}
a:hover {
color: #181818;
background-color: #aeaeae;
}
nav {
margin-bottom: 1rem;
}
</style>
<nav>
<slot name="list">Loading menu...</slot>
</nav>
<article>
<slot name="view">Select a file to view...</slot>
</article>
`
```
This gives us our boilerplate, which we use in the `viva-log` element:
```
customElements.define('viva-log', class extends HTMLElement {
constructor() {
super()
this.shadow = this.attachShadow({ mode: 'open' })
this.shadow.appendChild(template.content.cloneNode(true))
this.listSlot = this.shadow.querySelector('slot[name=list]')
this.viewSlot = this.shadow.querySelector('slot[name=view]')
const url = new URL(window.location)
if (url.pathname !== '/') {
this.view(url.pathname)
}
this.list()
}
async list() {
const data = await fetch(`/cgi/ls`)
const response = await data.json()
const files = response.files
const div = document.createElement('div')
for (const file of files) {
const a = document.createElement('a')
a.setAttribute('href', `${file}`)
a.onclick = (e) => {
e.preventDefault()
const url = new URL(e.target.href)
this.view(url.pathname)
}
a.appendChild(document.createTextNode(`${file}`))
div.appendChild(a)
div.appendChild(document.createElement('br'))
}
this.listSlot.replaceChild(div, this.listSlot.firstChild)
}
async view(file) {
this.viewSlot.innerHTML = `Loading '${file}'...`
const data = await fetch(`/cgi/view?${file}`)
const response = await data.json()
history.pushState({}, `vlv.io :: ${response.filename}`, `${file}`)
const text = response.contents
this.viewSlot.innerHTML = text
}
})
```
## Caveats
### Performance
- The first improvement would be to generate the HTML from Markdown to a
temporary file, as right now each request triggers Pandoc to parse and
convert our Markdown file to HTML.
- - An alternative is to convert from Markdown to HTML on the client.
#### Update -- 2020-06-22
An additional script, `generate.sh` now converts the raw Markdown to
HTML using Pandoc statically, and then generates the JSON for the API
statically as well, effectively pre-rendering (if not caching) the
pages.
It has the benefit of being statically explorable in addition to the
JavaScript client: [blog/2020/06](https://vlv.io/blog/2020/06/)
### Security
- Depends on how strict the Bash scripts are implemented, e.g. if not
jailed to a specific directory, and the FastCGI user has read-access,
a script could potentially return the contents of arbitrary files.
## F.A.Q
> Couldn't you have used SSI to accomplish the exact same behaviour?
Yes.
> ES Modules? Custom elements? ShadowRoot? Templates & slots? History
> API? All that for _this_ tiny little thing?
Yes.
> Babel? Polyfills? Transpilation?
No.

181
src/posts/2019/chili.md Normal file
View file

@ -0,0 +1,181 @@
---
title: World's best chili
date: 2019-11-01
pub:
year: 2019
---
_(at some point i might translate this amazing Chili recipe)_
# VÄRLDENS BÄSTA CHILI
Receptet hittade vi ursprungligen i en bok av Texas-journalisten Francis X
Tolbert, en man som vigde sitt liv åt denna eldiga köttgryta.
Det hela började på det tidiga 60-talet då Tolbert skrev en artikel som fick
rubriken "Jakten på den äkta chilin".
Under de kommande åren fick han 48 000 brevsvar från världens alla hörn. Han
läste, lagade, reste, provåt och intervjuade levande legender som Cap Warren,
den siste ranchkocken som fortfarande kokade mat åt sina cowboys på en vedeldad
spis bakpå den täckta kokvagnen.
Ganska snart kunde Tolbert slå fast vad som a b s o l u t inte får finnas med i
en chili:
- tomater
- vita bönor
- gul lök
- köttfärs
Äkta chili lagar man nämligen på hela köttbitar och rätt lagad ska den vara
just vad namnet chili con carne antyder: rödpeppar med en viss tillsats av
kött.
Det stora problemet visade sig vara att hitta den rätta blandningen av olika
pepparsorter så att chilin får en bred fyllig hetta som varar länge och inte
enbart blir olidligt skarp.
Till sist tvingades Tolbert arrangera ett världens första chili-VM och det
recept som vi publicerar här är en variant av det som segrade - NORTH TEXAS
RED.
Det recept som följer är en lätt försvanekad version. Ingen som prövat det har
klagat på styrkan, men många har haft svårt att hitta de rätta ingredienserna.
För er som bor i Stockholm rekommenderar vi en butik som heter BBQ & Chili som
har det mesta i chili-väg och dessutom en stor sortering salsor och såser.
Det här är vad du behöver:
- En mycket stor svart järngryta
- En stekpanna
- En liten kastrull
- En helflaska tequila
- Sex burkar ljust öl, helst det mexikanska Corona.
- 5 torkade ancho-pepparfrukter (De är stora, mörkt brunröda och finns i
affärer som säljer latinamerikansk mat. I nödfall kan de ersättas med en
blandning av mörkrött chilipulver och ett antal flådda, urkärnade röda
paprikor.)
- 1 chipotle-peppar (Röd, rökt. Finns konserverad i latinamerikanska affärer.)
- 3 birdseye-pepparfrukter (Små spetsiga klarröda. Kan ersättas av torkad, mald
piri-piri).
- 4 jalapeno-pepparfrukter (Knubbiga, gröna. Finns på burk i de flesta
välsorterade livsmedelsaffärer. Ta den starka varianten.)
- Baconfett (eller olja) att steka i.
- 10 stora vitlöksklyftor, grovt hackade.
- 5 kilo oxkött, skuret i centimeterstora tärningar.
- En halv kopp mjöl.
- En kopp chilipulver.
- Två koppar mörk oxbuljong.
- 2 matskedar spiskummin. (Kännarna kan inte komma överens om kryddan ska
rostas innan den används eller inte.)
- 2 matskedar oregano.
- 2 matskedar malda korianderfrön.
- 1/2 matsked socker.
- Salt, efter smak. Börja försiktigt!
- Lite grovt majsmjöl, masa harina.
Så här gör du:
1. Ta dig en rejäl tequila. En platta med gamla Hank Williams-låtar bidrar
också till så att det rätta chili-perspektivet på tillvaron infinner sig.
1. Börja sedan med pepparn. Rensa bort stjälkar och frön. Koka den torkade
pepparn 15 minuter under lock, ställ åt sidan och låtsvalna.
1. Rensa och hacka den övriga pepparn. Ställ åt sidan. (Här är en varning på
plats. Peppar är starkt. Den BRÄNNS. Se upp för ångorna när du kokar och tvätta
händerna noga efteråt. Och tänk noga på vad då gör med fingrarna det närmaste
dygnet. Om du petar dig i näsan kan du lika gärna göra det med lödkolven.)
1. Ta ett glas tequila till, ett rejält glas. Det kommer att behövas. Nu börjar
det nämligen dra ihop sig.
1. Fräs vitlöken mjuk och brun. Lägg i grytan.
1. Öka värmen i stekpannan och börja stek köttet. Ta lite i sänder och rör om
ordentligt så att bitarna steks på alla sidor. Lägg ner i grytan. Detta är ett
varmt, osigt och tidsödande slitgöra som kräver både tålamod och tequila.
1. Blanda mjöl och chilipulver. Strö över köttet i grytan.
1. Sila av den blötlagda pepparn, men spara vattnet. Mosa den kokta pepparn,
tillsätt sedan all peppar till köttet.
1. Häll på pepparvattnet, oxbuljongen och öl tills vätskan täcker köttet. Koka
upp.
1. Nu är chilin på väg. En nöjd kock kan ta ett steg tillbaka, beundra sin
skapelse och belönar sig själv med ytterligare en tequila, raskt åtföljd av det
resterande ölet.
1. Låt chilin småkoka. Rör ner spiskummin, oregano och koriander. Rör ofta så
att mästerverket inte bränns fast i botten.
1. Fortsätt kokningen tills köttet börjar falla sönder. Det bör ta två, tre
timmar.
1. Tequila!!!
1. Det kan hända att chilin är lite lös när den närmar sig slutkokningen.
Riktiga Texasbor reder den då med grovt majsmjöl.
1. Gör slut på den sista skvätten tequila (om du inte redan gjort det).
1. Ta av grytan och skumma bort det fett som samlats ovanpå.
Den här satsen räcker till ett tjugotal normala människor, men högst tio
chiliälskare.
Servera chilin i små, djupa tallrikar. Många gillar att äta den med majschips
till, en klick cremé fraiche eller lite grovt riven cheddarost ovanpå. Servera
sallad, bröd, guacamole (avocadoröra) och stora mängder ljust öl till.
Musiken är nästan lika viktig. Satsa på någon genuint: Hank Williams, Buddy
Holly, Joe Ely, Jerry Jeff Walker, Flaco Jiminez, Butch Hancock, Commander Cody
and his Lost Planet Airmen eller Gram Parsons. Fram mot natten passar det
utmärkt att spela Freddy Fenders odödliga "Before the next teardrops falls",
Doug Sahms "Wasted days and wasted nights" eller Creedende Clearwater Revivals
"Lodi".
Det går också att spela Wilco, Weeping Willows och helst bör alla sjunga
allsång i någon gammal Carter Family sång typ "Will The Circle Be Unbroken"
Volymen bör vara öronbedövande. Sjung med. Skråla gärna. Och kom ihåg, en äkta
chiliafton S K A spåra ur fram mot natten.
---
FOTNOT 1: Sedan första publiceringen har vi mottagit en rad klagomål mot
tequilan i detta recept. De som har hört av sig har varit rörande ense om att
en helflaska är alldeles för lite. Naturligvis var den mängden enbart en
rekommendation, anpassad för en person.
Och kollegan Peter Svensson har förslagit en intressant variant. Den följer
här:
SNABB-CHILI:
I nödfall kan alla ingredienser utom tequilan utgå. I sådana fall förkortas
koktiden avsevärt.
---
FOTNOT 2: Nyårsafton 1990 lagade Jan Gradvall och Stefan Lindström chili och
följde detta recept slaviskt. Ingen av dem minns någonting efter klockan 18.00
men överlevande har berättat att kvällen var ovanligt lyckad.
---
FOTNOT 3: Texten ovan är en lätt omarbetad och uppdaterad version av ett recept
som publicerats två gånger i Expressen. Först 1988 och sedan 1991. Under några
år var detta den mest efterfrågade artikeln i tidningens arkiv. Vännerna på
textarkivet berättar att de till och med fick en förfrågan på en kopia från
Saudiarabien under Desert Storm.
---
FOTNOT 4: denna text får fritt spridas och kopieras under förutsättning att ni
inte blandar bönor i chilin.
---

View file

@ -0,0 +1,23 @@
---
title: Tao Te Ching
date: 2019-08-30
pub:
year: 2019
---
# Tao Te Ching ([Stephen Mitchell](http://taoteching.org.uk/index.php?c=30&a=Stephen+Mitchell))
```
The Master does his job
and then stops.
He understands that the universe
is forever out of control,
and that trying to dominate events
goes against the current of the Tao.
Because he believes in himself,
he doesn't try to convince others.
Because he is content with himself,
he doesn't need others' approval.
Because he accepts himself,
the whole world accepts him.
```

View file

@ -0,0 +1,8 @@
---
title: We did a thing
date: 2019-07-22
pub:
year: 2019
---
![Welcome to the world, little one.](/assets/P7240102.jpg)

31
src/posts/2020/house.md Normal file
View file

@ -0,0 +1,31 @@
---
title: 2020 so far
date: 2020-06-01
pub:
year: 2020
---
# 2020 so far
This has been a stranger year than most, even when not taking COVID-19
into account.
Buying a house, selling our apartment, renovations, contractors taking
our money and then ghosting us, water damage under the floors -- and
more!
Shoulder dislocations aplenty; when moving appliances, hastily grabbing
a coffee cup before our boy did, when stretching out my chest, when
sleeping with my hand behind my head.
Funeral attendance over Zoom. I had to put my distrust of Zoom aside for
that, not my hill to die on. Most of our combined families are in Sweden
whereas we ourselves are in Norway. The lockdown has made the 600 km
divide between us accute.
This is a personal memo to remember these 6 months, and that freedom to
travel is a privilege and not a right. Throughout my life borders have
become looser and easier to traverse, and it has been an assumption of
mine that it would continue in that direction. One virus is all it took
to fundamentally challenge that.
Half the year to go, I wonder what lasting changes we'll see.

View file

@ -0,0 +1,15 @@
---
title: Keep up the good work
date: 2020-06-30
pub:
year: 2020
---
"Keep up the good work", he said, fully intending it as words of
acknowledgement of the quality of work being done, and as encouragement
to carry on doing good work.
What is the implication though? That good things will come if you do
good work? Promotion? Money? Status? Are those good things?
As if.

View file

@ -0,0 +1,17 @@
---
title: The Key to Success
date: 2020-06-01
pub:
year: 2020
---
# The Key to success
Wisdom from my grandparents:
> Nyckeln till framgång är att vid slutet av dagen vara nöjd med det man
> gjort.
Translation:
> The key to success is to at the end of the day, be content with what
> one has done.

View file

@ -0,0 +1,95 @@
---
title: Monitoring with IRC
date: 2020-09-01
pub:
year: 2020
---
# Monitoring with IRC
I would like to keep an eye on my servers from the comfort of my IRC
client.
I have also been meaning to play around with
[ii](https://git.suckless.org/ii/file/README.html) for years and decided
that now is the time.
# Idea outline
- Host a private IRC server.
- A computer joins the IRC server and joins a specified channel.
- When a server task finishes, it sends a message to the channel about
what it did, and the result.
- A server can respond to commands sent to it by monitoring the channel
messages.
# Software
- [ii](https://git.suckless.org/ii/file/README.html)
Chosen for its simplicity. It is a file-based IRC client with a FIFO
"In" file for sending commands, and a normal "out" file for messages.
This characteristic makes it easy to build a bot using bash.
- [ngircd](https://github.com/ngircd/ngircd) Chosen for its simplicity.
It performs nicely as a small IRC server, and is very easy to
configure.
# Result
When the server "cc" successfully backups using rclone to Google Photos:
```
21:40 < cc> [OK] rclone backed up /data/media/pictures to photos:album/backups
```
A failure message looks like:
```
21:56 < cc> [ERR] rclone failed to backup /data/media/pictures to photos:album/backups:
21:56 < cc> 2020/09/14 21:56:02 NOTICE: iphone_2/Europa turné 2012/Thumbs.db: There was an error while trying to create this media item. (3)
21:56 < cc> full logs in /home/varl/logs/rclone
```
# Code
I use a helper command `say` that redirects the given arguments to the
"in" file to send them over IRC:
```bash
#!/usr/bin/env bash
: "${ircdir:=$HOME/irc}"
: "${network:=irc.server.tld}"
: "${channel:=#channel}"
printf -- "%s\n" "$@" > "$ircdir/$network/$channel/in"
```
And the backup script is also kept small and runs with `cron` every
night.
```bash
#!/usr/bin/env bash
: "${src:=/path/to/stuff}"
: "${dest:=photos:album/backups}"
: "${logfile:=/path/to/logs/rclone}"
rclone copy "$src" "$dest" --log-file "$logfile" --log-level NOTICE
if [ $? -eq 0 ]; then
say "[OK] rclone backed up ${src} to ${dest}"
else
say "[ERR] rclone failed to backup ${src} to ${dest}:"
say "$(tail -n1 $logfile)"
say "full logs in ${logfile}"
fi
```
That's it for now, currently it's a proof of concept, and I will be
trying out how this works for me and add more things to monitor over
time. Infrastructre is in place and works nicely for now though.
Over and out.

View file

@ -0,0 +1,393 @@
---
title: varl's nixtapes vol.1
date: 2023-08-08
pub:
year: 2023
collection: nixtapes
---
> #### TL;DR
>
> At the end of this post we will have reached a point where we can load a
> shell with specific tools, and when we leave that shell, the global
> environment is untouched.
# volume 1: Setting up Nix
## Background
Over the last year or so I have experimented with using `nix` to manage
my development environments across multiple machines.
I like to keep e.g. my personal projects and work projects separated as
much as possible, so on my work projects I don't want my personal
development stack (tools, versions, etc.) to be loaded and when I work
on my personal projects the same applies.
I also want the development environments to be declarative, but the
actual use case is that I want them to be portable to other machines, so
I can spin up a new development environment fairly quickly.
There are other ways to achieve full isolation, e.g. a complete virtual
machine, but I don't like the overhead that brings.
I also _like_ the way I have my global environment setup, and prefer to
use that as a base, and then bring whatever tools I need for the context
I am working in into it.
I'm not sure when it happened, but `nix` the package manager (not "nix the
language", and not "nix the operating system"), is quite mature[^1]
across both Linux and MacOS, so I decided to brush off my "nix the
language" concerns and dive into it. Again.
[^1]: Well, `nix-env` and other `nix-*` commands _are_ mature, but the
next-gen all-in-one `nix` CLI is definitely _not_ mature. It's not
even final:
https://nixos.org/manual/nix/stable/command-ref/experimental-commands
## Prerequisites
- A supported OS:
- Linux
- MacOS
- Windows with WSL2 with `systemd` enabled[^2]
- Comfortable using a shell
- A high tolerance for obtuse languages[^3]
[^2]: Configuration guide:
https://devblogs.microsoft.com/commandline/systemd-support-is-now-available-in-wsl/
[^3]: We will be using a bare minimum of "nix the language" to
accomplish our goals, so there are better ways to do most things I
do, but I don't understand any of them so I'm sticking to what I can
only describe as "simple but dumb".
## A note on "Nixes"
Nix is somewhat of an overloaded term, as it can mean:
- Nix the language
- Nix the CLI
- Nix the OS[^4]
- Nix the package manager
- Nix the utilities
- Nix the store
[^4]: Most often called NixOS but searching for "nix" often winds up in
NixOS-land which is not always helpful.
I will use a subset of those in this guide, and do my best to
differeniate them.
The in-scope Nix terms will be:
- Nix the language (try as I might, there is no avoiding it)
- Nix the package manager
- Nix the utilities
This will give us all we need to accomplish our goal.
## A note on Nix installers
There are many ways to install Nix (the package manager) without NixOS,
and it works on any Linux distribution alongside the system package
manager.
It also works on MacOS, but there are a few rough patches of terrain. We
can mostly avoid them, or implement workarounds for the nasty ones.
### Official installer
> https://nixos.org/download
The official installer is available for Linux, MacOS, and Windows and is
the most unopinionated of the lot. It provides a predictable
environment, with opt-in to experimental functionality (that we will
_not_ use in this article anyway), and is managed by the Nix developers.
### Distro installer
> Arch Linux, Gentoo, Debian, Alpine, etc. all provide packages for
> their distro that can be used with various caveats.
I use the Arch Linux `nix` package on my workstation, for example, and
haven't had issues with it. It may be slightly out-of-date versus the
official one, but the package maintainer has been fast with updates so I
have never had issues.
I would be more sceptical of the Debian stable package version, and
perhaps opt for the official installer on that distro. Your mileage may
vary, but you are on Linux so you should be used to research and inform
your own choices.
### Determinate Nix installer
> https://github.com/DeterminateSystems/nix-installer
This installer is developed by [Determinate
Systems](https://determinate.systems/)[^5] and is an opinionated
installer, in that it makes different choices on how Nix should be
installed and setup than the official installer does. It leans into the
advanced functionality (often experimental) that the official installer
leaves off by default, and goes as far as to disable or dissuade users
from using the stable nix utilities.
[^5]: A consultancy that provides services related to Nix training for
teams and companies.
It's a great installer, but if used, some configuration options must be
manually reverted to the official defaults. I tried this installer, but
wound up uninstalling and using the official one to avoid diving into
all the changes and assumptions they make.
## Installing using the official installer
If you decided to install Nix using the distro package manager or the
determinate systems' installer, feel free to jump ahead.
### Linux
Given you have Linux running `systemd`, with SELinux disabled, and can
authenticate with `sudo`, the multi-user installation method is
recommended:
```
sh <(curl -L https://nixos.org/nix/install) --daemon
```
If you cannot use the multi-user installation, you must use the
single-user installation method:
```
sh <(curl -L https://nixos.org/nix/install) --no-daemon
```
Follow along the installation, answering the prompts, and you will end
up with a Nix installation.
### MacOS
For MacOS a multi-user installation is recommended:
```
sh <(curl -L https://nixos.org/nix/install)
```
Same deal here, follow along the instructions and the result should be a
working Nix installation.
## Using the environment
Now that we have Nix setup, we can try out a few tricks. If you haven't,
you will need to open a new shell.
### Starting a shell with specific tools
> `nix-shell` reference manual:
> https://nixos.org/manual/nix/stable/command-ref/nix-shell
`nix-shell` starts an interactive shell based on a Nix expression, but
we will stick to our guns and define packages instead of an expression.
We can start a pure environment that inherits nothing from the global
environment:
```
nix-shell --pure
```
In which we don't even have `ping` defined:
```
[nix-shell:~/dev]$ ping
bash: ping: command not found
```
Type `exit` to go back to the global shell, and try this instead:
```
nix-shell --pure --packages cowsay
```
```
[nix-shell:~/dev]$ cowsay "hi nixer !"
____________
< hi nixer ! >
------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
```
Hit `Ctrl-D` or type `exit` again. Multiple packages can be listed on
the commandline separated by spaces:
```
nix-shell --pure --packages less vim
```
Notice that when we request packages we don't have, we get these
messages:
```
these 2 paths will be fetched (8.06 MiB download, 38.84 MiB unpacked):
/nix/store/h5m8kaai6x64j1q6r7ffvq20f06r77m3-less-633
/nix/store/6j38m8vm8gp9a8qpw3b7dj9g50x1w95n-vim-9.0.1562
copying path '/nix/store/h5m8kaai6x64j1q6r7ffvq20f06r77m3-less-633' from 'https://cache.nixos.org'...
copying path '/nix/store/6j38m8vm8gp9a8qpw3b7dj9g50x1w95n-vim-9.0.1562' from 'https://cache.nixos.org'...
```
The short version is that each package and version is hashed and stored
in the Nix store (`/nix`). The consequence of this is that multiple
versions of the same package can exist at the same time (at the cost of
disk space) without causing conflicts across the environment. There is
also a long version[^6]. Probably a longer version somewhere as well.
[^6]: Understanding the Nix store by looking at how Nix works:
https://nixos.org/guides/how-nix-works.html
### Installing packages into the environment
> `nix-shell` reference manual:
> https://nixos.org/manual/nix/stable/command-ref/nix-env#name
Being able to run a shell with specific packages is handy, and
`nix-shell` will be a underlying driver further into the nixtapes.
Instead let's take a look at the `nix-env` command.
Using `nix-env` we can manipulate a Nix user environment, which means
un/installing packages, searching for packages, and, rolling back the
environment.
Let's find some packages to install.
```
nix-env --query --available vim
vim-9.0.1562
```
Great, love that version. Let's install it:
```
nix-env --install vim-9.0.1562
installing 'vim-9.0.1562'
building '/nix/store/08xr544knfcahpn9xykql2xzsg374pxl-user-environment.drv'...
which vim
/home/varl/.nix-profile/bin/vim
readlink /home/varl/.nix-profile/bin/vim
/nix/store/6j38m8vm8gp9a8qpw3b7dj9g50x1w95n-vim-9.0.1562/bin/vim
```
Given that `/home/varl/.nix-profile/bin` is added to my `PATH` before
any paths that has my system version of `vim`, it will be picked up
correctly and when I run `vim` I will get the nix-installed version. No
shell-trickery and `vim` will be available across any other interactive
shells I open and use.
It is also possible to preserve installed alternative derivations of a
package, but I haven't had to use it much.
One thing we will use a lot though is the `--remove-all` flag. This
removes _all_ the installed packages in an environment, and only
installs the target package in the environment, so if we run:
```
nix-env --install vim nodejs python3
installing 'vim-9.0.1562'
installing 'nodejs-18.16.1'
installing 'python3-3.12.0b3'
these 3 paths will be fetched (65.51 MiB download, 219.36 MiB unpacked):
/nix/store/pxv7fa7ysw18kqrlvs1g0f9q66l7paz3-nodejs-18.16.1-libv8
/nix/store/3v1hjf626mh7mdii28m0srdbl8ch3dka-python3-3.12.0b3
/nix/store/b9a3j1rvcgj4wxpb30yzdi7ba62g3ha8-python3-3.12.0b3-debug
copying path '/nix/store/pxv7fa7ysw18kqrlvs1g0f9q66l7paz3-nodejs-18.16.1-libv8' from 'https://cache.nixos.org'...
copying path '/nix/store/b9a3j1rvcgj4wxpb30yzdi7ba62g3ha8-python3-3.12.0b3-debug' from 'https://cache.nixos.org'...
copying path '/nix/store/3v1hjf626mh7mdii28m0srdbl8ch3dka-python3-3.12.0b3' from 'https://cache.nixos.org'...
building '/nix/store/jid36dzng4pjqph3f0bdyzmsvaq5fl0h-user-environment.drv'...
```
And then do:
```
nix-env --install --remove-all vim
```
Only `vim` will be installed, and `python3` and `nodejs` are purged from
the environment. This is an important feature, as it allows us to
install any tools and versions we want to play around with `nix-env --install`, and then go back to scratch without worrying about leaving a
mess in our environments.
### Uninstalling packages from the environment
Uninstalling is without drama:
```
nix-env --uninstall vim
```
### Environment generations
Every time we use `nix-env` to modify our environment, Nix creates a new
generation. We can jump between generations, rollback, and delete them.
```
nix-env --rollback
```
This rolls back the current environment one generation, and is just a
convenience wrapper around `--list-generations` and
`--switch-generation`.
```
nix-env --list-generations
...
113 2023-06-21 16:39:06
114 2023-08-08 16:25:50
115 2023-08-08 16:36:01
116 2023-08-08 16:37:42 (current)
```
Using the id in the left column, we can jump to different generations of
our environment.
Deleting generations (they add up) can be done on a one-by-one basis, or
using smart values like `+5` (save last 5) and `30d` (save last 30
days).
```
nix-env --delete-generations 113
nix-env --delete-generations +5
nix-env --delete-generations 30d
```
## Where are we now ?
We have installed Nix (the package manager) and the Nix utilities
(`nix-*` commands) that we need.
We have explored how to create ad-hoc shell environments that only have
specific packages available, which is useful for trying out new things and
switching between versions of e.g. language runtimes.
We have learned how to manipulate the user's environment by installing
packages that persist in the environment (as opposed to the ephemeral
ones in `nix-shell`).
We've seen that Nix creates generations of the user's environments, and
that we can switch between generations or simply rollback the
environment to a previous state.
These are the building blocks for managing our user's environment, as
well as the various development environments that we strive for.
/v.

View file

@ -0,0 +1,464 @@
---
title: varl's nixtapes vol.2
date: 2023-08-09
pub:
year: 2023
collection: nixtapes
---
> #### TL;DR
>
> Now that we have a working nix installation and understand how the nix
> utilities (`nix-*` commands) work at a basic level, we can integrate nix
> more deeply into our user environment to manage packages.
>
> For MacOS we will end up with a decent replacement for
> [Homebrew](https://brew.sh), and for Linux, we will have a package
> manager that is independent from the system package manager that we can
> use to manage packages for our user.
# volume 2: integrating nix with the environment
## Prerequisites
- A working nix installation, see [vol.1](varl-s-nixtapes-vol1)
## Nix Channels
> A new concept has appeared !
To find Nix packages, there are different channels that exist under the
Git repository [`nixpkgs`](https://github.com/nixos/nixpkgs).
There are stable and unstable, large and small, channels that have
different use cases.
I like to be able to lock my channel to a specific commit, so I clone
nixpkgs to `~/.nix-defexpr` so I can manipulate it at will by switch
branches, updating, making local changes to try out (read: break)
various things.[^1]
[^1]:
I use the `master` branch, which is unstable. For my usage I
haven't had any real breakages. Simply switch branch to swap the
channel. Simply `git switch nixos-23.05` to use the stable branch.
All in all, I find it handy, so I would recommend doing the same:
```
rm -rd ~/.nix-defexpr
git clone --depth=1 https://github.com/NixOS/nixpkgs.git ~/.nix-defexpr
```
To make it stick immediately (we will make it more permanent later) run:
```
export NIX_PATH="nixpkgs=$HOME/.nix-defexpr"
```
Channels can be listed with:
```
nix-channel --list
```
`nix-channel` also provides `--add`, `--update`, and `--remove`
commands.
I would recommend removing all existing channels if you go along with
having a local clone of `nixpkgs`.
```
nix-channel --remove {channel-alias}
```
It is by far the simplest way, so until you need multiple channels,
sticking with one is the most predictable. Each channel also has the
concept of generations, and it is possible to rollback an `--update` to
a previous generation.
Quite powerful, and overkill for my ends. Onwards !
## Configure user packages
A promise I made is declarative package management, and that boils down
being able to state what packages we want, and have the package manager
make sure that we have those in our environment.
Let's take a look at how to configure at the user level, as opposed to
system, and project, level.
If you don't have the file `~/.config/nixpkgs/config.nix` we need to
create it:
```
mkdir -p ~/.config/nixpkgs
touch ~/.config/nixpkgs/config.nix
```
Open the `config.nix` file in an editor and let's dip into
Nix-the-language.
Paste the following into the file:
```nix
pkgs : {
allowUnfree = true;
packageOverrides = pkgs: with pkgs; rec {
nixUserProfile = writeText "nix-user-profile" ''
export PATH=$HOME/.nix-profile/bin:/nix/var/nix/profiles/default/bin:/sbin:/bin:/usr/sbin:/usr/bin
export NIX_PATH="nixpkgs=$HOME/.nix-defexpr"
'';
userBasePkgs = pkgs.buildEnv {
name = "user-base";
paths = [
(runCommand "profile" {} ''
mkdir -p $out/etc/profile.d
cp ${nixUserProfile} $out/etc/profile.d/nix-user-profile.sh
'')
];
pathsToLink = [
"/share"
"/bin"
"/etc"
];
extraOutputsToInstall = [ "man" "doc" ];
};
};
}
```
I'm not going to go into the semantics of the language, but rather talk
a bit about what we are doing and why it is helpful.[^2]
[^2]:
You can dive into the language yourself if you wish:
https://nixos.org/manual/nix/stable/language/index.html
`allowUnfree` determines if we are able to use non-free, as in
proprietrary, packages from `nixpkgs`. By default only free software is
allowed to install, and your mileage will vary if you need non-free
software or not. I do, so I have it set to `true`.
`packageOverrides` is where we will define our own packages to
accomplish a one-shot command to install all the packages we want in our
environment.
The first package we define is `nixUserProfile` which uses a `writeText`
helper function to build a package that consists of a file with the text
we have defined.
The output file will have the contents:
```shell
export PATH=$HOME/.nix-profile/bin:/nix/var/nix/profiles/default/bin:/sbin:/bin:/usr/sbin:/usr/bin
export NIX_PATH="nixpkgs=$HOME/.nix-defexpr"
```
This does two things.
First, it sets up our `PATH` variable to include the `bin` folders that
contain our nix package binaries. We want them first in `PATH` to ensure
that the nix packages are used before the system packages that may
already exist on the system.
Second, it sets the `NIX_PATH` to the `~/.nix-defexpr` folder where we
cloned the `nixpkgs` repo to. I mentioned that we did a one-off `export`
to set this, and this is the more permanent fix to make sure it sticks
across reboots and all interactive shells we use.
However. This simply builds a package that consists of a file, but it
doesn't run it.
The next package we define is our `userBasePkgs`, and here is a trimmed
version of the above to make it easier to deconstruct.
```nix
userBasePkgs = pkgs.buildEnv {
name = "user-base";
paths = [
(runCommand "profile" {} ''
mkdir -p $out/etc/profile.d
cp ${nixUserProfile} $out/etc/profile.d/nix-user-profile.sh
'')
];
};
```
We have to give it a `name`, so we can refer to it, and I chose
`user-base`. The convention will make more sense down the line.
In `paths` it is a bit murkier. Right off the bat we invoke another nix
helper function to run a command to trigger a side effect on our system.
```nix
(runCommand "profile" {} ''
mkdir -p $out/etc/profile.d
cp ${nixUserProfile} $out/etc/profile.d/nix-user-profile.sh
'')
```
`$out` refers to our `~/.nix-profile` folder, so it creates the folder
`~/.nix-profile/etc/profile.d` directory and copies the output from the
`nixUserProfile` package we built before to it.
Now that we have a package that has a build output, we need to install
the package to reflect the change to our environment.
## Install `user-base` with `nix-env`
Now that we have our `user-base` package, let us install it and continue
hooking up our user environment with the nix user environment.
```
nix-env --install --remove-all user-base
```
Or, short form:
```
nix-env -ir user-base
```
This evaluates our nix package definition in
`~/.config/nixpkgs/config.nix` and builds our derivation, and installs
it into our environment. We didn't define any additional software yet so
we can check if `~/.nix-profile/etc/profile.d/nix-user-profile.sh`
contains the two lines we expect:
```
cat ~/.nix-profile/etc/profile.d/nix-user-profile.sh
export PATH=$HOME/.nix-profile/bin:/nix/var/nix/profiles/default/bin:/sbin:/bin:/usr/sbin:/usr/bin
export NIX_PATH="nixpkgs=$HOME/.nix-defexpr"
```
Nothing in our shell knows this file exists though, so we need to wire
up a few more things.
## Loading scripts in `~/.nix-profile/etc/profile.d`
Add this chunk to `~/.zprofile` (zsh), `~/.bash_profile` (bash), or
`~/.profile` (bash, sh).
It loads scripts in `~/.nix-profile/etc/profile.d`, and this will handle
our script as well, and ensure that we have `NIX_PATH` and `PATH` setup
correctly.
```shell
if [ -d $HOME/.nix-profile/etc/profile.d ]; then
for i in $HOME/.nix-profile/etc/profile.d/*.sh; do
if [ -r $i ]; then
. $i
fi
done
fi
```
Source the profile file, or start a new shell, and then test it:
```
echo $PATH
/home/varl/.nix-profile/bin:/nix/var/nix/profiles/default/bin:/sbin:/bin:/usr/sbin:/usr/bin
echo $NIX_PATH
nixpkgs=/home/varl/.nix-defexpr
```
## Managing packages
I promised package management for this volume, and it shall be done. We
have the right amount of environment integration to make it all work.
Back in the `~/.config/nixpkgs/config.nix` file, let's extend it to
cover:
- **user-base**: packages that are cross-platform and I want available
always, both on mac and linux.
- **user-linux**: linux specific packages that do not exist, or I don't
want, on MacOS.
- **user-macos**: macos specific packages that i don't want/need on other
OSes.
```nix
pkgs : {
allowUnfree = true;
packageOverrides = pkgs: with pkgs; rec {
nixUserProfile = writeText "nix-user-profile" ''
export PATH=$HOME/.nix-profile/bin:/nix/var/nix/profiles/default/bin:/sbin:/bin:/usr/sbin:/usr/bin
export NIX_PATH="nixpkgs=$HOME/.nix-defexpr"
'';
userBasePkgs = pkgs.buildEnv {
name = "user-base";
paths = [
(runCommand "profile" {} ''
mkdir -p $out/etc/profile.d
cp ${nixUserProfile} $out/etc/profile.d/nix-user-profile.sh
'')
fzf
fd
ripgrep
vim
rsync
gnupg
curl
wget
];
pathsToLink = [
"/share"
"/bin"
"/etc"
];
extraOutputsToInstall = [ "man" "doc" ];
};
userLinuxPkgs = pkgs.buildEnv {
name = "user-linux";
paths = [
userBasePkgs
bitwarden
bitwarden-cli
signal-desktop
xcolor
];
pathsToLink = [] ++ (userBasePkgs.pathsToLink or []);
extraOutputsToInstall = []
++ (userBasePkgs.extraOutputsToInstall or []);
};
userMacOSPkgs = pkgs.buildEnv {
name = "user-macos";
paths = [
userBasePkgs
bash
zsh
zsh-completions
alacritty
karabiner-elements
coreutils
podman
podman-compose
];
pathsToLink = [
"/Applications"
] ++ (userBasePkgs.pathsToLink or []);
extraOutputsToInstall = []
++ (userBasePkgs.extraOutputsToInstall or []);
};
};
}
```
Key here is to notice that we defined two more packages, `userLinuxPkgs`
and `userMacOSPkgs`[^3], and the first item in the `paths` list is
`userBasePkgs`.
[^3]:
Notice that the `pathsToLink` for `userMacOSPkgs` includes
`/Applications`. This is an attempt to link the package to the
`/Applications` to allow e.g. Spotlight to find the application we
installed with Nix.
This is there so that when we install `user-linux` or `user-macos`, it
installs the `user-base` package which includes any common packages that
we might have. We definitely have one in common, the package that
creates the script to update our environment, but the list can be as
long or short as we want it.
On Linux I would run:
```
nix-env -ir user-linux
```
And on MacOS I would run:
```
nix-env -ir user-macos
```
`nix-env` would then rebuild my environment and install any packages
defined in `~/.config/nixpkgs/config.nix`.
To remove packages, we just delete them from the `config.nix` file, and
re-run the relevant `nix-env -ir` command.
## Finding package names to use in `config.nix`
Since we can pin our `nixpkgs` to a commit, it is nice to avoid some
versions on packages in `config.nix`.
When we search with `nix-env -qa vim`, we would get back a string like
`vim-9.0.1642`. We can add that to the `paths` list, but when there is a
new version it would stop working.
Instead we can search for packages with the `-P` flag to find
the unambiguous attribute path.
```
nix-env -qaP vim
vim vim-9.0.1642
```
The first column, `vim`, is the path we can use to refer to the `vim`
package without the version.
For NodeJS it can look like:
```
nix-env -qaP nodejs
nodejs-14_x nodejs-14.21.3
nodejs_14 nodejs-14.21.3
nodejs-16_x nodejs-16.20.1
nodejs_16 nodejs-16.20.1
elmPackages.nodejs nodejs-18.17.0
nodejs-18_x nodejs-18.17.0
nodejs_18 nodejs-18.17.0
nodejs_20 nodejs-20.5.0
```
Here we could opt for `nodejs_18` to get the latest 18.x version.
When looking for multiple packages, I would recommend doing something
like this:
```
nix-env -qaP | fzf
```
`nix-env -qa` is a very slow command that loads all the packages before
searching according to a pattern, so loading the packages once, then
piping them into `fzf` provides a nice and fast search interface over
all the packages in `nixpkgs`.
## Where are we now ?
We have set up our `NIX_PATH` to refer to a local clone of `nixpkgs`
that we use to pin software versions.
We have also configured our `PATH` to include directories where nix
places binaries that we want to take precedence.
We did so by leveraging a custom package that generates a script to
update our environment.
We still needed to manually wire up our shell to actually run the
scripts when the shell starts, and we did that in our `.profile` file
(different for sh/bash/zsh).
We then added more packages to our `config.nix` and looked at how to
manage packages in two ways: 1) common packages that are cross-platform
and 2) packages that are specific to an operating system (mac/linux).
We can install all the common + operating system specific packages with
a single command using nested package evaluation in Nix the language.
And finally we took a look at how to search for packages and refer to
them in `config.nix`.
All this means that you can declare packages in `config.nix`, and move
that file across computers. It will work the same regardless of machine.
Put it in a Git repo and keep your user environments in sync.
/v.

View file

@ -0,0 +1,15 @@
---
title: varl's nixtapes vol.3
date: 2023-08-15
pub:
year: 2023
collection: nixtapes
---
# Context
# Prerequisites
- Declarative development environments
- Nested nix environments.
- Setting up WARP and `app` command

15
src/posts/foo.md Normal file
View file

@ -0,0 +1,15 @@
---
title: foo
keywords:
- foo
- bar
draft: true
pub:
year: 2023
---
asdf
```javascript
const foo = true;
```

8
tailwind.config.js Normal file
View file

@ -0,0 +1,8 @@
export default {
// we need to run the class extraction on the build result
content: ['./build/**/*.html'],
theme: {
extend: {},
},
plugins: [],
};