1
0
Fork 0

Compare commits

...

10 commits

Author SHA1 Message Date
Daniel Kempkens 781c947905
Add footer 2023-01-17 16:46:23 +01:00
Daniel Kempkens 9a1e3233dc Cloudflare Pages 2022-05-27 19:36:06 +02:00
Daniel Kempkens d58e00ce74 Build Docker image after each push 2021-08-28 21:51:45 +02:00
Daniel Kempkens 3b4f2f6312 Move to hugo 2021-08-28 21:50:49 +02:00
Daniel Kempkens 7e9da6e61a Updates 2018-05-10 13:00:09 +02:00
Daniel Kempkens f3c1653f3f Remove analytics and comments 2018-05-10 12:57:57 +02:00
Daniel Kempkens b9614073d3 Brotli compression 2016-02-29 14:54:13 +01:00
Daniel Kempkens e8149c9dad HAProxy post 2016-01-31 17:46:05 +01:00
Daniel Kempkens 0eaf6d2445 Typo 2015-08-03 22:17:27 +02:00
Daniel Kempkens 923ed0ccbc Update OCSP post 2015-08-03 20:55:15 +02:00
64 changed files with 472 additions and 1776 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

43
.github/workflows/build-image.yml vendored Normal file
View file

@ -0,0 +1,43 @@
name: Build Image
on:
push:
branches: ['master']
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
submodules: recursive
- name: Log in to the Container registry
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v3
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push Docker image
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

7
.gitignore vendored
View file

@ -1,7 +1,8 @@
public/
resources/
.DS_Store .DS_Store
_site
.jekyll-assets-cache
_drafts
*~ *~
*.swp *.swp
*.swo *.swo
.hugo_build.lock

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "themes/terminal"]
path = themes/terminal
url = https://github.com/panr/hugo-theme-terminal.git

View file

@ -1,9 +0,0 @@
---
layout: default
title: "404: Page not found"
---
<div class="page">
<h1 class="page-title">404: Page not found</h1>
<p class="lead">Sorry, we've misplaced that URL or it's pointing to something that doesn't exist. <a href="{{ site.baseurl }}">Head back home</a> to try finding it again.</p>
</div>

5
Dockerfile Normal file
View file

@ -0,0 +1,5 @@
FROM klakegg/hugo:ext-alpine-onbuild AS hugo
FROM nginx:mainline-alpine
COPY --from=hugo /target /usr/share/nginx/html

View file

@ -1,8 +0,0 @@
source 'https://rubygems.org'
gem 'jekyll', '~> 2.5.3'
group :extensions do
gem 'jekyll-assets', '~> 0.14.0'
gem 'yui-compressor', '~> 0.12.0'
end

View file

@ -1,99 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.3.8)
blankslate (2.1.2.4)
celluloid (0.16.0)
timers (~> 4.0.0)
classifier-reborn (2.0.3)
fast-stemmer (~> 1.0)
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.9.1.1)
colorator (0.1)
execjs (2.5.2)
fast-stemmer (1.0.2)
fastimage (1.7.0)
addressable (~> 2.3, >= 2.3.5)
ffi (1.9.10)
hike (1.2.3)
hitimes (1.2.2)
jekyll (2.5.3)
classifier-reborn (~> 2.0)
colorator (~> 0.1)
jekyll-coffeescript (~> 1.0)
jekyll-gist (~> 1.0)
jekyll-paginate (~> 1.0)
jekyll-sass-converter (~> 1.0)
jekyll-watch (~> 1.1)
kramdown (~> 1.3)
liquid (~> 2.6.1)
mercenary (~> 0.3.3)
pygments.rb (~> 0.6.0)
redcarpet (~> 3.1)
safe_yaml (~> 1.0)
toml (~> 0.1.0)
jekyll-assets (0.14.0)
fastimage (~> 1.6)
jekyll (~> 2.0)
mini_magick (~> 4.1)
sass (~> 3.2)
sprockets (~> 2.10)
sprockets-helpers
sprockets-sass
jekyll-coffeescript (1.0.1)
coffee-script (~> 2.2)
jekyll-gist (1.2.1)
jekyll-paginate (1.1.0)
jekyll-sass-converter (1.3.0)
sass (~> 3.2)
jekyll-watch (1.2.1)
listen (~> 2.7)
kramdown (1.8.0)
liquid (2.6.2)
listen (2.10.1)
celluloid (~> 0.16.0)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9)
mercenary (0.3.5)
mini_magick (4.2.7)
multi_json (1.11.2)
parslet (1.5.0)
blankslate (~> 2.0)
posix-spawn (0.3.11)
pygments.rb (0.6.3)
posix-spawn (~> 0.3.6)
yajl-ruby (~> 1.2.0)
rack (1.6.4)
rb-fsevent (0.9.5)
rb-inotify (0.9.5)
ffi (>= 0.5.0)
redcarpet (3.3.2)
safe_yaml (1.0.4)
sass (3.4.16)
sprockets (2.12.4)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sprockets-helpers (1.2.1)
sprockets (>= 2.2)
sprockets-sass (1.3.1)
sprockets (~> 2.0)
tilt (~> 1.1)
tilt (1.4.1)
timers (4.0.1)
hitimes
toml (0.1.2)
parslet (~> 1.5.0)
yajl-ruby (1.2.1)
yui-compressor (0.12.0)
PLATFORMS
ruby
DEPENDENCIES
jekyll (~> 2.5.3)
jekyll-assets (~> 0.14.0)
yui-compressor (~> 0.12.0)

View file

@ -1,29 +0,0 @@
all: compile compress upload superfeedr
compile: clean
@echo "=== Generating static files"
@bundle exec jekyll build --lsi
@echo "Done."
compress:
@echo "=== Compressing generated files"
@find ./_site -type f | xargs zopfli --gzip --i30
@echo "Done."
upload:
@echo "=== Syncing files"
@rsync -avz --no-o --no-g -e ssh --chmod=og=r -p --delete _site/ kempkens.io:/iocage/jails/506fd9f8-15c0-11e5-adf5-477a0b920463/root/var/www/blog
@echo "Done."
@echo "=== Changing permissions"
@ssh kempkens.io chmod 755 /iocage/jails/506fd9f8-15c0-11e5-adf5-477a0b920463/root/var/www/blog
@ssh kempkens.io find /iocage/jails/506fd9f8-15c0-11e5-adf5-477a0b920463/root/var/www/blog -type d -exec chmod 755 {} +
@echo "Done."
superfeedr:
@echo "=== Notifying Superfeedr"
@curl -X POST https://kempkens.superfeedr.com -d "hub.mode=publish" -d "hub.url=https://blog.kempkens.io/feed.xml"
@curl -X POST https://kempkens.superfeedr.com -d "hub.mode=publish" -d "hub.url=https://blog.kempkens.io/feed-with-links.xml"
@echo "Done."
clean:
@rm -rf ./_site

View file

@ -1,5 +0,0 @@
/*
*= require poole
*= require syntax
*= require hyde
*/

View file

@ -1,250 +0,0 @@
/*
* __ __
* /\ \ /\ \
* \ \ \___ __ __ \_\ \ __
* \ \ _ `\/\ \/\ \ /'_` \ /'__`\
* \ \ \ \ \ \ \_\ \/\ \_\ \/\ __/
* \ \_\ \_\/`____ \ \___,_\ \____\
* \/_/\/_/`/___/> \/__,_ /\/____/
* /\___/
* \/__/
*
* Designed, built, and released under MIT license by @mdo. Learn more at
* https://github.com/poole/hyde.
*/
/*
* Contents
*
* Global resets
* Sidebar
* Container
* Reverse layout
* Themes
*/
/*
* Global resets
*
* Update the foundational and global aspects of the page.
*/
html {
font-family: "PT Sans", Helvetica, Arial, sans-serif;
}
@media (min-width: 48em) {
html {
font-size: 16px;
}
}
@media (min-width: 58em) {
html {
font-size: 20px;
}
}
/*
* Sidebar
*
* Flexible banner for housing site name, intro, and "footer" content. Starts
* out above content in mobile and later moves to the side with wider viewports.
*/
.sidebar {
text-align: center;
padding: 2rem 1rem;
color: rgba(255,255,255,.5);
background-color: #202020;
}
@media (min-width: 48em) {
.sidebar {
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: 18rem;
text-align: left;
}
}
/* Sidebar links */
.sidebar a {
color: #fff;
}
/* About section */
.sidebar-about h1 {
color: #fff;
margin-top: 0;
font-family: "Abril Fatface", serif;
font-size: 3.25rem;
}
/* Sidebar nav */
.sidebar-nav {
margin-bottom: 1rem;
}
.sidebar-nav-item {
display: block;
line-height: 1.75;
}
a.sidebar-nav-item:hover,
a.sidebar-nav-item:focus {
text-decoration: underline;
}
.sidebar-nav-item.active {
font-weight: bold;
}
/* Sticky sidebar
*
* Add the `sidebar-sticky` class to the sidebar's container to affix it the
* contents to the bottom of the sidebar in tablets and up.
*/
@media (min-width: 48em) {
.sidebar-sticky {
position: absolute;
right: 1rem;
bottom: 1rem;
left: 1rem;
}
}
/* Container
*
* Align the contents of the site above the proper threshold with some margin-fu
* with a 25%-wide `.sidebar`.
*/
.content {
padding-top: 4rem;
padding-bottom: 4rem;
}
@media (min-width: 48em) {
.content {
max-width: 38rem;
margin-left: 20rem;
margin-right: 2rem;
}
}
@media (min-width: 64em) {
.content {
margin-left: 22rem;
margin-right: 4rem;
}
}
/*
* Reverse layout
*
* Flip the orientation of the page by placing the `.sidebar` on the right.
*/
@media (min-width: 48em) {
.layout-reverse .sidebar {
left: auto;
right: 0;
}
.layout-reverse .content {
margin-left: 2rem;
margin-right: 20rem;
}
}
@media (min-width: 64em) {
.layout-reverse .content {
margin-left: 4rem;
margin-right: 22rem;
}
}
/*
* Themes
*
* As of v1.1, Hyde includes optional themes to color the sidebar and links
* within blog posts. To use, add the class of your choosing to the `body`.
*/
/* Base16 (http://chriskempson.github.io/base16/#default) */
/* Red */
.theme-base-08 .sidebar {
background-color: #ac4142;
}
.theme-base-08 .content a,
.theme-base-08 .related-posts li a:hover {
color: #ac4142;
}
/* Orange */
.theme-base-09 .sidebar {
background-color: #d28445;
}
.theme-base-09 .content a,
.theme-base-09 .related-posts li a:hover {
color: #d28445;
}
/* Yellow */
.theme-base-0a .sidebar {
background-color: #f4bf75;
}
.theme-base-0a .content a,
.theme-base-0a .related-posts li a:hover {
color: #f4bf75;
}
/* Green */
.theme-base-0b .sidebar {
background-color: #90a959;
}
.theme-base-0b .content a,
.theme-base-0b .related-posts li a:hover {
color: #90a959;
}
/* Cyan */
.theme-base-0c .sidebar {
background-color: #75b5aa;
}
.theme-base-0c .content a,
.theme-base-0c .related-posts li a:hover {
color: #75b5aa;
}
/* Blue */
.theme-base-0d .sidebar {
background-color: #6a9fb5;
}
.theme-base-0d .content a,
.theme-base-0d .related-posts li a:hover {
color: #6a9fb5;
}
/* Magenta */
.theme-base-0e .sidebar {
background-color: #aa759f;
}
.theme-base-0e .content a,
.theme-base-0e .related-posts li a:hover {
color: #aa759f;
}
/* Brown */
.theme-base-0f .sidebar {
background-color: #8f5536;
}
.theme-base-0f .content a,
.theme-base-0f .related-posts li a:hover {
color: #8f5536;
}

View file

