init blog
This commit is contained in:
commit
0caaae801e
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
build/
|
||||
node_modules/
|
16
lib/css/main.css
Normal file
16
lib/css/main.css
Normal 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
23
lib/layouts/default.njk
Normal 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
22
lib/layouts/index.njk
Normal 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
21
lib/layouts/post.njk
Normal 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
69
lib/tailwind-renderer.js
Normal 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
140
metalsmith.js
Normal 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
1570
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
26
package.json
Normal file
26
package.json
Normal 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
3
prettier.config.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
singleQuote: true,
|
||||
};
|
BIN
src/assets/P7240102.jpg
Normal file
BIN
src/assets/P7240102.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 675 KiB |
6
src/index.md
Normal file
6
src/index.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: index
|
||||
layout: index.njk
|
||||
---
|
||||
|
||||
asdf
|
453
src/posts/2019/bash-powered-flat-file-blog.md
Normal file
453
src/posts/2019/bash-powered-flat-file-blog.md
Normal 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
181
src/posts/2019/chili.md
Normal 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.
|
||||
|
||||
---
|
||||
|
23
src/posts/2019/tao-te-ching.md
Normal file
23
src/posts/2019/tao-te-ching.md
Normal 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.
|
||||
```
|
8
src/posts/2019/we-did-a-thing.md
Normal file
8
src/posts/2019/we-did-a-thing.md
Normal 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
31
src/posts/2020/house.md
Normal 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.
|
15
src/posts/2020/keep-up-the-good-work.md
Normal file
15
src/posts/2020/keep-up-the-good-work.md
Normal 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.
|
17
src/posts/2020/key-to-success.md
Normal file
17
src/posts/2020/key-to-success.md
Normal 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.
|
95
src/posts/2020/monitor-with-irc.md
Normal file
95
src/posts/2020/monitor-with-irc.md
Normal 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.
|
393
src/posts/2023/varls-nixtape-vol-1.md
Normal file
393
src/posts/2023/varls-nixtape-vol-1.md
Normal 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.
|
464
src/posts/2023/varls-nixtape-vol-2.md
Normal file
464
src/posts/2023/varls-nixtape-vol-2.md
Normal 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.
|
15
src/posts/2023/varls-nixtape-vol-3.md
Normal file
15
src/posts/2023/varls-nixtape-vol-3.md
Normal 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
15
src/posts/foo.md
Normal 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
8
tailwind.config.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
export default {
|
||||
// we need to run the class extraction on the build result
|
||||
content: ['./build/**/*.html'],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
Loading…
Reference in a new issue