Merge pull request 'node_migration' (#1) from node_migration into main

Reviewed-on: https://git.yarmo.eu/yarmo/yarmo.eu/pulls/1
This commit is contained in:
Yarmo Mackenbach 2020-09-25 17:30:16 +00:00
commit e971028e7d
71 changed files with 3610 additions and 3119 deletions

View File

@ -1,87 +0,0 @@
---
kind: pipeline
type: docker
name: deploy dev
steps:
- name: composer
image: composer
commands:
- composer install
- composer run-script minifyCSS
- name: rsync to prism
image: drillster/drone-rsync
settings:
hosts:
from_secret: ssh_host
port:
from_secret: ssh_port
user:
from_secret: ssh_user
key:
from_secret: ssh_key
source: ./
target: ~/web/dev.yarmo.eu
exclude: [ ".git/", ".gitignore", ".drone.yml", "composer.json", "composer.lock" ]
- name: purge cache
image: appleboy/drone-ssh
settings:
host:
from_secret: ssh_host
port:
from_secret: ssh_port
username:
from_secret: ssh_user
key:
from_secret: ssh_key
script:
- rm -rf web/dev.yarmo.eu/cache/*
trigger:
branch:
- dev
---
kind: pipeline
type: docker
name: deploy prod
steps:
- name: composer
image: composer
commands:
- composer install
- composer run-script minifyCSS
- name: rsync to prism
image: drillster/drone-rsync
settings:
hosts:
from_secret: ssh_host
port:
from_secret: ssh_port
user:
from_secret: ssh_user
key:
from_secret: ssh_key
source: ./
target: ~/web/yarmo.eu
exclude: [ ".git/", ".gitignore", ".drone.yml", "composer.json", "composer.lock" ]
- name: purge cache
image: appleboy/drone-ssh
settings:
host:
from_secret: ssh_host
port:
from_secret: ssh_port
username:
from_secret: ssh_user
key:
from_secret: ssh_key
script:
- rm -rf web/yarmo.eu/cache/*
trigger:
branch:
- main

3
.gitignore vendored
View File

@ -1,3 +1,2 @@
.well-known
vendor
cache
node_modules

View File

@ -1,15 +0,0 @@
{
"require": {
"pug-php/pug": "^3.4",
"altorouter/altorouter": "^2.0",
"pagerange/metaparsedown": "^1.0",
"bhaktaraz/php-rss-generator": "dev-master",
"symfony/yaml": "^5.1",
"tubalmartin/cssmin": "^4.1"
},
"scripts": {
"minifyCSS": [
"php scripts/minifyCSS.php"
]
}
}

1023
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -70,7 +70,7 @@ In other words, VPSs are severely lacking in sources of entropy. That is why the
There is a way to remedy the situation: [haveged](https://wiki.archlinux.org/index.php/Haveged). Having only discovered it last night, I do not fully understand it yet but from what I have read, it is a pseudorandom number generator (PRNG) that fills the `entropy pool` with "pseudorandomness". Installing `haveged` immediately solved my issue, all docker commands were running instantly again.
![Available entropy suddenly increases after installing haveged](/content/img/entropy_haveged.png)
![Available entropy suddenly increases after installing haveged](/static/entropy_haveged.png)
*Can you tell when I installed haveged?*
## Caveat: pseudorandomness

View File

@ -28,13 +28,13 @@ I decided to use [WebPageTest.org](https://www.webpagetest.org) to measure load
First, a baseline measurement of my existing Cloudways solution.
![Cloudways - overview](/content/img/wpt_1_1a.png)
![Cloudways - overview](/static/wpt_1_1a.png)
*Cloudways - overview*
![Cloudways - rating](/content/img/wpt_1_1b.png)
![Cloudways - rating](/static/wpt_1_1b.png)
*Cloudways - rating*
![Cloudways - waterfall](/content/img/wpt_1_1c.png)
![Cloudways - waterfall](/static/wpt_1_1c.png)
*Cloudways - waterfall*
So the server returns the first byte of information after 480 milliseconds. Now, I should tell you that my website is based on [Phug](https://phug-lang.com), the PHP port of [pug.js templating](https://pugjs.org). The page is rendered in real-time and apparently, that takes a little over 300 ms.
@ -53,13 +53,13 @@ Anyway, can Caddy do better?
### Caddy
![Caddy - overview](/content/img/wpt_1_2a.png)
![Caddy - overview](/static/wpt_1_2a.png)
*Caddy - overview*
![Caddy - rating](/content/img/wpt_1_2b.png)
![Caddy - rating](/static/wpt_1_2b.png)
*Caddy - rating*
![Caddy - waterfall](/content/img/wpt_1_2c.png)
![Caddy - waterfall](/static/wpt_1_2c.png)
*Caddy - waterfall*
Well, as it turns out, it's largely the same performance. First byte arrived after 459 ms, but I've ran it a few times and there's really little difference between Cloudways and Caddy.
@ -76,13 +76,13 @@ I've tried a lot of things, I'll just narrow it down to the two most important f
As it turned out, I had a few small SVG icons and some CSS files. I tried rendering them into the HTML page, so the data would be sent on the first data transmission and no separate requests were needed. For good measure, I also minified the CSS files which, for one file, reduced the size by 30%!
![Caddy+inline - overview](/content/img/wpt_1_6a.png)
![Caddy+inline - overview](/static/wpt_1_6a.png)
*Caddy+inline - overview*
![Caddy+inline - rating](/content/img/wpt_1_6b.png)
![Caddy+inline - rating](/static/wpt_1_6b.png)
*Caddy+inline - rating*
![Caddy+inline - waterfall](/content/img/wpt_1_6c.png)
![Caddy+inline - waterfall](/static/wpt_1_6c.png)
*Caddy+inline - waterfall*
On the waterfall above, you can clearly see the `dank-mono.css` was not inlined but I tried multiple configurations, there was no real gain as the image also needed to load and took longer anyway. So, all in all, inlining the SVG and CSS content did little in this case.
@ -97,13 +97,13 @@ As described on [their website](https://phug-lang.com/#usage), PHUG has support
### Caddy - PHUG optimization
![Caddy+PHUG - overview](/content/img/wpt_1_7a.png)
![Caddy+PHUG - overview](/static/wpt_1_7a.png)
*Caddy+PHUG - overview*
![Caddy+PHUG - rating](/content/img/wpt_1_7b.png)
![Caddy+PHUG - rating](/static/wpt_1_7b.png)
*Caddy+PHUG - rating*
![Caddy+PHUG - waterfall](/content/img/wpt_1_7c.png)
![Caddy+PHUG - waterfall](/static/wpt_1_7c.png)
*Caddy+PHUG - waterfall*
Well, there it is!!! First byte of data arrived after a mere 173 ms, website is useable in less than half a second and all scores are `A`!

View File

@ -71,7 +71,7 @@ I have bought .io domains in the past. I did not have the knowledge of what was
---
## Update 1<a name="update-1"></a>
## Update 1
There is also the issue of the **.io** TLD's [future](https://www.prolificlondon.co.uk/marketing-tech-news/tech-news/2019/05/future-popular-io-domains-question-over-british-empire-row):
@ -81,7 +81,7 @@ Who knows what will happen to your domain registration when control is passed to
---
## Update 2<a name="update-2"></a>
## Update 2
It has been pointed out by many that this post focuses too much on the **.xyz** gTLD. This was not my intention. In fact, any gTLD will do just fine, after all they are generic. A non-exhaustive list of gTLDs that could perfectly replace **.io** (assuming **.io** simply stands for "input/output"):

View File

@ -26,19 +26,19 @@ When you load that specific page, make sure to load it in a private session or w
It is slow. Really slow. I noticed so too and decided to run a [Webpagetest (link to result)](https://www.webpagetest.org/result/200627_0Q_044080ef3ab8a678721658c90d2f4706/). Out of three runs, we analyze only the median run (so not the best one, not the worst one).
![Keybase encrypt Webpagetest overview](/content/img/keybase_encrypt__wpt_overview.png)
![Keybase encrypt Webpagetest overview](/static/keybase_encrypt__wpt_overview.png)
*keybase.io/encrypt*
## The content loaded
It takes **6.25&nbsp;seconds** to fully load the **2.9&nbsp;megabytes** that are used on this page. That is hefty for a page that is essentially a single form. I mean, look at it:
![Keybase encrypt page](/content/img/keybase_encrypt.png)
![Keybase encrypt page](/static/keybase_encrypt.png)
*Why 2.9 megabytes?*
That's a regular web form. What could possibly be **2.9&nbsp;megabytes**? The javascript?
![Webpagetest run 1 overview](/content/img/keybase_encrypt__wpt_1.png)
![Webpagetest run 1 overview](/static/keybase_encrypt__wpt_1.png)
*How many requests? How many bytes?*
Most requests are fonts. That makes sense. Earlier, we saw the page only makes **12&nbsp;requests**, so I could imagine a few of those being several fonts files. Fortunately, fonts are only **6.5%** of the bytes loaded, so we'll forgive them.
@ -49,7 +49,7 @@ Most requests are fonts. That makes sense. Earlier, we saw the page only makes *
Let's grab the [waterfall](https://www.webpagetest.org/result/200627_0Q_044080ef3ab8a678721658c90d2f4706/1/details/#waterfall_view_step1) and see what is going on:
![Webpagetest run 1 waterfall](/content/img/keybase_encrypt__wpt_1_waterfall.png)
![Webpagetest run 1 waterfall](/static/keybase_encrypt__wpt_1_waterfall.png)
*Run 1 waterfall*
At two points in time, the loading of the website stalls. The first stall is **2.6&nbsp;seconds** for the file `sitewide-js.js`. The second stall is **2.5&nbsp;seconds** for the file `footprints_transp.png`. Let's go.
@ -58,7 +58,7 @@ At two points in time, the loading of the website stalls. The first stall is **2
This file is **4.7&nbsp;megabytes** raw and **1.2&nbsp;megabytes** gzipped. Let us look at a random excerpt:
![Javascript excerpt](/content/img/keybase_encrypt__js_excerpt.png)
![Javascript excerpt](/static/keybase_encrypt__js_excerpt.png)
*Javascript excerpt*
This is not really optimized for performance: one could choose to minimize the javascript. Allow me to use `@node-minify/cli`.
@ -78,7 +78,7 @@ Well, I need to specify one thing: the website loads a gzipped version of the or
Have you found the image yet? It's the little image at the bottom of the dog (?) following footprints. Cute :)
![Footprints image](/content/img/keybase_encrypt__img.png)
![Footprints image](/static/keybase_encrypt__img.png)
*Footprints image*
Dimensions on page: **330&nbsp;x&nbsp;90 pixels**
@ -122,7 +122,7 @@ Anything else? Given that this is all cryptography related, maybe some security
## Security
![Webpagetest security score](/content/img/keybase_encrypt__security.png)
![Webpagetest security score](/static/keybase_encrypt__security.png)
*Webpagetest security score*
I've ran quite a few webpagetests on different website, but a **0** security score is new to me. What does that even mean?

View File

@ -14,39 +14,39 @@ I'm not going to lie, Flipper Zero sounds like a cool project for hackers. Here'
Something extremely scummy is going on right now! Have a look:
![Flipper Zero Kickstarter](/content/img/kickstarted_counter__1a.png)
![Flipper Zero Kickstarter](/static/kickstarted_counter__1a.png)
Looking good, lot's of stuff to read, let's take our time.
![Flipper Zero Kickstarter](/content/img/kickstarted_counter__1b.png)
![Flipper Zero Kickstarter](/static/kickstarted_counter__1b.png)
My word, they're almost out of Early Birds! Please, for the love of god, if you want to save some money, pledge now, only 9 left and it clearly says "Limited"!
### One minute later
![Flipper Zero Kickstarter](/content/img/kickstarted_counter__2.png)
![Flipper Zero Kickstarter](/static/kickstarted_counter__2.png)
A person has just pledged! Where's my credit card?
### Another minute later
![Flipper Zero Kickstarter](/content/img/kickstarted_counter__3.png)
![Flipper Zero Kickstarter](/static/kickstarted_counter__3.png)
Wait, 9 left? Someone bailed? Doesn't matter, I need this!
### Yet another minute later
![Flipper Zero Kickstarter](/content/img/kickstarted_counter__4.png)
![Flipper Zero Kickstarter](/static/kickstarted_counter__4.png)
Wait, what?
### And it goes on
![Flipper Zero Kickstarter](/content/img/kickstarted_counter__5.png)
![Flipper Zero Kickstarter](/static/kickstarted_counter__5.png)
### And on
![Flipper Zero Kickstarter](/content/img/kickstarted_counter__6.png)
![Flipper Zero Kickstarter](/static/kickstarted_counter__6.png)
## This needs to stop

View File

@ -1,12 +0,0 @@
- week: 2020-26
year: 2017
artist: Peter Silberman
title: Impermanence
- week: 2020-25
year: 1984
artist: Jean-Michel Jarre
title: Zoolook
- week: 2020-24
year: 1962
artist: Booker T. & the M.G.'s
title: Green Onions

View File

@ -14,5 +14,5 @@ If you haven't cleaned the fan in a while, your best bet is to open the NUC up a
To prevent having to open a NUC up too often, I bought a few cans of compressed air and regularly blow air through the device. I'm also looking into placing air filters near the air intake.
![NUC cools down when fan is cleaned](/content/img/nuc_temp_fan_cleaning.png)
![NUC cools down when fan is cleaned](/static/nuc_temp_fan_cleaning.png)
*Can you tell when compressed air was applied to the NUC?*

View File

@ -10,7 +10,7 @@ published: true
I rarely interact with [Github](https://github.com) anymore. All my projects are either on my selfhosted [Gitea](https://gitea.io) instance or on [Codeberg.org](https://codeberg.org/). That's why I missed the following on [Github Status](https://www.githubstatus.com/):
![Github status shows a lot of downtimes](/content/img/github_status.png)
![Github status shows a lot of downtimes](/static/github_status.png)
*Yikes*
Yikes, indeed. How everyone handles this is up to them. Large projects will find it hard to move, no doubt.

View File

@ -1,235 +0,0 @@
<?php
include_once __DIR__ . '/vendor/autoload.php';
use Pagerange\Markdown\MetaParsedown;
use Bhaktaraz\RSSGenerator\Item;
use Bhaktaraz\RSSGenerator\Feed;
use Bhaktaraz\RSSGenerator\Channel;
use Symfony\Component\Yaml\Yaml;
function getIcons() {
$icons = array();
$icons["github"] = file_get_contents('static/img/github.svg');
$icons["gitlab"] = file_get_contents('static/img/gitlab.svg');
$icons["gnuprivacyguard"] = file_get_contents('static/img/gnuprivacyguard.svg');
$icons["mail"] = file_get_contents('static/img/mail.svg');
$icons["mastodon"] = file_get_contents('static/img/mastodon.svg');
$icons["rss"] = file_get_contents('static/img/rss.svg');
$icons["xmpp"] = file_get_contents('static/img/xmpp.svg');
return $icons;
}
function getBlogPosts($params) {
$posts = scandirRec('content/blog/', 'blog', $params);
$posts = array_reverse($posts);
return $posts;
}
function getNotes($params) {
$posts = scandirRec('content/notes/', 'notes', $params);
$posts = array_reverse($posts);
return $posts;
}
function getProjects($params) {
$projects = scandirRec('content/projects/', 'projects', $params);
return $projects;
}
function getReplies($params) {
$projects = scandirRec('content/replies/', 'replies', $params);
return $projects;
}
function getWebmentions($target) {
$data = json_decode(file_get_contents("https://webm.yarmo.eu/get?target=$target"), true);
foreach ($data as $id => $entry) {
$time = strtotime($entry["created_at"]);
$data[$id]["date"] = date("Y-m-d",$time);
$data[$id]["time"] = date("H:i:s",$time);
}
return $data;
}
function getFOSS() {
return Yaml::parseFile('content/foss/foss.yaml');
}
function getVinyl() {
return Yaml::parseFile('content/music/vinyl.yaml');
}
function getAOTW() {
return Yaml::parseFile('content/music/aotw.yaml');
}
function getRSS($params) {
header("Content-Type: text/xml");
$feed = new Feed();
$channel = new Channel();
$channel
->title($params['feed']['title'])
->description($params['feed']['description'])
->url($params['feed']['url'])
->atomLinkSelf($params['feed']['linkself'])
->appendTo($feed);
$dates = array_column($params['posts'], 'date');
array_multisort($dates, SORT_DESC, $params['posts']);
foreach($params['posts'] as $post) {
$item = new Item();
$item
->title($post['title'])
->creator($post['author'])
// ->content($post['content'])
->description($post['content'])
->url($post['url'])
->guid($post['url'], true)
->pubDate(strtotime($post['date']))
->appendTo($channel);
}
echo $feed;
}
function scandirRec($path, $category, $params) {
$Parsedown = new Parsedown();
$mp = new MetaParsedown();
$items = array();
// If a directory
if(is_dir($path)) {
$files = glob($path . '*', GLOB_MARK);
foreach($files as $file) {
if(is_dir($file)) {
$items = array_merge($items, scandirRec($file, $category, $params));
} else {
$return = scandirRec($file, $category, $params);
if (!empty($return)) {
$items[] = $return;
}
}
}
return $items;
}
// If a file
if (($path == '.') || ($path == '..')) {
return;
}
$content = file_get_contents($path);
$meta = $mp->meta($content);
switch ($category) {
case 'blog':
$item = array(
"type" => "blog",
"title" => $meta["title"],
"author" => $meta["author"],
"urlrel" => "/post/".$meta["slug"],
"url" => "https://yarmo.eu/post/".$meta["slug"],
"slug" => $meta["slug"],
"date" => $meta["date"],
"published" => $meta["published"],
"discussion" => $meta["discussion"],
"content" => $mp->text($content));
$date = new DateTime($item['date']);
$item['date_formatted'] = $date->format('Y-m-d');
$item['date_rss'] = $date->format('Y-m-d');
if (!$item['published'] && !(isset($params['slug']) && $item['slug'] == $params['slug'])) {
return;
}
break;
case 'notes':
$item = array(
"type" => "note",
"title" => $meta["title"],
"author" => $meta["author"],
"urlrel" => "/post/".$meta["slug"],
"url" => "https://yarmo.eu/post/".$meta["slug"],
"slug" => $meta["slug"],
"date" => $meta["date"],
"published" => $meta["published"],
"discussion" => $meta["discussion"],
"content" => $mp->text($content));
$date = new DateTime($item['date']);
$item['date_formatted'] = $date->format('Y-m-d');
$item['date_rss'] = $date->format('Y-m-d');
if (!$item['published'] && !(isset($params['slug']) && $item['slug'] == $params['slug'])) {
return;
}
break;
case 'projects':
$item = array(
"title" => $meta["title"],
"status" => $meta["status"],
"urlrel" => "/projects/".$meta["slug"],
"url" => "https://yarmo.eu/projects/".$meta["slug"],
"slug" => $meta["slug"],
"date" => $meta["date"],
"listed" => $meta["listed"],
"content" => $mp->text($content));
$date = new DateTime($item['date']);
$item['date_formatted'] = $date->format('Y-m-d');
$item['date_rss'] = $date->format('Y-m-d');
if (!$item['listed'] && !(isset($params['slug']) && $item['slug'] == $params['slug'])) {
return;
}
break;
case 'replies':
$item = array(
"type" => "note",
"title" => $meta["title"],
"reply-url" => $meta["reply-url"],
"reply-title" => $meta["reply-title"],
"author" => $meta["author"],
"urlrel" => "/reply/".$meta["slug"],
"url" => "https://yarmo.eu/reply/".$meta["slug"],
"slug" => $meta["slug"],
"date" => $meta["date"],
"published" => $meta["published"],
"content" => $mp->text($content));
$date = new DateTime($item['date']);
$item['date_formatted'] = $date->format('Y-m-d');
$item['date_rss'] = $date->format('Y-m-d');
if (!$item['published'] && !(isset($params['slug']) && $item['slug'] == $params['slug'])) {
return;
}
break;
default:
return;
break;
}
if (isset($params['slug']) && $item['slug'] != $params['slug']) {
return;
}
if (isset($params['slug'])) {
$item["webmentions"] = array_merge(
getWebmentions($item["url"]),
getWebmentions("https://yarmo.eu/blog/".$meta["slug"]),
getWebmentions("https://yarmo.eu/notes/".$meta["slug"])
);
$item["hasWebmentions"] = count($item["webmentions"]) > 0;
}
return $item;
}

17
index.js Normal file
View File

@ -0,0 +1,17 @@
const express = require('express')
const app = express()
const fs = require('fs')
require('dotenv').config()
app.set('env', process.env.NODE_ENV || "production")
app.set('view engine', 'pug')
app.set('port', process.env.PORT || 3000)
app.use('/', require('./routes/main'))
app.use('/', require('./routes/static'))
app.use('/feed', require('./routes/feed'))
app.use('/rss', require('./routes/feed'))
app.listen(app.get('port'), () => {
console.log(`Node server listening at http://localhost:${app.get('port')}`)
})

413
index.php
View File

@ -1,413 +0,0 @@
<?php
include_once __DIR__ . '/vendor/autoload.php';
include 'functions.php';
use Pagerange\Markdown\MetaParsedown;
// Init router
$router = new AltoRouter();
// Router mapping
$router->map('GET', '/', function() {}, 'home');
$router->map('GET', '/rss', function() {}, 'rss');
$router->map('GET', '/rss.xml', function() {}, 'rss-xml');
$router->map('GET', '/rss/all', function() {}, 'rss-all');
$router->map('GET', '/rss/blog', function() {}, 'rss-blog');
$router->map('GET', '/rss/notes', function() {}, 'rss-notes');
$router->map('GET', '/about', function() {}, 'about');
$router->map('GET', '/feeds', function() {}, 'feeds');
$router->map('GET', '/uses', function() {}, 'uses');
$router->map('GET', '/now', function() {}, 'now');
$router->map('GET', '/blogroll', function() {}, 'blogroll');
$router->map('GET', '/blog', function() {}, 'blog');
$router->map('GET', '/blog/[*:slug]', function() {}, 'blog_post');
$router->map('GET', '/notes', function() {}, 'notes');
$router->map('GET', '/notes/[*:slug]', function() {}, 'notes_post');
$router->map('GET', '/post/[*:slug]', function() {}, 'post');
$router->map('GET', '/reply/[*:slug]', function() {}, 'reply');
$router->map('GET', '/projects', function() {}, 'projects');
$router->map('GET', '/projects/[*:slug]', function() {}, 'projects_details');
$router->map('GET', '/foss', function() {}, 'foss');
$router->map('GET', '/music', function() {}, 'music');
$router->map('GET', '/vinyl', function() {}, 'vinyl');
$router->map('GET', '/aotw', function() {}, 'aotw');
$router->map('GET', '/pgp', function() {}, 'pgp');
$router->map('GET', '/contact', function() {}, 'contact');
// Router matching
$match = $router->match();
// Template engine settings and variables
$basetitle = 'yarmo';
$environment = getenv('ENVIRONMENT') ?: 'production';
$options = [
'paths' => [
'views/',
],
'cache_dir' => 'cache/',
'enable_profiler' => false,
'profiler' => [
'time_precision' => 3,
'line_height' => 30,
'display' => true,
'log' => false,
],
];
$variables = [
'title' => $basetitle
];
// If we are dealing with the home page
if ($match['name'] == 'home') {
$variables['posts'] = getBlogPosts($match['params']);
$variables['title'] = 'Blog — '.$basetitle;
// $variables['icons'] = getIcons();
}
// If we are dealing with the about page
if ($match['name'] == 'about') {
}
// If we are dealing with the rss feed
if ($match['name'] == 'rss' || $match['name'] == 'rss-all' || $match['name'] == 'rss-xml') {
$blogposts = getBlogPosts($match['params']);
$notes = getNotes($match['params']);
$variables['posts'] = array_merge($blogposts, $notes);
$variables['feed']['title'] = "Yarmo's blog and notes";
$variables['feed']['description'] = "Blog and notes feed for yarmo.eu discussing homelab and selfhosted stuff";
$variables['feed']['url'] = "https://yarmo.eu";
$variables['feed']['linkself'] = "https://yarmo.eu/rss/all";
}
// If we are dealing with the blog rss feed
if ($match['name'] == 'rss-blog') {
$variables['posts'] = getBlogPosts($match['params']);
$variables['feed']['title'] = "Yarmo's blog";
$variables['feed']['description'] = "Blog feed for yarmo.eu discussing homelab and selfhosted stuff";
$variables['feed']['url'] = "https://yarmo.eu/blog";
$variables['feed']['linkself'] = "https://yarmo.eu/rss/blog";
}
// If we are dealing with the notes rss feed
if ($match['name'] == 'rss-notes') {
$variables['posts'] = getNotes($match['params']);
$variables['feed']['title'] = "Yarmo's blog";
$variables['feed']['description'] = "Notes feed for yarmo.eu discussing homelab and selfhosted stuff";
$variables['feed']['url'] = "https://yarmo.eu/notes";
$variables['feed']['linkself'] = "https://yarmo.eu/rss/notes";
}
// If we are dealing with the feeds
if ($match['name'] == 'feeds') {
$variables['title'] = 'Feeds — '.$basetitle;
}
// If we are dealing with the uses
if ($match['name'] == 'uses') {
$variables['title'] = 'Uses — '.$basetitle;
}
// If we are dealing with the now
if ($match['name'] == 'now') {
$variables['title'] = 'Now — '.$basetitle;
}
// If we are dealing with the blogroll
if ($match['name'] == 'blogroll') {
$variables['title'] = 'Blogroll — '.$basetitle;
}
// If we are dealing with the blog
if ($match['name'] == 'blog') {
$variables['posts'] = getBlogPosts($match['params']);
$variables['title'] = 'Blog — '.$basetitle;
}
// If we are dealing with the notes
if ($match['name'] == 'notes') {
$variables['posts'] = getNotes($match['params']);
$variables['title'] = 'Notes — '.$basetitle;
}
// If we are dealing with a post (old format)
if (($match['name'] == 'blog_post') || ($match['name'] == 'notes_post')) {
header('Location: https://yarmo.eu/post/'.$match['params']['slug']);
}
// If we are dealing with a post
if ($match['name'] == 'post') {
$variables['post'] = getBlogPosts($match['params']);
if (count($variables['post']) == 0) {
$variables['post'] = getNotes($match['params']);
}
if (count($variables['post']) > 0) {
$variables['post'] = $variables['post'][0];
$variables['title'] = htmlspecialchars_decode(str_replace('&middot;','·',$variables['post']['title'])).' — '.$basetitle;
} else {
$match['name'] = '404';
}
}
// If we are dealing with a reply
if ($match['name'] == 'reply') {
$variables['reply'] = getReplies($match['params']);
$variables['reply'] = $variables['reply'][0];
$variables['title'] = htmlspecialchars_decode(str_replace('&middot;','·',$variables['reply']['title'])).' — '.$basetitle;
}
// If we are dealing with the projects
if ($match['name'] == 'projects') {
$variables['projects'] = getProjects($match['params']);
$variables['title'] = 'Projects — '.$basetitle;
}
// If we are dealing with a project's details
if ($match['name'] == 'projects_details') {
$variables['project'] = getProjects($match['params']);
$variables['project'] = $variables['project'][0];
$variables['title'] = htmlspecialchars_decode(str_replace('&middot;','·',$variables['project']['title'])).' — '.$basetitle;
}
// If we are dealing with foss
if ($match['name'] == 'foss') {
$variables['foss'] = getFOSS();
$variables['title'] = 'FOSS — '.$basetitle;
}
// If we are dealing with vinyl
if ($match['name'] == 'vinyl') {
$variables['vinyl'] = getVinyl();
$variables['albums'] = $variables['vinyl']['albums'];
$variables['title'] = 'Vinyl — '.$basetitle;
}
// If we are dealing with Album Of The Week
if ($match['name'] == 'aotw') {
$variables['albums'] = getAOTW();
$variables['title'] = 'Album Of The Week — '.$basetitle;
}
// If we are dealing with the pgp page
if ($match['name'] == 'pgp') {
$variables['title'] = 'PGP — '.$basetitle;
}
// If we are dealing with the contact page
if ($match['name'] == 'contact') {
$variables['title'] = 'Contact me — '.$basetitle;
}
// Add Phug dyninclude
Phug::addKeyword('dyninclude', function ($args) {
return array(
'beginPhp' => 'echo file_get_contents(' . $args . ');',
);
});
// PRODUCTION
// Render the appropriate route
if ($environment === 'production') {
if(is_array($match) && is_callable($match['target'])) {
switch ($match['name']) {
case 'home':
// Phug optimizer
\Phug\Optimizer::call('displayFile', ['index', $variables], $options);
// Phug::displayFile('index', $variables, $options);
break;
case 'about':
// Phug optimizer
\Phug\Optimizer::call('displayFile', ['about', $variables], $options);
// Phug::displayFile('index', $variables, $options);
break;
case 'rss':
case 'rss-xml':
case 'rss-all':
case 'rss-blog':
case 'rss-notes':
getRSS($variables);
break;
case 'feeds':
\Phug\Optimizer::call('displayFile', ['feeds', $variables], $options);
break;
case 'uses':
\Phug\Optimizer::call('displayFile', ['uses', $variables], $options);
break;
case 'now':
\Phug\Optimizer::call('displayFile', ['now', $variables], $options);
break;
case 'blogroll':
\Phug\Optimizer::call('displayFile', ['blogroll', $variables], $options);
break;
case 'blog':
\Phug\Optimizer::call('displayFile', ['blog', $variables], $options);
break;
case 'blog_post':
\Phug\Optimizer::call('displayFile', ['blog_post', $variables], $options);
break;
case 'notes':
\Phug\Optimizer::call('displayFile', ['notes', $variables], $options);
break;
case 'notes_post':
\Phug\Optimizer::call('displayFile', ['notes_post', $variables], $options);
break;
case 'post':
\Phug\Optimizer::call('displayFile', ['post', $variables], $options);
break;
case 'reply':
\Phug\Optimizer::call('displayFile', ['reply', $variables], $options);
break;
case 'projects':
\Phug\Optimizer::call('displayFile', ['projects', $variables], $options);
break;
case 'projects_details':
\Phug\Optimizer::call('displayFile', ['projects_details', $variables], $options);
break;
case 'foss':
\Phug\Optimizer::call('displayFile', ['foss', $variables], $options);
break;
case 'music':
\Phug\Optimizer::call('displayFile', ['music', $variables], $options);
break;
case 'vinyl':
\Phug\Optimizer::call('displayFile', ['vinyl', $variables], $options);
break;
case 'aotw':
\Phug\Optimizer::call('displayFile', ['aotw', $variables], $options);
break;
case 'contact':
\Phug\Optimizer::call('displayFile', ['contact', $variables], $options);
break;
case 'pgp':
\Phug\Optimizer::call('displayFile', ['pgp', $variables], $options);
break;
default:
\Phug\Optimizer::call('displayFile', ['404', $variables], $options);
break;
}
} else {
// No route was matched
\Phug\Optimizer::call('displayFile', ['404', $variables], $options);
}
exit;
}
# DEVELOPMENT
// Render the appropriate route
if(is_array($match) && is_callable($match['target'])) {
switch ($match['name']) {
case 'home':
Phug::displayFile('index', $variables, $options);
break;
case 'about':
Phug::displayFile('about', $variables, $options);
break;
case 'rss':
case 'rss-xml':
case 'rss-all':
case 'rss-blog':
case 'rss-notes':
getRSS($variables);
break;
case 'feeds':
Phug::displayFile('feeds', $variables, $options);
break;
case 'uses':
Phug::displayFile('uses', $variables, $options);
break;
case 'now':
Phug::displayFile('now', $variables, $options);
break;
case 'blogroll':
Phug::displayFile('blogroll', $variables, $options);
break;
case 'blog':
Phug::displayFile('blog', $variables, $options);
break;
case 'blog_post':
Phug::displayFile('blog_post', $variables, $options);
break;
case 'notes':
Phug::displayFile('notes', $variables, $options);
break;
case 'notes_post':
Phug::displayFile('notes_post', $variables, $options);
break;
case 'post':
Phug::displayFile('post', $variables, $options);
break;
case 'reply':
Phug::displayFile('reply', $variables, $options);
break;
case 'projects':
Phug::displayFile('projects', $variables, $options);
break;
case 'projects_details':
Phug::displayFile('projects_details', $variables, $options);
break;
case 'foss':
Phug::displayFile('foss', $variables, $options);
break;
case 'music':
Phug::displayFile('music', $variables, $options);
break;
case 'vinyl':
Phug::displayFile('vinyl', $variables, $options);
break;
case 'aotw':
Phug::displayFile('aotw', $variables, $options);
break;
case 'contact':
Phug::displayFile('contact', $variables, $options);
break;
case 'pgp':
Phug::displayFile('pgp', $variables, $options);
break;
default:
Phug::displayFile('404', $variables, $options);
break;
}
} else {
// No route was matched
Phug::displayFile('404', $variables, $options);
}

6
nodemon.json Normal file
View File

@ -0,0 +1,6 @@
{
"ignore": [],
"env": {
"NODE_ENV": "development"
}
}

1803
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

36
package.json Normal file
View File

@ -0,0 +1,36 @@
{
"name": "yarmo.eu",
"version": "0.1.0",
"description": "Yarmo.eu website",
"main": "index.js",
"scripts": {
"start": "node index",
"dev": "nodemon --config nodemon.json index"
},
"repository": {
"type": "git",
"url": "https://git.yarmo.eu/yarmo/yarmo.eu"
},
"keywords": [],
"author": "Yarmo Mackenbach <yarmo@yarmo.eu> (https://yarmo.eu)",
"license": "AGPL-3.0-or-later",
"dependencies": {
"bent": "^7.3.10",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"express-validator": "^6.6.1",
"feed": "^4.2.1",
"jstransformer-markdown-it": "^2.1.0",
"lodash": "^4.17.20",
"luxon": "^1.25.0",
"markdown-it-anchor": "^5.3.0",
"markdown-it-table-of-contents": "^0.4.4",
"markdown-it-title": "^3.0.0",
"pug": "^3.0.0",
"yaml": "^1.10.0",
"yaml-front-matter": "^4.1.0"
},
"devDependencies": {
"nodemon": "^2.0.4"
}
}

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 838 B

After

Width:  |  Height:  |  Size: 838 B

View File

Before

Width:  |  Height:  |  Size: 850 B

After

Width:  |  Height:  |  Size: 850 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 327 B

After

Width:  |  Height:  |  Size: 327 B

View File

Before

Width:  |  Height:  |  Size: 990 B

After

Width:  |  Height:  |  Size: 990 B

View File

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

Before

Width:  |  Height:  |  Size: 416 B

After

Width:  |  Height:  |  Size: 416 B

View File

Before

Width:  |  Height:  |  Size: 658 B

After

Width:  |  Height:  |  Size: 658 B

358
public/style.css Normal file
View File

@ -0,0 +1,358 @@
@font-face {
font-family: "Lora";
src: url("/static/fonts/Lora-Regular.otf");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "Lora";
src: url("/static/fonts/Lora-Bold.otf");
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: "Lora";
src: url("/static/fonts/Lora-Italic.otf");
font-weight: normal;
font-style: italic;
}
@font-face {
font-family: "Lora";
src: url("/static/fonts/Lora-BoldItalic.otf");
font-weight: bold;
font-style: italic;
}
:root {
--clr-bg: hsl(0,0%,95%);
--clr-bg-alt: hsl(0,0%,85%);
--clr-border: hsl(0,0%,80%);
--clr-text: hsl(0,0%,15%);
--clr-text-long: hsl(0,0%,10%);
--clr-text-highlight: hsl(0,50%,40%);
--clr-header: hsl(0,0%,15%);
--clr-subheader: hsl(0,0%,15%);
--clr-link: hsl(175,50%,40%);
--clr-link-alt: hsl(175,38%,60%);
--clr-code: hsl(0,0%,15%);
}
* {
box-sizing: border-box;
}
body {
font-family: 'dm', 'Courier New', Courier, monospace;
color: var(--clr-text);
line-height: 1.4em;
font-size: 1.1em;
background-color: var(--clr-bg);
margin: 24px;
}
header {
margin: 0 0 48px;
padding: 0 0 24px;
border-bottom: 3px dashed rgba(0,0,0,0.5);
}
footer {
margin: 64px 0 0;
text-align: center;
border-top: 3px dashed rgba(0,0,0,0.5);
}
.container {
width: 100%;
max-width: 720px;
margin: 64px auto 128px;
overflow: hidden;
}
.select-all {
user-select: all;
-ms-user-select: all;
-moz-user-select: all;
-webkit-user-select: all;
}
a {
color: var(--clr-link);
font-weight: bold;
text-decoration: underline;
}
a:hover {
color: var(--clr-text);
background-color: var(--clr-link-alt);
text-decoration: none;
}
a.header-anchor {
text-decoration: none;
opacity: 0.5;
}
h2:hover a.header-anchor {
opacity: 1;
}
h1 {
line-height: 1.2em;
margin: 8px 0 12px 0;
}
h2 {
color: var(--clr-header);
font-weight: bold;
margin: 64px 0 0;
line-height: 1.2em;
}
h3 {
margin: 32px 0 0;
color: var(--clr-subheader);
line-height: 1.2em;
}
.small {
font-size: 0.75em !important;
}
pre {
padding: 0 4px;
background-color: var(--clr-bg-alt);
border: 2px solid var(--clr-border);
overflow-x: auto;
}
code {
font-family: 'dm', 'Courier New', Courier, monospace;
color: var(--clr-code);
background-color: var(--clr-bg-alt);
border: 2px solid var(--clr-border);
font-size: 1rem !important;
line-height: 1.5rem;
}
pre > code {
border: 0;
}
p > code, li > code {
padding: 0 2px;
}
ul li {
list-style-type: "|> ";
}
blockquote {
margin-left: 0;
padding-left: 32px;
border-left: 2px solid #4ab4ab;
}
blockquote strong em {
color: var(--clr-text-highlight);
}
.id-card {
margin: 0 0 64px 0;
}
.ellipsis {
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.id-card {
/* display: flex;
align-items: top;
flex-wrap: wrap; */
position: relative;
padding: 24px 24px 0px;
background: #f9f9f9;
border: 2px solid rgba(0,0,0,0.5);
}
.id-card .header-image {
/* flex: 1;
flex-basis: auto; */
/* margin: 8px 32px 8px 0; */
text-align: center;
}
.id-card .header-description {
width: 100%;
/* flex: 100;
flex-basis: auto; */
/* margin: 8px 0; */
/* border-left: 2px solid var(--clr-border);
border-right: 2px solid var(--clr-border); */
}
.id-card .header-description-id {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
margin: 0 0 1em 0;
}
.id-card .profile-picture {
/* position: absolute; */
float: right;
width: 128px;
margin: 0 0 12px 12px;
/* margin: 8px 0; */
/* margin: 0 1em 0 0; */
border-radius: 100%;
}
.id-card h1 {
font-size: 1.6em;
/* margin: 0; */
margin: 0 0 1em;
/* margin: 0 0 16px; */
}
.id-card p {
width: 100%;
font-size: 1.1em;
margin: 0 0 1em 0;
}
.id-card .social a {
display: inline-block;
width: 20px;
height: 20px;
padding: 8px;
margin: 0 4px;
border: 0;
}
.wrapper-table {
display: block;
margin: 24px 0 48px;
overflow-x: auto;
}
table {
width: 100%;
min-width: 640px;
border-top: 1px var(--clr-text) solid;
border-bottom: 1px var(--clr-text) solid;
border-collapse: collapse;
line-height: 1.4em;
}
table thead {
border-bottom: 1px var(--clr-text) solid;
}
table th {
padding: 8px 12px;
}
table td {
padding: 10px 12px;
}
table.academic-record th:nth-child(1) {
width: 15%;
}
table.academic-record th:nth-child(2) {
width: 35%;
}
table.academic-record th:nth-child(3) {
width: auto;
}
table.work-experience th:nth-child(1) {
width: 15%;
}
table.work-experience th:nth-child(2) {
width: 35%;
}
table.work-experience th:nth-child(3) {
width: auto;
}
table.publications th:nth-child(1) {
width: 15%;
}
table.publications th:nth-child(2) {
width: 35%;
}
table.publications th:nth-child(3) {
width: auto;
}
table.music th:nth-child(1) {
width: 15%;
}
table.music th:nth-child(2) {
width: 35%;
}
table.music th:nth-child(3) {
width: auto;
}
/* Longform */
.longform_list {
margin: 2em 0 0;
}
.longform_list__item {
margin: 2em 0 0;
font-family: 'Lora', Georgia, Times, serif;
}
.longform_list__item a {
display: inline-block;
margin: 4px 0;
font-size: 1.3em;
}
.longform__header {
font-size: 0.9em;
color: var(--clr-subheader);
}
.longform__content {
margin: 64px 0 0;
font-family: 'Lora', Georgia, Times, serif;
font-size: 1.1em;
line-height: 1.8em;
color: var(--clr-text-long);
}
.longform__content p {
margin: 1.5em 0;
}
.longform__content code {
font-size: 0.8em;
}
.longform__content ul li {
list-style-type: "- ";
}
.longform__content img {
display: block;
max-width: 100%;
max-height: 480px;
margin: 0 auto;
text-align: center;
}
.longform__content img + br {
display: none;
}
.longform__content img + br + em {
display: block;
text-align: center;
font-style: normal;
font-size: 90%;
}
.comment {
padding: 12px;
margin: 32px 0 0;
background-color: var(--clr-bg-alt);
}
.comment .quote {
margin: 0 0 12px;
font-family: 'Lora', Georgia, Times, serif;
}
.comment .sub {
margin: 0;
}
.subsection {
margin: 64px 0 0;
border-top: 3px dashed rgba(0,0,0,0.5);
}
.subsection h2 {
margin: 48px 0 0;
}
/* Lists */
.list {
margin: 64px 0 0;
}
.list__item p {
margin: 0 0 8px;
}
@media screen and (max-width: 560px) {
.id-card .profile-picture {
display: block;
float: none;
margin: 0 auto 1em;
}
}

