Compare commits

...

No commits in common. 'main' and 'restructure' have entirely different histories.

  1. 3
      .Dockerignore
  2. 19
      .github/workflows/deploy.yml
  3. 36
      .gitignore
  4. 9
      Dockerfile
  5. 2
      LICENSE
  6. 14
      README.md
  7. 141
      components/BlogList.js
  8. 66
      components/Header.js
  9. 62
      components/Layout.js
  10. 278
      components/Meta.js
  11. 42
      components/Profile.js
  12. 11
      content/posts/2020-11-06-current-doings.md
  13. 75
      content/posts/2020-11-17-booleans-are-wasted-memory.md
  14. 8
      content/posts/2020-12-18-update-december.md
  15. 10
      content/posts/2021-01-07-delete-facebook.md
  16. 8
      content/posts/2021-01-11-100daystooffload.md
  17. 14
      content/posts/2021-01-11-are-humans-still-evolving.md
  18. 16
      content/posts/2021-01-13-512kb-club.md
  19. 53
      content/posts/2021-01-15-compiling-your-own-kernel.md
  20. 62
      content/posts/2021-01-18-reasons-the-fediverse-is-better.md
  21. 14
      content/posts/2021-01-23-signal-to-noise.md
  22. 72
      content/posts/2021-01-26-vim-macros.md
  23. 52
      content/posts/2021-01-29-sudo-to-doas.md
  24. 72
      content/posts/2021-02-02-bem-methodology.md
  25. 51
      content/posts/2021-02-07-storage-setup.md
  26. 14
      content/posts/2021-02-11-10-percent-100daystooffload.md
  27. 44
      content/posts/2021-02-17-notes-on-flutter-web.md
  28. 98
      content/posts/2021-02-20-changelogs.md
  29. 57
      content/posts/2021-02-24-vim-terminal-strategies.md
  30. 28
      content/posts/2021-03-13-git-builtin-lifesaver.md
  31. 247
      content/posts/2021-04-07-pgp-guide.md
  32. 8
      content/posts/_2021-01-14-diy-software.md
  33. 12
      content/posts/_2021-01-22-library-of-babel.md
  34. 37
      content/posts/_2021-01-25-kotlin-refactor-strategies.md
  35. 81
      content/posts/fighting-array-functions-with-es6.md
  36. 59
      content/posts/introducing-slashdev-space.md
  37. 83
      content/posts/lightweight-vpn-with-wireguard.md
  38. 63
      content/posts/patch-based-git-workflow.md
  39. 28
      content/posts/quick-tip-terminal-pastebin.md
  40. 120
      content/posts/testing-isnt-hard.md
  41. 40
      content/posts/whom-do-you-trust.md
  42. 1
      favicon.svg
  43. 93
      fonts/work-sans/OFL.txt
  44. BIN
      fonts/work-sans/WorkSans-Italic-VariableFont_wght.ttf
  45. BIN
      fonts/work-sans/WorkSans-VariableFont_wght.ttf
  46. 20
      gen-post.sh
  47. 131
      index.html
  48. 93
      lib/rss.js
  49. 12
      next.config.js
  50. 13393
      package-lock.json
  51. 21
      package.json
  52. 5
      pages/_app.js
  53. 208
      pages/posts/[post].js
  54. 49
      pages/posts/index.js
  55. 4
      public/.well-known/brave-rewards-verification.txt
  56. BIN
      public/assets/flutter_web_renderer_canvaskit.png
  57. BIN
      public/assets/flutter_web_renderer_html.png
  58. BIN
      public/assets/signed_commit.png
  59. 1
      public/favicon.svg
  60. 4
      public/vercel.svg
  61. 36
      styles/atoms.css
  62. 17
      styles/contact.css
  63. 18
      styles/darkmode.css
  64. 39
      styles/index.css
  65. 61
      styles/services.css
  66. 16
      styles/support.css
  67. 25
      styles/typography.css
  68. 35
      styles/welcome.css

3
.Dockerignore

@ -0,0 +1,3 @@
node_modules/
out/
.next/

19
.github/workflows/deploy.yml

@ -0,0 +1,19 @@
on:
push:
branches:
- master
name: Deploy to Github Pages
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Deploy
uses: JamesIves/github-pages-deploy-action@master
env:
ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages
FOLDER: out
BUILD_SCRIPT: npm ci && npm run build && touch out/.nojekyll
CNAME: blog.garrit.xyz

36
.gitignore

@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
public/rss.xml

9
Dockerfile

@ -0,0 +1,9 @@
FROM node:13-alpine
COPY . /app
WORKDIR /app
RUN npm install && npm run build
FROM nginx:alpine
COPY --from=0 /app/out /usr/share/nginx/html
EXPOSE 80

2
LICENSE

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 Garrit Franke <garrit@slashdev.space>
Copyright (c) 2020 Garrit Franke
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

14
README.md