@ -1,465 +0,0 @@
/*
* ___
* /\_ \
* _____ ___ ___\//\ \ __
* /\ '__`\ / __`\ / __`\\ \ \ /'__`\
* \ \ \_\ \/\ \_\ \/\ \_\ \\_\ \_/\ __/
* \ \ ,__/\ \____/\ \____//\____\ \____\
* \ \ \/ \/___/ \/___/ \/____/\/____/
* \ \_\
* \/_/
*
* Designed, built, and released under MIT license by @mdo. Learn more at
* https://github.com/poole/poole.
*/
/*
* Contents
*
* Body resets
* Custom type
* Messages
* Container
* Masthead
* Posts and pages
* Pagination
* Reverse layout
* Themes
*/
/*
* Body resets
*
* Update the foundational and global aspects of the page.
*/
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
}
html {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 1.5;
}
@media (min-width: 38em) {
html {
font-size: 20px;
}
}
body {
color: #515151;
background-color: #fff;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
/* No `:visited` state is required by default (browsers will use `a`) */
a {
color: #268bd2;
text-decoration: none;
}
a strong {
color: inherit;
}
/* `:focus` is linked to `:hover` for basic accessibility */
a:hover,
a:focus {
text-decoration: underline;
}
/* Headings */
h1, h2, h3, h4, h5, h6 {
margin-bottom: .5rem;
font-weight: bold;
line-height: 1.25;
color: #313131;
text-rendering: optimizeLegibility;
}
h1 {
font-size: 2rem;
}
h2 {
margin-top: 1rem;
font-size: 1.5rem;
}
h3 {
margin-top: 1.5rem;
font-size: 1.25rem;
}
h4, h5, h6 {
margin-top: 1rem;
font-size: 1rem;
}
/* Body text */
p {
margin-top: 0;
margin-bottom: 1rem;
}
strong {
color: #303030;
}
/* Lists */
ul, ol, dl {
margin-top: 0;
margin-bottom: 1rem;
}
dt {
font-weight: bold;
}
dd {
margin-bottom: .5rem;
}
/* Misc */
hr {
position: relative;
margin: 1.5rem 0;
border: 0;
border-top: 1px solid #eee;
border-bottom: 1px solid #fff;
}
abbr {
font-size: 85%;
font-weight: bold;
color: #555;
text-transform: uppercase;
}
abbr[title] {
cursor: help;
border-bottom: 1px dotted #e5e5e5;
}
/* Code */
code,
pre {
font-family: Menlo, Monaco, "Courier New", monospace;
}
code {
padding: .25em .5em;
font-size: 85%;
color: #bf616a;
background-color: #f9f9f9;
border-radius: 3px;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
padding: 1rem;
font-size: .8rem;
line-height: 1.4;
white-space: pre;
white-space: pre-wrap;
word-break: break-all;
word-wrap: break-word;
background-color: #f9f9f9;
}
pre code {
padding: 0;
font-size: 100%;
color: inherit;
background-color: transparent;
}
/* Pygments via Jekyll */
.highlight {
margin-bottom: 1rem;
border-radius: 4px;
}
.highlight pre {
margin-bottom: 0;
}
/* Gist via GitHub Pages */
.gist .gist-file {
font-family: Menlo, Monaco, "Courier New", monospace !important;
}
.gist .markdown-body {
padding: 15px;
}
.gist pre {
padding: 0;
background-color: transparent;
}
.gist .gist-file .gist-data {
font-size: .8rem !important;
line-height: 1.4;
}
.gist code {
padding: 0;
color: inherit;
background-color: transparent;
border-radius: 0;
}
/* Quotes */
blockquote {
padding: .5rem 1rem;
margin: .8rem 0;
color: #7a7a7a;
border-left: .25rem solid #e5e5e5;
}
blockquote p:last-child {
margin-bottom: 0;
}
@media (min-width: 30em) {
blockquote {
padding-right: 5rem;
padding-left: 1.25rem;
}
}
img {
display: block;
max-width: 100%;
margin: 0 0 1rem;
border-radius: 5px;
}
/* Tables */
table {
margin-bottom: 1rem;
width: 100%;
border: 1px solid #e5e5e5;
border-collapse: collapse;
}
td,
th {
padding: .25rem .5rem;
border: 1px solid #e5e5e5;
}
tbody tr:nth-child(odd) td,
tbody tr:nth-child(odd) th {
background-color: #f9f9f9;
}
/*
* Custom type
*
* Extend paragraphs with `.lead` for larger introductory text.
*/
.lead {
font-size: 1.25rem;
font-weight: 300;
}
/*
* Messages
*
* Show alert messages to users. You may add it to single elements like a `<p>`,
* or to a parent if there are multiple elements to show.
*/
.message {
margin-bottom: 1rem;
padding: 1rem;
color: #717171;
background-color: #f9f9f9;
}
/*
* Container
*
* Center the page content.
*/
.container {
max-width: 38rem;
padding-left: 1rem;
padding-right: 1rem;
margin-left: auto;
margin-right: auto;
}
/*
* Masthead
*
* Super small header above the content for site name and short description.
*/
.masthead {
padding-top: 1rem;
padding-bottom: 1rem;
margin-bottom: 3rem;
}
.masthead-title {
margin-top: 0;
margin-bottom: 0;
color: #505050;
}
.masthead-title a {
color: #505050;
}
.masthead-title small {
font-size: 75%;
font-weight: 400;
color: #c0c0c0;
letter-spacing: 0;
}
/*
* Posts and pages
*
* Each post is wrapped in `.post` and is used on default and post layouts. Each
* page is wrapped in `.page` and is only used on the page layout.
*/
.page,
.post {
margin-bottom: 4em;
}
/* Blog post or page title */
.page-title,
.post-title,
.post-title a {
color: #303030;
}
.page-title,
.post-title {
margin-top: 0;
}
/* Meta data line below post title */
.post-date {
display: block;
margin-top: -.5rem;
margin-bottom: 1rem;
color: #9a9a9a;
}
/* Related posts */
.related {
padding-top: 2rem;
padding-bottom: 2rem;
border-top: 1px solid #eee;
}
.related-posts {
padding-left: 0;
list-style: none;
}
.related-posts h3 {
margin-top: 0;
}
.related-posts li small {
font-size: 75%;
color: #999;
}
.related-posts li a:hover {
color: #268bd2;
text-decoration: none;
}
.related-posts li a:hover small {
color: inherit;
}
/*
* Pagination
*
* Super lightweight (HTML-wise) blog pagination. `span`s are provide for when
* there are no more previous or next posts to show.
*/
.pagination {
overflow: hidden; /* clearfix */
margin-left: -1rem;
margin-right: -1rem;
font-family: "PT Sans", Helvetica, Arial, sans-serif;
color: #ccc;
text-align: center;
}
/* Pagination items can be `span`s or `a`s */
.pagination-item {
display: block;
padding: 1rem;
border: 1px solid #eee;
}
.pagination-item:first-child {
margin-bottom: -1px;
}
/* Only provide a hover state for linked pagination items */
a.pagination-item:hover {
background-color: #f5f5f5;
}
/* Custom */
.post-date a {
color: #9a9a9a;
}
.post-date a:hover {
color: #303030;
text-decoration: none;
}
.post-link > span {
font-weight: bold;
}
.footer {
margin-top: 3rem;
font-size: 75%;
}
.footer-links {
margin: 0;
padding: 0;
list-style-type: none;
text-align: center;
}
.footer-links li {
display: inline;
}
.footer-links li.divider:after {
content: "|";
color: #ccc;
}
.footer-links li a {
color: #515151;
}
@media (min-width: 30em) {
.pagination {
margin: 3rem 0;
}
.pagination-item {
float: left;
width: 50%;
}
.pagination-item:first-child {
margin-bottom: 0;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.pagination-item:last-child {
margin-left: -1px;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
}

View file

@ -1,65 +0,0 @@
.highlight .hll { background-color: #ffc; }
.highlight .c { color: #999; } /* Comment */
.highlight .err { color: #a00; background-color: #faa } /* Error */
.highlight .k { color: #069; } /* Keyword */
.highlight .o { color: #555 } /* Operator */
.highlight .cm { color: #09f; font-style: italic } /* Comment.Multiline */
.highlight .cp { color: #099 } /* Comment.Preproc */
.highlight .c1 { color: #999; } /* Comment.Single */
.highlight .cs { color: #999; } /* Comment.Special */
.highlight .gd { background-color: #fcc; border: 1px solid #c00 } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #f00 } /* Generic.Error */
.highlight .gh { color: #030; } /* Generic.Heading */
.highlight .gi { background-color: #cfc; border: 1px solid #0c0 } /* Generic.Inserted */
.highlight .go { color: #aaa } /* Generic.Output */
.highlight .gp { color: #009; } /* Generic.Prompt */
.highlight .gs { } /* Generic.Strong */
.highlight .gu { color: #030; } /* Generic.Subheading */
.highlight .gt { color: #9c6 } /* Generic.Traceback */
.highlight .kc { color: #069; } /* Keyword.Constant */
.highlight .kd { color: #069; } /* Keyword.Declaration */
.highlight .kn { color: #069; } /* Keyword.Namespace */
.highlight .kp { color: #069 } /* Keyword.Pseudo */
.highlight .kr { color: #069; } /* Keyword.Reserved */
.highlight .kt { color: #078; } /* Keyword.Type */
.highlight .m { color: #f60 } /* Literal.Number */
.highlight .s { color: #d44950 } /* Literal.String */
.highlight .na { color: #4f9fcf } /* Name.Attribute */
.highlight .nb { color: #366 } /* Name.Builtin */
.highlight .nc { color: #0a8; } /* Name.Class */
.highlight .no { color: #360 } /* Name.Constant */
.highlight .nd { color: #99f } /* Name.Decorator */
.highlight .ni { color: #999; } /* Name.Entity */
.highlight .ne { color: #c00; } /* Name.Exception */
.highlight .nf { color: #c0f } /* Name.Function */
.highlight .nl { color: #99f } /* Name.Label */
.highlight .nn { color: #0cf; } /* Name.Namespace */
.highlight .nt { color: #2f6f9f; } /* Name.Tag */
.highlight .nv { color: #033 } /* Name.Variable */
.highlight .ow { color: #000; } /* Operator.Word */
.highlight .w { color: #bbb } /* Text.Whitespace */
.highlight .mf { color: #f60 } /* Literal.Number.Float */
.highlight .mh { color: #f60 } /* Literal.Number.Hex */
.highlight .mi { color: #f60 } /* Literal.Number.Integer */
.highlight .mo { color: #f60 } /* Literal.Number.Oct */
.highlight .sb { color: #c30 } /* Literal.String.Backtick */
.highlight .sc { color: #c30 } /* Literal.String.Char */
.highlight .sd { color: #c30; font-style: italic } /* Literal.String.Doc */
.highlight .s2 { color: #c30 } /* Literal.String.Double */
.highlight .se { color: #c30; } /* Literal.String.Escape */
.highlight .sh { color: #c30 } /* Literal.String.Heredoc */
.highlight .si { color: #a00 } /* Literal.String.Interpol */
.highlight .sx { color: #c30 } /* Literal.String.Other */
.highlight .sr { color: #3aa } /* Literal.String.Regex */
.highlight .s1 { color: #c30 } /* Literal.String.Single */
.highlight .ss { color: #fc3 } /* Literal.String.Symbol */
.highlight .bp { color: #366 } /* Name.Builtin.Pseudo */
.highlight .vc { color: #033 } /* Name.Variable.Class */
.highlight .vg { color: #033 } /* Name.Variable.Global */
.highlight .vi { color: #033 } /* Name.Variable.Instance */
.highlight .il { color: #f60 } /* Literal.Number.Integer.Long */
.css .o,
.css .o + .nt,
.css .nt + .nt { color: #999; }

View file

@ -1,38 +0,0 @@
# Dependencies
markdown: kramdown
highlighter: pygments
# Permalinks
permalink: /:categories/:title/
relative_permalinks: true
# Setup
title: tail call
tagline: Ramblings in software development
description: The ramblings of a 25-year-old software developer from Germany.
url: https://blog.kempkens.io
hub_url: https://kempkens.superfeedr.com
baseurl: /
author:
name: Daniel Kempkens
location: Wassenberg, North Rhine-Westphalia, Germany
url: https://kempkens.io
email: daniel+blog@kempkens.io
twitter: '@nifoc_'
paginate: 5
include: [".htaccess"]
exclude: ["tmp", "Gemfile", "Gemfile.lock", "Makefile"]
# Custom
assets:
gzip: []
css_compressor: yui
disqus_shortname: blog-kempkens-io
footer:
github: nifoc
ipv6_check: kempkens.io

View file

@ -1,16 +0,0 @@
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
(function() {
var host = window.location.hostname;
if (host !== 'localhost' && host !== '127.0.0.1') { // No tracking on localhost
ga('create', 'UA-28082386-3', host);
ga('set', 'anonymizeIp', true);
ga('send', 'pageview');
}
})();
</script>

View file

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>{{ site.title | append: include.title }}</title>
<link rel="self" href="{{ include.path | prepend: site.url }}" />
<link href="{{ site.url }}/" />
<link rel="hub" href="{{ site.hub_url }}/" />
<updated>{{ site.time | date_to_xmlschema }}</updated>
<id>{{ site.url | append: include.id }}</id>
<author>
<name>{{ site.author.name }}</name>
<email>{{ site.author.email }}</email>
</author>
{% for post in include.posts %}
<entry>
<id>{{ site.url }}{{ post.id }}</id>
<title>{% if post.category == "links" %}Link: {% endif %}{{ post.title }}</title>
<link href="{{ post.url | prepend: site.url }}" />
<published>{{ post.date | date_to_xmlschema }}</published>
{% if post.modified != nil %}
<updated>{{ post.modified | date_to_xmlschema }}</updated>
{% else %}
<updated>{{ post.date | date_to_xmlschema }}</updated>
{% endif %}
{% if post.category == "links" %}
<content type="html">
{% capture linktext %}<p><strong>Link:</strong> <a href="{{ post.link }}">{{ post.link | hostname }}</a></p>{% endcapture %}
{{ linktext | xml_escape }}
{{ post.content | xml_escape }}
</content>
{% else %}
<content type="html">{{ post.content | xml_escape }}</content>
{% endif %}
</entry>
{% endfor %}
</feed>

View file

@ -1,15 +0,0 @@
<div class="footer">
<ul class="footer-links">
<li><a href="{{ "feed.xml" | prepend: site.baseurl }}">Feed</a></li>
<li class="divider"></li>
<li><a href="{{ "feed-with-links.xml" | prepend: site.baseurl }}">Feed (with links)</a></li>
<li class="divider"></li>
<li><a href="{{ site.footer.github | prepend: "https://github.com/" }}">GitHub</a></li>
<li class="divider"></li>
<li><a href="{{ site.footer.ipv6_check | prepend: "http://ip6.nl/#!" }}">IPv6</a></li>
<li class="divider"></li>
<li><a href="https://www.freebsd.org">FreeBSD</a></li>
<li class="divider"></li>
<li><a href="https://www.vultr.com/?ref=6819371" title="Affiliate link">Vultr</a></li>
</ul>
</div>

View file

@ -1,51 +0,0 @@
<head>
<link href="http://gmpg.org/xfn/11" rel="profile">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<!-- Enable responsiveness on mobile devices-->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1">
<title>
{% if page.title == "Home" %}
{{ site.title }} &middot; {{ site.tagline }}
{% else %}
{{ page.title }} &middot; {{ site.title }}
{% endif %}
</title>
<!-- CSS -->
{% stylesheet app %}
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=PT+Sans:400,400italic,700|Abril+Fatface">
<!-- Icons -->
<link rel="shortcut icon" href="{{ "favicon.ico" | prepend: site.baseurl }}">
<link rel="icon" type="image/png" href="{{ "favicon.png" | prepend: site.baseurl }}" />
<!-- RSS -->
<link rel="alternate" type="application/atom+xml" title="{{ site.title }} Feed" href="{{ "/feed.xml" | prepend: site.url }}">
<link rel="alternate" type="application/atom+xml" title="{{ site.title }} Feed (with links)" href="{{ "/feed-with-links.xml" | prepend: site.url }}">
<!-- humans.txt -->
<link rel="author" href="{{ "humans.txt" | prepend: site.baseurl }}">
{% if page.category == "posts" %}
<!-- Twitter -->
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@nifoc_">
<meta name="twitter:title" content="{{ page.title }}">
<meta name="twitter:description" content="{{ page.description }}">
<meta name="twitter:url" content="{{ site.url }}{{ page.url }}">
<!-- Facebook -->
<meta property="og:type" content="website">
<meta property="og:site_name" content="{{ site.title }}">
<meta property="og:title" content="{{ page.title }}">
<meta property="og:description" content="{{ page.description }}">
<meta property="og:url" content="{{ site.url }}{{ page.url }}">
<!-- Schema.org -->
<meta itemprop="name" content="{{ page.title }}">
<meta itemprop="description" content="{{ page.description }}">
{% endif %}
</head>

View file

@ -1,32 +0,0 @@
<div class="sidebar">
<div class="container sidebar-sticky">
<div class="sidebar-about">
<h1>
<a href="{{ site.baseurl }}">
{{ site.title }}
</a>
</h1>
<p class="lead">{{ site.description }}</p>
</div>
<nav class="sidebar-nav">
<a class="sidebar-nav-item{% if page.url == site.baseurl %} active{% endif %}" href="{{ site.baseurl }}">Home</a>
{% comment %}
The code below dynamically generates a sidebar nav of pages with
`layout: page` in the front-matter. See readme for usage.
{% endcomment %}
{% assign pages_list = site.pages %}
{% for node in pages_list %}
{% if node.title != null %}
{% if node.layout == "page" %}
<a class="sidebar-nav-item{% if page.url == node.url %} active{% endif %}" href="{{ node.url }}">{{ node.title }}</a>
{% endif %}
{% endif %}
{% endfor %}
</nav>
<p>&copy; {{ site.time | date: '%Y' }}. All rights reserved.</p>
</div>
</div>

View file

@ -1,18 +0,0 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en-us">
{% include head.html %}
<body>
{% include sidebar.html %}
<div class="content container">
{{ content }}
{% include foot.html %}
</div>
{% include analytics.html %}
</body>
</html>

View file

@ -1,12 +0,0 @@
---
layout: default
---
<div class="post">
<h1 class="post-title">&#9875; {{ page.title }}</h1>
<span class="post-date">{{ page.date | date_to_string }}</span>
<p class="post-link"><span>Link:</span> <a href="{{ page.link }}" rel="external">{{ page.link | hostname }}</a></p>
{{ content }}
</div>

View file

@ -1,8 +0,0 @@
---
layout: default
---
<div class="page">
<h1 class="page-title">{{ page.title }}</h1>
{{ content }}
</div>

View file

@ -1,45 +0,0 @@
---
layout: default
---
<div class="post">
<h1 class="post-title">{{ page.title }}</h1>
<span class="post-date">{{ page.date | date_to_string }}</span>
{{ content }}
</div>
<div class="related">
<h2>Related Posts</h2>
<ul class="related-posts">
{% for post in site.related_posts limit:3 %}
<li>
<h3>
<a href="{{ post.url }}">
{% if post.category == "links" %}&#9875;{% endif %}
{{ post.title }}
<small>{{ post.date | date_to_string }}</small>
</a>
</h3>
</li>
{% endfor %}
</ul>
</div>
{% if page.comments %}
<div class="comments">
<h2>Comments</h2>
<div id="disqus_thread"></div>
</div>
<script type="text/javascript">
/* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
var disqus_shortname = '{{ site.disqus_shortname }}';
/* * * DON'T EDIT BELOW THIS LINE * * */
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
{% endif %}

View file

@ -1,2 +0,0 @@
require 'bundler'
Bundler.require(:extensions)

View file

@ -1,12 +0,0 @@
require 'uri'
module Jekyll
module HostnameFilter
def hostname(input)
parsed_uri = URI.parse(input)
parsed_uri.host
end
end
end
Liquid::Template.register_filter(Jekyll::HostnameFilter)

View file

@ -1,189 +0,0 @@
---
layout: post
title: OCSP Stapling with nginx
description: "A general explanation of how to set up OCSP stapling with nginx."
date: 2014-03-30 21:30:00 CEST
modified: 2014-03-31 23:10:00 CEST
category: posts
tags: [nginx, ocsp, ssl, ops, english]
comments: true
---
Setting up [OCSP stapling](https://en.wikipedia.org/wiki/OCSP_stapling) with [nginx](http://nginx.org/) is more or less straightforward, but depending on what's in your `ssl_certificate` you might run into some issues with it silently failing. So I've decided to write about how I set up OCSP stapling with certificates from [StartSSL](https://www.startssl.com/) and [CAcert](http://www.cacert.org/).
I have only tested this using nginx version 1.5.12, but the configuration options I'm using have been in nginx since version 1.3.7. If it does not work for some reason, feel free to leave a comment below.
To enable OCSP stapling, you simply have to add the following lines to your (already SSL enabled) site:
{% highlight nginx %}
resolver 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=300s;
resolver_timeout 10s;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/certs/startssl.stapling.crt;
{% endhighlight %}
The important thing here is the usage of `ssl_trusted_certificate`. The [nginx documentation](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_stapling_verify) states that:
> For verification to work, the certificate of the server certificate issuer, **the root certificate, and all intermediate certificates** should be configured as trusted using the ssl_trusted_certificate directive.
You have to include the root certificate (and intermediate certificates) for OCSP stapling to work, which is why `ssl_trusted_certificate` will be set to a *special* certificate file that includes those. If your `ssl_certificate` already does, you might be able to skip using `ssl_trusted_certificate`.
## CAcert
For CAcert (and unless you have a Class 3 certificate) you only have to include the Class 1 certificate in your stapling file.
{% highlight text %}
-----BEGIN CERTIFICATE-----
MIIHPTCCBSWgAwIBAgIBADANBgkqhkiG9w0BAQQFADB5MRAwDgYDVQQKEwdSb290
IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB
IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA
Y2FjZXJ0Lm9yZzAeFw0wMzAzMzAxMjI5NDlaFw0zMzAzMjkxMjI5NDlaMHkxEDAO
BgNVBAoTB1Jvb3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEi
MCAGA1UEAxMZQ0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJ
ARYSc3VwcG9ydEBjYWNlcnQub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
CgKCAgEAziLA4kZ97DYoB1CW8qAzQIxL8TtmPzHlawI229Z89vGIj053NgVBlfkJ
8BLPRoZzYLdufujAWGSuzbCtRRcMY/pnCujW0r8+55jE8Ez64AO7NV1sId6eINm6
zWYyN3L69wj1x81YyY7nDl7qPv4coRQKFWyGhFtkZip6qUtTefWIonvuLwphK42y
fk1WpRPs6tqSnqxEQR5YYGUFZvjARL3LlPdCfgv3ZWiYUQXw8wWRBB0bF4LsyFe7
w2t6iPGwcswlWyCR7BYCEo8y6RcYSNDHBS4CMEK4JZwFaz+qOqfrU0j36NK2B5jc
G8Y0f3/JHIJ6BVgrCFvzOKKrF11myZjXnhCLotLddJr3cQxyYN/Nb5gznZY0dj4k
epKwDpUeb+agRThHqtdB7Uq3EvbXG4OKDy7YCbZZ16oE/9KTfWgu3YtLq1i6L43q
laegw1SJpfvbi1EinbLDvhG+LJGGi5Z4rSDTii8aP8bQUWWHIbEZAWV/RRyH9XzQ
QUxPKZgh/TMfdQwEUfoZd9vUFBzugcMd9Zi3aQaRIt0AUMyBMawSB3s42mhb5ivU
fslfrejrckzzAeVLIL+aplfKkQABi6F1ITe1Yw1nPkZPcCBnzsXWWdsC4PDSy826
YreQQejdIOQpvGQpQsgi3Hia/0PsmBsJUUtaWsJx8cTLc6nloQsCAwEAAaOCAc4w
ggHKMB0GA1UdDgQWBBQWtTIb1Mfz4OaO873SsDrusjkY0TCBowYDVR0jBIGbMIGY
gBQWtTIb1Mfz4OaO873SsDrusjkY0aF9pHsweTEQMA4GA1UEChMHUm9vdCBDQTEe
MBwGA1UECxMVaHR0cDovL3d3dy5jYWNlcnQub3JnMSIwIAYDVQQDExlDQSBDZXJ0
IFNpZ25pbmcgQXV0aG9yaXR5MSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0QGNhY2Vy
dC5vcmeCAQAwDwYDVR0TAQH/BAUwAwEB/zAyBgNVHR8EKzApMCegJaAjhiFodHRw
czovL3d3dy5jYWNlcnQub3JnL3Jldm9rZS5jcmwwMAYJYIZIAYb4QgEEBCMWIWh0
dHBzOi8vd3d3LmNhY2VydC5vcmcvcmV2b2tlLmNybDA0BglghkgBhvhCAQgEJxYl
aHR0cDovL3d3dy5jYWNlcnQub3JnL2luZGV4LnBocD9pZD0xMDBWBglghkgBhvhC
AQ0ESRZHVG8gZ2V0IHlvdXIgb3duIGNlcnRpZmljYXRlIGZvciBGUkVFIGhlYWQg
b3ZlciB0byBodHRwOi8vd3d3LmNhY2VydC5vcmcwDQYJKoZIhvcNAQEEBQADggIB
ACjH7pyCArpcgBLKNQodgW+JapnM8mgPf6fhjViVPr3yBsOQWqy1YPaZQwGjiHCc
nWKdpIevZ1gNMDY75q1I08t0AoZxPuIrA2jxNGJARjtT6ij0rPtmlVOKTV39O9lg
18p5aTuxZZKmxoGCXJzN600BiqXfEVWqFcofN8CCmHBh22p8lqOOLlQ+TyGpkO/c
gr/c6EWtTZBzCDyUZbAEmXZ/4rzCahWqlwQ3JNgelE5tDlG+1sSPypZt90Pf6DBl
Jzt7u0NDY8RD97LsaMzhGY4i+5jhe1o+ATc7iwiwovOVThrLm82asduycPAtStvY
sONvRUgzEv/+PDIqVPfE94rwiCPCR/5kenHA0R6mY7AHfqQv0wGP3J8rtsYIqQ+T
SCX8Ev2fQtzzxD72V7DX3WnRBnc0CkvSyqD/HMaMyRa+xMwyN2hzXwj7UfdJUzYF
CpUCTPJ5GhD22Dp1nPMd8aINcGeGG7MW9S/lpOt5hvk9C8JzC6WZrG/8Z7jlLwum
GCSNe9FINSkYQKyTYOGWhlC0elnYjyELn8+CkcY7v2vcB5G5l1YjqrZslMZIBjzk
zk6q5PYvCdxTby78dOs6Y5nCpqyJvKeyRKANihDjbPIky/qbn3BHLt4Ui9SyIAmW
omTxJBzcoTWcFbLUvFUufQb1nA5V9FrWk9p2rSVzTMVD
-----END CERTIFICATE-----
{% endhighlight %}
## StartSSL
StartSSL - using a Class 1 certificate again - has an intermediate certificate in their chain, so you have to include this one in your stapling file, too.
{% highlight text %}
-----BEGIN CERTIFICATE-----
MIIGNDCCBBygAwIBAgIBGDANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW
MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwHhcNMDcxMDI0MjA1NDE3WhcNMTcxMDI0MjA1NDE3WjCB
jDELMAkGA1UEBhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsT
IlNlY3VyZSBEaWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxODA2BgNVBAMTL1N0
YXJ0Q29tIENsYXNzIDEgUHJpbWFyeSBJbnRlcm1lZGlhdGUgU2VydmVyIENBMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtonGrO8JUngHrJJj0PREGBiE
gFYfka7hh/oyULTTRwbw5gdfcA4Q9x3AzhA2NIVaD5Ksg8asWFI/ujjo/OenJOJA
pgh2wJJuniptTT9uYSAK21ne0n1jsz5G/vohURjXzTCm7QduO3CHtPn66+6CPAVv
kvek3AowHpNz/gfK11+AnSJYUq4G2ouHI2mw5CrY6oPSvfNx23BaKA+vWjhwRRI/
ME3NO68X5Q/LoKldSKqxYVDLNM08XMML6BDAjJvwAwNi/rJsPnIO7hxDKslIDlc5
xDEhyBDBLIf+VJVSH1I8MRKbf+fAoKVZ1eKPPvDVqOHXcDGpxLPPr21TLwb0pwID
AQABo4IBrTCCAakwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
VR0OBBYEFOtCNNCYsKuf9BtrCPfMZC7vDixFMB8GA1UdIwQYMBaAFE4L7xqkQFul
F2mHMMo0aEPQQa7yMGYGCCsGAQUFBwEBBFowWDAnBggrBgEFBQcwAYYbaHR0cDov
L29jc3Auc3RhcnRzc2wuY29tL2NhMC0GCCsGAQUFBzAChiFodHRwOi8vd3d3LnN0
YXJ0c3NsLmNvbS9zZnNjYS5jcnQwWwYDVR0fBFQwUjAnoCWgI4YhaHR0cDovL3d3
dy5zdGFydHNzbC5jb20vc2ZzY2EuY3JsMCegJaAjhiFodHRwOi8vY3JsLnN0YXJ0
c3NsLmNvbS9zZnNjYS5jcmwwgYAGA1UdIAR5MHcwdQYLKwYBBAGBtTcBAgEwZjAu
BggrBgEFBQcCARYiaHR0cDovL3d3dy5zdGFydHNzbC5jb20vcG9saWN5LnBkZjA0
BggrBgEFBQcCARYoaHR0cDovL3d3dy5zdGFydHNzbC5jb20vaW50ZXJtZWRpYXRl
LnBkZjANBgkqhkiG9w0BAQUFAAOCAgEAIQlJPqWIbuALi0jaMU2P91ZXouHTYlfp
tVbzhUV1O+VQHwSL5qBaPucAroXQ+/8gA2TLrQLhxpFy+KNN1t7ozD+hiqLjfDen
xk+PNdb01m4Ge90h2c9W/8swIkn+iQTzheWq8ecf6HWQTd35RvdCNPdFWAwRDYSw
xtpdPvkBnufh2lWVvnQce/xNFE+sflVHfXv0pQ1JHpXo9xLBzP92piVH0PN1Nb6X
t1gW66pceG/sUzCv6gRNzKkC4/C2BBL2MLERPZBOVmTX3DxDX3M570uvh+v2/miI
RHLq0gfGabDBoYvvF0nXYbFFSF87ICHpW7LM9NfpMfULFWE7epTj69m8f5SuauNi
YpaoZHy4h/OZMn6SolK+u/hlz8nyMPyLwcKmltdfieFcNID1j0cHL7SRv7Gifl9L
WtBbnySGBVFaaQNlQ0lxxeBvlDRr9hvYqbBMflPrj0jfyjO1SPo2ShpTpjMM0InN
SRXNiTE8kMBy12VLUjWKRhFEuT2OKGWmPnmeXAhEKa2wNREuIU640ucQPl2Eg7PD
wuTSxv0JS3QJ3fGz0xk+gA2iCxnwOOfFwq/iI9th4p1cbiCJSS4jarJiwUW0n6+L
p/EiO/h94pDQehn7Skzj0n1fSoMD7SfWI55rjbRZotnvbIIp3XUZPD9MEI3vu3Un
0q6Dp6jOW6c=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW
MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9
MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi
U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh
cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk
pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf
OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C
Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT
Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi
HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM
Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w
+2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+
Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3
Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B
26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID
AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE
FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j
ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js
LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM
BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0
Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy
dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh
cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh
YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg
dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp
bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ
YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT
TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ
9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8
jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW
FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz
ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1
ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L
EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu
L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq
yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC
O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V
um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh
NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14=
-----END CERTIFICATE-----
{% endhighlight %}
After you've done all that, you can restart nginx or reload the configuration and you should be good to go!
You can verify that everything works using the `openssl` command line tool.
{% highlight bash %}
# CAcert
openssl s_client -servername blog.kempkens.io -connect blog.kempkens.io:443 -tls1 -tlsextdebug -status
# StartSSL
openssl s_client -servername kempkens.io -connect kempkens.io:443 -tls1 -tlsextdebug -status
{% endhighlight %}
Both of those should include a section (with data) named "OCSP Response Data".
An alternative way to test if OCSP stapling is supported, is by using [Qualys SSL Labs](https://www.ssllabs.com/ssltest/).
Keep in mind that nginx does not include OCSP data in the first response, because it has to fetch it, too. So you probably have to try at least two times to verify if it works or not.
**Update #1**
If you have more than one virtual host with SSL enabled, you have to enable OCSP stapling for every single one. Otherwise nginx will fail silently and not include any stapled OCSP data. (Thanks to [@rmoriz](https://roland.io) for figuring this out)

View file

@ -1,23 +0,0 @@
---
layout: page
title: Post Archive
---
{% for post in site.posts %}
{% capture month %}{{ post.date | date: '%m%Y' }}{% endcapture %}
{% capture nmonth %}{{ post.next.date | date: '%m%Y' }}{% endcapture %}
{% if month != nmonth %}
{% if forloop.index != 1 %}
</ul>
{% endif %}
<h3>{{ post.date | date: '%B %Y' }}</h3>
<ul>
{% endif %}
<li>
{% if post.category == "links" %}&#9875;{% endif %}
<a href="{{ post.url }}">{{ post.title }}</a>
</li>
{% endfor %}

View file

@ -1,20 +0,0 @@
---
layout: page
title: Tag Archive
---
{% capture site_tags %}{% for tag in site.tags %}{{ tag | first }}{% unless forloop.last %},{% endunless %}{% endfor %}{% endcapture %}
{% assign tags_list = site_tags | split:',' | sort %}
{% for tag in tags_list %}
<h2 id="tag-{{ tag }}">{{ tag }}</h2>
<ul>
{% for post in site.tags[tag] %}
{% if post.title != null %}
<li>
<a href="{{ post.url }}">{{ post.title }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
{% endfor %}

38
config.toml Normal file
View file

@ -0,0 +1,38 @@
baseURL = "https://blog.kempkens.io"
languageCode = "en-en"
title = "tail call"
theme = "terminal"
paginate = 5
[params]
contentTypeName = "posts"
themeColor = "orange"
showMenuItems = 2
showLanguageSelector = false
fullWidthTheme = false
centerTheme = true
showLastUpdated = false
[languages]
[languages.en]
languageName = "English"
title = "tail call"
subtitle = "The ramblings of a software developer from Germany"
owner = "Daniel Kempkens"
keywords = ""
copyright = ""
menuMore = "Show more"
readMore = "Read more"
readOtherPosts = "Read other posts"
newerPosts = "Newer posts"
olderPosts = "Older posts"
missingContentMessage = "Page not found..."
missingBackButtonLabel = "Back to home page"
[languages.en.params.logo]
logoText = "tail call"
logoHomeLink = "/"
[outputFormats]
[outputFormats.RSS]
baseName = "feed"

6
content/_headers Normal file
View file

@ -0,0 +1,6 @@
https://blog.kempkens.io/*
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer
Content-Security-Policy: default-src 'none'; manifest-src https://blog.kempkens.io; script-src 'self'; img-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; form-action 'none'; frame-ancestors 'none'; base-uri 'self'

View file

@ -1,18 +1,19 @@
--- ---
layout: post date: "2014-02-16T15:30:00Z"
description: Description and implementation of a function that joins a list of binaries.
tags:
- erlang
- programming
- english
slug: joining-a-list-of-binaries-in-erlang
title: Joining a List of Binaries in Erlang title: Joining a List of Binaries in Erlang
description: "Description and implementation of a function that joins a list of binaries."
date: 2014-02-16 15:30:00 CET
category: posts
tags: [erlang, programming, english]
comments: true
--- ---
The binary module in Erlang provides an easy way to split binaries using `split/2,3`, but what if you want to join a list of binaries back together? The binary module in Erlang provides an easy way to split binaries using `split/2,3`, but what if you want to join a list of binaries back together?
There is no built-in function to do this, so I've decided to write my own. There is no built-in function to do this, so I've decided to write my own.
{% highlight erlang %} {{< highlight erlang >}}
-spec binary_join([binary()], binary()) -> binary(). -spec binary_join([binary()], binary()) -> binary().
binary_join([], _Sep) -> binary_join([], _Sep) ->
<<>>; <<>>;
@ -20,13 +21,13 @@ binary_join([Part], _Sep) ->
Part; Part;
binary_join([Head|Tail], Sep) -> binary_join([Head|Tail], Sep) ->
lists:foldl(fun (Value, Acc) -> <<Acc/binary, Sep/binary, Value/binary>> end, Head, Tail). lists:foldl(fun (Value, Acc) -> <<Acc/binary, Sep/binary, Value/binary>> end, Head, Tail).
{% endhighlight %} {{< / highlight >}}
It works just like you would expect: It works just like you would expect:
{% highlight erlang %} {{< highlight erlang >}}
binary_join([<<"Hello">>, <<"World">>], <<", ">>) % => <<"Hello, World">> binary_join([<<"Hello">>, <<"World">>], <<", ">>) % => <<"Hello, World">>
binary_join([<<"Hello">>], <<"...">>) % => <<"Hello">> binary_join([<<"Hello">>], <<"...">>) % => <<"Hello">>
{% endhighlight %} {{< / highlight >}}
Hope you find this useful! Hope you find this useful!

View file

@ -1,24 +1,26 @@
--- ---
layout: post date: "2014-02-17T21:45:00Z"
description: Simple nginx configuration that allows you to proxy most of Microsoft Exchange.
tags:
- nginx
- exchange
- ops
- english
slug: exchange-reverse-proxy-using-nginx
title: Exchange Reverse Proxy Using nginx title: Exchange Reverse Proxy Using nginx
description: "Simple nginx configuration that allows you to proxy most of Microsoft Exchange."
date: 2014-02-17 21:45:00 CET
category: posts
tags: [nginx, exchange, ops, english]
comments: true
--- ---
As it turns out, setting up [nginx](http://nginx.org) as a reverse proxy for Microsoft Exchange is not as easy as [some](http://blog.friedlandreas.net/2013/07/reverseproxy-fur-eas-exchange-activesync-und-owa-outlookwebapp-mit-nginx/) [posts](http://www.administrator.de/wissen/ngnix-als-reverse-proxy-für-exchange-2010-192711.html) suggest. As it turns out, setting up [nginx](http://nginx.org) as a reverse proxy for Microsoft Exchange is not as easy as [some](http://blog.friedlandreas.net/2013/07/reverseproxy-fur-eas-exchange-activesync-und-owa-outlookwebapp-mit-nginx/) [posts](http://www.administrator.de/wissen/ngnix-als-reverse-proxy-für-exchange-2010-192711.html) suggest.
The issue that for some calls (Autodiscovery, RPC, …) IIS asks for an `Authorization` header, which nginx can pass through by doing: The issue that for some calls (Autodiscovery, RPC, …) IIS asks for an `Authorization` header, which nginx can pass through by doing:
{% highlight nginx %} {{< highlight nginx >}}
proxy_pass_header Authorization; proxy_pass_header Authorization;
{% endhighlight %} {{< / highlight >}}
Only problem is: It doesn't work. Thankfully someone on StackOverflow already had a [solution](http://stackoverflow.com/a/19714696) for this: Only problem is: It doesn't work. Thankfully someone on StackOverflow already had a [solution](http://stackoverflow.com/a/19714696) for this:
{% highlight nginx %} {{< highlight nginx >}}
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_pass_request_headers on; proxy_pass_request_headers on;
proxy_pass_header Date; proxy_pass_header Date;
@ -31,12 +33,12 @@ more_set_input_headers 'Authorization: $http_authorization';
proxy_set_header Accept-Encoding ""; proxy_set_header Accept-Encoding "";
more_set_headers -s 401 'WWW-Authenticate: Basic realm="your.mail.host"'; more_set_headers -s 401 'WWW-Authenticate: Basic realm="your.mail.host"';
proxy_pass https://your.mail.host; proxy_pass https://your.mail.host;
{% endhighlight %} {{< / highlight >}}
You'll need the [HttpHeadersMore](http://wiki.nginx.org/HttpHeadersMoreModule) module for this to work. On Ubuntu 12.04 (using the [nginx/stable PPA](https://launchpad.net/~nginx/+archive/stable)) all you need to install is: You'll need the [HttpHeadersMore](http://wiki.nginx.org/HttpHeadersMoreModule) module for this to work. On Ubuntu 12.04 (using the [nginx/stable PPA](https://launchpad.net/~nginx/+archive/stable)) all you need to install is:
{% highlight bash %} {{< highlight bash >}}
apt-get install nginx-extras apt-get install nginx-extras
{% endhighlight %} {{< / highlight >}}
And you're good to go! And you're good to go!

View file

@ -1,16 +1,18 @@
--- ---
layout: post date: "2014-02-24T22:00:00Z"
description: Some links regarding Telegram and its security model.
tags:
- telegram
- whatsapp
- app
- english
slug: telegram-and-security
title: Telegram and Security title: Telegram and Security
description: "Some links regarding Telegram and its security model."
date: 2014-02-24 22:00:00 CET
category: posts
tags: [telegram, whatsapp, app, english]
comments: true
--- ---
Ever since [Facebook bought WhatsApp](http://techcrunch.com/2014/02/19/facebook-buying-whatsapp-for-16b-in-cash-and-stock-plus-3b-in-rsus/) more and more of the people I know switch to [Telegram](https://telegram.org). One of the main reasons they choose Telegram is because it's "secure" and Telegram itself seems really keen on being seen as a secure, privacy-focused WhatsApp alternative. Ever since [Facebook bought WhatsApp](http://techcrunch.com/2014/02/19/facebook-buying-whatsapp-for-16b-in-cash-and-stock-plus-3b-in-rsus/) more and more of the people I know switch to [Telegram](https://telegram.org). One of the main reasons they choose Telegram is because it's "secure" and Telegram itself seems really keen on being seen as a secure, privacy-focused WhatsApp alternative.
People that are way smarter than I am when it comes to cryptography have a sligtly different opinion on the "secure, privacy-focused" part … People that are way smarter than I am when it comes to cryptography have a slightly different opinion on the "secure, privacy-focused" part …
- [Telegram, AKA "Stand back, we have Math PhDs!"](http://unhandledexpression.com/2013/12/17/telegram-stand-back-we-know-maths/) - [Telegram, AKA "Stand back, we have Math PhDs!"](http://unhandledexpression.com/2013/12/17/telegram-stand-back-we-know-maths/)
- [A Crypto Challenge For The Telegram Developers](http://thoughtcrime.org/blog/telegram-crypto-challenge/) - [A Crypto Challenge For The Telegram Developers](http://thoughtcrime.org/blog/telegram-crypto-challenge/)

View file

@ -1,18 +1,19 @@
--- ---
layout: post date: "2014-03-23T20:00:00Z"
description: Blow up a polyline to search inside the generated polygon.
tags:
- javascript
- programming
- english
slug: buffered-polyline
title: Buffered Polyline title: Buffered Polyline
description: "Blow up a polyline to search inside the generated polygon."
date: 2014-03-23 20:00:00 CET
category: posts
tags: [javascript, programming, english]
comments: true
--- ---
At work, we needed a simple way to buffer a polyline in order to search for stuff along the route from A to B. I'll explain how we used Google's Maps API and [JSTS](https://github.com/bjornharrtell/jsts) in order to achieve this easily. At work, we needed a simple way to buffer a polyline in order to search for stuff along the route from A to B. I'll explain how we used Google's Maps API and [JSTS](https://github.com/bjornharrtell/jsts) in order to achieve this easily.
The first thing we had to do was to "transform" each element in the `overview_path` (which the `DirectionsService` returns) to GeoJSON, because that's what JSTS understands. The first thing we had to do was to "transform" each element in the `overview_path` (which the `DirectionsService` returns) to GeoJSON, because that's what JSTS understands.
{% highlight javascript linenos %} {{< highlight javascript "linenos=table" >}}
var overviewPath = response.routes[0].overview_path, var overviewPath = response.routes[0].overview_path,
overviewPathGeo = []; overviewPathGeo = [];
for(var i = 0; i < overviewPath.length; i++) { for(var i = 0; i < overviewPath.length; i++) {
@ -20,11 +21,11 @@ for(var i = 0; i < overviewPath.length; i++) {
[overviewPath[i].lng(), overviewPath[i].lat()] [overviewPath[i].lng(), overviewPath[i].lat()]
); );
} }
{% endhighlight %} {{< / highlight >}}
The next step was getting `overviewPathGeo` into JSTS and letting it do the work of buffering the line. The next step was getting `overviewPathGeo` into JSTS and letting it do the work of buffering the line.
{% highlight javascript linenos %} {{< highlight javascript "linenos=table" >}}
var distance = 10/111.12, // Roughly 10km var distance = 10/111.12, // Roughly 10km
geoInput = { geoInput = {
type: "LineString", type: "LineString",
@ -34,7 +35,7 @@ var geoReader = new jsts.io.GeoJSONReader(),
geoWriter = new jsts.io.GeoJSONWriter(); geoWriter = new jsts.io.GeoJSONWriter();
var geometry = geoReader.read(geoInput).buffer(distance); var geometry = geoReader.read(geoInput).buffer(distance);
var polygon = geoWriter.write(geometry); var polygon = geoWriter.write(geometry);
{% endhighlight %} {{< / highlight >}}
The `polygon` variable now contains a polygon that neatly fits around the `overviewPath`, with a *distance* (buffer size) of roughly 10km. The `polygon` variable now contains a polygon that neatly fits around the `overviewPath`, with a *distance* (buffer size) of roughly 10km.
@ -42,4 +43,4 @@ Since it is nested, you need to call `polygon.coordinates[0]` in order to get ba
You could then use the the coordinates to draw the generated polygon on the map (along with the route), in order to produce something like this: You could then use the the coordinates to draw the generated polygon on the map (along with the route), in order to produce something like this:
{% image buffered-polyline-1.png %} ![Example image of a buffered polyline](/posts/buffered-polyline-1.png)

View file

@ -1,11 +1,12 @@
--- ---
layout: post date: "2014-03-26T13:55:00Z"
description: Collection of some interesting DEF CON videos.
tags:
- defcon
- videos
- english
slug: some-defcon-videos
title: Some DEF CON Videos title: Some DEF CON Videos
description: "Collection of some interesting DEF CON videos."
date: 2014-03-26 13:55:00 CET
category: posts
tags: [defcon, videos, english]
comments: true
--- ---
Over the weekend I watched some [DEF CON](https://www.defcon.org) videos on [YouTube](https://www.youtube.com/user/DEFCONConference/videos) and thought I should share some of the interesting ones on here. The two talks on Bitsquatting (by Stucke and Schultz) are especially entertaining and scary. Over the weekend I watched some [DEF CON](https://www.defcon.org) videos on [YouTube](https://www.youtube.com/user/DEFCONConference/videos) and thought I should share some of the interesting ones on here. The two talks on Bitsquatting (by Stucke and Schultz) are especially entertaining and scary.

View file

@ -0,0 +1,74 @@
---
date: "2014-03-30T21:30:00Z"
description: A general explanation of how to set up OCSP stapling with nginx.
modified: 2015-08-03 20:55:00 CEST
tags:
- nginx
- ocsp
- ssl
- ops
- english
slug: ocsp-stapling-with-nginx
title: OCSP Stapling with nginx
---
Setting up [OCSP stapling](https://en.wikipedia.org/wiki/OCSP_stapling) with [nginx](http://nginx.org/) is more or less straightforward, but depending on what's in your `ssl_certificate` you might run into some issues with it silently failing. So I've decided to write about how I set up OCSP stapling with certificates from [StartSSL](https://www.startssl.com/) and [CAcert](http://www.cacert.org/).
I have only tested this using nginx version 1.5.12, but the configuration options I'm using have been in nginx since version 1.3.7. If it does not work for some reason, feel free to leave a comment below.
To enable OCSP stapling, you simply have to add the following lines to your (already SSL enabled) site:
{{< highlight nginx >}}
resolver 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=300s;
resolver_timeout 10s;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/certs/startssl.stapling.crt;
{{< / highlight >}}
The important thing here is the usage of `ssl_trusted_certificate`. The [nginx documentation](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_stapling_verify) states that:
> For verification to work, the certificate of the server certificate issuer, **the root certificate, and all intermediate certificates** should be configured as trusted using the ssl_trusted_certificate directive.
You have to include the root certificate (and intermediate certificates) for OCSP stapling to work, which is why `ssl_trusted_certificate` will be set to a *special* certificate file that includes those. If your `ssl_certificate` already does, you might be able to skip using `ssl_trusted_certificate`.
## CAcert
For CAcert (and unless you have a Class 3 certificate) you only have to include the Class 1 certificate in your stapling file.
{{< highlight text >}}
03/08/2015 Removed. Get the Class 1 certificate here: http://www.cacert.org/index.php?id=3
{{< / highlight >}}
## StartSSL
StartSSL - using a Class 1 certificate again - has an intermediate certificate in their chain, so you have to include this one in your stapling file, too.
{{< highlight text >}}
03/08/2015: Removed. Get the root certificate and the intermediate certificate here: https://www.startssl.com/certs/
{{< / highlight >}}
After you've done all that, you can restart nginx or reload the configuration and you should be good to go!
You can verify that everything works using the `openssl` command line tool.
{{< highlight bash >}}
openssl s_client -servername blog.kempkens.io -connect blog.kempkens.io:443 -tls1 -tlsextdebug -status
{{< / highlight >}}
Both of those should include a section (with data) named "OCSP Response Data".
An alternative way to test if OCSP stapling is supported, is by using [Qualys SSL Labs](https://www.ssllabs.com/ssltest/).
Keep in mind that nginx does not include OCSP data in the first response, because it has to fetch it, too. So you probably have to try at least two times to verify if it works or not.
**Update #1**
If you have more than one virtual host with SSL enabled, you have to enable OCSP stapling for every single one. Otherwise nginx will fail silently and not include any stapled OCSP data. (Thanks to [@rmoriz](https://roland.io) for figuring this out)
**Update #2**
This post has been quite popular, so I decided to update it a little bit. The biggest change is that I have removed the certificate chains that were originally included in this post. I did this to not confuse people too much and because I don't want to keep these chains up to date.
The most important thing to remember when setting up OCSP stapling with any CA is to make sure that the entire trust chain (including the root certificate) is included in the `ssl_trusted_certificate` file.

View file

@ -1,11 +1,14 @@
--- ---
layout: post date: "2014-04-06T20:15:00Z"
description: Property-based testing in Erlang, with some basic usage examples.
tags:
- erlang
- quickcheck
- testing
- programming
- english
slug: property-based-testing-with-proper
title: Property-based Testing with PropEr title: Property-based Testing with PropEr
description: "Property-based testing in Erlang, with some basic usage examples."
date: 2014-04-06 20:15:00 CEST
category: posts
tags: [erlang, quickcheck, testing, programming, english]
comments: true
--- ---
[PropEr](http://proper.softlab.ntua.gr/) is a [QuickCheck](https://en.wikipedia.org/wiki/QuickCheck)-inspired property-based testing tool for Erlang. In contrast to traditional testing methodologies, the tester only has to provide the generic structure of valid inputs and some properties, which specify the relation between input and output. The testing tool will then produce progressively more complex valid inputs and compare the results it gets to what it expected (based on the defined properties). [PropEr](http://proper.softlab.ntua.gr/) is a [QuickCheck](https://en.wikipedia.org/wiki/QuickCheck)-inspired property-based testing tool for Erlang. In contrast to traditional testing methodologies, the tester only has to provide the generic structure of valid inputs and some properties, which specify the relation between input and output. The testing tool will then produce progressively more complex valid inputs and compare the results it gets to what it expected (based on the defined properties).
@ -13,28 +16,28 @@ PropEr is one such testing tool for Erlang. It is [open-source](https://github.c
Assuming you already have your `$ERL_LIBS` set up, you can simply run the following commands to install PropEr and make it available *globally*. Assuming you already have your `$ERL_LIBS` set up, you can simply run the following commands to install PropEr and make it available *globally*.
{% highlight bash linenos %} {{< highlight bash "linenos=table" >}}
cd $ERL_LIBS cd $ERL_LIBS
git clone git://github.com/manopapad/proper.git git clone git://github.com/manopapad/proper.git
cd proper cd proper
make fast make fast
{% endhighlight %} {{< / highlight >}}
After that, you can use PropEr in *all* your Erlang projects. After that, you can use PropEr in *all* your Erlang projects.
I like using [EUnit](http://www.erlang.org/doc/apps/eunit/chapter.html) as the runner for my property based tests. There are [some caveats](https://github.com/manopapad/proper#using-proper-in-conjunction-with-eunit), but all in all it works without any issues. I like using [EUnit](http://www.erlang.org/doc/apps/eunit/chapter.html) as the runner for my property based tests. There are [some caveats](https://github.com/manopapad/proper#using-proper-in-conjunction-with-eunit), but all in all it works without any issues.
What I don't like is defining my property-based tests in the same module as my unit tests, because it feels wrong to do so. I generally define one `_test_` function that calls `proper:module/2`. What I don't like is defining my property-based tests in the same module as my unit tests, because it feels wrong to do so. I generally define one `_test_` function that calls `proper:module/2`.
{% highlight erlang linenos %} {{< highlight erlang "linenos=table" >}}
proper_module_test_() -> proper_module_test_() ->
{timeout, 180, ?_assertEqual([], proper:module(example_utils_prop, [long_result, {to_file, user}]))}. {timeout, 180, ?_assertEqual([], proper:module(example_utils_prop, [long_result, {to_file, user}]))}.
{% endhighlight %} {{< / highlight >}}
This allows me to have all of the unit tests in `example_utils_test` and all of the property-based tests in `example_utils_prop`. This allows me to have all of the unit tests in `example_utils_test` and all of the property-based tests in `example_utils_prop`.
Let's write a simple property-based test for the `binary_join` function I've [introduced in February]({{ site.url }}/posts/joining-a-list-of-binaries-in-erlang/). Let's write a simple property-based test for the `binary_join` function I've [introduced in February]({{ site.url }}/posts/joining-a-list-of-binaries-in-erlang/).
{% highlight erlang linenos %} {{< highlight erlang "linenos=table" >}}
prop_binary_join() -> prop_binary_join() ->
?FORALL({Bs, S}, {list(binary()), binary()}, begin ?FORALL({Bs, S}, {list(binary()), binary()}, begin
ExpectedLength = if ExpectedLength = if
@ -44,7 +47,7 @@ prop_binary_join() ->
end, end,
ExpectedLength =:= byte_size(example_utils:binary_join(Bs, S)) ExpectedLength =:= byte_size(example_utils:binary_join(Bs, S))
end). end).
{% endhighlight %} {{< / highlight >}}
This will test if the size of the output of `example_utils:binary_join/2` is what we would expect: This will test if the size of the output of `example_utils:binary_join/2` is what we would expect:
@ -60,11 +63,11 @@ The `?FORALL` macro takes three aguments:
If you actually run the test, the output will look something like this: If you actually run the test, the output will look something like this:
{% highlight text %} {{< highlight text >}}
Testing example_utils_prop:prop_binary_join/0 Testing example_utils_prop:prop_binary_join/0
.................................................................................................... ....................................................................................................
OK: Passed 100 test(s). OK: Passed 100 test(s).
{% endhighlight %} {{< / highlight >}}
By default, PropEr will generate 100 random valid inputs and compare the output to the (length) properties we defined above. By default, PropEr will generate 100 random valid inputs and compare the output to the (length) properties we defined above.

View file

@ -1,11 +1,12 @@
--- ---
layout: post date: "2014-04-12T21:25:00Z"
description: Short description of how to install Erlang/OTP 17.0 using kerl on Mac OS X.
tags:
- erlang
- programming
- english
slug: installing-erlang-17-0-using-kerl
title: Installing Erlang 17.0 On Mac OS X title: Installing Erlang 17.0 On Mac OS X
description: "Short description of how to install Erlang/OTP 17.0 using kerl on Mac OS X."
date: 2014-04-12 21:25:00 CEST
category: posts
tags: [erlang, programming, english]
comments: true
--- ---
It's been a few days since [Erlang/OTP 17.0](http://www.erlang.org/news/73) has been released. Installing 64 Bit Erlang (with [Observer](http://www.erlang.org/doc/man/observer.html) support) on Mac OS X has always been a bit tricky, but with 17.0 it has gotten significantly easier. It's been a few days since [Erlang/OTP 17.0](http://www.erlang.org/news/73) has been released. Installing 64 Bit Erlang (with [Observer](http://www.erlang.org/doc/man/observer.html) support) on Mac OS X has always been a bit tricky, but with 17.0 it has gotten significantly easier.
@ -18,36 +19,36 @@ First you need to install/get some prerequisites:
After you've gotten these three, we start by installing *wxWidgets*. It's required in order to use the Observer GUI. After you've gotten these three, we start by installing *wxWidgets*. It's required in order to use the Observer GUI.
{% highlight bash %} {{< highlight bash >}}
brew install wxmac brew install wxmac
{% endhighlight %} {{< / highlight >}}
Compiling *wxWidgets* might take some time. After it's done, you should create a `~/.kerlrc` file. It's basically a configuration file for *kerl*, which will be used for every Erlang version you compile. You can find the list of available options in the [*kerl* readme](https://github.com/spawngrid/kerl#tuning). Mine looks like this: Compiling *wxWidgets* might take some time. After it's done, you should create a `~/.kerlrc` file. It's basically a configuration file for *kerl*, which will be used for every Erlang version you compile. You can find the list of available options in the [*kerl* readme](https://github.com/spawngrid/kerl#tuning). Mine looks like this:
{% highlight bash linenos %} {{< highlight bash "linenos=table" >}}
CPPFLAGS="-march=native -mtune=native -O3 -g" CPPFLAGS="-march=native -mtune=native -O3 -g"
KERL_CONFIGURE_OPTIONS="--disable-debug --without-javac --enable-shared-zlib --enable-dynamic-ssl-lib --enable-smp-support --enable-threads --enable-hipe --enable-kernel-poll --enable-darwin-64bit --with-wx" KERL_CONFIGURE_OPTIONS="--disable-debug --without-javac --enable-shared-zlib --enable-dynamic-ssl-lib --enable-smp-support --enable-threads --enable-hipe --enable-kernel-poll --enable-darwin-64bit --with-wx"
KERL_DEFAULT_INSTALL_DIR="$KERL_BASE_DIR/installs" KERL_DEFAULT_INSTALL_DIR="$KERL_BASE_DIR/installs"
{% endhighlight %} {{< / highlight >}}
We can now move on to actually installing Erlang/OTP 17.0. We can now move on to actually installing Erlang/OTP 17.0.
{% highlight bash %} {{< highlight bash >}}
kerl update releases kerl update releases
kerl build 17.0 17.0 kerl build 17.0 17.0
kerl install 17.0 17.0 kerl install 17.0 17.0
{% endhighlight %} {{< / highlight >}}
After the installation is finished, *kerl* will output instructions on how to enable a specific Erlang version globally. In general, you will put something like the following in your `~/.zshrc` or `~/.bash_profile`: After the installation is finished, *kerl* will output instructions on how to enable a specific Erlang version globally. In general, you will put something like the following in your `~/.zshrc` or `~/.bash_profile`:
{% highlight bash %} {{< highlight bash >}}
source $HOME/.kerl/installs/17.0/activate source $HOME/.kerl/installs/17.0/activate
{% endhighlight %} {{< / highlight >}}
The following `alias` makes it easier to start the Observer from a terminal. The only command you have to remember is `erlobserver`. The following `alias` makes it easier to start the Observer from a terminal. The only command you have to remember is `erlobserver`.
{% highlight bash %} {{< highlight bash >}}
alias erlobserver='erl -sname observer -run observer -detached' alias erlobserver='erl -sname observer -run observer -detached'
{% endhighlight %} {{< / highlight >}}
{% image installing-erlang-17-0-using-kerl-1.png %} ![Image of a running Observer instance](/posts/installing-erlang-17-0-using-kerl-1.png)

View file

@ -1,11 +1,12 @@
--- ---
layout: post date: "2014-04-18T20:25:00Z"
description: Simple workaround for supporting deprecated types and warnings_as_errors.
tags:
- erlang
- programming
- english
slug: erlang-17-0-supporting-deprecated-types-without-removing-warnings_as_errors
title: 'Erlang 17.0: Supporting Deprecated Types Without Removing warnings_as_errors' title: 'Erlang 17.0: Supporting Deprecated Types Without Removing warnings_as_errors'
description: "Simple workaround for supporting deprecated types and warnings_as_errors."
date: 2014-04-18 20:25:00 CEST
category: posts
tags: [erlang, programming, english]
comments: true
--- ---
Erlang 17.0 [deprecated some pre-defined types](http://www.erlang.org/download/otp_src_17.0.readme) like `dict()` and `gb_tree()` in favor of `dict:dict()` and `gb_tree:tree()`. The workaround they suggest (`nowarn_deprecated_type`) works in 17.0, but would break once the deprecated types are removed. Erlang 17.0 [deprecated some pre-defined types](http://www.erlang.org/download/otp_src_17.0.readme) like `dict()` and `gb_tree()` in favor of `dict:dict()` and `gb_tree:tree()`. The workaround they suggest (`nowarn_deprecated_type`) works in 17.0, but would break once the deprecated types are removed.
@ -25,22 +26,22 @@ A rather nice solution to this issue an option that [rebar](https://github.com/r
This allows us to set a `namespaced_types` option on 17.0+ only. This allows us to set a `namespaced_types` option on 17.0+ only.
{% highlight erlang %} {{< highlight erlang >}}
{erl_opts, [ {erl_opts, [
{platform_define, "^[0-9]+", namespaced_types}, {platform_define, "^[0-9]+", namespaced_types},
debug_info, debug_info,
warnings_as_errors warnings_as_errors
]}. ]}.
{% endhighlight %} {{< / highlight >}}
In your source files you can then check if the option is set and define an (internal) type accordingly. In your source files you can then check if the option is set and define an (internal) type accordingly.
{% highlight erlang linenos %} {{< highlight erlang "linenos=table" >}}
-ifdef(namespaced_types). -ifdef(namespaced_types).
-type xxx_dict() :: dict:dict(). -type xxx_dict() :: dict:dict().
-else. -else.
-type xxx_dict() :: dict(). -type xxx_dict() :: dict().
-endif. -endif.
{% endhighlight %} {{< / highlight >}}
I learned about this trick by looking at how [meck](https://github.com/eproxus/meck) handles Erlang 17.0 support. I learned about this trick by looking at how [meck](https://github.com/eproxus/meck) handles Erlang 17.0 support.

View file

@ -1,11 +1,14 @@
--- ---
layout: post date: "2014-05-04T21:55:00Z"
description: Introductory post about the Riemann client I've written.
tags:
- erlang
- programming
- riemann
- katja
- english
slug: katja-riemann-client-written-in-erlang
title: 'Katja: Riemann Client Written In Erlang' title: 'Katja: Riemann Client Written In Erlang'
description: "Introductory post about the Riemann client I've written."
date: 2014-05-04 21:55:00 CEST
category: posts
tags: [erlang, programming, riemann, katja, english]
comments: true
--- ---
[Riemann](http://riemann.io) is a network monitoring system written in Clojure, it offers a rather simple [protobuf](https://de.wikipedia.org/wiki/Protocol_Buffers)-based API. I have just tagged [Katja](https://github.com/nifoc/katja) [version 0.1](https://github.com/nifoc/katja/tree/v0.1), my Riemann client written in Erlang. [Riemann](http://riemann.io) is a network monitoring system written in Clojure, it offers a rather simple [protobuf](https://de.wikipedia.org/wiki/Protocol_Buffers)-based API. I have just tagged [Katja](https://github.com/nifoc/katja) [version 0.1](https://github.com/nifoc/katja/tree/v0.1), my Riemann client written in Erlang.
@ -22,12 +25,12 @@ Katja only supports Erlang/OTP R16B01+. This is mostly because in releases befor
## Sending Events ## Sending Events
{% highlight erlang linenos %} {{< highlight erlang "linenos=table" >}}
Event = [{service, "katja demo"}, {metric, 9000.1}], Event = [{service, "katja demo"}, {metric, 9000.1}],
ok = katja:send_event(Event), ok = katja:send_event(Event),
Event2 = [{service, <<"katja demo">>}, {metric, 9000.1}, {tags, ["demo"]}], Event2 = [{service, <<"katja demo">>}, {metric, 9000.1}, {tags, ["demo"]}],
ok = katja:send_events([Event, Event2]). ok = katja:send_events([Event, Event2]).
{% endhighlight %} {{< / highlight >}}
Katja allows you to send either a single event or multiple ones. Events are simple property lists with the following (possible) keys: time, state, service, host, description, tags, ttl, attributes, metric. Katja allows you to send either a single event or multiple ones. Events are simple property lists with the following (possible) keys: time, state, service, host, description, tags, ttl, attributes, metric.
@ -35,12 +38,12 @@ The entire `katja:event()` type definition can be found on [GitHub](https://gith
## Sending States ## Sending States
{% highlight erlang linenos %} {{< highlight erlang "linenos=table" >}}
State = [{service, "katja demo"}, {state, "testing"}], State = [{service, "katja demo"}, {state, "testing"}],
ok = katja:send_state(State), ok = katja:send_state(State),
State2 = [{service, "katja demo"}, {state, "testing"}, {tags, ["demo"]}], State2 = [{service, "katja demo"}, {state, "testing"}, {tags, ["demo"]}],
ok = katja:send_states([State, State2]). ok = katja:send_states([State, State2]).
{% endhighlight %} {{< / highlight >}}
States and events are very similar, so much so that the (possible) keys of a state property list are almost identical to the keys of an event property list: time, state, service, host, description, tags, ttl, once. States and events are very similar, so much so that the (possible) keys of a state property list are almost identical to the keys of an event property list: time, state, service, host, description, tags, ttl, once.
@ -48,9 +51,9 @@ Once again, the entire `katja:state()` type definition can be found on [GitHub](
## Querying Events ## Querying Events
{% highlight erlang linenos %} {{< highlight erlang "linenos=table" >}}
{ok, Events} = katja:query("service = \"katja demo\""). {ok, Events} = katja:query("service = \"katja demo\"").
{% endhighlight %} {{< / highlight >}}
`katja:query/1` will return a list of events. Events are a property list of type `katja:event()`, so what you send to Riemann is also what you get back when querying. There is one important thing to keep in mind: All `undefined` or `[]` values will be removed from the returned property list(s). `katja:query/1` will return a list of events. Events are a property list of type `katja:event()`, so what you send to Riemann is also what you get back when querying. There is one important thing to keep in mind: All `undefined` or `[]` values will be removed from the returned property list(s).
@ -58,11 +61,11 @@ You can find example queries in the [Riemann test suite](https://github.com/aphy
## Sending Entities ## Sending Entities
{% highlight erlang linenos %} {{< highlight erlang "linenos=table" >}}
Event = [{service, "katja demo"}, {metric, 9000.1}], Event = [{service, "katja demo"}, {metric, 9000.1}],
State = [{service, "katja demo"}, {state, "testing"}], State = [{service, "katja demo"}, {state, "testing"}],
ok = katja:send_entities([{events, [Event]}, {states, [State]}]). ok = katja:send_entities([{events, [Event]}, {states, [State]}]).
{% endhighlight %} {{< / highlight >}}
Katja also allows you to send mutiple events and/or states in a single request via `katja:send_entities/1`. Katja also allows you to send mutiple events and/or states in a single request via `katja:send_entities/1`.

View file

@ -1,11 +1,14 @@
--- ---
layout: post date: "2014-12-07T22:59:00Z"
description: Example GitLab CI setup that can run tests for Erlang projects.
tags:
- erlang
- testing
- programming
- gitlabci
- english
slug: gitlab-ci-for-erlang-projects
title: GitLab CI for Erlang Projects title: GitLab CI for Erlang Projects
description: "Example GitLab CI setup that can run tests for Erlang projects."
date: 2014-12-07 22:59:00 CET
category: posts
tags: [erlang, testing, programming, gitlabci, english]
comments: true
--- ---
[GitLab CI](https://about.gitlab.com/gitlab-ci/) is GitLab's continuous integration software. It integrates with [GitLab](https://about.gitlab.com) and runs your tests every time a commit is pushed to the remote repository. Getting it to run tests for projects written in Erlang can be a bit of hassle, so in this post I will share and explain my setup. [GitLab CI](https://about.gitlab.com/gitlab-ci/) is GitLab's continuous integration software. It integrates with [GitLab](https://about.gitlab.com) and runs your tests every time a commit is pushed to the remote repository. Getting it to run tests for projects written in Erlang can be a bit of hassle, so in this post I will share and explain my setup.
@ -16,7 +19,7 @@ The CI runner (v5.0.0) is installed on an Ubuntu 14.04 machine and it talks to G
The build script that is used to run Erlang tests looks like this: The build script that is used to run Erlang tests looks like this:
{% highlight bash %} {{< highlight bash >}}
# Setup # Setup
source "$HOME/.kerl/installs/17.3/activate" source "$HOME/.kerl/installs/17.3/activate"
gitlabdir=$(basename "$(pwd)") gitlabdir=$(basename "$(pwd)")
@ -39,7 +42,7 @@ ls -rt $(find ./logs -name "cover.html") | tail -n 1 | xargs cat | grep -F 'Tota
cd .. cd ..
mv "$appdir" "$gitlabdir" mv "$appdir" "$gitlabdir"
cd "$gitlabdir" cd "$gitlabdir"
{% endhighlight %} {{< / highlight >}}
**Setup** **Setup**
@ -59,9 +62,9 @@ My projects use [erlang.mk](https://github.com/ninenines/erlang.mk), which is wh
The test coverage is extracted from the latest `cover.html` file. It will be printed to *stdout* like this: The test coverage is extracted from the latest `cover.html` file. It will be printed to *stdout* like this:
{% highlight text %} {{< highlight text >}}
Total97 %742 Total97 %742
{% endhighlight %} {{< / highlight >}}
Since we're only interest in the `97`, we can set the "Test coverage parsing" option to this regular expression: `Total(\d+)\s\%`. Since we're only interest in the `97`, we can set the "Test coverage parsing" option to this regular expression: `Total(\d+)\s\%`.

View file

@ -1,28 +1,29 @@
--- ---
layout: post date: "2014-12-22T19:45:00Z"
description: Choosing a small number of newer ciphers prevents SSH bots from connecting.
tags:
- ssh
- ops
- english
slug: ssh-bots-do-not-know-about-my-ciphers
title: SSH Bots Don't Know about My Ciphers title: SSH Bots Don't Know about My Ciphers
description: "Choosing a small number of newer ciphers prevents SSH bots from connecting."
date: 2014-12-22 19:45:00 CET
category: posts
tags: [ssh, ops, english]
comments: true
--- ---
Last weekend I decided to update my `sshd_config` to include a very limited set of ciphers, MACs and key exchange algorithms. I did this to tighten the security of my `sshd` and not because I wanted to prevent bots from trying (and failing) to log in to my servers. I'm already using [fail2ban](http://www.fail2ban.org) for that. Last weekend I decided to update my `sshd_config` to include a very limited set of ciphers, MACs and key exchange algorithms. I did this to tighten the security of my `sshd` and not because I wanted to prevent bots from trying (and failing) to log in to my servers. I'm already using [fail2ban](http://www.fail2ban.org) for that.
However, after I updated my configuration I noticed failed login attempts basically dropped to zero, because all these bots do not support my very restrictive set of ciphers. However, after I updated my configuration I noticed failed login attempts basically dropped to zero, because all these bots do not support my very restrictive set of ciphers.
{% highlight text %} {{< highlight text >}}
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256 KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256
{% endhighlight %} {{< / highlight >}}
In order to use these settings, you need a recent version of [OpenSSH](http://www.openssh.com). I'm running 6.6 locally and on my servers, the minimum version that supports these settings is 6.4. In order to use these settings, you need a recent version of [OpenSSH](http://www.openssh.com). I'm running 6.6 locally and on my servers, the minimum version that supports these settings is 6.4.
After these changes your `auth.log` will probably contain this line rather often: After these changes your `auth.log` will probably contain this line rather often:
{% highlight text %} {{< highlight text >}}
fatal: no matching cipher found: client aes128-ctr,aes192-ctr,aes256-ctr,aes256-cbc,rijndael-cbc@lysator.liu.se,aes192-cbc,aes128-cbc,blowfish-cbc,arcfour128,arcfour,cast128-cbc,3des-cbc server chacha20-poly1305@openssh.com,aes256-gcm@openssh.com [preauth] fatal: no matching cipher found: client aes128-ctr,aes192-ctr,aes256-ctr,aes256-cbc,rijndael-cbc@lysator.liu.se,aes192-cbc,aes128-cbc,blowfish-cbc,arcfour128,arcfour,cast128-cbc,3des-cbc server chacha20-poly1305@openssh.com,aes256-gcm@openssh.com [preauth]
{% endhighlight %} {{< / highlight >}}
Please keep in mind that this will not prevent bots from attacking you (in the future) and that you have to mitigate these attacks by other means. I only wrote this post because I thought it was kind of interesting that SSH bots do not support these settings *right now*. Please keep in mind that this will not prevent bots from attacking you (in the future) and that you have to mitigate these attacks by other means. I only wrote this post because I thought it was kind of interesting that SSH bots do not support these settings *right now*.

View file

@ -1,39 +1,40 @@
--- ---
layout: post date: "2014-12-24T13:25:00Z"
description: Simple workaround for supporting deprecated types and warnings_as_errors with erlang.mk.
tags:
- erlang
- programming
- english
slug: supporting-deprecated-types-with-erlang-mk
title: Supporting Deprecated Types with erlang.mk title: Supporting Deprecated Types with erlang.mk
description: "Simple workaround for supporting deprecated types and warnings_as_errors with erlang.mk."
date: 2014-12-24 13:25:00 CET
category: posts
tags: [erlang, programming, english]
comments: true
--- ---
In April I wrote about how I use the `platform_define` feature of [rebar](https://github.com/rebar/rebar) to make deprecated types work without removing `warnings_as_errors`. I have switched to [erlang.mk](https://github.com/ninenines/erlang.mk) since then, but needed a similar feature for [one of my libraries](https://github.com/nifoc/noesis). In April I wrote about how I use the `platform_define` feature of [rebar](https://github.com/rebar/rebar) to make deprecated types work without removing `warnings_as_errors`. I have switched to [erlang.mk](https://github.com/ninenines/erlang.mk) since then, but needed a similar feature for [one of my libraries](https://github.com/nifoc/noesis).
Since erlang.mk only uses [make](http://en.wikipedia.org/wiki/Make_(software)), adding support for a `platform_define`-like feature is very straightforward. Since erlang.mk only uses [make](http://en.wikipedia.org/wiki/Make_(software)), adding support for a `platform_define`-like feature is very straightforward.
{% highlight makefile %} {{< highlight makefile >}}
otp_release = $(shell erl -noshell -eval 'io:format("~s", [erlang:system_info(otp_release)]), init:stop()') otp_release = $(shell erl -noshell -eval 'io:format("~s", [erlang:system_info(otp_release)]), init:stop()')
otp_17plus = $(shell echo $(otp_release) | grep -q -E "^[[:digit:]]+$$" ; echo $$?) otp_17plus = $(shell echo $(otp_release) | grep -q -E "^[[:digit:]]+$$" ; echo $$?)
{% endhighlight %} {{< / highlight >}}
The first variable (`otp_release`) will contain the OTP version (e.g. `17`). The second variable (`otp_17plus`) will be either `0` or `1`, depending on wether or not `otp_release` matches a regular expression. The regular expression checks if `otp_release` is just a number (e.g. `17`) or not (e.g. `R16B03-1`). The first variable (`otp_release`) will contain the OTP version (e.g. `17`). The second variable (`otp_17plus`) will be either `0` or `1`, depending on wether or not `otp_release` matches a regular expression. The regular expression checks if `otp_release` is just a number (e.g. `17`) or not (e.g. `R16B03-1`).
That's enough to conditionally add options to `erlc`. That's enough to conditionally add options to `erlc`.
{% highlight makefile %} {{< highlight makefile >}}
ifeq ($(otp_17plus),0) ifeq ($(otp_17plus),0)
ERLC_OPTS += -Dnamespaced_types=1 ERLC_OPTS += -Dnamespaced_types=1
TEST_ERLC_OPTS += -Dnamespaced_types=1 TEST_ERLC_OPTS += -Dnamespaced_types=1
endif endif
{% endhighlight %} {{< / highlight >}}
This will define `namespaced_types` only on Erlang 17+, allowing us to use the same `ifdef`-switch from April. This will define `namespaced_types` only on Erlang 17+, allowing us to use the same `ifdef`-switch from April.
{% highlight erlang linenos %} {{< highlight erlang "linenos=table" >}}
-ifdef(namespaced_types). -ifdef(namespaced_types).
-type xxx_dict() :: dict:dict(). -type xxx_dict() :: dict:dict().
-else. -else.
-type xxx_dict() :: dict(). -type xxx_dict() :: dict().
-endif. -endif.
{% endhighlight %} {{< / highlight >}}

View file

@ -1,11 +1,14 @@
--- ---
layout: link date: "2014-12-31T16:33:00Z"
description: 'Link: [media.ccc.de](http://media.ccc.de/browse/congress/2014/31c3_-_6369_-_en_-_saal_1_-_201412272145_-_ecchacks_-_djb_-_tanja_lange.html#video)'
tags:
- links
url: links/a-gentle-introduction-to-elliptic-curve-cryptography
title: A Gentle Introduction to Elliptic-Curve Cryptography title: A Gentle Introduction to Elliptic-Curve Cryptography
date: 2014-12-31 16:33:00 CET
category: links
link: http://media.ccc.de/browse/congress/2014/31c3_-_6369_-_en_-_saal_1_-_201412272145_-_ecchacks_-_djb_-_tanja_lange.html#video
--- ---
Link: [media.ccc.de](http://media.ccc.de/browse/congress/2014/31c3_-_6369_-_en_-_saal_1_-_201412272145_-_ecchacks_-_djb_-_tanja_lange.html#video)
A talk by djb and Tanja Lange. A talk by djb and Tanja Lange.
> This talk will explain how to work with elliptic curves constructively to obtain secure and efficient implementations, and will highlight pitfalls that must be avoided when implementing elliptic-curve crypto (ECC). The talk will also explain what all the buzz in curve choices for TLS is about. This talk does not require any prior exposure to ECC. > This talk will explain how to work with elliptic curves constructively to obtain secure and efficient implementations, and will highlight pitfalls that must be avoided when implementing elliptic-curve crypto (ECC). The talk will also explain what all the buzz in curve choices for TLS is about. This talk does not require any prior exposure to ECC.

View file

@ -1,11 +1,14 @@
--- ---
layout: link date: "2015-01-05T17:39:00Z"
description: 'Link: [www.marco.org](http://www.marco.org/2015/01/04/apple-lost-functional-high-ground)'
tags:
- links
url: links/apple-has-lost-the-functional-high-ground
title: Apple Has Lost the Functional High Ground title: Apple Has Lost the Functional High Ground
date: 2015-01-05 17:39:00 CET
category: links
link: http://www.marco.org/2015/01/04/apple-lost-functional-high-ground
--- ---
Link: [www.marco.org](http://www.marco.org/2015/01/04/apple-lost-functional-high-ground)
Marco Arment's view on the rapid decline in Apple's software quality. Marco Arment's view on the rapid decline in Apple's software quality.
> I fear that Apples leadership doesnt realize quite how badly and deeply their software flaws have damaged their reputation, because if they realized it, theyd make serious changes that dont appear to be happening. Instead, the opposite appears to be happening: the pace of rapid updates on multiple product lines seems to be expanding and accelerating. > I fear that Apples leadership doesnt realize quite how badly and deeply their software flaws have damaged their reputation, because if they realized it, theyd make serious changes that dont appear to be happening. Instead, the opposite appears to be happening: the pace of rapid updates on multiple product lines seems to be expanding and accelerating.

View file

@ -1,11 +1,13 @@
--- ---
layout: post date: "2015-02-01T20:59:00Z"
description: Explanation of how to use spiped to encrypt HTTP traffic to backend systems.
tags:
- spiped
- nginx
- ops
- english
slug: securing-http-traffic-with-spiped
title: Securing HTTP Traffic with spiped title: Securing HTTP Traffic with spiped
description: "Explanation of how to use spiped to encrypt HTTP traffic to backend systems."
date: 2015-02-01 20:59:00 CET
category: posts
tags: [spiped, nginx, ops, english]
comments: true
--- ---
[spiped](http://www.tarsnap.com/spiped.html) - the secure pipe daemon - is a utility for creating symmetrically encrypted and authenticated pipes between socket addresses. I recently used it to encrypt HTTP traffic from my main webserver to a Raspberry Pi that I run at home. [spiped](http://www.tarsnap.com/spiped.html) - the secure pipe daemon - is a utility for creating symmetrically encrypted and authenticated pipes between socket addresses. I recently used it to encrypt HTTP traffic from my main webserver to a Raspberry Pi that I run at home.
@ -16,25 +18,25 @@ I'm using [nginx](http://nginx.org) on the main webserver and the Raspberry Pi,
After installing spiped, the first thing you have to do is to create a (private) key. I ran the following command on my webserver: After installing spiped, the first thing you have to do is to create a (private) key. I ran the following command on my webserver:
{% highlight bash %} {{< highlight bash >}}
dd if=/dev/urandom bs=32 count=1 of=/usr/local/etc/nginx/certs/spiped.key dd if=/dev/urandom bs=32 count=1 of=/usr/local/etc/nginx/certs/spiped.key
{% endhighlight %} {{< / highlight >}}
After that, the key has to be securely copied to the backend machine. You can - for example - do this using `scp`. I copied it to `/etc/nginx/certs/spiped.key`, but your paths may vary. After that, the key has to be securely copied to the backend machine. You can - for example - do this using `scp`. I copied it to `/etc/nginx/certs/spiped.key`, but your paths may vary.
That's it. You can now start spiped. That's it. You can now start spiped.
{% highlight bash %} {{< highlight bash >}}
spiped -D -e -s [127.0.0.1]:9080 -t rpi.kempkens.io:9080 -k /usr/local/etc/nginx/certs/spiped.key spiped -D -e -s [127.0.0.1]:9080 -t rpi.kempkens.io:9080 -k /usr/local/etc/nginx/certs/spiped.key
{% endhighlight %} {{< / highlight >}}
That command tells spiped to listen on `127.0.0.1:9080`, encrypt the incoming data and send it to `rpi.kempkens.io:9080`. That command tells spiped to listen on `127.0.0.1:9080`, encrypt the incoming data and send it to `rpi.kempkens.io:9080`.
To set up the receiving end of the pipe, you have to run something like this: To set up the receiving end of the pipe, you have to run something like this:
{% highlight bash %} {{< highlight bash >}}
spiped -D -d -s [0.0.0.0]:9080 -t [127.0.0.1]:80 -k /etc/nginx/certs/spiped.key spiped -D -d -s [0.0.0.0]:9080 -t [127.0.0.1]:80 -k /etc/nginx/certs/spiped.key
{% endhighlight %} {{< / highlight >}}
spiped will listen for incoming connections on port 9080, decrypt the data it receives and forward it to `127.0.0.1:80`. spiped will listen for incoming connections on port 9080, decrypt the data it receives and forward it to `127.0.0.1:80`.
@ -42,11 +44,11 @@ And that's it.
On the main webserver I have defined an `upstream` that forwards traffic to `127.0.0.1:9080`. On the main webserver I have defined an `upstream` that forwards traffic to `127.0.0.1:9080`.
{% highlight nginx %} {{< highlight nginx >}}
upstream rpi { upstream rpi {
server 127.0.0.1:9080; server 127.0.0.1:9080;
} }
{% endhighlight %} {{< / highlight >}}
The nginx installation on the Raspberry Pi listens on port 80, so there is nothing special you have to set up there. It will simply receive the decrypted HTTP traffic from spiped. The only thing that you have to keep in mind is that the server name (`Host`-header) has to be the same on the main webserver and the Raspberry Pi. The nginx installation on the Raspberry Pi listens on port 80, so there is nothing special you have to set up there. It will simply receive the decrypted HTTP traffic from spiped. The only thing that you have to keep in mind is that the server name (`Host`-header) has to be the same on the main webserver and the Raspberry Pi.

View file

@ -1,11 +1,12 @@
--- ---
layout: post date: "2015-03-12T22:03:00Z"
description: Description and implementation of functions that encode and decode polylines.
tags:
- erlang
- programming
- english
slug: encoding-and-decoding-polylines-with-erlang
title: Encoding and Decoding Polylines with Erlang title: Encoding and Decoding Polylines with Erlang
description: "Description and implementation of functions that encode and decode polylines."
date: 2015-03-12 22:03:00 CET
category: posts
tags: [erlang, programming, english]
comments: true
--- ---
If you have ever worked with the [Google Directions API](https://developers.google.com/maps/documentation/directions/?csw=1) you probably came across [encoded polylines](https://developers.google.com/maps/documentation/utilities/polylinealgorithm). I wanted to decode and encode these using Erlang but was unable to find an existing implementation. So I decided to write my own. If you have ever worked with the [Google Directions API](https://developers.google.com/maps/documentation/directions/?csw=1) you probably came across [encoded polylines](https://developers.google.com/maps/documentation/utilities/polylinealgorithm). I wanted to decode and encode these using Erlang but was unable to find an existing implementation. So I decided to write my own.
@ -14,14 +15,14 @@ If you have ever worked with the [Google Directions API](https://developers.goog
* A path, i.e. a decoded polyline, is represented as a list of point tuples * A path, i.e. a decoded polyline, is represented as a list of point tuples
* An encoded polyline is represented as a simple binary string * An encoded polyline is represented as a simple binary string
{% highlight erlang %} {{< highlight erlang >}}
Point = {Lng, Lat}, Point = {Lng, Lat},
Path = [{Lng, Lat}, {Lng2, Lat2}]. Path = [{Lng, Lat}, {Lng2, Lat2}].
{% endhighlight %} {{< / highlight >}}
The `encode/1` function takes a path and returns an encoded polyline. The `encode/1` function takes a path and returns an encoded polyline.
{% highlight erlang linenos %} {{< highlight erlang "linenos=table" >}}
encode(Path) -> encode_acc(Path, 0, 0, <<>>). encode(Path) -> encode_acc(Path, 0, 0, <<>>).
% Private % Private
@ -41,11 +42,11 @@ encode_part(Num, Result) when Num < 32 -> <<Result/binary, (Num + 63)>>;
encode_part(Num, Result) -> encode_part(Num, Result) ->
Value = (32 bor (Num band 31)) + 63, Value = (32 bor (Num band 31)) + 63,
encode_part(Num bsr 5, <<Result/binary, Value>>). encode_part(Num bsr 5, <<Result/binary, Value>>).
{% endhighlight %} {{< / highlight >}}
The `decode/1` function takes an encoded polyline and returns a path. The `decode/1` function takes an encoded polyline and returns a path.
{% highlight erlang linenos %} {{< highlight erlang "linenos=table" >}}
decode(Line) -> decode_acc(Line, 0, 0, []). decode(Line) -> decode_acc(Line, 0, 0, []).
% Private % Private
@ -68,7 +69,7 @@ decode_part(<<C:8, Rest/binary>>, _OldB, Shift, Result) ->
B = C - 63, B = C - 63,
Result2 = Result bor ((B band 31) bsl Shift), Result2 = Result bor ((B band 31) bsl Shift),
decode_part(Rest, B, Shift + 5, Result2). decode_part(Rest, B, Shift + 5, Result2).
{% endhighlight %} {{< / highlight >}}
I have written these functions for [noesis](https://github.com/nifoc/noesis), which is a library that contains useful utility functions. Right now the implementation is only available in the [development branch](https://github.com/nifoc/noesis/blob/development/src/noesis_polyline.erl). It is tested using [EUnit](https://github.com/nifoc/noesis/blob/f3e9ae21d53e09bea9ca48fe4a56ddd006952e0a/test/noesis_polyline_test.erl) and [QuickCheck](https://github.com/nifoc/noesis/blob/f3e9ae21d53e09bea9ca48fe4a56ddd006952e0a/test/noesis_polyline_triq.erl). I have written these functions for [noesis](https://github.com/nifoc/noesis), which is a library that contains useful utility functions. Right now the implementation is only available in the [development branch](https://github.com/nifoc/noesis/blob/development/src/noesis_polyline.erl). It is tested using [EUnit](https://github.com/nifoc/noesis/blob/f3e9ae21d53e09bea9ca48fe4a56ddd006952e0a/test/noesis_polyline_test.erl) and [QuickCheck](https://github.com/nifoc/noesis/blob/f3e9ae21d53e09bea9ca48fe4a56ddd006952e0a/test/noesis_polyline_triq.erl).

View file

@ -1,18 +1,19 @@
--- ---
layout: post date: "2015-07-12T18:46:00Z"
description: Introducing my port of the pcsensor utility to FreeBSD.
tags:
- pcsensor
- freebsd
- english
slug: porting-pcsensor-to-freebsd
title: Porting pcsensor to FreeBSD title: Porting pcsensor to FreeBSD
description: "Introducing my port of the pcsensor utility to FreeBSD."
date: 2015-07-12 18:46:00 CEST
category: posts
tags: [pcsensor, freebsd, english]
comments: true
--- ---
Last week, it got rather hot where I live and so I got interested in measuring the temperature of the room where I keep my NAS and various other devices. I started looking for cheap USB thermometers and quickly found [this one](http://www.amazon.de/gp/product/B009RETJIO). It has some decent reviews and costs only around 16€, which seemed perfect to simply play around with. Last week, it got rather hot where I live and so I got interested in measuring the temperature of the room where I keep my NAS and various other devices. I started looking for cheap USB thermometers and quickly found [this one](http://www.amazon.de/gp/product/B009RETJIO). It has some decent reviews and costs only around 16€, which seemed perfect to simply play around with.
The device only comes bundled with Windows software, but there is an open source utility called `pcsensor` which allows you to use it via the command line on Linux. I don't have any Linux devices in the room that I wanted to measure. Since the source code was pretty straightforward and only minimal changes were required to port the utility to FreeBSD, I did just that! You can find the ported source code on [GitHub](https://github.com/nifoc/pcsensor-freebsd). The device only comes bundled with Windows software, but there is an open source utility called `pcsensor` which allows you to use it via the command line on Linux. I don't have any Linux devices in the room that I wanted to measure. Since the source code was pretty straightforward and only minimal changes were required to port the utility to FreeBSD, I did just that! You can find the ported source code on [GitHub](https://github.com/nifoc/pcsensor-freebsd).
{% highlight text %} {{< highlight text >}}
$ pcsensor -h $ pcsensor -h
pcsensor version 1.0.3 pcsensor version 1.0.3
Aviable options: Aviable options:
@ -26,6 +27,6 @@ pcsensor version 1.0.3
-d output with Bus and Device number -d output with Bus and Device number
-D display device list -D display device list
-D[n] specific device number -D[n] specific device number
{% endhighlight %} {{< / highlight >}}
If you want to read the temperature from the device, you might have to prefix the call to `pcsensor` with `sudo`. If you want to read the temperature from the device, you might have to prefix the call to `pcsensor` with `sudo`.

View file

@ -0,0 +1,27 @@
---
date: "2016-01-31T17:46:00Z"
description: Description of how to easily anonymize IPs using HAProxy.
tags:
- haproxy
- ops
- english
slug: anonymizing-ips-using-haproxy
title: Anonymizing IPs Using HAProxy
---
At work, I had to come up with an easy way to anonymize the last octet of a logged IP address in order to comply with German data protection laws. If you're using [HAProxy](http://www.haproxy.org) (1.5+), you can do this in one line.
If you want to forward the source IP address to a backend server, you would usually use `option forwardfor`. Sadly you can't set or change the forwarded IP using that option, so instead you have to set the `X-Forwarded-For` header manually.
{{< highlight text >}}
http-request set-header X-Forwarded-For %[src,ipmask(24)]
{{< / highlight >}}
This will set the last octet of the source IP address to zero.
The HAProxy documentation has more information on the various things I used in this post:
* [option forwardfor](https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#4-option%20forwardfor)
* [http-request](https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#4-http-request)
* [src sample](https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#7.3.3-src)
* [ipmask converter](https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#7.3.1-ipmask)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -1,4 +0,0 @@
---
layout: null
---
{% include feed.xml id="/feed-with-links.xml" path="/feed-with-links.xml" title=" (with links)" posts=site.posts %}

View file

@ -1,4 +0,0 @@
---
layout: null
---
{% include feed.xml path="/feed.xml" posts=site.categories.posts %}

43
flake.lock Normal file
View file

@ -0,0 +1,43 @@
{
"nodes": {
"flake-utils": {
"locked": {
"lastModified": 1652776076,
"narHash": "sha256-gzTw/v1vj4dOVbpBSJX4J0DwUR6LIyXo7/SuuTJp1kM=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "04c1b180862888302ddfb2e3ad9eaa63afc60cf8",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1653326962,
"narHash": "sha256-W8feCYqKTsMre4nAEpv5Kx1PVFC+hao/LwqtB2Wci/8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "41cc1d5d9584103be4108c1815c350e07c807036",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

18
flake.nix Normal file
View file

@ -0,0 +1,18 @@
{
description = "blog.kempkens.io build environment";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.default = pkgs.mkShell {
packages = with pkgs; [
hugo
];
};
});
}

View file

@ -1,12 +0,0 @@
---
layout: null
---
/* TEAM */
Author: {{ site.author.name }}
Contact: {{ site.author.email }}
Twitter: {{ site.author.twitter }}
From: {{ site.author.location }}
/* SITE */
Last update: {{ site.time | date: '%Y/%m/%d' }}
Software: Jekyll, MacVim

View file

@ -1,49 +0,0 @@
---
layout: page
permalink: /impressum/
title: Impressum
---
Daniel Kempkens
Kugelsberger Weg 6
41849 Wassenberg
Email: {{ site.author.email }}
## Domains
* [blog.kempkens.io](https://blog.kempkens.io)
* [daniel.xxx](https://daniel.xxx)
## Security
### Email
My public PGP key can be found on [Keybase](https://keybase.io/nifoc) ([.asc file](https://keybase.io/nifoc/key.asc)) and on [pgp.mit.edu](http://pgp.mit.edu/pks/lookup?op=vindex&search=0xB0680B61D48AE85C).
Public key fingerprint: `E1B8 971D D5B5 EF35 90B7 730D B068 0B61 D48A E85C`
### HTTP
This blog makes use of [HSTS](http://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security), [HPKP](https://developer.mozilla.org/en-US/docs/Web/Security/Public_Key_Pinning), [strong ciphers](https://www.ssllabs.com/ssltest/analyze.html?d={{ site.url | hostname }}&hideResults=on) and various other security-related technologies.
*German law demands the following statements, too.*
## Datenschutzerklärung
Die Nutzung unserer Webseite ist in der Regel ohne Angabe personenbezogener Daten möglich. Soweit auf unseren Seiten personenbezogene Daten (beispielsweise Name, Anschrift oder eMail-Adressen) erhoben werden, erfolgt dies, soweit möglich, stets auf freiwilliger Basis.
Der Nutzung von im Rahmen der Impressumspflicht veröffentlichten Kontaktdaten durch Dritte zur Übersendung von nicht ausdrücklich angeforderter Werbung und Informationsmaterialien wird hiermit ausdrücklich widersprochen. Die Betreiber der Seiten behalten sich ausdrücklich rechtliche Schritte im Falle der unverlangten Zusendung von Werbeinformationen, etwa durch Spam-Mails, vor.
### Disqus
Die Disqus-Kommentierungsfunktion wird von der Big Head Labs, Inc., San Francisco/USA als Dienstleistung zur Verfügung gestellt. Disqus ist ein interaktives Kommentarsystem, das es dem Nutzer ermöglicht, mit nur einer Anmeldung auf allen Internetangeboten, die Disqus als Kommentarsystem verwenden, zu kommentieren. Außerdem können sich die Nutzer über bestehende Accounts bei Facebook (über Facebook Connect), Twitter, Yahoo, Google und OpenID anmelden. Auch die Erstellung von Kommentare ohne Registrierung und Anmeldung (als “Gast”) ist möglich. Nähere Informationen zu Disqus und seinen Funktionen finden Sie unter [www.disqus.com](https://www.disqus.com).
Ihre Daten werden von Disqus verarbeitet und auf deren System gespeichert. Für die Erhebung, Verarbeitung und Nutzung der betreffenden Daten gelten die Nutzungsbedingungen und Datenschutzhinweise von Disqus, zu finden unter [http://docs.disqus.com/help/30/](http://docs.disqus.com/help/30/) und [http://docs.disqus.com/help/29/](http://docs.disqus.com/help/29/). Wenn Sie sich über Ihren Facebook-, Twitter-, Yahoo-, Google oder OpenID-Account anmelden, werden auch von diesen Anbietern möglicherweise Daten erhoben, gespeichert und genutzt. Einzelheiten dazu finden sich in den Datenschutzbestimmungen des jeweiligen Anbieters.
### Google Analytics
Diese Website benutzt Google Analytics, einen Webanalysedienst der Google Inc. ("Google"). Google Analytics verwendet sog. "Cookies", Textdateien, die auf Ihrem Computer gespeichert werden und die eine Analyse der Benutzung der Website durch Sie ermöglichen. Die durch den Cookie erzeugten Informationen über Ihre Benutzung dieser Website werden in der Regel an einen Server von Google in den USA übertragen und dort gespeichert. Im Falle der Aktivierung der IP-Anonymisierung auf dieser Webseite wird Ihre IP-Adresse von Google jedoch innerhalb von Mitgliedstaaten der Europäischen Union oder in anderen Vertragsstaaten des Abkommens über den Europäischen Wirtschaftsraum zuvor gekürzt.
Nur in Ausnahmefällen wird die volle IP-Adresse an einen Server von Google in den USA übertragen und dort gekürzt. Im Auftrag des Betreibers dieser Website wird Google diese Informationen benutzen, um Ihre Nutzung der Website auszuwerten, um Reports über die Websiteaktivitäten zusammenzustellen und um weitere mit der Websitenutzung und der Internetnutzung verbundene Dienstleistungen gegenüber dem Websitebetreiber zu erbringen. Die im Rahmen von Google Analytics von Ihrem Browser übermittelte IP-Adresse wird nicht mit anderen Daten von Google zusammengeführt.
Sie können die Speicherung der Cookies durch eine entsprechende Einstellung Ihrer Browser-Software verhindern; wir weisen Sie jedoch darauf hin, dass Sie in diesem Fall gegebenenfalls nicht sämtliche Funktionen dieser Website vollumfänglich werden nutzen können. Sie können darüber hinaus die Erfassung der durch das Cookie erzeugten und auf Ihre Nutzung der Website bezogenen Daten (inkl. Ihrer IP-Adresse) an Google sowie die Verarbeitung dieser Daten durch Google verhindern, indem sie das unter dem folgenden Link verfügbare Browser-Plugin herunterladen und installieren: [http://tools.google.com/dlpage/gaoptout?hl=de](http://tools.google.com/dlpage/gaoptout?hl=de).

View file

@ -1,47 +0,0 @@
---
layout: default
title: Home
---
<div class="posts">
{% for post in paginator.posts %}
<div class="post">
<h1 class="post-title">
{% if post.category == "links" %}
&#9875; <a href="{{ post.link }}" rel="external">{{ post.title }}</a>
{% else %}
<a href="{{ post.url }}">{{ post.title }}</a>
{% endif %}
</h1>
<span class="post-date">
{{ post.date | date_to_string }}
&middot;
<a href="{{ post.url }}" title="Permalink">&#8734;</a>
</span>
{% if post.category == "links" %}
{{ post.content }}
{% else %}
{{ post.excerpt }}
{% endif %}
</div>
{% endfor %}
</div>
<div class="pagination">
{% if paginator.next_page %}
<a class="pagination-item older" href="{{ site.baseurl }}page{{paginator.next_page}}">Older</a>
{% else %}
<span class="pagination-item older">Older</span>
{% endif %}
{% if paginator.previous_page %}
{% if paginator.page == 2 %}
<a class="pagination-item newer" href="{{ site.baseurl }}">Newer</a>
{% else %}
<a class="pagination-item newer" href="{{ site.baseurl }}page{{paginator.previous_page}}">Newer</a>
{% endif %}
{% else %}
<span class="pagination-item newer">Newer</span>
{% endif %}
</div>

View file

@ -0,0 +1,4 @@
<div>
<a href="https://keyoxide.org/028BCE9BABB5145AAAA1FB8410BE1D47E5ADFF92" rel="me">Keyoxide</a>
<a href="https://mastodon.kempkens.io/@daniel" rel="me">Mastodon</a>
</div>

View file

@ -1,6 +0,0 @@
---
layout: null
---
User-agent: *
Allow: /
Sitemap: {{ "/sitemap.xml" | prepend: site.url }}

View file

@ -1,29 +0,0 @@
---
layout: null
---
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>{{ site.url }}</loc>
<lastmod>{{ site.time | date_to_xmlschema }}</lastmod>
</url>
{% for page in site.pages %}
{% if page.title != null and page.url != '/404.html' %}
<url>
<loc>{{ site.url }}{{ page.url }}</loc>
</url>
{% endif %}
{% endfor %}
{% for post in site.posts %}
<url>
<loc>{{ site.url }}{{ post.url }}</loc>
{% if post.modified != nil %}
<lastmod>{{ post.modified | date_to_xmlschema }}</lastmod>
{% else %}
<lastmod>{{ post.date | date_to_xmlschema }}</lastmod>
{% endif %}
</url>
{% endfor %}
</urlset>

View file

Before

Width:  |  Height:  |  Size: 228 KiB

After

Width:  |  Height:  |  Size: 228 KiB

View file

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

1
themes/terminal Submodule

@ -0,0 +1 @@
Subproject commit e0213b0d4eaa399770a11be1faff89529b2ced0b