45
routes/feed.js Normal file
View File

@ -0,0 +1,45 @@
const router = require('express').Router()
const fs = require('fs')
const _ = require('lodash')
const util = require('../server/util')
router.get('/all', async (req, res) => {
res.setHeader('Content-Type', 'application/rss+xml')
res.end(await util.getRSS({ channel: 'all', format: 'rss' }))
})
router.get('/all.atom', async (req, res) => {
res.setHeader('Content-Type', 'application/atom+xml')
res.end(await util.getRSS({ channel: 'all', format: 'atom' }))
})
router.get('/all.json', async (req, res) => {
res.setHeader('Content-Type', 'application/feed+json')
res.end(await util.getRSS({ channel: 'all', format: 'json' }))
})
router.get('/blog', async (req, res) => {
res.setHeader('Content-Type', 'application/rss+xml')
res.end(await util.getRSS({ include: ['blog'], channel: 'blog', format: 'rss' }))
})
router.get('/blog.atom', async (req, res) => {
res.setHeader('Content-Type', 'application/atom+xml')
res.end(await util.getRSS({ include: ['blog'], channel: 'blog', format: 'atom' }))
})
router.get('/blog.json', async (req, res) => {
res.setHeader('Content-Type', 'application/feed+json')
res.end(await util.getRSS({ include: ['blog'], channel: 'blog', format: 'json' }))
})
router.get('/notes', async (req, res) => {
res.setHeader('Content-Type', 'application/rss+xml')
res.end(await util.getRSS({ include: ['notes'], channel: 'notes', format: 'rss' }))
})
router.get('/notes.atom', async (req, res) => {
res.setHeader('Content-Type', 'application/atom+xml')
res.end(await util.getRSS({ include: ['notes'], channel: 'notes', format: 'atom' }))
})
router.get('/notes.json', async (req, res) => {
res.setHeader('Content-Type', 'application/feed+json')
res.end(await util.getRSS({ include: ['notes'], channel: 'notes', format: 'json' }))
})
module.exports = router