@ -1,3 +1,13 @@
# /dev.space Landing Page
# blog.garrit.xyz
Sourcecode for [slashdev.space](https://slashdev.space).
This is the repository for my personal blog.
## Generating posts
Running the following command will generate a new blog post with the necessary boilerplate.
```
./contrib/gen-post.sh My first post
# -> 2021-04-12-my-first-post.md
```
https://blog.garrit.xyz

141
components/BlogList.js

@ -0,0 +1,141 @@
import Link from "next/link";
import ReactMarkdown from "react-markdown";
function reformatDate(fullDate) {
const date = new Date(fullDate);
return date.toDateString().slice(4);
}
function truncateSummary(content) {
return content.slice(0, 200).trimEnd() + "...";
}
const BlogList = ({ posts }) => {
return (
<>
<ul className="list">
{posts.length > 1 &&
posts
// Filter drafts
.filter((post) => !post.slug.startsWith("_"))
// Ternary operator is used to fix chromium sorting
// See: https://stackoverflow.com/a/36507611
.sort((a, b) => (a.frontmatter.date < b.frontmatter.date ? 1 : -1))
.map((post) => (
<Link key={post.slug} href={{ pathname: `/posts/${post.slug}` }}>
<a>
<li>
<div className="blog__info">
<h2>{post.frontmatter.title}</h2>
<h3> {reformatDate(post.frontmatter.date)}</h3>
<p>
<ReactMarkdown
source={truncateSummary(post.markdownBody)}
/>
</p>
</div>
</li>
</a>
</Link>
))}
</ul>
<style jsx>
{`
margin-bottom: 0;
a:hover {
opacity: 1;
}
a:hover li div.hero_image img {
opacity: 0.8;
transition: opacity 0.3s ease;
}
a:hover li .blog__info h2,
a:hover li .blog__info h3,
a:hover li .blog__info p {
transform: translateX(10px);
transition: transform 0.5s ease-out;
}
@media (prefers-reduced-motion) {
a:hover li .blog__info h2,
a:hover li .blog__info h3,
a:hover li .blog__info p {
transform: translateX(0px);
}
}
.hero_image {
width: 100%;
height: 33vh;
overflow: hidden;
background-color: #000;
}
.hero_image img {
object-fit: cover;
object-position: 50% 50%;
opacity: 1;
transition: opacity 0.3s ease;
min-height: 100%;
}
.blog__info {
display: flex;
flex-direction: column;
justify-content: center;
padding: 1.5rem 1.25rem;
transform: translateX(0px);
transition: transform 0.3s ease-in;
}
.blog__info h2,
.blog__info h3,
.blog__info p {
transform: translateX(0px);
transition: transform 0.5s ease-out;
}
li {
opacity: inherit;
display: flex;
flex-direction: column;
min-height: 38vh;
margin-bottom: 0;
}
h2 {
margin-bottom: 0.5rem;
}
h3 {
margin-bottom: 1rem;
}
p {
max-width: 900px;
}
@media (min-width: 768px) {
li {
min-height: 250px;
height: 33.333vh;
flex-direction: row;
}
.hero_image {
height: 100%;
}
.hero_image img {
min-width: 100%;
height: 100%;
width: auto;
min-height: 0;
}
.blog__info {
min-width: 70%;
}
}
@media (min-width: 1280px) {
.blog__info {
padding: 3rem;
}
h3 {
margin-bottom: 1.2rem;
}
}
`}
</style>
</>
);
};
export default BlogList;

66
components/Header.js

@ -0,0 +1,66 @@
import { useLayoutEffect, useState } from "react";
import Link from "next/link";
import Profile from "./Profile";
function useWindowSize() {
const [size, setSize] = useState([0, 0]);
useLayoutEffect(() => {
function updateSize() {
setSize([window.innerWidth, window.innerHeight]);
}
window.addEventListener("resize", updateSize);
updateSize();
return () => window.removeEventListener("resize", updateSize);
}, []);
return size;
}
export default function Header(props) {
const [windowWidth, windowHeight] = useWindowSize();
return (
<header className="header">
<nav className="nav" role="navigation" aria-label="main navigation">
<Link href="/">
<a>
<h1>{props.siteTitle}</h1>
</a>
</Link>
{windowWidth >= 768 && <Profile></Profile>}
</nav>
<style jsx>
{`
h1 {
margin-bottom: 0;
}
h1:hover {
cursor: pointer;
}
nav {
padding: 1.5rem 1.25rem;
display: flex;
justify-content: space-between;
flex-direction: row;
align-items: center;
}
@media (min-width: 768px) {
.header {
height: 100vh;
position: fixed;
left: 0;
top: 0;
}
.nav {
padding: 2rem;
width: 30vw;
height: 100%;
border-bottom: none;
flex-direction: column;
align-items: flex-start;
}
}
`}
</style>
</header>
);
}

62
components/Layout.js

@ -0,0 +1,62 @@
import { useState, useLayoutEffect } from "react";
import Header from "./Header";
import Meta from "./Meta";
import Profile from "./Profile";
function useWindowSize() {
const [size, setSize] = useState([0, 0]);
useLayoutEffect(() => {
function updateSize() {
setSize([window.innerWidth, window.innerHeight]);
}
window.addEventListener("resize", updateSize);
updateSize();
return () => window.removeEventListener("resize", updateSize);
}, []);
return size;
}
export default function Layout({
siteTitle,
siteDescription,
children,
pathname,
}) {
const [windowWidth, windowHeight] = useWindowSize();
return (
<section className={`layout`}>
<Meta siteTitle={siteTitle} siteDescription={siteDescription} />
<Header siteTitle="~/garrit" />
<div className="content">{children}</div>
{windowWidth <= 768 && <Profile className="content"></Profile>}
<style jsx>
{`
.layout {
overflow-x: hidden;
display: flex;
flex-direction: column;
min-height: 100vh;
}
.layout .info_page {
color: #ebebeb;
}
.content {
flex-grow: 1;
}
@media (min-width: 768px) {
.layout {
display: block;
}
.content {
flex-grow: none;
width: 70vw;
margin-left: 30vw;
}
}
`}
</style>
</section>
);
}

278
components/Meta.js

@ -0,0 +1,278 @@
import Head from "next/head";
export default function Meta(props) {
return (
<>
<Head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charSet="utf-8" />
<title>{props.siteTitle}</title>
<meta
name="Description"
content="Random thoughts, tips and rants about software"
></meta>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<script async defer data-domain="blog.garrit.xyz" src="https://analytics.slashdev.space/js/plausible.js"></script>
</Head>
<style jsx global>
{`
@import url("https://fonts.googleapis.com/css?family=Work+Sans&display=swap");
* {
box-sizing: inherit;
}
html {
box-sizing: border-box;
overflow-y: scroll;
}
body {
margin: 0;
font-family: "Work Sans", "Helvetica Neue", Helvetica, sans-serif;
overflow-x: hidden;
color: #000;
font-size: 16px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
text-decoration: none;
color: inherit;
transition: opacity 0.2s ease;
}
a:hover {
transition: opacity 0.2s ease;
opacity: 0.5;
text-decoration-color: inherit;
}
ul {
list-style: none;
margin: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
list-style-position: outside;
list-style-image: none;
}
ol {
margin: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
list-style-position: outside;
list-style-image: none;
}
ul,
ol,
p {
margin-bottom: 1.45rem;
}
img {
max-width: 100%;
}
img,
figure,
table,
fieldset {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
pre {
margin-left: 0;
margin-right: 0;
margin-top: 0;
margin-bottom: 1.45rem;
font-size: 0.85rem;
line-height: 1.42;
background: hsla(0, 0%, 0%, 0.04);
border-radius: 3px;
overflow: auto;
word-wrap: normal;
padding: 1.45rem;
}
table {
font-size: 1rem;
line-height: 1.45rem;
border-collapse: collapse;
width: 100%;
}
blockquote {
margin-left: 1.45rem;
margin-right: 1.45rem;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
strong {
font-weight: bold;
}
li {
margin-bottom: calc(1.45rem / 2);
}
ol li {
padding-left: 0;
}
ul li {
padding-left: 0;
}
li > ol {
margin-left: 1.45rem;
margin-bottom: calc(1.45rem / 2);
margin-top: calc(1.45rem / 2);
}
li > ul {
margin-left: 1.45rem;
margin-bottom: calc(1.45rem / 2);
margin-top: calc(1.45rem / 2);
}
blockquote *:last-child {
margin-bottom: 0;
}
li *:last-child {
margin-bottom: 0;
}
p *:last-child {
margin-bottom: 0;
}
li > p {
margin-bottom: calc(1.45rem / 2);
}
code {
line-height: 1.45rem;
}
p code {
background: hsla(0, 0%, 0%, 0.1);
padding: 0 0.4rem;
}
@media (prefers-reduced-motion) {
* {
transition: none !important;
}
}
{
/* //TYPOGRAPHY------------------------------------- */
}
h1,
h2,
h3,
h4,
h5,
h6,
p {
font-family: "Work Sans", "Helvetica Neue", Helvetica, sans-serif;
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
color: inherit;
text-rendering: optimizeLegibility;
}
h1,
h2 {
font-weight: 500;
}
h1 {
font-size: 2rem;
letter-spacing: -1px;
line-height: 1.1875;
}
h2 {
font-size: 1.7rem;
letter-spacing: -0.75px;
line-height: 1.2;
}
h3 {
font-size: 1.2rem;
letter-spacing: -0.5px;
line-height: 1.1875;
color: #a0a0a0;
font-weight: normal;
}
p {
font-size: 1.2rem;
letter-spacing: -0.5px;
line-height: 1.5;
color: #464646;
}
@media (min-width: 1280px) {
h1 {
font-size: 2rem;
letter-spacing: -1px;
line-height: 1.1875;
}
h2 {
font-size: 1.5rem;
letter-spacing: -0.75px;
line-height: 1.1667;
}
h3 {
font-size: 1rem;
letter-spacing: -0.5px;
line-height: 1.1875;
color: #a0a0a0;
font-weight: normal;
}
p {
line-height: 1.4375;
}
}
// FIXME: I could not get this to work inside the post component,
// but here it apparently works. Maybe an overriding selector?
.blog__body a,
.blog__footer a {
text-decoration: underline;
}
@media (prefers-color-scheme: dark) {
:root {
background-color: #161618;
color: #dbd7db;
}
html {
scrollbar-color: #dbd7db #161618 !important;
}
h1,
h2,
h3,
h4,
p,
pre,
a,
ul,
li,
blog__body > * {
color: #dbd7db;
}
.button__link {
background-color: #67676c;
}
a {
color: #dbd7db;
}
}
`}
</style>
</>
);
}

42
components/Profile.js

@ -0,0 +1,42 @@
import Link from "next/link";
export default function Profile(props) {
return (
<div className="profile">
<h2>Garrit Franke</h2>
<p>Random thoughts, tips and rants about software</p>
<Link href="https://lists.sr.ht/~garritfra/public-inbox">
Public Inbox
</Link>
<br />
<Link href="https://matrix.to/#/@garrit:matrix.slashdev.space">
Matrix
</Link>
<br />
<Link href="https://garrit.xyz">Website</Link>
<br />
<Link href="https://github.com/garritfra">Github</Link>
<br />
<Link href="https://www.linkedin.com/in/garritfranke/">LinkedIn</Link>
<br />
<Link href="/rss.xml">RSS</Link>
<style jsx>
{`
img {
width: 12rem;
}
Link {
margin: 1rem;
}
@media (max-width: 767px) {
.profile {
padding: 1.5rem 1.25rem;
}
}
`}
</style>
</div>
);
}

11
content/posts/2020-11-06-current-doings.md

@ -0,0 +1,11 @@
---
title: Updates, November 2020
date: "2020-11-06"
---
Hi, I wanted to share some things I'm currently working on. Maybe I'll turn this into a monthly thing, who knows. :)
One major goal I set for myself in the upcoming months is to build a SaaS for freelancers. Some features of this will include handling clients, projects and expenses. A thing I'm struggeling with right now it to find a lightweight way to host it. It is currently deployable through docker containers, but I am not 100% satisfied with my current setup. I will give some updates on this in the future. I aim to release a very early alpha version for free soon, so that some people can stress test it extensively. For now, you can of course self-host it. I really don't want to impose subscription fees for it's users, but I will see how it goes. You can find the source code for it [here](https://github.com/garritfra/omega-crm).
Recently, I increasingly gained interested in the [Gemini project](https://gemini.circumlunar.space/). In a nutshell, this is a very minimal alternative to HTTP, with a strong emphasis on simplicity. The maintainers clearly embrace a DIY mindset, which I want to follow. I set myself the rule to only interact with gemini using tools I wrote myself. To achive this, I am currently writing [my own Gemini server called "Taurus"](https://git.sr.ht/~garritfra/taurus) to eventually set up my own geminispace. I have not yet looked deeply into building a client, but I might do this once I'm happy with my server. I admit that I'm currently cheating a bit, by testing my server using a browser recommended by the gemini team ;)
If you are interested in this project, I highly recommend to check out the [gemini specification](https://gemini.circumlunar.space/docs/specification.html), and play around with some geminispaces. Maybe you could set up a server for yourself?

75
content/posts/2020-11-17-booleans-are-wasted-memory.md

@ -0,0 +1,75 @@
---
title: Booleans are wasted memory
date: "2020-11-17"
---
A boolean is either `true` or `false`. That translates to `1` or `0`. If you think that one bit is enough to store this information, you'd be wrong.
In order to keep the binary layout of a program simple and convenient, most languages store information in 8 bit (1 byte) blocks.
If you allocate a `bool` in Rust or (most) other languages that are based on LLVM, [it will take up 1 `i1`, or 1 byte of memory](https://llvm.org/docs/LangRef.html#simple-constants). If you allocate a boolean value in C, you will get [an integer constant with a value of either 1 or 0](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdbool.h.html).
If you find yourself having to store multiple boolean states somewhere, you might simply declare those booleans and call it a day:
```c
#include <stdbool.h>
#include <stdio.h>
int main()
{
bool can_read = true;
bool can_write = true;
bool can_execute = false;
if (can_read)
printf("read bit set\n");
if (can_write)
printf("write bit set\n");
if (can_execute)
printf("execute bit set\n");
// Output:
// read bit set
// write bit set
}
```
## We can do better than this
An alternative approach to store boolean values is to share a "chunk" of bits with other values. This is usually done using bitwise operations:
```c
#include <stdbool.h>
#include <stdio.h>
// Define permissions
#define PERM_NONE 0b000
#define PERM_READ 0b001
#define PERM_WRITE 0b010
#define PERM_EXECUTE 0b100
#define PERM_ALL PERM_READ | PERM_WRITE | PERM_EXECUTE
int main()
{
// Allocate 1 byte for permissions
char permissions = PERM_READ | PERM_WRITE;
if (permissions & PERM_READ)
printf("write bit set\n");
if (permissions & PERM_WRITE)
printf("read bit set\n");
if (permissions & PERM_EXECUTE)
printf("execute bit set\n");
// Output:
// read bit set
// write bit set
}
```
This example still wastes 5 bits since we only use 3 out of 8 possible bits of the char type, but I'm sure you get the point. Allocating 3 boolean values independently would waste 7 * 3 = 21 bits, so it's a massive improvement. Whenever you find yourself needing multiple boolean values, think twice if you can use this pattern.
Microcontrollers have a very constrainted environment, therefore bitwise operations are essential in those scenarios. 7 wasted bits are a lot if there are only 4 kb of total memory available. For larger systems we often forget about these constraints, until they add up.
## My Plea
* Be mindful about the software you create.
* Appreciate the resources at your disposal.

8
content/posts/2020-12-18-update-december.md

@ -0,0 +1,8 @@
---
title: Updates, December 2020
date: "2020-12-18"
---
It's christmas season! I hope you and your family are safe and sound. My main focus this month is to expand my knowledge about compilers, by [building one from scratch](https://sr.ht/~garritfra/sabre/). It's a compiler for a very simple language, but it gets the job done. So far, you can write [simple algorithms](https://git.sr.ht/~garritfra/sabre/tree/master/examples), and compile them to JavaScript. A more sophisticated C backend is in development, but I still need a plan for expanding the target-specific builtin functions to provide more features in the standard library. An important topic at the moment is the [documentation of the project](https://garritfra.github.io/sabre/). Since the compiler itself has gotten relatively stable, all the language-features now need to be captured and written down. There is also a [contributing guide](https://garritfra.github.io/sabre/developers/contributing.html), if you want to help out, or want to get into compiler design.
Stay home and stay safe!

10
content/posts/2021-01-07-delete-facebook.md

@ -0,0 +1,10 @@
---
title: I closed my Facebook account, and you should too
date: "2021-01-07"
---
I know I should have done this a while ago, but with ever-increasing scandals about data privacy surrounding Facebook, I finally decided to get rid of it.
I haven't used the service in a long time anyway, but I always told myself "what if I needed the data later?", or "what if a friend contacted me, and I didn't respond?", "what if I missed the birthday of someone I'm close with?!". Well, according to my facebook inbox, the only messages I received lately were some random links from people I'm not really in touch with anymore. Birthdays? Do you think someone you haven't talked to in over three years will get mad at you, for forgetting their birthday? And regarding your data: you won't loose it! [This guide](https://www.facebook.com/help/212802592074644) describes how you can download a copy of your data as html and/or json.
Go ahead and ask yourself: Is there anything holding you back from deleting your Facebook account? What would you loose? How often do you even use the service? Do the social benefits of Facebook **really** outweigh the negative aspects (privacy concerns, data collection, etc.)?

8
content/posts/2021-01-11-100daystooffload.md

@ -0,0 +1,8 @@
---
title: 100DaysToOffload
date: "2021-01-11"
---
For some time now, I've seen this #100DaysToOffload hashtag on my social medias. I knew that it was some kind of writing challenge, but I never thought about taking part in it. Since I recently started to blog more frequently though, I think this challenge could be very beneficial to my writing skills, and just jotting my thoughts down in general. So, starting with this entry, I will try to publish 100 (hopefully) useful posts on this blog within one year. My "deadline" for this will be January 11, 2022. I will post every entry to [my mastodon account](https://fosstodon.org/@garritfra).
This is post 001 of [#100DaysToOffload](https://100daystooffload.com/).

14
content/posts/2021-01-11-are-humans-still-evolving.md

@ -0,0 +1,14 @@
---
title: Are humans still evolving?
date: "2021-01-11"
---
This is by no means a scientifically accurate article. These are my thoughts, and I'd be happy to discuss this topic. Take it with a grain of salt.
Evolution builds upon natural selection. With every generation of a species, there is a slight chance of mutation, possibly giving an individual advantages or disadvantages in the ability to survive and give offspring. Somewhere way up in our evolutionary tree, a microbe might have mutated a gene that allowed it expand and retract a part of its body. As it turns out, this proves useful in fleeing from predators, while other individuals of this species might fall prey on their first day. The microbe has a slightly larger chance to survive and give offspring. On the other hand, mutations might also result in a fatal illness (cancer, in other words). Oftentimes, this individual does not survive long enough to give offspring.
150 years ago, giving birth was literally an act of life and death. Many children died at a young age. They were not tough enough from an evolutionary standpoint, and were therefore "filtered" out by natural selection. Only the strongest survived and gave offspring.
Todays medicine is (fortunately!) very powerful. Very few children die at birth and a lot less people are dying from an illness like the flu. This is an evolutionary anomaly. Natural selection has been defeated to a certain degree. We don't need to run from predators anymore and are less prone to desease. Every one of us is more or less equally able to give offspring. We can't really gain an advantage over others anymore. We are not evolving.
This is post 002 of [#100DaysToOffload](https://100daystooffload.com/).

16
content/posts/2021-01-13-512kb-club.md

@ -0,0 +1,16 @@
---
title: I joined the 512KB club
date: "2021-01-13"
---
JavaScript rules the web, literally. In fact, this website is built with JavaScript (Next.js). I recently started to think about if I really needed this much overhead for a simple site like this. After all, I don't have any fancy user interaction features or complex animation that would justify the JavaScript on this page.
There is a new (no, not that new) philosophy called [the lean web](https://leanweb.dev/). It essentially tries to keep websites tiny and semantically correct. This has many benefits, ranging from less pollution generated by your site to improved SEO, since many search engines favor a semantically correct website over a site that abuses JavaScript to mimic the features, that are baked into html anyway.
In order to get lean, I decided to join [the 512KB club](https://512kb.club/). This website lists sites that are below 512KB in total (uncompressed, with all dependencies). To get below that mark, I had to remove my face from the frontpage (I'm sure you'll miss it😅), since the image itself was roughly 750KB. I'm now just below 500KB, which qualifies me to join the blue team.
[![Blue Team](https://512kb.club/images/blue-team.svg)](https://512kb.club)
I'm not planning to stop here though. I think keeping a website small and simple is an excellent practice. My next step will be to get rid of all the JS junk on this site and only rely on HTML and CSS. I still want to be able to write my posts in Markdown, so I will have to come up with a way to generate pages from them. A safe bet for this would be to use a SSG like [Hugo](https://gohugo.io/). Frankly, [writing my own simple SSG probably wouldn't hurt either](https://erikwinter.nl/articles/2020/why-i-built-my-own-shitty-static-site-generator/). Let's see how high I can climb the ranks of the 512KB club. Care to join me?
This is post 003 of [#100DaysToOffload](https://100daystooffload.com/).

53
content/posts/2021-01-15-compiling-your-own-kernel.md

@ -0,0 +1,53 @@
---
title: Compiling your own kernel
date: "2021-01-15"
---
I'm currently in the midst of fiddling around with the kernel a bit, and I figured I just documented my process a bit. Unfortunately, since I'm using a Mac for day to day work, I have to rely on a virtual machine to run anything Linux-related. VirtualBox doesn't support the most recent kernels (5.9 is the most recent supported one), so there won't be any cutting-edge development happening here. I decided to use ubuntu as my guest system, since it's very easy to set up.
So, the first step is to get the sources. You could simply go ahead and download a specific release from [kernel.org](https://kernel.org/), but since I want to hack on it, I decided to go the git-route. Simply download the sources from [their repo](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/) and check out the tag you want to build.
> **Note**: this might take a while. Their repository is huge! If you want to only need the `HEAD` and want to build on bare-metal (no VirtualBox), you could only clone the latest commit using `git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git --depth=1`.
Next up, you need to generate a `.config`. This file describes which features you want to compile into your kernel. To make a generic config that only compiles drivers for the hardware of your system, you can run the following commands:
```bash
# Copy the config of your current kernel into the repo
make oldconfig
# Only enable modules that are currently used by the system
make localmodconfig
```
Now, let's get to actually compiling the kernel. In my case, I assigned 4 cores to my VM. The `-j` option tells make to run 4 jobs in parallel.
> **Caution**: Just providing -j will freeze your system, since make will try to launch an infinite amount of processes!
```
make -j4
```
Again, this might take some time. Go for a walk, get a coffee or watch your favorite TV-show. After compilation has finished, we need to install the kernel. To do so, run the following commands:
```
sudo make modules_install
sudo make install
```
In order to boot, we need to tell our bootloader about our new kernel. Run this command to update your grub config:
```
sudo update-grub2
```
And voila! Your new kernel should be ready.
Reboot the system, and grub should pick up the new kernel and boot to it. If that's not the case, you should be able to pick the kernel from the grub menu under `advanced options`.
## Retrospective
I found that building my own kernel is a highly educational and fun experience. Using VirtualBox is a pain in the `/dev/null` to work with, since it has to add a lot of overhead to the system in order to work. You sometimes have to wait over 6 month until the support for a new kernel arrives. This problem should not apply if you compile on bare metal systems.
Thanks for your time!
This is post 004 of [#100DaysToOffload](https://100daystooffload.com/).

62
content/posts/2021-01-18-reasons-the-fediverse-is-better.md

@ -0,0 +1,62 @@
---
title: 6 reasons the Fediverse is better than regular social media
date: "2021-01-18"
---
Social media sucks. Platforms like Twitter, Facebook and Instagram are designed to turn your precious free time into money. What we see as a nice way to stay in touch with our friends, in reality are just many hits of dopamine stimulating precise spots in your brain, leading to you spending more time on the platform consuming ads.
But what if I told you that there is a huge ad-free social network out there, not governed by a central authority, full of great people and completely free to use? This place is called the fediverse. Well, it's not really **a** place, it's many places.
## What is the fediverse?
At its core, the fediverse is a mesh of interconnected nodes on the internet, all communicating in the same language. Every instance on the fediverse implements the [ActivityPub](https://activitypub.rocks/) protocol, which allows it to talk to other instances on the network. The phrase "Fedi" comes from "federated", meaning that content on the network is shared and accessible by anyone.
There are many different software projects for the fediverse out there. If you like the concept of Twitter, you could take a look at [Mastodon](https://joinmastodon.org/), a microblogging-platform for the fediverse. There's also [PeerTube](https://joinpeertube.org/), a federated clone of YouTube (PeerTube recently [released support for peer2peer live streaming support](https://framablog.org/2021/01/07/peertube-v3-its-a-live-a-liiiiive/), like what?! 🤯). You like instagram? [Pixelfed](https://pixelfed.org/) got you covered. Of course, there are many other services worth mentioning, so feel free to dig around a bit! As mentioned, the great thing about the fediverse is that all of these services are connected with each other. If I signed up for an account on a mastodon instance, I can subscribe to your posts on Pixelfed, and vice versa. If I want to get notified about your videos on PeerTube, I can just go ahead and follow your account and comment on your videos.
> "This all sounds great, but why should I bother?"
Let me give you 6 reasons why the fediverse is far superior to all other social media platforms out there, and why you should consider signing up for an account on one of the many instances of the fediverse.
## Reason 1: It's decentralized
Regular social media platforms like Facebook have a **single point of failure**. If their servers go down, your content goes down with it. Content on the fediverse on the other hand is scattered around many instances, which means it is very resilient. If your instance dies, you can move to a new instance. This goes hand in hand with the next reason.
## Reason 2: It can't be censored
You probably heard that the Twitter-account of Donald Trump recently got compromised by the owners of the platform. I don't want to engage in any political discussions, but the main flaw with this is the violation of **freedom of speech**. Even if everything someone says is controversial nonsense, it is still in his good right to express his thoughts.
On the fediverse, a scenario like this would certainly not happen, given its decentralized nature. Some instances still moderate their content, meaning that if someone posts inappropriate content, it might get blocked. The twist here is, if that person disagrees with the rules of the instance, he is free to join another instance.
## Reason 3: Free as in freedom
There's this saying, criticizing modern software projects:
> "If it's free, you're the product"
This is not true in all cases. "Free" can be understood in two ways.
**"Free as in beer"** means that something might seem free at first glance (E.g. Free beer at Oktoberfest), but in the end you often leave with less than you came with. In the case of beer, you often buy another beer after the first free one. Therefore, even though you think you've saved the money for one beer, in reality bought an extra beer. In the case of proprietary software, it's a similar story. While you think that a service is free, you give up your privacy and get monetized with ads.
**"Free as in freedom"** on the other hand means that you won't get "screwed over" like this. Most, if not all of the software for the fediverse is **free and open source software** (FOSS). If you don't like how a certain feature works, **you are completely free to change it**. You can look at the source code and propose changes to the main project, or launch your own spin of that product.
Since everyone can openly look at the source code, it is **audited** by many people, including security experts. This vastly improves the security and stability of the product. If the developers would do shady things, they will most certainly get called out by people, as soon as that code enters the main repositories. Proprietary (closed sourced) platforms like Facebook and Twitter can not be audited. The owners can do whatever they want, including spying on their users, or collect and sell the data of their users.
## Reason 4: It respects your privacy
Since the software on the fediverse is audited by a lot of people, you can be almost 100% certain that joining an instance will not collect any of your personal data. If you are still concerned about your privacy though, you can still be part of the network by launching your own instance, with your own rules. There are tutorials out there, explaining how you can set up a small instance for a very cheap price on your local network.
## Reason 5: It's all about the community
I used to spend a lot of time on "regular" social media platforms. From personal experiences, these platforms are all about promoting yourself and building up your follower count. Connecting with your friends is of little importance. In the eye of some people, you are not worthy to talk to if the amount of followers or likes-per-post didn't exceed a certain threshold.
It has now been about 5 months since I created [my mastodon account](https://fosstodon.org/@garritfra). Talking to people on the fediverse is a completely different experience compared to Facebook or Twitter. Almost everyone I talked to is a polite, grounded person, willing to engage in constructive and fun discussions. I met many people who disagree with my views, but instead of leaving a comment saying that this post sucks, all of them took the time to express their alternative opinions. Every member of the fediverse wants to drive the network forward, which is reflected in their posts.
## Reason 6: There's an instance for everyone
Whether you're into Gaming, Painting, or Spanish dancing music, there is an instance for you. If it isn't, you are free to create one and promote it to people of that niche. You won't loose the social aspect by launching your own instance, since you are still available to other people on the network. If you just want to get started with the fediverse, I recommend that you check out one of the many [lists of mastodon instances](https://instances.social/). If you like instagram and want to stay in a familiar environment, take a look at [Pixelfed](https://pixelfed.org/), and join an instance from the list they provide.
I myself am a person who cares a lot about free and open source software, therefore my choice of instance was [fosstodon.org](https://fosstodon.org/), a mastodon instance geared towards awesome like-minded people.
**Note**: This post has generated some interesting discussions on [Hacker News](https://news.ycombinator.com/item?id=25820646).
This is post 005 of [#100DaysToOffload](https://100daystooffload.com/).

14
content/posts/2021-01-23-signal-to-noise.md

@ -0,0 +1,14 @@
---
title: Signal-to-Noise, or why nobody cares about your GitHub project
date: "2021-01-23"
---
For a very long time, the thought of leaving GitHub and moving to another platform daunted me. Having more users on one platforms means that more people will contribute to my project, right? Wrong.
The problem with GitHub is that there's a lot of things going on around you. How many times have you discovered a cool project on GitHub, starred it and never heard from it again? In essence, this is the same phenomenon as with modern social media. A bombardment of positive stimulants makes the user crave for more, letting them forget about previously consumed content. Sure, if you just want to get your code out there, GitHub might be a great place, but if you are just starting out as a developer and you're looking for contributers and feedback, you will probably be very bummed to find out that nobody cares about your work. Many developers are using the platform because other developers are using it. Your project on GitHub is a drop in an ocean of other projects.
A few months ago, I decided to make the leap and switch most of my development over to [Sourcehut](https://sourcehut.org/), a free and open source code-hosting platform. Besides its great tooling (mailing lists, automated builds, etc.), it has the benefit of a high **signal-to-noise ratio**. Less developers are using the platform, but most of them are very passionate about their work. They care about collaborating with others and they believe in what they are doing, which probably lead them to sign up for this platform in the first place.
Of course, switching away from a platform like GitHub alone does not ensure more contributions. You might be trying to advertise your projects by spamming links on popular newsboards and forums, but this only generates **noise**. Instead, you should intentionally talk about your personal journey with the project in a smaller circle. If other developers in your niche see that you continuously give updates about the project and its improvements, they will eventually start to relate to it. Some of them will look at your project and give feedback, or even contribute patches.
This is post 006 of [#100DaysToOffload](https://100daystooffload.com/).

72
content/posts/2021-01-26-vim-macros.md

@ -0,0 +1,72 @@
---
title: Using Macros in Vim
date: "2021-01-26"
---
For a long time, macros in Vim were a huge mystery for me. I knew they existed, but I didn't know how or why you'd use them. A recent task of mine involved replacing the unsafe operator (`!!`) in a large kotlin codebase with a null-safe operator (`?`). This game me a good opportunity to learn about macros. This is a snippet I encountered numerous times:
```kt
mLeftButton!!.text = "Left"
mLeftButton!!.setOnClickListener(leftListener)
mLeftButton!!.visibility = View.VISIBLE
mRightButton!!.text = "Right"
mRightButton!!.setOnClickListener(rightListener)
mRightButton!!.visibility = View.VISIBLE
```
You could go ahead and change each line individually, or use the IDEs built in "multi-cursor" tool to save you some work. But, let me show you how I automated this using a Vim-Plugin for Android Studio. Not that the plugin matter, it will work in every Vim-like editor.
A macro in Vim works like this:
1. Record any sequence of keystrokes and assign them to a key
1. Execute that sequence as often as you wish
So let's see how we'd do that.
## Recording a macro
To record a macro in Vim, you press `q` (In normal mode) followed by a key you want to assign the macro to. So, if you wanted to record a macro and save it to the `q` key, you'd press `qq`. Vim will notify you that a macro is being recorded. Now, you can press the keystrokes that define your actions. When you're done, press `q` in normal mode again to quit your macro.
Coming back to my task, I would want to do the following:
1. `qq` Record a macro and save it to the `q` key
1. `_` - Jump to the beginning of the line
1. `f!` - Find next occurrence of `!`
1. `cw` - Change word (Delete word and enter insert mode)
1. `?.` - Insert the new characters
1. `<esc>` - Enter normal mode
1. `j` - go down a line
1. `q` - Finish macro
If everything went right, this line:
```
mLeftButton!!.text = "Left"
```
Should now look like this:
```
mLeftButton?.text = "Left"
```
and your macro should be saved under the `q` key.
## Using the macro
In order to use a macro in vim, you press the `@` key, followed by the key the macro is saved under. Since our macro is defined as `q`, we'd press `@q`, and the macro is executed immediately.
Let's take this further. You might have noticed that I went down a line before closing the macro. This becomes handy when you want to execute it many times. In our case we have 6 lines we want to refactor. 1 line has already been altered, so we have to execute it 5 more times. As per usual with vim, you can execute an action n times by specifying a number before doing the action. Let's press `5@q` to execute the macro 5 times. And voila! Our unsafe code is now null-safe.
```kt
mLeftButton?.text = "Left"
mLeftButton?.setOnClickListener(leftListener)
mLeftButton?.visibility = View.VISIBLE
mRightButton?.text = "Right"
mRightButton?.setOnClickListener(rightListener)
mRightButton?.visibility = View.VISIBLE
```
Macros are really satisfying to watch, if you ask me!
This is post 007 of [#100DaysToOffload](https://100daystooffload.com/).

52
content/posts/2021-01-29-sudo-to-doas.md

@ -0,0 +1,52 @@
---
title: From sudo to doas
date: "2021-01-29"
---
You might have heard that there is currently [a pretty significant vulnerability](https://www.qualys.com/2021/01/26/cve-2021-3156/baron-samedit-heap-based-overflow-sudo.txt) affecting `sudo`, the program we all know and love. It is the de facto standard for when you want to run a command as a priviledged user, but that's really it. Under the hood, sudo is a very powerful tool with a lot of features. It can be used to build out complex permission-systems that span across entire clusters of servers. But all of these features come at a price: **complexity**. Last time I checked, the [source code](https://www.sudo.ws/repos/sudo) of sudo had about 330k lines of code (using cloc as a benchmark). This massive complexity plays a large role in its security.
Luckily, there is a **far** more lightweight alternative to sudo called [doas](https://github.com/Duncaen/OpenDoas.git). It essentially does all the things you'd expect from sudo for your average end user. Doas is written in just over 3k lines of code, which, if you think of it, should be more than enough to provide a tool that executes a command as a priviledged user.
## Setup
While there are packages for [some distibutions](https://github.com/slicer69/doas#installation-via-packagesrepositories), I personally had trouble setting it up on arch using yay (for permission reasons, ironically). I recommend going the extra mile and building it from source, which consists of a few commands and some seconds of your time:
```sh
git clone https://github.com/slicer69/doas
cd doas
make
sudo make install
```
Next, you will need to create a config file at `/usr/local/etc/doas.conf`. Paste the following line into it to give your user root access:
```sh
permit alice as root
```
You obviously want to substitute alice with your username. If you have multiple users on your system, simply duplicate that line and substitute the username accordingly. Just restart your terminal window, and you should be able to run programs as root using doas instead of sudo:
```sh
➜ ~ doas id
uid=0(root) gid=0(root) groups=0(root)
```
## Bonus: Save your muscle memory
If you still want to "use" sudo on your machine, you can set up a simple alias in your `.{bash|zsh|fish}rc`. This will also help with compatibility issues of some scripts, if you decide to ditch the actual sudo from your Box entirely. Just paste this line into your corresponding rc file:
```
alias sudo="doas"
```
## Bonus Bonus: Passwordless authentification
You can setup doas to skip the password prompt every time you run a command with it. Simply add the `nopass` option in your doas configuration file:
```sh
permit nopass alice as root
```
I hope you found this useful!
This is post 008 of [#100DaysToOffload](https://100daystooffload.com/).

72
content/posts/2021-02-02-bem-methodology.md

@ -0,0 +1,72 @@
---
title: Notes about BEM (Block Element Modifier)
date: "2021-02-02"
---
In the coming weeks, months and years, I will be working on frontend-development as part of my dayjob. These are some personal notes I took during my research about the BEM methodology. If you want to read the official introduction, you should visit [their website](http://getbem.com/).
# Overview - What is BEM?
BEM — Block Element Modifier is a methodology that helps you to create reusable components and code sharing in front-end development. It aims to group css-classes in a meaningful way, making it easier to understand
1. where this class is used
2. what it describes and
3. what state the element is in.
The BEM-notation is divided into three main parts: Blocks, Elements and Modifiers.
## Blocks
A standalone entity that is meaningful on its own. Some examples might be **headers, containers, menus, inputs, checkboxes**, etc.
## Elements
A part of a block that has no standalone meaning and is semantically tied to its block. This could be a **menu item or an input placeholder**.
## Modifiers
A flag on a block or an element. Used to change appearance or behavior. This might be **disabled, checked, fixed, big**, etc.
# Putting it together
A block itself is referenced though its name.
```css
.button {
}
```
To reference elements inside of the block, you add it to the block element with two underscores (`__`):
```css
.button {
}
.button__text {
}
```
If you want to add a modifier to a block or an element, you separate it with two dashes (`--`):
```css
.button {
}
.button--disabled {
}
.button__text--inverted {
}
```
# Benefits of BEM
**Modularity**: Block styles never depend on one another. They can easily be moved to other parts of the app.
**Reusability**: Composing styles in a meaningful way reduces the amount of code duplication.
**Structure**: BEM gives your code a solid structure that is both easy to understand and to expand.
# References
- http://getbem.com/
- https://csswizardry.com/2013/01/mindbemding-getting-your-head-round-bem-syntax/
This is post 009 of [#100DaysToOffload](https://100daystooffload.com/).

51
content/posts/2021-02-07-storage-setup.md

@ -0,0 +1,51 @@
---
title: My storage setup (Feburary 2021)
date: "2021-02-07"
---
I used to rely on Google Drive and Photos to store my entire data. Now, that [Google has decided to ditch unlimited photo storage in the near future](https://blog.google/products/photos/storage-changes/) and Google basically being the devil himself, I decided to step up my game and get my hands dirty on a DIY storage solution.
## The goal
Before I got started, I thought about the expectations I have towards a system like this. It boils down to these four points (in this order): I want my solution to be **resiliant**, **scalable**, **easy to maintain** and **easy to access**. Looking back, I think I met all of these requirements fairly well. Let me walk you through how I managed to do that.
## Data resiliance
Keeping data on a single device is obviously a really bad idea. Drives eventually fail, which means that your data will be lost. Heck, even my house could burn down, which means that any number of local copies could burn to ashes. To prevent data loss, I strictly adhere to the [3-2-1 backup strategy](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/). A 3-2-1 strategy means having **at least three total copies of your data, two of which are local but on different mediums (read: devices), and at least one copy off-site**. If a drive fails, I can replace it. If my house burns down, I get two new drives and clone my offsite backup to them.
To get an offsite backup, I set up a spare Raspberry Pi with a single large HDD and instructed it to do daily backups of my entire data. I asked a family member if they would be willing to have a tiny computer plugged in to their router 24/7, and they kindly agreed. A Pi and a HDD are very efficient in terms of power, so there is not a lot to worry about.
## Scalability
I currently don't have a huge amount of data. If that were to change (i.e. if I continue to shoot a lot of high-res photos and shove them into my setup), I need a way to simply attach more drives, or ones with more capacity. I looked at different file-systems that allowed to easy extendability while also being resiliant.
An obvious candidate was **ZFS**, but there are a couple of reasons I ditched this idea. First of all, it is really hard to get up and running on Raspberry Pi running Linux, since it's not natively supported by all distributions. This increases the complexity of the setup. Another reason is that I don't like the way it scales. Please correct me if I'm wrong here, since I only did limited research on this. From what I know though, ZFS can only be extended by shoving a large amount of drives in the setup to achieve perfect redundancy.
In the end, I settled on **BTRFS**. For me, it scratches all the itches that ZFS has. It is baked into the linux kernel, which makes it really easy to install on most distributions, and I can scale it to any number of drives I want. If I find a spare drive somewhere with any storage capacity, I can plug it into the system and it will just work, without having to think about balancing or redundancy shenanigans.
## Maintainability
I need my setup to be easy to maintain. If a drive fails, I want to be able to replace it within a matter of minutes, not hours. If my host (a Raspberry Pi) bursts into flames, I want to be able to swap in a new one and still access my data. If I'm out and about and something goes south, I want to be able to fix it remotely. BTRFS helps a lot here. It's really the foundation for all the key points mentioned here. It gives me a simple interface to maintain the data on the drives, and tries to fix issues itself whenever possible.
Exposing random ports to the general public is a huge security risk. To still be able to access the Pi remotely, I set up **an encrypted WireGuard tunnel**. This way, I only have to expose a single port for WireGuard to talk to the device as if I'm sitting next to it.
## Accessibility
Since the data needs to be accessed frequently, I need a simple interface for it that can be used on any device. I decided to host a **Nextcloud** instance and mount the drive as external storage. Why external storage? Because Nextcloud does some weird thing with the data it stores. If I decide to ditch Nextcloud at some point, I have the data on the disks "as is", without some sort of abstraction on top of it. This also has the benefit of allowing access from multiple sources. I don't have to use Nextcloud, but instead can mount the volume as a FTP, SMB or NFS share and do whatever I want with it. From the nextcloud perspective, this has some drawbacks like inefficient caching or file detection, but I'm willing to make that tradeoff.
## In a nutshell
This entire setup cost me about 150€ in total. Some components were scraped from old PC parts. So, what does the solution look like? Here is the gist:
- A Raspberry Pi 4 as a main host and an older Raspberry Pi 3 for offsite backup, both running Raspberry Pi OS
- Two external harddrives in a RAID 1 (mirrored) configuration, running on an external USB3 hub
- A single internal HDD that served no purpose in my old PC, now serving as backup storage
- All drives are using BTRFS
- WireGuard tunnels between main and remote host, as well as most access devices
- Nextcloud on main host, accessible over TLS (if I need to access data from outside the secure tunnel-network)
- SMB share accessible from within the tunnel-network
- Circa 4.5 terabyte total disk size; 1.5 terabyte of usable storage
- Snapper for local incremental backups on main host; BTRBK for remote incremental backups
- Cron jobs for regular backups and repairs (scrub/rebalance)
This is post 010 of [#100DaysToOffload](https://100daystooffload.com/).

14
content/posts/2021-02-11-10-percent-100daystooffload.md

@ -0,0 +1,14 @@
---
title: Thoughts after 10 Days of 100DaysToOffload
date: "2021-02-11"
---
Coming into this, I didn't know what to expect. I'm not a huge writer, but so far I am pleasantly surprised about how relaxing this challenge is.
At first glance, writing a blog post every 3-5 days seems daunting. But the more I write, the more it becomes an enjoyable habit. I'm oftentimes looking forward to writing these posts. Whenever I have something on my mind, I jot it down without a plan or structure. And that's exactly the point of [#100DaysToOffload](https://100daystooffload.com/): **Just. Write.**
So far, these blog posts have helped me get a lot of my thoughts out of my head and onto paper (or on a screen). While writing, I reflect on what I think. I sometimes realize that what I thought is utter nonsense, but this in itself is an important reflection. With each post, I feel like I am getting more confident about the process.
If you're reading this and you don't have a blog yet, I would encourage you to give this technique a try. It doesn't matter that you produce quality content, nor does anyone have to see this. It's not the content that matters, but the process of producing it. Set up your own blog on [write.as](https://write.as/) or simply open your text editor of your choice and **Just. Write.**
This is post 011 of [#100DaysToOffload](https://100daystooffload.com/).

44
content/posts/2021-02-17-notes-on-flutter-web.md

@ -0,0 +1,44 @@
---
title: Flutter Web - the Good, the Bad and the Ugly
date: "2021-02-17"
---
These are some notes I took for the evaluation of Flutter web for a potential project at work. I decided to build a frontend for [Miniflux](https://miniflux.app/), since I figured it may enclose many pitfalls an application could potentially have. You can find the current prototype [here](https://github.com/garritfra/FlutterFlux).
## The Good
- **Trivial to set up**: Running a Flutter application in a browser, no matter if it is an existing app or a fresh project, can be done by simply specifying the -d chrome flag.
- **Same behavior compared to mobile app**: Since the app is quite literally a mobile application running in the browser, the page looks and feels like a mobile application. It gives the app a consistent look and feel across all devices. I can imagine this coming in handy for web applications that are primarily used on phones and tablets.
- **Browser API integration**: It seems like many of the libraries make use of Web APIs. For example: I was able to get location updates using the location package, and store data using [localstorage](https://pub.dev/packages/localstorage). Whether the Web target is supported, is noted as a flag in the package documentation.
- **Alternative Backends**: There are two [rendering backends](https://flutter.dev/docs/development/tools/web-renderers), both with its own benefits and drawbacks. The HTML renderer optimizes the page for the browser, which improves performance at the cost of consistency. The CanvasKit renderer renders WebGL using WebAssembly. This gives a consistent look and feel across all devices, at the cost of Performance and download size. If auto is specified, the renderer will be determined based on the device type. Here’s a comparison of the same app rendered with both backends:
| HTML | CanvasKit |
| :----------------------: | :-----------------------: |
| ![](/assets/flutter_web_renderer_html.png) | ![](/assets/flutter_web_renderer_canvaskit.png) |
## The Bad
- **Still in Beta**: Flutter web requires the developer to use the beta channel of Flutter. I didn’t encounter any issues, but it could be that some features are unstable.
- **No native HTML (With an exception)**: Flutter Web renders the application into its own container, which is not using semantic HTML. The resulting application is also not debuggable using standard web-dev tools, but flutters debugging features can be used. There is a workaround though. Using the [easy_web_view](https://pub.dev/packages/easy_web_view) package, I was able to embed html components as flutter widgets. The embedded code is actual HTML code that the browser itself is rendering, not Flutter. This solution is cross-platform, meaning that it also works flawlessly for mobile builds of the application. This might come in handy if the project demands to embed a javascript component like a video player. This approach could technically also improve SEO, but I’m unsure how a full-blown application only using this approach would behave.
## The Ugly
- **Scrolling feels sluggish**: The scrolling behavior is implemented by flutter itself and does not feel as smooth as the native scrolling behavior of modern browsers.
- **SEO nearly impossible**: Since the application is a SPA and it is not using semantic HTML, it’s very difficult to do any kind of SEO. Lighthouse rated the demo application with a perfect 100 for SEO, but this is probably because it is only aware of the html that surrounds the flutter container. I didn’t find a way to Inject Metatags on a per-site basis.
- **Heavy and slow on old devices**: Even a basic application like the Todo app is very heavy and slow when compared to a “regular” website.
## Conclusion
Flutter Web seems to be a viable candidate to build truly cross-platform applications. Adding Web as a target for existing Flutter mobile apps should be relatively easy. The layout will probably need to be optimized to get the full experience. Native Web APIs seem to be well supported and documented.
The resulting web application is a PWA running inside a container. It is relatively heavy and requires much more resources to run, when compared to a “regular” web application.
I hope you found this useful!
This is post 012 of [#100DaysToOffload](https://100daystooffload.com/).

98
content/posts/2021-02-20-changelogs.md

@ -0,0 +1,98 @@
---
title: Writing good changelogs
date: "2021-02-20"
---
Today, I finally added a proper changelog to [my current project](https://github.com/garritfra/sabre/blob/master/CHANGELOG.md). My obvious first step was to search the web for `changelog.md`, since that's the naming convention many projects are using for their changelog. I was surprised that I was immediately redirected to "[changelog.md](https://changelog.md)", since it is a valid domain name. This website is a great guide on the essense of a good changelog. This is also where I got most of my findings from. Let me walk you through some of the most important ones:
## Changelogs are a vital part of every serious project
The whole point of a changelog is to keep track of how the project evolves over time. When working with multiple people, it helps getting everyone on the same page. Keeping a changelog reduces a possible monopoly of information, since all contributers know what is going on. Of course, users also benefit from your changelog. They will know what changes they can expect when they do an update.
## Entries should have a standardized format
Changelogs are mainly meant to be readable by humans. Here are some important points to watch out for when writing a changelog:
- Every version of your software (major, minor and patch) should have one section and one section only
- Recent releases should be added at the top of the changelog (reverse chronological order)
- Each version _should_ display its release date in ISO format (YYYY-MM-DD) next to the version name
## What types of changes need to be included?
You could just go ahead and throw some changes in a big list and call it a day. To make the changelog more readable though, you should categorize every change by its type. Here's an example of a set of categories that could be included:
- **Features**: New features or additions
- **Fixes**: Bugfixes
- **Security** Important changes regarding holes in your security
- **Documentation**: Changes or additions in your documentation should go here
This is just an example that illustrates how **I** decided to note down my changes. [changelog.md](https://changelog.md) suggests a slightly different convention, but how you're handling it doesn't really matter.
## An example
Here's an example of how a changelog could look like. It's taken from [Sabre](https://github.com/garritfra/sabre), a project I'm currently working on. The full changelog can be found [here](https://github.com/garritfra/sabre/blob/master/CHANGELOG.md).
```md
# Changelog
## v0.4.0 (2021-02-20)
This release introduces the concept of structs, alongside many improvements to the documentation.
**Features**
- Assignment operators (#10)
- Structs (#12)
**Fixes**
None
**Documentation**
- Fixed some typose and broken links
- Document boolean values
- Added this changelog!
## v0.3.0 (2021-02-12)
This release adds type inference to Sabre. There are also a lot of improvements in terms of documentation. The docs are now at a state that can be considered "usable".
**Features**
- Type inference
- The `any` type
- First attempt of LLVM backend
**Fixes**
- Fixed an error when printing numbers
**Documentation**
- Added documentation for for loops
- Added documentation for while loops
- Documented LLVM backend
- Documented comments
- Updated contributing guidelines
```
## Personal recommendations
When releasing a new version, don't just add an entry to your changelog. You should use **git tags** whenever working with versions, to mark the exact commit of the released version.
Read up on **semantic versioning**! This is the most common convention when it comes to versioning your software. ([here](https://www.geeksforgeeks.org/introduction-semantic-versioning/) is a simple guide, [here](https://semver.org/) is the official specification).
I'd also advise you to keep a log of your commits in the description of the tag. Here's a command that does all of this for you:
```
git tag -a <new release> -m "$(git shortlog <last release>..HEAD)"
```
So, if you're releasing version `v0.2.0` after `v0.1.5`, you would run this command to tag your current commit with a good commit history:
```
git tag -a v0.2.0 -m "$(git shortlog v0.1.5..HEAD)"
```
This is post 013 of [#100DaysToOffload](https://100daystooffload.com/).

57
content/posts/2021-02-24-vim-terminal-strategies.md

@ -0,0 +1,57 @@
---
title: Strategies to use a terminal alongside (Neo)Vim
date: "2021-02-23"
---
One thing that bothered me about vim for a long time, was the lack of a terminal
directly in your editor. If I'm not using Vim, I'm most definetely using VSCode
and its built-in Terminal. After searching the webs for possible solutions, I
came across a couple of strategies to achive this.
## Executing single commands
If you just want to issue a single command without spawning an entire shell,
you can just use the `:!` command:
```
:! printf "Hello Sailor"
```
## Vims builtin terminal
I couldn't believe my eyes when I read this, but Vim ships with a builtin
terminal! Executing `:term` will spawn it in your current buffer. How you
integrate it in your workflow is up to you. You could use tabs or open a
horizontal buffer and spawn it there. I must say that it is rather clunky to
use, since its literally a Vim buffer that forwards stdin and stdout to the
buffer, but it's there for you to use.
## Vim x Tmux
Another great alternative is to set up Tmux with two windows, one for Vim and
one for your terminal, and switch between them. This works great on a minimal
system, but on MacOS for example, it is easier to simply use cmd+1 and cmd+2 to
switch between two tabs of the Terminal application.
## Pausing and resuming Vim
This one is my personal favorite. The idea comes from
[this](https://stackoverflow.com/a/1258318/9046809) stackoverflow answer.
The plan is to pause the Vim process and resume it later. To pause Vim, you
press `<ctrl>-z`. This sends the process in the background. Then, to resume the
process, simply issue the `fg` command and Vims process resumes in the
foreground.
## Conclusion
I'm sure there are many more strategies that could be added to this list. I'd be
interested to hear how your setup works! If you liked these techniques, you
might be interested in
[@lopeztel](https://fosstodon.org/web/accounts/211905)s
[cheat sheet](https://lopeztel.xyz/2021/02/21/my-neovim-cheatsheet/) for Vim.
This is post 014 of [#100DaysToOffload](https://100daystooffload.com/).

28
content/posts/2021-03-13-git-builtin-lifesaver.md

@ -0,0 +1,28 @@
---
title: Git's built-in lifesaver
date: "2021-03-13"
---
Everyone was in this situation at some point. You wasted a days worth of work by accidentally deleting a branch. But, all hope is not lost! Git never forgets.
Every action, be it committing changes, deleting or switching branches, is noted down by Git. To see your latest actions, you can simply run `git reflog` (It's pronounced `ref-log` but `re-flog` sounds just as reasonable):
```
5704fba HEAD@{45}: commit: docs: update changelog
b471457 HEAD@{46}: commit: chore: refactor binop checks in parse_expression
5f5c5d4 HEAD@{47}: commit: fix: struct imports
76db271 HEAD@{48}: commit: chore: fix clippy warning
ac3e11c HEAD@{49}: commit: fix: circular imports
0cbdc88 HEAD@{50}: am: lexer: handle ' or " within the string properly
27699f9 HEAD@{51}: commit: docs: spec: add notation
```
Commits in Git are just data that is not associated by anything. If you accidentally delete a branch, the commits will stay where they are, and you can reference them directly. To recreate your deleted branch, simply run this command:
```
git checkout -b <branch> <sha>
```
And that's it! Your branch is restored. Remember to commit early and often, or prepare to loose your work!
This is post 015 of [#100DaysToOffload](https://100daystooffload.com/).

247
content/posts/2021-04-07-pgp-guide.md

@ -0,0 +1,247 @@
---
title: A pretty good guide to pretty good privacy
date: "2021-04-07"
---
In the past week, I've been experimenting with PGP, or GPG in particular. In a nutshell, PGP is an encryption standard with a wide range of use cases. For quite some time, I didn't see the point of keeping a PGP keypair. It seemed like a burden to securely keep track of the key(s). Once you loose it, you will loose the trust of others. But after doing some research on the topic, I found that it's not actually that much of a hassle, while giving you many benefits.
# The Why
The most obvious benefit is encrypting and decrypting messages and files. If you upload your public key, I can encrypt our private conversations. Nobody will be able to read what we're chatting about. If you fear that cloud providers will read through your documents, you can also go ahead and encrypt all of your data with your keypair.
But PGP is not just about encryption. A keypair also gives you a proof of identity. If I see that a piece of work is signed by you, I can be certain that you and only you have worked on this. By signing the keys of people we trust, we build a "chain of trust". A key with many signatures generally has a higher reputation than one without any signatures.
Take Git commits for example. All it takes is a `git config user.email "elon@spacex.com"` and I can publish code under a different identity. But if everyone on the team signed their work, they will quickly see that a commit is missing its signature, because I'm simply not able to sign my work with Elon Musk's keypair. Only if they see a badge like this, they will know that they can trust it.
Your keypair can also come in handy as a SSH key. Before I knew about PGP, I always had to install one key per machine I was working on. With PGP, you only have a single identity, and therefore you only have to install one key on your servers.
# The How
Let's first go over the process of setting up a keypair. For this, we will need the `gpg` command installed on our system. Usually, this is just a `<package manager> install gpg` away. Then, we will have to generate a keypair. The quickest way to get one is to use `gpg --gen-key`, but that will make some quirky assumptions about how you want to use your key.
In PGP, there is this concept of a **keyring**. A keyring has one master key and many subkeys. It is generally a good idea to have one fat master key that never expires and many small subkeys that last about a year or two. The benefit of structuring your keys like this is that you will always have your trusted keychain, and in case something goes south, E.g. your key gets compromised, you can replace that subkey and keep your identity.
With that in mind, let's create our master key. Run `gpg --full-gen-key` and follow the instructions. You probably want to use the `RSA and RSA (default)` option, and a key that is 4096 bits long (remember, this is the fat master key that never expires, so it must be secure). The comment can be left blank, unless you know what you are doing with that field. Enter a strong passphrase! If your private key were to get compromised, this passphrase is your last line of defense. Make it long, hard to crack but still rememberable. If everything went well, your key should be generated. Here's the full example output:
```
root@c6acc9eb4fd1:/# gpg --full-gen-key
gpg (GnuPG) 2.2.19; Copyright (C) 2019 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Please select what kind of key you want:
(1) RSA and RSA (default)
(2) DSA and Elgamal
(3) DSA (sign only)
(4) RSA (sign only)
(14) Existing key from card
Your selection? 1
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (3072) 4096
Requested keysize is 4096 bits
Please specify how long the key should be valid.
0 = key does not expire
<n> = key expires in n days
<n>w = key expires in n weeks
<n>m = key expires in n months
<n>y = key expires in n years
Key is valid for? (0)
Key does not expire at all
Is this correct? (y/N) y
GnuPG needs to construct a user ID to identify your key.
Real name: Foo
Name must be at least 5 characters long
Real name: Foo Bar
Email address: foo@bar.com
Comment:
You selected this USER-ID:
"Foo Bar <foo@bar.com>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: key C8E4854970B7A1A3 marked as ultimately trusted
gpg: revocation certificate stored as '/root/.gnupg/openpgp-revocs.d/4E83F95221E92EDB933F155AC8E4854970B7A1A3.rev'
public and secret key created and signed.
pub rsa4096 2021-04-07 [SC]
4E83F95221E92EDB933F155AC8E4854970B7A1A3
uid Foo Bar <foo@bar.com>
sub rsa4096 2021-04-07 [E]
```
You could stop here and use this key, but let's instead create some subkeys under that key, to make our lives a bit easier. Take the fingerprint of the key (that large number in the output) and run `gpg --edit-key --expert <your fingerprint>`. Run `addkey` three times to add these three keys:
## Signing key
This key will be used to sign your work (git commits, tags, etc.).
```
gpg> addkey
```
1. Choose option "RSA (set your own capabilities)", which is currently number 8.
1. Toggle E (Encryption) so the "Current allowed actions" only lists Sign and confirm with Q.
1. Choose the keysize 2048 (or whatever you prefer).
1. Choose the key expire date 1y (or whatever you prefer).
1. Confirm twice, then enter your passphrase.
## Encryption key
This key will be used to encrypt and decrypt messages.
```
gpg> addkey
```
1. Choose option "RSA (set your own capabilities)", which is currently number 8.
1. Toggle S (Sign) so the "Current allowed actions" only lists Encryption and confirm with Q.
1. Choose the keysize 2048 (or whatever you prefer).
1. Choose the key expire date 1y (or whatever you prefer).
1. Confirm twice, then enter your passphrase.
## Authentication key
This key will be used for SSH authentication.
```
gpg> addkey
```
1. Choose option "RSA (set your own capabilities)", which is currently number 8.
1. Toggle S (Signing), E (Encryption) and A (Authentication) so the "Current allowed actions" only lists Authenticate and confirm with Q.
1. Choose the keysize 2048 (or whatever you prefer).
1. Choose the key expire date 1y (or whatever you prefer).
1. Confirm twice, then enter your passphrase.
Now you should have one key per use case: signing, encrypting and authentication, each with an expiration date:
```
sec rsa4096/C8E4854970B7A1A3
created: 2021-04-07 expires: never usage: SC
trust: ultimate validity: ultimate
ssb rsa4096/C5F71423813B40A0
created: 2021-04-07 expires: never usage: E
ssb rsa2048/52D4D1D19533D8A5
created: 2021-04-07 expires: 2022-04-07 usage: S
ssb rsa2048/072D841844E3F949
created: 2021-04-07 expires: 2022-04-07 usage: E
ssb rsa2048/42E4F6E376DD92F6
created: 2021-04-07 expires: 2022-04-07 usage: A
[ultimate] (1). Foo Bar <foo@bar.com>
```
Save your key, and optionally upload it to one of the many keyservers:
```
gpg> save
$ gpg --keyserver keys.openpgp.org --send-keys foo@bar.com
```
**Pro tip**: To set a default keyserver (I use `keys.opengpg.org`, but there are many others out there!), simply add it in your `~/.gnupg/gpg.conf` file:
```
keyserver keys.openpgp.org
```
People can now import your public key via `gpg --keyserver keys.opengpg.org --search-keys foo@bar.com`.
We're done with the setup, let's put our keys to use!
## Code Signing
To sign your code, you will have to tell git which key to use. Edit your global git options (`~/.gitconfig`) and add these fields:
```
[commit]
gpgsign = true
[tag]
gpgsign = true
[user]
name = Foo Bar
signingkey = 52D4D1D19533D8A5 # Use the ID of your signing key
email = foo@bar.com
```
Now, whenever you add a commit, git will sign it with your key. You will have to let your git hosting provider know that this key is yours. Go to your account settings and look for a tab that says "Manage (GPG) keys". Where this tab is depends on your choice of service. Next, run `gpg --export --armor <your master key id>` and copy the resulting key into the input field of your git hosting service.