58
routes/main.js Normal file
View File

@ -0,0 +1,58 @@
const router = require('express').Router()
const fs = require('fs')
const _ = require('lodash')
const mw = require('../server/middlewares')
const util = require('../server/util')
router.param('slug', mw.getPostBySlug)
router.get('/', mw.getBlogPosts, (req, res) => {
res.render('blog', { title: 'Blog — yarmo.eu' })
})
router.get('/blog', mw.getBlogPosts, (req, res) => {
res.render('blog', { title: 'Blog — yarmo.eu' })
})
router.get('/blog/:s', (req, res) => {
res.redirect(`/post/${req.params.s}`)
})
router.get('/notes', mw.getNotes, (req, res) => {
res.render('notes', { title: 'Notes — yarmo.eu' })
})
router.get('/notes/:s', (req, res) => {
res.redirect(`/post/${req.params.s}`)
})
router.get('/post/:slug', (req, res) => {
res.render('post', { title: `${res.locals.post.title} — yarmo.eu` })
})
router.get('/blogroll', (req, res) => {
res.render('blogroll', { title: 'Blogroll — yarmo.eu' })
})
router.get('/feeds', (req, res) => {
res.render('feeds', { title: 'Feeds — yarmo.eu' })
})
router.get('/about', (req, res) => {
res.render('about', { title: 'About me — yarmo.eu' })
})
router.get('/work', (req, res) => {
res.render('work', { title: 'Work — yarmo.eu' })
})
router.get('/contact', (req, res) => {
res.render('contact', { title: 'Contact — yarmo.eu' })
})
router.get('/now', (req, res) => {
res.render('now', { title: 'Now — yarmo.eu' })
})
router.get('/uses', (req, res) => {
res.render('uses', { title: 'Uses — yarmo.eu' })
})
router.get('/music', (req, res) => {
res.render('music', { title: 'Music — yarmo.eu' })
})
router.get('/vinyl', mw.getVinyl, (req, res) => {
res.render('vinyl', { title: 'Vinyl — yarmo.eu' })
})
router.get('/pgp', (req, res) => {
res.render('pgp', { title: 'PGP — yarmo.eu' })
})
module.exports = router

16
routes/static.js Normal file
View File

@ -0,0 +1,16 @@
const express = require('express')
const router = require('express').Router()
const path = require('path')
router.use('/favicon.png', express.static(path.join(__dirname, '../', 'public', 'favicon.png')))
router.use('/static', express.static(path.join(__dirname, '../', 'public')))
router.use('/static', express.static(path.join(__dirname, '../', 'content', 'img')))
router.use('/*.asc', (req, res, next) => {
res.sendFile(path.join(__dirname, '../', 'keys', `${req.params[0]}.asc`))
})
router.use('/*.pgp', (req, res, next) => {
res.sendFile(path.join(__dirname, '../', 'keys', `${req.params[0]}.pgp`))
})
module.exports = router

View File

@ -1,19 +0,0 @@
<?php
require './vendor/autoload.php';
use tubalmartin\CssMin\Minifier as CSSmin;
$compressor = new CSSmin;
$compressor->removeImportantComments();
$compressor->setLineBreakPosition(1000);
$input_css = file_get_contents('./static/style.css');
$output_css = $compressor->run($input_css);
file_put_contents('./static/style.css', $output_css);
$input_css = file_get_contents('./static/norm.css');
$output_css = $compressor->run($input_css);
file_put_contents('./static/norm.css', $output_css);
?>

32
server/middlewares.js Normal file
View File

@ -0,0 +1,32 @@
const fs = require('fs')
const path = require('path')
const YAML = require('yaml')
const util = require('./util')
module.exports.getBlogPosts = async (req, res, next) => {
res.locals.blogPosts = await util.getBlogPosts({ publishedOnly: true })
next()
}
module.exports.getNotes = async (req, res, next) => {
res.locals.notes = await util.getNotes({ publishedOnly: true })
next()
}
module.exports.getPostBySlug = async (req, res, next, slug) => {
let post = await util.getPost(slug)
if (post) {
post.webmentions = await util.getWebmentions(post.url)
post.hasWebmentions = post.webmentions.length > 0
}
res.locals.post = post
next()
}
module.exports.getVinyl = async (req, res, next) => {
const file = fs.readFileSync(path.join(__dirname, '../', 'content', 'music', 'vinyl.yaml'), 'utf8')
res.locals.vinyl = YAML.parse(file)
next()
}

206
server/util.js Normal file
View File

@ -0,0 +1,206 @@
const fs = require('fs')
const { readdir } = require('fs').promises
const path = require('path')
const { resolve } = require('path')
const bent = require('bent')
const getJSON = bent('json')
const _ = require('lodash')
const md = require('markdown-it')({ typographer: true })
const yamlFront = require('yaml-front-matter')
const { DateTime } = require('luxon')
const Feed = require('feed').Feed
md.use(require("markdown-it-anchor"), {
"level": 2,
"permalink": true,
"permalinkClass": 'header-anchor',
"permalinkSymbol": '¶',
"permalinkBefore": false
})
async function* getFiles(dir) {
const dirents = await readdir(dir, { withFileTypes: true })
for (const dirent of dirents) {
const res = resolve(dir, dirent.name)
if (dirent.isDirectory()) {
yield* getFiles(res)
} else {
yield res
}
}
}
const getBlogPosts = async (opts) => {
if (!opts) { opts = {} }
opts.publishedOnly = 'publishedOnly' in opts ? opts.publishedOnly : true
let data = []
for await (const f of getFiles(path.join(__dirname, '../', 'content', 'blog'))) {
const rawContent = fs.readFileSync(f, 'utf8')
const fm = yamlFront.loadFront(rawContent)
if (opts.publishedOnly && !fm.published) { continue }
data.push({
type: 'blog',
title: fm.title,
author: fm.author,
urlrel: `/post/${fm.slug}`,
url: `https://yarmo.eu/post/${fm.slug}`,
slug: fm.slug,
date: fm.date,
date_formatted: DateTime.fromFormat(fm.date, 'yyyy-LL-dd hh:mm:ss').setLocale("en").toLocaleString(DateTime.DATE_MED),
published: fm.published,
discussion: fm.discussion,
content: md.render(fm.__content)
})
}
return data.reverse()
}
const getNotes = async (opts) => {
if (!opts) { opts = {} }
opts.publishedOnly = 'publishedOnly' in opts ? opts.publishedOnly : true
let data = []
for await (const f of getFiles(path.join(__dirname, '../', 'content', 'notes'))) {
const rawContent = fs.readFileSync(f, 'utf8')
const fm = yamlFront.loadFront(rawContent)
if (opts.publishedOnly && !fm.published) { continue }
data.push({
type: 'note',
title: fm.title,
author: fm.author,
urlrel: `/post/${fm.slug}`,
url: `https://yarmo.eu/post/${fm.slug}`,
slug: fm.slug,
date: fm.date,
date_formatted: DateTime.fromFormat(fm.date, 'yyyy-LL-dd hh:mm:ss').setLocale("en").setZone('utc').toLocaleString(DateTime.DATE_MED),
published: fm.published,
discussion: fm.discussion,
content: md.render(fm.__content)
})
}
return data.reverse()
}
const getPost = async (slug) => {
let post = null, posts = await getBlogPosts()
posts = _.filter(posts, (p) => { return slug == p.slug })
post = posts.length > 0 ? posts[0] : null
if (!post) {
posts = await getNotes()
posts = _.filter(posts, (p) => { return slug == p.slug })
post = posts.length > 0 ? posts[0] : null
}
return post
}
const getWebmentions = async (url) => {
const data_1 = await getJSON(`https://webm.yarmo.eu/get?target=${url}`)
const data_2 = await getJSON(`https://webm.yarmo.eu/get?target=${url.replace('/post/', '/blog/')}`)
const data_3 = await getJSON(`https://webm.yarmo.eu/get?target=${url.replace('/post/', '/notes/')}`)
const dataRaw = data_1.concat(data_2).concat(data_3)
const data = _.map(dataRaw, (x) => {
x.date = DateTime.fromISO(x.created_at).setLocale("en").setZone('utc').toLocaleString(DateTime.DATE_MED),
x.time = DateTime.fromISO(x.created_at).setLocale("en").setZone('utc').toLocaleString(DateTime.TIME_24_WITH_SHORT_OFFSET)
return x
})
return data
}
const getRSS = async (opts) => {
if (!opts) { opts = {} }
opts.include = 'include' in opts ? opts.include : ['blog', 'notes']
opts.channel = 'channel' in opts ? opts.channel : 'all'
opts.format = 'format' in opts ? opts.format : 'rss'
const feed = new Feed({
title: "Yarmo's blog and notes",
description: "Blog posts and notes feed for yarmo.eu discussing open source, privacy and selfhosted stuff",
id: "https://yarmo.eu",
link: "https://yarmo.eu",
language: "en",
favicon: "https://yarmo.eu/favicon.png",
copyright: "All rights reserved 2020 Yarmo Mackenbach",
updated: new Date(Date.now()),
feedLinks: {
rss: `https://yarmo.eu/feed/${opts.channel}`,
json: `https://yarmo.eu/feed/${opts.channel}.json`,
atom: `https://yarmo.eu/feed/${opts.channel}.atom`
},
author: {
name: "Yarmo Mackenbach",
email: "yarmo@yarmo.eu",
link: "https://yarmo.eu"
}
})
feed.addCategory("Technology")
feed.addCategory("FOSS")
feed.addCategory("Decentralization")
feed.addCategory("Privacy")
feed.addCategory("Identity")
feed.addCategory("Cryptography")
feed.addCategory("Selfhosted")
let posts = []
if (opts.include.includes('blog')) {
posts = posts.concat(await getBlogPosts())
}
if (opts.include.includes('notes')) {
posts = posts.concat(await getNotes())
}
posts = _.sortBy(posts, ['date']).reverse()
posts.forEach((item, i) => {
feed.addItem({
title: item.title,
id: item.url,
link: item.url,
description: item.content,
content: item.content,
author: [
{
name: "Yarmo Mackenbach",
email: "yarmo@yarmo.eu",
link: "https://yarmo.eu"
}
],
date: new Date(item.date)
})
})
let response
switch (opts.format) {
case 'rss':
case 'xml':
default:
response = feed.rss2()
break;
case 'atom':
response = feed.atom1()
break;
case 'json':
response = feed.json1()
break;
}
return response
}
module.exports = {
getFiles: getFiles,
getBlogPosts: getBlogPosts,
getNotes: getNotes,
getPost: getPost,
getWebmentions: getWebmentions,
getRSS: getRSS
}

View File

@ -1,351 +0,0 @@
@font-face {
font-family: "Lora";
src: url("/static/fonts/Lora-Regular.otf");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "Lora";
src: url("/static/fonts/Lora-Bold.otf");
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: "Lora";
src: url("/static/fonts/Lora-Italic.otf");
font-weight: normal;
font-style: italic;
}
@font-face {
font-family: "Lora";
src: url("/static/fonts/Lora-BoldItalic.otf");
font-weight: bold;
font-style: italic;
}
:root {
--clr-bg: hsl(0,0%,95%);
--clr-bg-alt: hsl(0,0%,85%);
--clr-border: hsl(0,0%,80%);
--clr-text: hsl(0,0%,15%);
--clr-text-long: hsl(0,0%,10%);
--clr-text-highlight: hsl(0,50%,40%);
--clr-header: hsl(0,0%,15%);
--clr-subheader: hsl(0,0%,15%);
--clr-link: hsl(175,50%,40%);
--clr-link-alt: hsl(175,38%,60%);
--clr-code: hsl(0,0%,15%);
}
* {
box-sizing: border-box;
}
body {
font-family: 'dm', 'Courier New', Courier, monospace;
color: var(--clr-text);
line-height: 1.4em;
font-size: 1.1em;
background-color: var(--clr-bg);
margin: 24px;
}
header {
margin: 0 0 48px;
padding: 0 0 24px;
border-bottom: 3px dashed rgba(0,0,0,0.5);
}
footer {
margin: 64px 0 0;
text-align: center;
border-top: 3px dashed rgba(0,0,0,0.5);
}
.container {
width: 100%;
max-width: 720px;
margin: 64px auto 128px;
overflow: hidden;
}
.select-all {
user-select: all;
-ms-user-select: all;
-moz-user-select: all;
-webkit-user-select: all;
}
a {
color: var(--clr-link);
font-weight: bold;
text-decoration: underline;
}
a:hover {
color: var(--clr-text);
background-color: var(--clr-link-alt);
text-decoration: none;
}
h1 {
line-height: 1.2em;
margin: 8px 0 12px 0;
}
h2 {
color: var(--clr-header);
font-weight: bold;
margin: 64px 0 0;
line-height: 1.2em;
}
h3 {
margin: 32px 0 0;
color: var(--clr-subheader);
line-height: 1.2em;
}
.small {
font-size: 0.75em !important;
}
pre {
padding: 0 4px;
background-color: var(--clr-bg-alt);
border: 2px solid var(--clr-border);
overflow-x: auto;
}
code {
font-family: 'dm', 'Courier New', Courier, monospace;
color: var(--clr-code);
background-color: var(--clr-bg-alt);
border: 2px solid var(--clr-border);
font-size: 1rem !important;
line-height: 1.5rem;
}
pre > code {
border: 0;
}
p > code, li > code {
padding: 0 2px;
}
ul li {
list-style-type: "|> ";
}
blockquote {
margin-left: 0;
padding-left: 32px;
border-left: 2px solid #4ab4ab;
}
blockquote strong em {
color: var(--clr-text-highlight);
}
.id-card {
margin: 0 0 64px 0;
}
.ellipsis {
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.id-card {
/* display: flex;
align-items: top;
flex-wrap: wrap; */
position: relative;
padding: 24px 24px 0px;
background: #f9f9f9;
border: 2px solid rgba(0,0,0,0.5);
}
.id-card .header-image {
/* flex: 1;
flex-basis: auto; */
/* margin: 8px 32px 8px 0; */
text-align: center;
}
.id-card .header-description {
width: 100%;
/* flex: 100;
flex-basis: auto; */
/* margin: 8px 0; */
/* border-left: 2px solid var(--clr-border);
border-right: 2px solid var(--clr-border); */
}
.id-card .header-description-id {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
margin: 0 0 1em 0;
}
.id-card .profile-picture {
/* position: absolute; */
float: right;
width: 128px;
margin: 0 0 12px 12px;
/* margin: 8px 0; */
/* margin: 0 1em 0 0; */
border-radius: 100%;
}
.id-card h1 {
font-size: 1.6em;
/* margin: 0; */
margin: 0 0 1em;
/* margin: 0 0 16px; */
}
.id-card p {
width: 100%;
font-size: 1.1em;
margin: 0 0 1em 0;
}
.id-card .social a {
display: inline-block;
width: 20px;
height: 20px;
padding: 8px;
margin: 0 4px;
border: 0;
}
.wrapper-table {
display: block;
margin: 24px 0 48px;
overflow-x: auto;
}
table {
width: 100%;
min-width: 640px;
border-top: 1px var(--clr-text) solid;
border-bottom: 1px var(--clr-text) solid;
border-collapse: collapse;
line-height: 1.4em;
}
table thead {
border-bottom: 1px var(--clr-text) solid;
}
table th {
padding: 8px 12px;
}
table td {
padding: 10px 12px;
}
table.academic-record th:nth-child(1) {
width: 15%;
}
table.academic-record th:nth-child(2) {
width: 35%;
}
table.academic-record th:nth-child(3) {
width: auto;
}
table.work-experience th:nth-child(1) {
width: 15%;
}
table.work-experience th:nth-child(2) {
width: 35%;
}
table.work-experience th:nth-child(3) {
width: auto;
}
table.publications th:nth-child(1) {
width: 15%;
}
table.publications th:nth-child(2) {
width: 35%;
}
table.publications th:nth-child(3) {
width: auto;
}
table.music th:nth-child(1) {
width: 15%;
}
table.music th:nth-child(2) {
width: 35%;
}
table.music th:nth-child(3) {
width: auto;
}
/* Longform */
.longform_list {
margin: 2em 0 0;
}
.longform_list__item {
margin: 2em 0 0;
font-family: 'Lora', Georgia, Times, serif;
}
.longform_list__item a {
display: inline-block;
margin: 4px 0;
font-size: 1.3em;
}
.longform__header {
font-size: 0.9em;
color: var(--clr-subheader);
}
.longform__content {
margin: 64px 0 0;
font-family: 'Lora', Georgia, Times, serif;
font-size: 1.1em;
line-height: 1.8em;
color: var(--clr-text-long);
}
.longform__content p {
margin: 1.5em 0;
}
.longform__content code {
font-size: 0.8em;
}
.longform__content ul li {
list-style-type: "- ";
}
.longform__content img {
display: block;
max-width: 100%;
max-height: 480px;
margin: 0 auto;
text-align: center;
}
.longform__content img + br {
display: none;
}
.longform__content img + br + em {
display: block;
text-align: center;
font-style: normal;
font-size: 90%;
}
.comment {
padding: 12px;
margin: 32px 0 0;
background-color: var(--clr-bg-alt);
}
.comment .quote {
margin: 0 0 12px;
font-family: 'Lora', Georgia, Times, serif;
}
.comment .sub {
margin: 0;
}
.subsection {
margin: 64px 0 0;
border-top: 3px dashed rgba(0,0,0,0.5);
}
.subsection h2 {
margin: 48px 0 0;
}
/* Lists */
.list {
margin: 64px 0 0;
}
.list__item p {
margin: 0 0 8px;
}
@media screen and (max-width: 560px) {
.id-card .profile-picture {
display: block;
float: none;
margin: 0 auto 1em;
}
}

View File

@ -1,8 +1,8 @@
extends layout
extends templates/main
block content
h2 HTTP 404
p
| This page could not be found... Go back to
a(href="/") yarmo.eu
| and try again!
h2 HTTP 404
p
| This page could not be found... Go back to
a(href="/") yarmo.eu
| and try again!

View File

@ -1,172 +1,169 @@
extends layout
extends templates/main
block content
header
nav
| about me
h1
| About Me
nav
| Go to:
a(href="/now") now
| |
a(href="/") blog
| |
a(href="/notes") notes
| |
a(href="/feeds") feeds
| |
a(href="/blogroll") blogroll
| |
a(href="/uses") uses
| |
a(href="/foss") FOSS
| |
a(href="/projects") projects
| |
a(href="/music") music
| |
a(href="/pgp") PGP
| |
a(href="/contact") contact
main
include id
h2 &gt;&gt; About me
h3 Principles
ul
li The internet needs to be decentralized
li
a(href="https://publiccode.eu/") Public money? Public code!
li Open science benefits society
li Code documentation is essential
li
| Remove
a(href="https://en.wikipedia.org/wiki/Big_Four_tech_companies") GAFAM
| from one's life
h3 Interests
ul
li Cryptography and decentralized identity
li Advancing and democratizing science
li Building and maintaining a homelab
li Statistics, machine learning
li Halo universe
li Making music
li Podcasts
h3 Languages (spoken, written, read)
ul
li Dutch (native)
li English (+++)
li French (+++)
li Portuguese (+)
h2 &gt;&gt; Science
p I have finished a PhD project in the field of Neurosciences. My research focused on the neuronal basis of directional hearing.
header
h1
| About Me
nav
| &gt;&gt;
a(href="/about") about me
nav
| Go to:
a(href="/") blog
| |
a(href="/notes") notes
| |
a(href="/feeds") feeds
| |
a(href="/now") now
| |
a(href="/uses") uses
| |
a(href="/work") work
| |
a(href="/music") music
| |
a(href="/pgp") PGP
| |
a(href="/contact") contact
main
include id
h2 &gt;&gt; About me
h3 Principles
ul
li The internet needs to be decentralized
li
a(href="https://publiccode.eu/") Public money? Public code!
li Open science benefits society
li Code documentation is essential
li
| Remove
a(href="https://en.wikipedia.org/wiki/Big_Four_tech_companies") GAFAM
| from one's life
h3 Interests
ul
li Cryptography and decentralized identity
li Advancing and democratizing science
li Building and maintaining a homelab
li Statistics, machine learning
li Halo universe
li Making music
li Podcasts
h3 Languages (spoken, written, read)
ul
li Dutch (native)
li English (+++)
li French (+++)
li Portuguese (+)
h2 &gt;&gt; Science
p I have finished a PhD project in the field of Neurosciences. My research focused on the neuronal basis of directional hearing.
h3 Skills
ul
li Experimental electrophysiology
li Histology
li Behavioral techniques
li Programming &amp; data analysis
ul
li Data visualisation
li Fourier / signal processing
li Circular statistics
li Machine learning
li Database management (SQL &amp; NoSQL)
h3 Work experience
.wrapper-table
table.work-experience
thead
tr
th Years
th Name
th Location
tbody
tr
td 2015-2019
td PhD in Neurosciences
td Erasmus MC, Rotterdam, The Netherlands
h3 Academic record
.wrapper-table
table.academic-record
thead
tr
th Years
th Name
th Location
tbody
tr
td 2010-2013
td Bachelor Biology
td Université Victor Segalen, Bordeaux, France
tr
td 2013-2015
td Master Neurosciences &amp; Neuropsychopharmacology
td Université Victor Segalen, Bordeaux, France
tr
td 2013-2015
td Master Cellular &amp; Molecular Biology
td Universidade de Coimbra, Portugal
tr
td 2015-2019
td PhD in Neurosciences
td Erasmus MC, Rotterdam, The Netherlands
h3 Publications
.wrapper-table
table.publications
thead
tr
th Years
th Authors
th Title
tbody
tr
td 2017
td Arnau Busquets-Garcia, [...], Giovanni Marsicano
td
a(href="https://www.nature.com/articles/mp20174") Pregnenolone blocks cannabinoid-induced acute psychotic-like states in mice
h2 &gt;&gt; Programming
h3 Skills
ul
li Experimental electrophysiology
li Histology
li Behavioral techniques
li Programming &amp; data analysis
ul
li Data visualisation
li Fourier / signal processing
li Circular statistics
li Machine learning
li Database management (SQL &amp; NoSQL)
h3 Work experience
.wrapper-table
table.work-experience
thead
tr
th Years
th Name
th Location
tbody
tr
td 2015-2019
td PhD in Neurosciences
td Erasmus MC, Rotterdam, The Netherlands
h3 Academic record
.wrapper-table
table.academic-record
thead
tr
th Years
th Name
th Location
tbody
tr
td 2010-2013
td Bachelor Biology
td Université Victor Segalen, Bordeaux, France
tr
td 2013-2015
td Master Neurosciences &amp; Neuropsychopharmacology
td Université Victor Segalen, Bordeaux, France
tr
td 2013-2015
td Master Cellular &amp; Molecular Biology
td Universidade de Coimbra, Portugal
tr
td 2015-2019
td PhD in Neurosciences
td Erasmus MC, Rotterdam, The Netherlands
h3 Publications
.wrapper-table
table.publications
thead
tr
th Years
th Authors
th Title
tbody
tr
td 2017
td Arnau Busquets-Garcia, [...], Giovanni Marsicano
td
a(href="https://www.nature.com/articles/mp20174") Pregnenolone blocks cannabinoid-induced acute psychotic-like states in mice
h2 &gt;&gt; Programming
h3 Languages used
ul
li Matlab / Octave
li Python / R
li HTML / JS / (S)CSS
li PHP / NodeJS
li SQL / NoSQL (cypher)
h3 Languages used
ul
li Matlab / Octave
li Python / R
li HTML / JS / (S)CSS
li PHP / NodeJS
li SQL / NoSQL (cypher)
h3 Experience (science)
ul
li Data visualisation
li Fourier / signal processing
li Circular statistics
li Machine learning
h3 Experience (science)
ul
li Data visualisation
li Fourier / signal processing
li Circular statistics
li Machine learning
h3 Experience (hobbies)
ul
li Homelab / self-hosting
li Web design
li Raspberry Pi / Arduino
li Home automation
li Cryptography
h3 Open source contributions
p
| Go to:
a(href="/foss") FOSS
h2 &gt;&gt; Music
p
| Go to:
a(href="/music") music
h3 Experience (hobbies)
ul
li Homelab / self-hosting
li Web design
li Raspberry Pi / Arduino
li Home automation
li Cryptography
h3 Open source contributions
p
| Go to:
a(href="/foss") FOSS
h2 &gt;&gt; Music
p
| Go to:
a(href="/music") music

View File

@ -1,19 +0,0 @@
extends layout
mixin entry($item)
.list__item
p !{$item['week']} &gt;&gt; !{$item['artist']} - !{$item['title']} (!{$item['year']})
block content
header
nav
a(href="/about") about me
| &gt;
a(href="/music") music
| &gt; album of the week
h1 Yarmo's Album Of The Week
main
.list
each $item in $albums
+entry($item)

41
views/blog.pug Normal file
View File

@ -0,0 +1,41 @@
extends templates/main
mixin entry(item)
article.longform_list__item.h-entry
p
a(href=item['urlrel']).p-name.u-url !{item['title']}
br
| Posted on
time(datetime=item['date']).dt-published !{item['date_formatted']}
block content
.h-card
header
//nav
a(href="/about") about me
| &gt; blog
h1
| Yarmo's blog
nav
| &gt;&gt;
a(href="/") blog
nav
| Go to:
a(href="/about") about me
| |
a(href="/notes") notes
| |
a(href="/blogroll") blogroll
| |
a(href="/feeds") feeds
| |
a(href="/contact") contact
main
div(style="display:none")
include id
.longform_list.h-feed
each item in blogPosts
+entry(item)

View File

@ -1,24 +1,26 @@
extends layout
extends templates/main
block content
header
nav
a(href="/about") about me
| &gt; blogroll
h1
| Blogroll
main
a(href="https://ar.al/") Aral Balkan
br
a(href="https://write.privacytools.io/freddy/") Freddy's Ramblings
br
a(href="https://www.garron.blog/posts/") Guillermo Garron
br
a(href="https://goel.io/") Karan Goel
br
a(href="https://kevq.uk/") Kev Quirk
br
a(href="https://mikestone.me/") Mike Stone
br
a(href="https://degruchy.org/") Nathan DeGruchy
header
h1
| Blogroll
nav
| &gt;&gt;
a(href="/about") about me
| &gt;
a(href="/blogroll") blogroll
main
a(href="https://ar.al/") Aral Balkan
br
a(href="https://write.privacytools.io/freddy/") Freddy's Ramblings
br
a(href="https://www.garron.blog/posts/") Guillermo Garron
br
a(href="https://goel.io/") Karan Goel
br
a(href="https://kevq.uk/") Kev Quirk
br
a(href="https://mikestone.me/") Mike Stone
br
a(href="https://degruchy.org/") Nathan DeGruchy

View File

@ -1,145 +1,147 @@
extends layout
extends templates/main
block content
header
nav
a(href="/about") about me
| &gt; contact
h1
| Contact me
main
h2 &gt;&gt; Online presence
.wrapper-table
table
tbody
tr
td Fediverse
td
a(href="https://fosstodon.org/@yarmo" rel="me") @yarmo@fosstodon.org
tr
td Lobste.rs
td
a(href="https://lobste.rs/u/yarmo" rel="me") yarmo
tr
td Codeberg
td
a(href="https://codeberg.org/yarmo" rel="me") @yarmo
tr
td GitLab
td
a(href="https://gitlab.com/yarmo" rel="me") @yarmo
tr
td Github
td
a(href="https://github.com/YarmoM" rel="me") @YarmoM
h2 &gt;&gt; Instant messaging
.wrapper-table
table
tbody
tr
td Mail *
td
a(href="mailto:yarmo@yarmo.eu") yarmo@yarmo.eu
tr
td Delta Chat **
td
a(href="mailto:yarmo@yarmo.eu") yarmo@yarmo.eu
tr
td XMPP **
td
a(href="xmpp:yarmo@404.city") yarmo@404.city
tr
td Matrix **
td
a(href="https://matrix.to/#/@yarmo:matrix.org") @yarmo:matrix.org
tr
td Wire **
td @yarmo
tr
td Briar **
td
a(href="briar://acdxuw4lym5l77ezsonbswj3aerwphckgnnrskokxw3t3mz3ncxq4" rel="me") acdxuw4lym5l77ezsonbswj3aerwphckgnnrskokxw3t3mz3ncxq4
tr
td Session *
td 0526f4bf4feb46f61b3382b317276ec2e207d75b11c995d41a78c1f4f2cefbcd5f
tr
td Tox *
td AD4EF6D540949E55A666D8D7BF58EF5422A91F0C99EE334ADBB7123189C4C17A640915B3CC64
tr
td Signal *
td ask me on any platform above
tr
td Telegram
td
a(href="https://t.me/yarmom" rel="me") YarmoM
tr
td Manyverse
td ask me on any platform above
p
| ** Excellent for secure messaging
br
| * Adequate for secure messaging
h2 &gt;&gt; How to send an encrypted message
p If you wish to send me an encrypted message and make sure that I and only I can read it, use any of the instant messaging services marked with ** (preferred) or * from the list above.
p If you do not have accounts on any of the services above and/or prefer email, use any of the two guides below.
header
h1
| Contact me
nav
| &gt;&gt;
a(href="/about") about me
| &gt;
a(href="/contact") contact
main
h2 &gt;&gt; Online presence
.wrapper-table
table
tbody
tr
td Fediverse
td
a(href="https://fosstodon.org/@yarmo" rel="me") @yarmo@fosstodon.org
tr
td Lobste.rs
td
a(href="https://lobste.rs/u/yarmo" rel="me") yarmo
tr
td Codeberg
td
a(href="https://codeberg.org/yarmo" rel="me") @yarmo
tr
td GitLab
td
a(href="https://gitlab.com/yarmo" rel="me") @yarmo
tr
td Github
td
a(href="https://github.com/YarmoM" rel="me") @YarmoM
h2 &gt;&gt; Instant messaging
.wrapper-table
table
tbody
tr
td Mail *
td
a(href="mailto:yarmo@yarmo.eu") yarmo@yarmo.eu
tr
td Delta Chat **
td
a(href="mailto:yarmo@yarmo.eu") yarmo@yarmo.eu
tr
td XMPP **
td
a(href="xmpp:yarmo@404.city") yarmo@404.city
tr
td Matrix **
td
a(href="https://matrix.to/#/@yarmo:matrix.org") @yarmo:matrix.org
tr
td Wire **
td @yarmo
tr
td Briar **
td
a(href="briar://acdxuw4lym5l77ezsonbswj3aerwphckgnnrskokxw3t3mz3ncxq4" rel="me") acdxuw4lym5l77ezsonbswj3aerwphckgnnrskokxw3t3mz3ncxq4
tr
td Session *
td 0526f4bf4feb46f61b3382b317276ec2e207d75b11c995d41a78c1f4f2cefbcd5f
tr
td Tox *
td AD4EF6D540949E55A666D8D7BF58EF5422A91F0C99EE334ADBB7123189C4C17A640915B3CC64
tr
td Signal *
td ask me on any platform above
tr
td Telegram
td
a(href="https://t.me/yarmom" rel="me") YarmoM
tr
td Manyverse
td ask me on any platform above
p
| ** Excellent for secure messaging
br
| * Adequate for secure messaging
h2 &gt;&gt; How to send an encrypted message
p If you wish to send me an encrypted message and make sure that I and only I can read it, use any of the instant messaging services marked with ** (preferred) or * from the list above.
p If you do not have accounts on any of the services above and/or prefer email, use any of the two guides below.
h3#pgp-easy Using Keyoxide (easy)
h3#pgp-easy Using Keyoxide (easy)
ol
li
| Visit my
a(href="https://keyoxide.org/9f0048ac0b23301e1f77e994909f6bd6f80f485d", title="Keyoxide") Keyoxide profile
| .
li Press the <code>encrypt message</code> link at the bottom of the page.
li Write the message in the big textarea.
li Press the <code>ENCRYPT MESSAGE</code> button and copy the encrypted content.
li
| Send an email with the encrypted content to
a(href="mailto:yarmo@yarmo.eu") yarmo@yarmo.eu
| .
ol
li
| Visit my
a(href="https://keyoxide.org/9f0048ac0b23301e1f77e994909f6bd6f80f485d", title="Keyoxide") Keyoxide profile
| .
li Press the <code>encrypt message</code> link at the bottom of the page.
li Write the message in the big textarea.
li Press the <code>ENCRYPT MESSAGE</code> button and copy the encrypted content.
li
| Send an email with the encrypted content to
a(href="mailto:yarmo@yarmo.eu") yarmo@yarmo.eu
| .
h3#pgp-advanced#kleopatra Using the Kleopatra software (advanced)
ol
li
| Download my
a(href="/9F0048AC0B23301E1F77E994909F6BD6F80F485D.asc", download="/9F0048AC0B23301E1F77E994909F6BD6F80F485D.asc", title="PGP public key") public key
| .
li
| Download Kleopatra (
a(href="https://www.openpgp.org/software/kleopatra/") Linux
| -
a(href="https://www.gpg4win.org/get-gpg4win.html") Windows
| -
a(href="https://chocolatey.org/packages/Gpg4win") Chocolatey
| ) and run it.
li Press <code>Import</code> and locate the file downloaded in the first step.
li You may be asked to "Certify" the public key. This is optional (read below) and can be skipped.
li Go to the <code>Notepad</code> section.
li After you are done writing your message, go to the <code>Recipients</code> tab and choose <code>Yarmo Mackenbach</code> in the <code>Encrypt for others:</code> field.
li Return to the <code>Notepad</code> tab and press the <code>Encrypt Notepad</code> button.
li
| Copy the entire message that is now displayed and send it to
a(href="mailto:yarmo@yarmo.eu") yarmo@yarmo.eu
| .
p
| "Certifying" my key via Kleopatra requires you to make your own keypair. To manually verify the validity of my public key, right-click on my key, press on <code>Details</code> and make sure the <code>fingerprint</code> matches my
a(href="/pgp") PGP signature
| .
h3 Getting an encrypted response
h3#pgp-advanced Using the Kleopatra software (advanced)
ol
li
| Download my
a(href="/9F0048AC0B23301E1F77E994909F6BD6F80F485D.asc", download="/9F0048AC0B23301E1F77E994909F6BD6F80F485D.asc", title="PGP public key") public key
| .
li
| Download Kleopatra (
a(href="https://www.openpgp.org/software/kleopatra/") Linux
| -
a(href="https://www.gpg4win.org/get-gpg4win.html") Windows
| -
a(href="https://chocolatey.org/packages/Gpg4win") Chocolatey
| ) and run it.
li Press <code>Import</code> and locate the file downloaded in the first step.
li You may be asked to "Certify" the public key. This is optional (read below) and can be skipped.
li Go to the <code>Notepad</code> section.
li After you are done writing your message, go to the <code>Recipients</code> tab and choose <code>Yarmo Mackenbach</code> in the <code>Encrypt for others:</code> field.
li Return to the <code>Notepad</code> tab and press the <code>Encrypt Notepad</code> button.
li
| Copy the entire message that is now displayed and send it to
a(href="mailto:yarmo@yarmo.eu") yarmo@yarmo.eu
| .
p
| "Certifying" my key via Kleopatra requires you to make your own keypair. To manually verify the validity of my public key, right-click on my key, press on <code>Details</code> and make sure the <code>fingerprint</code> matches my
a(href="/pgp") PGP signature
| .
h3 Getting an encrypted response
p If you would like to get an encrypted response from me, please include the public key of your personal keypair or upload it to a key server.
p If you do not have a personal keypair yet, both Mailvelope and Kleopatra allow you to generate a new keypair. The public part of your keypair can be used by anyone to encrypt their messages to you, and the secret part MUST REMAIN SECRET and can be used by you AND ONLY YOU to decrypt those messages.
p Please note that dealing with PGP keys is advanced stuff: if you are not comfortable with PGP, let's talk using any of the encrypted instant messaging tools listed above.
p If you would like to get an encrypted response from me, please include the public key of your personal keypair or upload it to a key server.
p If you do not have a personal keypair yet, both Mailvelope and Kleopatra allow you to generate a new keypair. The public part of your keypair can be used by anyone to encrypt their messages to you, and the secret part MUST REMAIN SECRET and can be used by you AND ONLY YOU to decrypt those messages.
p Please note that dealing with PGP keys is advanced stuff: if you are not comfortable with PGP, let's talk using any of the encrypted instant messaging tools listed above.

View File

@ -1,18 +1,46 @@
extends layout
extends templates/main
block content
header
nav
a(href="/about") about me
| &gt; feeds
h1
| Feeds
main
ul
li
a(href="/rss/all") RSS blog and notes
li
a(href="/rss/blog") RSS blog
li
a(href="/rss/notes") RSS notes
header
h1
| Feeds
nav
| &gt;&gt;
a(href="/feeds") feeds
nav
| Go to:
a(href="/about") about me
| |
a(href="/blog") blog
| |
a(href="/notes") notes
main
ul
li
p
| Feed for blog posts and notes (
a(href="/feed/all") RSS
| -
a(href="/feed/all.atom") ATOM
| -
a(href="/feed/all.") JSON
| )
li
p
| Feed for blog posts (
a(href="/feed/blog") RSS
| -
a(href="/feed/blog.atom") ATOM
| -
a(href="/feed/blog.json") JSON
| )
li
p
| Feed for notes (
a(href="/feed/notes") RSS
| -
a(href="/feed/notes.atom") ATOM
| -
a(href="/feed/notes.json") JSON
| )

View File

@ -1,42 +1,42 @@
extends layout
extends templates/main
mixin foss_contribution($item)
p
a(href="{$item['url-repo']}") !{$item['repo']}
| —
a(href="{$item['url-item']}") #!{$item['id']}
br
| !{$item['title']}
p
a(href="{$item['url-repo']}") !{$item['repo']}
| —
a(href="{$item['url-item']}") #!{$item['id']}
br
| !{$item['title']}
block content
header
nav
a(href="/about") about me
| &gt; foss
h1
| FOSS
main
h2 &gt;&gt; VCS accounts
.wrapper-table
table
tbody
tr
td Codeberg
td
a(href="https://codeberg.org/yarmo" rel="me") @yarmo
tr
td GitLab
td
a(href="https://gitlab.com/yarmo" rel="me") @yarmo
tr
td Github
td
a(href="https://github.com/YarmoM" rel="me") @YarmoM
h2 &gt;&gt; Contributions
each $item in $foss
+foss_contribution($item)
header
nav
a(href="/about") about me
| &gt; foss
h1
| FOSS
main
h2 &gt;&gt; VCS accounts
.wrapper-table
table
tbody
tr
td Codeberg
td
a(href="https://codeberg.org/yarmo" rel="me") @yarmo
tr
td GitLab
td
a(href="https://gitlab.com/yarmo" rel="me") @yarmo
tr
td Github
td
a(href="https://github.com/YarmoM" rel="me") @YarmoM
h2 &gt;&gt; Contributions
each $item in $foss
+foss_contribution($item)

View File

@ -1,47 +1,47 @@
.id-card
img(src="/static/img/profile.png", alt="Yarmo's profile picture").profile-picture.u-photo
h1
a(href="https://yarmo.eu", rel="me", title="yarmo.eu").u-url.u-uid
span(style="display:none").p-honorific-prefix Mr.
span.p-name
span.p-given-name Yarmo
|
span.p-additional-name Mackenbach
br
p.p-note
| Developer of
a(href="https://keyoxide.org") Keyoxide.org
| .
br
| Finished a PhD in <span class="p-category">neurosciences</span>.
p
| Pronouns:
a(href="https://pronoun.is/he").u-pronoun he/him/his
p
| PGP:
a(href="https://keyoxide.org/9f0048ac0b23301e1f77e994909f6bd6f80f485d", rel="me", title="Keyoxide profile").u-url Keyoxide profile
| /
a(href="/9F0048AC0B23301E1F77E994909F6BD6F80F485D.asc", download="/9F0048AC0B23301E1F77E994909F6BD6F80F485D.asc", rel="pgpkey publickey authn", type="text/plain", title="PGP public key").u-key public.asc
| /
a(href="/static/img/qr_fp.png", title="PGP fingerprint QR") QR
p
| Topics:
| #<span class="p-category">FOSS</span>
| #<span class="p-category">privacy</span>
| #<span class="p-category">encryption</span>
| #<span class="p-category">decentralization</span>
| #<span class="p-category">music</span>
p
| Links:
a(href="mailto:yarmo@yarmo.eu", rel="me", title="Email").u-email Email
| /
a(href="xmpp:yarmo@snopyta.org", rel="me", title="XMPP").u-xmpp XMPP
| /
a(href="https://fosstodon.org/@yarmo", rel="me", title="Fediverse").u-url Fediverse
| /
a(href="https://git.yarmo.eu/yarmo", rel="me", title="Gitea").u-url Gitea
p
| Location:
span.p-country-name The Netherlands
pre(style="display:none").key
include ../9F0048AC0B23301E1F77E994909F6BD6F80F485D.asc
img(src="/static/img/profile.png", alt="Yarmo's profile picture").profile-picture.u-photo
h1
a(href="https://yarmo.eu", rel="me", title="yarmo.eu").u-url.u-uid
span(style="display:none").p-honorific-prefix Mr.
span.p-name
span.p-given-name Yarmo
|
span.p-additional-name Mackenbach
br
p.p-note
| Developer of
a(href="https://keyoxide.org") Keyoxide.org
| .
br
| Finished a PhD in <span class="p-category">neurosciences</span>.
p
| Pronouns:
a(href="https://pronoun.is/he").u-pronoun he/him/his
p
| PGP:
a(href="https://keyoxide.org/9f0048ac0b23301e1f77e994909f6bd6f80f485d", rel="me", title="Keyoxide profile").u-url Keyoxide profile
| /
a(href="/9F0048AC0B23301E1F77E994909F6BD6F80F485D.asc", download="/9F0048AC0B23301E1F77E994909F6BD6F80F485D.asc", rel="pgpkey publickey authn", type="text/plain", title="PGP public key").u-key public.asc
| /
a(href="/static/img/qr_fp.png", title="PGP fingerprint QR") QR
p
| Topics:
| #<span class="p-category">FOSS</span>
| #<span class="p-category">privacy</span>
| #<span class="p-category">encryption</span>
| #<span class="p-category">decentralization</span>
| #<span class="p-category">music</span>
p
| Links:
a(href="mailto:yarmo@yarmo.eu", rel="me", title="Email").u-email Email
| /
a(href="xmpp:yarmo@snopyta.org", rel="me", title="XMPP").u-xmpp XMPP
| /
a(href="https://fosstodon.org/@yarmo", rel="me", title="Fediverse").u-url Fediverse
| /
a(href="https://git.yarmo.eu/yarmo", rel="me", title="Gitea").u-url Gitea
p
| Location:
span.p-country-name The Netherlands
pre(style="display:none").key
include ../keys/9F0048AC0B23301E1F77E994909F6BD6F80F485D.asc

View File

@ -1,38 +0,0 @@
extends layout
mixin entry($item)
article.longform_list__item.h-entry
p
a(href="{$item['urlrel']}").p-name.u-url !{$item['title']}
br
| Posted on
time(datetime="{$item['date']}").dt-published !{$item['date_formatted']}
block content
.h-card
header
nav
a(href="/about") about me
| &gt; blog
h1
| Yarmo's blog
nav
| Go to:
a(href="/about") about me
| |
a(href="/notes") notes
| |
a(href="/blogroll") blogroll
| |
a(href="/feeds") feeds
| |
a(href="/contact") contact
main
div(style="display:none")
include id
.longform_list.h-feed
each $item in $posts
+entry($item)

View File

@ -1,37 +0,0 @@
html
head
meta(http-equiv="Content-Type", content="text/html;charset=UTF-8")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
meta(name="theme-color", content="#4ab4ab")
meta(http-equiv="X-UA-Compatible", content="ie=edge")
title #{$title}
link(rel="stylesheet", href="/static/dank-mono.css")
style
dyninclude "static/norm.css"
style
dyninclude "static/style.css"
link(rel="shortcut icon", href="/favicon.png")
link(rel="alternate", href="/rss/all", title="RSS blog feed for yarmo.eu", type="application/rss+xml")
link(rel="webmention", href="https://webm.yarmo.eu/receive")
script(asyc, defer, data-domain="yarmo.eu", src="https://plausible.io/js/plausible.js")
body
.container
block content
footer
p
| Ads: no.
br
| Analytics:
a(href="https://plausible.io") Plausible
|.
br
| Open source:
a(href="https://git.yarmo.eu/yarmo/yarmo.eu") source code
| and
a(href="https://drone.yarmo.eu/yarmo/yarmo.eu/") CI/CD pipelines
|.
br
| All content licensed under
a(href="https://creativecommons.org/licenses/by-nc-sa/4.0/") CC BY-NC-SA 4.0
| unless otherwise stated.

View File

@ -1,50 +1,50 @@
extends layout
extends templates/main
block content
header
nav
a(href="/about") about me
| &gt; music
h1 Yarmo's music
p
| Go to:
a(href="/vinyl") vinyl
| |
a(href="/aotw") Album Of The Week
header
h1 Yarmo's music
nav
| &gt;&gt;
a(href="/about") about me
| &gt;
a(href="/music") music
nav
| Go to:
a(href="/vinyl") vinyl
main
h3 Instruments
ul
li Piano / keyboards
li Organs
li Synths
main
h3 Instruments
ul
li Piano / keyboards
li Organs
li Synths
h3 Releases
.wrapper-table
table.music
thead
tr
th Years
th Band
th Title
tbody
tr
td 2013
td Sir Jupiter
td
a(href="https://song.link/album/nl/i/979132997") Sir Jupiter
tr
td 2015
td Sir Jupiter
td
a(href="https://song.link/album/nl/i/1041392608") EP Roots
tr
td 2016
td Sir Jupiter
td
a(href="https://song.link/album/nl/i/1151887625") EP Wings
tr
td 2018
td Sir Jupiter
td
a(href="https://song.link/album/nl/i/1454734101") EP Covered In Snow
h3 Releases
.wrapper-table
table.music
thead
tr
th Years
th Band
th Title
tbody
tr
td 2013
td Sir Jupiter
td
a(href="https://song.link/album/nl/i/979132997") Sir Jupiter
tr
td 2015
td Sir Jupiter
td
a(href="https://song.link/album/nl/i/1041392608") EP Roots
tr
td 2016
td Sir Jupiter
td
a(href="https://song.link/album/nl/i/1151887625") EP Wings
tr
td 2018
td Sir Jupiter
td
a(href="https://song.link/album/nl/i/1454734101") EP Covered In Snow

View File

@ -1,21 +1,33 @@
extends layout
extends templates/main
mixin entry($item)
article.longform_list__item.h-entry
p
a(href="{$item['urlrel']}").p-nameu-url !{$item['title']}
br
| Posted on
time(datetime="{$item['date']}").dt-published !{$item['date_formatted']}
mixin entry(item)
article.longform_list__item.h-entry
p
a(href=item['urlrel']).p-nameu-url !{item['title']}
br
| Posted on
time(datetime=item['date']).dt-published !{item['date_formatted']}
block content
header
nav
a(href="/about") about me
| &gt; notes
h1 Yarmo's notes
main
.longform_list
each $item in $posts
+entry($item)
header
//nav
a(href="/about") about me
| &gt; notes
h1 Yarmo's notes
nav
| &gt;&gt;
a(href="/notes") notes
nav
| Go to:
a(href="/about") about me
| |
a(href="/") blog
| |
a(href="/feeds") feeds
| |
a(href="/contact") contact
main
.longform_list
each item in notes
+entry(item)

View File

@ -1,24 +1,26 @@
extends layout
extends templates/main
block content
header
nav
a(href="/about") about me
| &gt; now
h1
| Now
main
h3 Working on
ul
li
a(href="https://keyoxide.org") Keyoxide.org
li finding a job
li finishing a scientific publication
li numerous FOSS projects
li new music
h3 Enjoying
ul
li room renovations
li modular synthesis
header
h1
| Now
nav
| &gt;&gt;
a(href="/about") about me
| &gt;
a(href="/now") now
main
h3 Working on
ul
li
a(href="https://keyoxide.org") Keyoxide.org
li finding a job
li finishing a scientific publication
li numerous FOSS projects
li new music
h3 Enjoying
ul
li room renovations
li modular synthesis

View File

@ -1,28 +1,30 @@
extends layout
extends templates/main
block content
header
nav
a(href="/about") about me
| &gt; pgp
h1
| PGP public key
header
h1
| PGP public key
nav
| &gt;&gt;
a(href="/about") about me
| &gt;
a(href="/pgp") pgp
main
h3#pgp-fingerprint Fingerprint
p 9f0048ac0b23301e1f77e994909f6bd6f80f485d
main
h3#pgp-fingerprint Fingerprint
p 9f0048ac0b23301e1f77e994909f6bd6f80f485d
h3 Key servers
p
a(href="https://keys.openpgp.org/") OpenPGP key server
h3 Files
p
a(href="/9F0048AC0B23301E1F77E994909F6BD6F80F485D.pgp", download="/9F0048AC0B23301E1F77E994909F6BD6F80F485D.pgp", rel="pgpkey", type="application/pgp-keys", title="PGP (binary)") Binary format
br
a(href="/9F0048AC0B23301E1F77E994909F6BD6F80F485D.asc", download="/9F0048AC0B23301E1F77E994909F6BD6F80F485D.asc", rel="pgpkey", type="text/plain", title="PGP (text)") Text format
h3#pgp-public-key Plaintext
pre.select-all
code
include ../9F0048AC0B23301E1F77E994909F6BD6F80F485D.asc
h3 Key servers
p
a(href="https://keys.openpgp.org/") OpenPGP key server
h3 Files
p
a(href="/9F0048AC0B23301E1F77E994909F6BD6F80F485D.pgp", download="/9F0048AC0B23301E1F77E994909F6BD6F80F485D.pgp", rel="pgpkey", type="application/pgp-keys", title="PGP (binary)") Binary format
br
a(href="/9F0048AC0B23301E1F77E994909F6BD6F80F485D.asc", download="/9F0048AC0B23301E1F77E994909F6BD6F80F485D.asc", rel="pgpkey", type="text/plain", title="PGP (text)") Text format
h3#pgp-public-key Plaintext
pre.select-all
code
include ../keys/9F0048AC0B23301E1F77E994909F6BD6F80F485D.asc

View File

@ -1,72 +1,72 @@
extends layout
extends templates/main
mixin webmention($item)
if (!array_key_exists('type', $item))
p
if (array_key_exists('title', $item))
a(href="{$item['source']}") !{$item['title']}
else
a(href="{$item['source']}") !{$item['source']}
if (array_key_exists('author_name', $item))
| by !{$item['author_name']}
if (array_key_exists('date', $item))
| on !{$item['date']}
if (array_key_exists('time', $item))
| at !{$item['time']} UTC
else if ($item['type'] == "comment")
.comment
p.quote
if (array_key_exists('title', $item))
strong !{$item['title']}
br
if (array_key_exists('content', $item))
| !{$item['content']}
p.sub
a(href="{$item['source']}") Commented
if (array_key_exists('author_name', $item))
| by !{$item['author_name']}
if (array_key_exists('date', $item))
| on !{$item['date']}
if (array_key_exists('time', $item))
| at !{$item['time']} UTC
mixin discussionLink($item)
mixin webmention(item)
if (!('type' in item))
p
a(href="{$item}") !{$item}
if ('title' in item)
a(href=item.source) !{item.title}
else
a(href=item.source) !{item.source}
if ('author_name' in item)
| by !{item.author_name}
if ('date' in item)
| on !{item.date}
if ('time' in item)
| at !{item.time}
else if (item.type == "comment")
.comment
p.quote
if ('title' in item)
strong !{item.title}
br
if ('content' in item)
| !{item.content}
p.sub
a(href=item.source) Commented
if ('author_name' in item)
| by !{item.author_name}
if ('date' in item)
| on !{item.date}
if ('time' in item)
| at !{item.time} UTC
mixin discussionLink(item)
p
a(href=item) !{item}
block content
header
nav
a(href="/about") about me
| &gt;
if ($post["type"] == "blog")
a(href="/") blog
if ($post["type"] == "note")
a(href="/notes") notes
| &gt; !{$post['slug']}
main
article.longform.h-entry
h1.p-name !{$post['title']}
p.longform__header
| Posted on
a(href="{$post['url']}" datetime="{$post['date']}").u-url.dt-published !{$post['date_formatted']}
| by
a(href="/" rel="author").p-author.h-card !{$post['author']}
.longform__content.e-content
| !{$post['content']}
header
nav
| &gt;&gt;
if post.type == 'blog'
a(href="/") blog
if post.type == 'note'
a(href="/notes") notes
| &gt;
a(href=post.url)= post.slug
main
article.longform.h-entry
h1.p-name !{post.title}
p.longform__header
| Posted on
a(href=post.url datetime="{post.date}").u-url.dt-published !{post.date_formatted}
| by
a(href="/" rel="author").p-author.h-card !{post.author}
.longform__content.e-content
| !{post.content}
if ('discussion' in post && post.discussion)
.discussion.subsection
h2 Join the discussion
each item in post.discussion
+discussionLink(item)
if (array_key_exists('discussion', $post) && isset($post['discussion']))
.discussion.subsection
h2 Join the discussion
each $item in $post["discussion"]
+discussionLink($item)
.webmentions.subsection
h2 Webmentions
if ($post['hasWebmentions'])
each $item in $post["webmentions"]
+webmention($item)
else
p This post has not been mentioned yet.
.webmentions.subsection
h2 Webmentions
if post.hasWebmentions
each item in post.webmentions
+webmention(item)
else
p This post has not been mentioned yet.

View File

@ -1,20 +1,20 @@
extends layout
extends templates/main
mixin entry($item)
article.longform_list__item
p
a(href="{$item['urlrel']}") !{$item['title']}
br
| Status: !{$item['status']}
article.longform_list__item
p
a(href="{$item['urlrel']}") !{$item['title']}
br
| Status: !{$item['status']}
block content
header
nav
a(href="/about") about me
| &gt; projects
h1 Yarmo's projects
main
.longform_list
each $item in $projects
+entry($item)
header
nav
a(href="/about") about me
| &gt; projects
h1 Yarmo's projects
main
.longform_list
each $item in $projects
+entry($item)

View File

@ -1,32 +1,32 @@
extends layout
extends templates/main
mixin webmention($item)
p
a(href="{$item['source']}") !{$item['title']}
| by !{$item['author_name']} on !{$item['date']} at !{$item['time']}
p
a(href="{$item['source']}") !{$item['title']}
| by !{$item['author_name']} on !{$item['date']} at !{$item['time']}
block content
header
nav
a(href="/about") about me
| &gt;
a(href="/projects") projects
| &gt; !{$project['slug']}
main
article.longform
h1 !{$project['title']}
p
| Got an idea or a comment?
a(href="/contact") Feel free to contact me!
p.longform__header
.longform__content
| !{$project['content']}
.webmentions.subsection
h2 Webmentions
if ($project['hasWebmentions'])
each $item in $project["webmentions"]
+webmention($item)
else
p This project has not been mentioned yet.
header
nav
a(href="/about") about me
| &gt;
a(href="/projects") projects
| &gt; !{$project['slug']}
main
article.longform
h1 !{$project['title']}
p
| Got an idea or a comment?
a(href="/contact") Feel free to contact me!
p.longform__header
.longform__content
| !{$project['content']}
.webmentions.subsection
h2 Webmentions
if ($project['hasWebmentions'])
each $item in $project["webmentions"]
+webmention($item)
else
p This project has not been mentioned yet.

View File

@ -1,61 +1,61 @@
extends layout
extends templates/main
mixin webmention($item)
if (!array_key_exists('type', $item))
p
if (array_key_exists('title', $item))
a(href="{$item['source']}") !{$item['title']}
else
a(href="{$item['source']}") !{$item['source']}
if (array_key_exists('author_name', $item))
| by !{$item['author_name']}
if (array_key_exists('date', $item))
| on !{$item['date']}
if (array_key_exists('time', $item))
| at !{$item['time']} UTC
else if ($item['type'] == "comment")
.comment
p.quote
if (array_key_exists('title', $item))
strong !{$item['title']}
br
if (array_key_exists('content', $item))
| !{$item['content']}
p.sub
a(href="{$item['source']}") Commented
if (array_key_exists('author_name', $item))
| by !{$item['author_name']}
if (array_key_exists('date', $item))
| on !{$item['date']}
if (array_key_exists('time', $item))
| at !{$item['time']} UTC
if (!array_key_exists('type', $item))
p
if (array_key_exists('title', $item))
a(href="{$item['source']}") !{$item['title']}
else
a(href="{$item['source']}") !{$item['source']}
if (array_key_exists('author_name', $item))
| by !{$item['author_name']}
if (array_key_exists('date', $item))
| on !{$item['date']}
if (array_key_exists('time', $item))
| at !{$item['time']} UTC
else if ($item['type'] == "comment")
.comment
p.quote
if (array_key_exists('title', $item))
strong !{$item['title']}
br
if (array_key_exists('content', $item))
| !{$item['content']}
p.sub
a(href="{$item['source']}") Commented
if (array_key_exists('author_name', $item))
| by !{$item['author_name']}
if (array_key_exists('date', $item))
| on !{$item['date']}
if (array_key_exists('time', $item))
| at !{$item['time']} UTC
block content
header
nav
a(href="/about") about me
| &gt; !{$reply['slug']}
main
article.longform.h-entry
h1.p-name !{$reply['title']}
p.longform__header
| Posted on
a(href="{$reply['url']}" datetime="{$reply['date']}").u-url.dt-published !{$reply['date_formatted']}
| by
a(href="/" rel="author").p-author.h-card !{$reply['author']}
.longform__content.e-content
| Reply to
a(href="{$reply['reply-url']}").in-reply-to !{$reply['reply-title']}
| :
br
| !{$reply['content']}
.webmentions.subsection
h2 Webmentions
if ($reply['hasWebmentions'])
each $item in $reply["webmentions"]
+webmention($item)
else
p This post has not been mentioned yet.
header
nav
a(href="/about") about me
| &gt; !{$reply['slug']}
main
article.longform.h-entry
h1.p-name !{$reply['title']}
p.longform__header
| Posted on
a(href="{$reply['url']}" datetime="{$reply['date']}").u-url.dt-published !{$reply['date_formatted']}
| by
a(href="/" rel="author").p-author.h-card !{$reply['author']}
.longform__content.e-content
| Reply to
a(href="{$reply['reply-url']}").in-reply-to !{$reply['reply-title']}
| :
br
| !{$reply['content']}
.webmentions.subsection
h2 Webmentions
if ($reply['hasWebmentions'])
each $item in $reply["webmentions"]
+webmention($item)
else
p This post has not been mentioned yet.

37
views/templates/main.pug Normal file
View File

@ -0,0 +1,37 @@
html
head
meta(http-equiv="Content-Type", content="text/html;charset=UTF-8")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
meta(name="theme-color", content="#4ab4ab")
meta(http-equiv="X-UA-Compatible", content="ie=edge")
title #{title}
link(rel="stylesheet", href="/static/dank-mono.css")
link(rel="stylesheet", href="/static/norm.css")
link(rel="stylesheet", href="/static/style.css")
link(rel="shortcut icon", href="/favicon.png")
link(rel="alternate", href="/feed/all", title="RSS blog feed for yarmo.eu", type="application/rss+xml")
link(rel="alternate", href="/feed/all.atom", title="RSS blog feed for yarmo.eu", type="application/atom+xml")
link(rel="alternate", href="/feed/all.json", title="RSS blog feed for yarmo.eu", type="application/feed+json")
link(rel="webmention", href="https://webm.yarmo.eu/receive")
script(asyc, defer, data-domain="yarmo.eu", src="https://plausible.io/js/plausible.js")
body
.container
block content
footer
p
| Ads: no.
br
| Analytics:
a(href="https://plausible.io") Plausible
|.
br
| Open source:
a(href="https://git.yarmo.eu/yarmo/yarmo.eu") source code
| and
a(href="https://drone.yarmo.eu/yarmo/yarmo.eu/") CI/CD pipelines
|.
br
| All content licensed under
a(href="https://creativecommons.org/licenses/by-nc-sa/4.0/") CC BY-NC-SA 4.0
| unless otherwise stated.

View File

@ -1,112 +1,114 @@
extends layout
extends templates/main
block content
header
nav
a(href="/about") about me
| &gt; uses
h1
| Uses
header
h1
| Uses
nav
| &gt;&gt;
a(href="/about") about me
| &gt;
a(href="/uses") uses
main
h2 Hardware
main
h2 Hardware
ul
li Main dev computer
ul
li MODEL: custom built
li CPU: AMD Ryzen 5 3600
li GPU: AMD RX580
li RAM: 16GB
li OS: Solus &amp; W10
li Homelab
ul
li MODEL: Intel NUC8i5BEK
li CPU: Intel Core i5-8259U
li RAM: 16GB
li OS: Ubuntu 18.04
li Laptop
ul
li MODEL: Lenovo Thinkpad x201i
li CPU: Intel Core i3-330M
li OS: Solus
li Phone
ul
li MODEL: OnePlus 5T
li OS: LineageOS
li Network
ul
li
a(href="https://www.asus.com/Networking/Blue-Cave/") ASUS Blue Cave
li HiFi
ul
li
a(href="https://www.project-audio.com/en/product/primary-e/") Pro-Ject Primary E
li
a(href="https://www.wharfedale.co.uk/diamond-11-2/") Wharfedale Diamond 11.2
li Music
ul
li
a(href="https://usa.yamaha.com/products/proaudio/speakers/hs_series/index.html") Yamaha HS8
li
a(href="https://www.zoom.co.jp/products/multi-track-recorder/r16-recorder-interface-controller") Zoom R16
li
a(href="https://www.nordkeyboards.com/products/nord-electro-5") Nord Electro 5D
li
a(href="https://www.arturia.com/microbrute-se/overview") Arturia Minibrute SE Pastel Orange
li
a(href="https://www.arturia.com/drumbrute/overview") Arturia DrumBrute
li Custom modular synth
ul
li Main dev computer
ul
li MODEL: custom built
li CPU: AMD Ryzen 5 3600
li GPU: AMD RX580
li RAM: 16GB
li OS: Solus &amp; W10
li Homelab
ul
li MODEL: Intel NUC8i5BEK
li CPU: Intel Core i5-8259U
li RAM: 16GB
li OS: Ubuntu 18.04
li Laptop
ul
li MODEL: Lenovo Thinkpad x201i
li CPU: Intel Core i3-330M
li OS: Solus
li Phone
ul
li MODEL: OnePlus 5T
li OS: LineageOS
li Network
ul
li
a(href="https://www.asus.com/Networking/Blue-Cave/") ASUS Blue Cave
li HiFi
ul
li
a(href="https://www.project-audio.com/en/product/primary-e/") Pro-Ject Primary E
li
a(href="https://www.wharfedale.co.uk/diamond-11-2/") Wharfedale Diamond 11.2
li Music
ul
li
a(href="https://usa.yamaha.com/products/proaudio/speakers/hs_series/index.html") Yamaha HS8
li
a(href="https://www.zoom.co.jp/products/multi-track-recorder/r16-recorder-interface-controller") Zoom R16
li
a(href="https://www.nordkeyboards.com/products/nord-electro-5") Nord Electro 5D
li
a(href="https://www.arturia.com/microbrute-se/overview") Arturia Minibrute SE Pastel Orange
li
a(href="https://www.arturia.com/drumbrute/overview") Arturia DrumBrute
li Custom modular synth
ul
li VCO:
a(href="https://www.roland.com/global/products/system-500_512/") Roland System-500 512
li VCF-VCA:
a(href="https://www.dreadbox-fx.com/eudemonia/") Dreadbox Eudemonia
li LFO-MOD:
a(href="https://www.dreadbox-fx.com/ataxia/") Dreadbox Ataxia
li CV PROC:
a(href="https://www.dreadbox-fx.com/utopia/") Dreadbox Utopia
li NOISE:
a(href="https://www.dreadbox-fx.com/dystopia/") Dreadbox Dystopia
li DELAY:
a(href="https://www.dreadbox-fx.com/nostalgia/") Dreadbox Nostalgia
li LFO:
a(href="http://www.doepfer.de/a146.htm") Doepfer A146
li CLK DIVIDER:
a(href="http://www.doepfer.de/a160.htm") Doepfer A160-1
li RM-S&amp;H-SLEW:
a(href="http://www.doepfer.de/a1841.htm") Doepfer A184-1
h2 Software
ul
li General
ul
li
a(href="https://pandoc.org/") Pandoc
li Dev
ul
li
a(href="https://atom.io/") Atom
li nano
li Homelab &amp; selfhosting
ul
li
a(href="https://www.docker.com/") Docker
li
a(href="https://containo.us/traefik/") Traefik v2
li
a(href="https://caddyserver.com/") Caddy v2
li
a(href="https://www.plex.tv/") Plex
li
a(href="https://mailcow.email/") Mailcow
li Science
ul
li
a(href="https://www.mathworks.com/products/matlab.html") Matlab R2010B
li Music
ul
li
a(href="https://new.steinberg.net/cubase/") Cubase
li VCO:
a(href="https://www.roland.com/global/products/system-500_512/") Roland System-500 512
li VCF-VCA:
a(href="https://www.dreadbox-fx.com/eudemonia/") Dreadbox Eudemonia
li LFO-MOD:
a(href="https://www.dreadbox-fx.com/ataxia/") Dreadbox Ataxia
li CV PROC:
a(href="https://www.dreadbox-fx.com/utopia/") Dreadbox Utopia
li NOISE:
a(href="https://www.dreadbox-fx.com/dystopia/") Dreadbox Dystopia
li DELAY:
a(href="https://www.dreadbox-fx.com/nostalgia/") Dreadbox Nostalgia
li LFO:
a(href="http://www.doepfer.de/a146.htm") Doepfer A146
li CLK DIVIDER:
a(href="http://www.doepfer.de/a160.htm") Doepfer A160-1
li RM-S&amp;H-SLEW:
a(href="http://www.doepfer.de/a1841.htm") Doepfer A184-1
h2 Software
ul
li General
ul
li
a(href="https://pandoc.org/") Pandoc
li Dev
ul
li
a(href="https://atom.io/") Atom
li nano
li Homelab &amp; selfhosting
ul
li
a(href="https://www.docker.com/") Docker
li
a(href="https://containo.us/traefik/") Traefik v2
li
a(href="https://caddyserver.com/") Caddy v2
li
a(href="https://www.plex.tv/") Plex
li
a(href="https://mailcow.email/") Mailcow
li Science
ul
li
a(href="https://www.mathworks.com/products/matlab.html") Matlab R2010B
li Music
ul
li
a(href="https://new.steinberg.net/cubase/") Cubase

View File

@ -1,19 +1,21 @@
extends layout
extends templates/main
mixin entry($item)
.list__item
p !{$item['artist']} - !{$item['title']} (!{$item['year']})
mixin entry(item)
.list__item
p !{item['artist']} - !{item['title']} (!{item['year']})
block content
header
nav
a(href="/about") about me
| &gt;
a(href="/music") music
| &gt; vinyl
h1 Yarmo's vinyl collection
header
h1 Yarmo's vinyl collection
nav
| &gt;&gt;
a(href="/about") about me
| &gt;
a(href="/music") music
| &gt;
a(href="/vinyl") vinyl
main
.list
each $item in $albums
+entry($item)
main
.list
each item in vinyl.albums
+entrysitem)

37
views/work.pug Normal file
View File

@ -0,0 +1,37 @@
extends templates/main
block content
header
h1
| Work
nav
| &gt;&gt;
a(href="/about") about me
| &gt;
a(href="/work") work
main
h2 &gt;&gt; Open Source developer
h3 Projects
p
| Currently working on
a(href="https://keyoxide.org") Keyoxide
| .
h3 VCS accounts
.wrapper-table
table
tbody
tr
td Codeberg
td
a(href="https://codeberg.org/yarmo" rel="me") @yarmo
tr
td GitLab
td
a(href="https://gitlab.com/yarmo" rel="me") @yarmo
tr
td Github
td
a(href="https://github.com/YarmoM" rel="me") @YarmoM