add gopher versions for feeds - www.codemadness.org - www.codemadness.org saait content files
HTML git clone git://git.codemadness.org/www.codemadness.org
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
DIR commit aa794d3b1909cb60c010583a2da9a3582cdc6b16
DIR parent 1af725a4591bf8242badc4baffec0bfc7fd038b0
HTML Author: Hiltjo Posthuma <hiltjo@codemadness.org>
Date: Fri, 16 May 2025 12:23:14 +0200
add gopher versions for feeds
Requested by kroovy, thanks.
Diffstat:
M Makefile | 5 +++++
M config.cfg | 5 ++++-
M feeds/pages/atom.md | 19 ++++++++++++++-----
M output/atom.html | 15 +++++++++++++--
M output/atom.md | 19 ++++++++++++++-----
M output/atom.xml | 2 +-
M output/atom_content.xml | 2 +-
A output/atom_content_gopher.xml | 3663 ++++++++++++++++++++++++++++++
A output/atom_gopher.xml | 429 +++++++++++++++++++++++++++++++
A output/jsonfeed.json | 285 +++++++++++++++++++++++++++++++
A output/jsonfeed_content.json | 285 +++++++++++++++++++++++++++++++
A output/jsonfeed_content_gopher.json | 285 +++++++++++++++++++++++++++++++
A output/jsonfeed_gopher.json | 285 +++++++++++++++++++++++++++++++
A output/mailservice.html | 184 +++++++++++++++++++++++++++++++
A output/mailservice.md | 150 +++++++++++++++++++++++++++++++
M output/phlog/atom | 13 +++++++++++--
A output/phlog/mailservice | 158 +++++++++++++++++++++++++++++++
A output/phlog/webdump | 151 +++++++++++++++++++++++++++++++
A output/phlog/xargs | 247 +++++++++++++++++++++++++++++++
A output/phlog/xml | 334 +++++++++++++++++++++++++++++++
A output/phlog/youtube-feed-improved | 167 +++++++++++++++++++++++++++++++
A output/rss_content_gopher.xml | 3489 +++++++++++++++++++++++++++++++
A output/rss_gopher.xml | 290 +++++++++++++++++++++++++++++++
A output/sfeed.tsv | 35 +++++++++++++++++++++++++++++++
A output/sfeed_content.tsv | 35 +++++++++++++++++++++++++++++++
A output/sfeed_content_gopher.tsv | 35 +++++++++++++++++++++++++++++++
A output/sfeed_gopher.tsv | 35 +++++++++++++++++++++++++++++++
A output/twtxt_gopher.txt | 35 +++++++++++++++++++++++++++++++
A output/webdump.html | 164 +++++++++++++++++++++++++++++++
A output/webdump.md | 143 +++++++++++++++++++++++++++++++
A output/xml.html | 347 +++++++++++++++++++++++++++++++
A output/xml.md | 326 +++++++++++++++++++++++++++++++
A templates/atom_content_gopher.xml/… | 1 +
A templates/atom_content_gopher.xml/… | 8 ++++++++
A templates/atom_content_gopher.xml/… | 15 +++++++++++++++
A templates/atom_gopher.xml/footer.x… | 1 +
A templates/atom_gopher.xml/header.x… | 8 ++++++++
A templates/atom_gopher.xml/item.xml | 12 ++++++++++++
A templates/rss_content_gopher.xml/f… | 2 ++
A templates/rss_content_gopher.xml/h… | 8 ++++++++
A templates/rss_content_gopher.xml/i… | 10 ++++++++++
A templates/rss_gopher.xml/footer.xml | 2 ++
A templates/rss_gopher.xml/header.xml | 8 ++++++++
A templates/rss_gopher.xml/item.xml | 8 ++++++++
A templates/twtxt_gopher.txt/item.txt | 1 +
45 files changed, 11704 insertions(+), 17 deletions(-)
---
DIR diff --git a/Makefile b/Makefile
@@ -14,9 +14,14 @@ generate: markdown sfeeddoc
# Atom to JSON Feed 1.1
sfeed < output/atom.xml | sfeed_json > output/jsonfeed.json
sfeed < output/atom_content.xml | sfeed_json > output/jsonfeed_content.json
+ sfeed < output/atom_gopher.xml | sfeed_json > output/jsonfeed_gopher.json
+ sfeed < output/atom_content_gopher.xml | sfeed_json > output/jsonfeed_content_gopher.json
# raw sfeed
sfeed < output/atom.xml > output/sfeed.tsv
sfeed < output/atom_content.xml > output/sfeed_content.tsv
+ sfeed < output/atom_gopher.xml > output/sfeed_gopher.tsv
+ sfeed < output/atom_content_gopher.xml > output/sfeed_content_gopher.tsv
+
# Atom feed page
saait -t feeds/templates feeds/pages/atom.cfg
# move index.gopher to index.
DIR diff --git a/config.cfg b/config.cfg
@@ -1,10 +1,13 @@
# last updated the site.
-siteupdated = 2025-05-03
+siteupdated = 2025-05-16
sitetitle = Codemadness
siteurl = https://www.codemadness.org
sitemail = hiltjo@AT@codemadness.DOT.org
sitegenerator = Static content generated using saait: https://codemadness.org/saait.html
+gopherurl = gopher://codemadness.org
+gophersurl = gophers://codemadness.org
+gopherphlog = /phlog/
lang = en
author = Hiltjo
DIR diff --git a/feeds/pages/atom.md b/feeds/pages/atom.md
@@ -1,24 +1,31 @@
## Atom
* Atom feed, smaller filesize with only a summary: [atom.xml](atom.xml).
-* Atom feed with content: [atom_content.xml](atom_content.xml).
+* Atom feed with content: [atom\_content.xml](atom_content.xml).
+* Atom feed, smaller filesize with only a summary: [atom\_gopher.xml](atom_gopher.xml).
+* Atom feed with content: [atom\_content\_gopher.xml](atom_content_gopher.xml).
## RSS
* RSS feed, smaller filesize with only a summary: [rss.xml](rss.xml).
-* RSS feed with content: [rss_content.xml](rss_content.xml).
+* RSS feed with content: [rss\_content.xml](rss_content.xml).
+* RSS feed, smaller filesize with only a summary, gopher: [rss\_gopher.xml](rss_gopher.xml).
+* RSS feed with content, gopher: [rss\_content\_gopher.xml](rss_content_gopher.xml).
## [Twtxt](https://twtxt.readthedocs.io/en/stable/)
-Twtxt feed: [twtxt.txt](twtxt.txt).
+* Twtxt feed: [twtxt.txt](twtxt.txt).
+* Twtxt feed, gopher: [twtxt\_gopher.txt](twtxt_gopher.txt).
## [sfeed TAB-separated format](sfeed.1.txt)
* Feed, smaller filesize with only a summary: [sfeed.tsv](sfeed.tsv).
-* Feed with content: [sfeed_content.tsv](sfeed_content.tsv).
+* Feed with content: [sfeed\_content.tsv](sfeed_content.tsv).
+* Feed, smaller filesize with only a summary, gopher: [sfeed\_gopher.tsv](sfeed_gopher.tsv).
+* Feed with content, gopher: [sfeed\_content\_gopher.tsv](sfeed_content_gopher.tsv).
Documentation:
* [sfeed TAB-separated data format documentation](sfeed.1.txt).
@@ -28,7 +35,9 @@ Documentation:
## [JSONfeed 1.1](https://www.jsonfeed.org/version/1.1/)
* JSONfeed 1.1 feed, smaller filesize with only a summary: [jsonfeed.json](jsonfeed.json).
-* JSONfeed 1.1 feed with content: [jsonfeed_content.json](jsonfeed_content.json).
+* JSONfeed 1.1 feed with content: [jsonfeed\_content.json](jsonfeed\_content.json).
+* JSONfeed 1.1 feed, smaller filesize with only a summary, gopher: [jsonfeed\_gopher.json](jsonfeed_gopher.json).
+* JSONfeed 1.1 feed with content, gopher: [jsonfeed\_content\_gopher.json](jsonfeed_content_gopher.json).
## Atom feeds for the git repositories
DIR diff --git a/output/atom.html b/output/atom.html
@@ -47,18 +47,27 @@
<ul>
<li>Atom feed, smaller filesize with only a summary: <a href="atom.xml">atom.xml</a>.</li>
<li>Atom feed with content: <a href="atom_content.xml">atom_content.xml</a>.</li>
+<li>Atom feed, smaller filesize with only a summary: <a href="atom_gopher.xml">atom_gopher.xml</a>.</li>
+<li>Atom feed with content: <a href="atom_content_gopher.xml">atom_content_gopher.xml</a>.</li>
</ul>
<h2>RSS</h2>
<ul>
<li>RSS feed, smaller filesize with only a summary: <a href="rss.xml">rss.xml</a>.</li>
<li>RSS feed with content: <a href="rss_content.xml">rss_content.xml</a>.</li>
+<li>RSS feed, smaller filesize with only a summary, gopher: <a href="rss_gopher.xml">rss_gopher.xml</a>.</li>
+<li>RSS feed with content, gopher: <a href="rss_content_gopher.xml">rss_content_gopher.xml</a>.</li>
</ul>
<h2><a href="https://twtxt.readthedocs.io/en/stable/">Twtxt</a></h2>
-<p>Twtxt feed: <a href="twtxt.txt">twtxt.txt</a>.</p>
+<ul>
+<li>Twtxt feed: <a href="twtxt.txt">twtxt.txt</a>.</li>
+<li>Twtxt feed, gopher: <a href="twtxt_gopher.txt">twtxt_gopher.txt</a>.</li>
+</ul>
<h2><a href="sfeed.1.txt">sfeed TAB-separated format</a></h2>
<ul>
<li>Feed, smaller filesize with only a summary: <a href="sfeed.tsv">sfeed.tsv</a>.</li>
<li>Feed with content: <a href="sfeed_content.tsv">sfeed_content.tsv</a>.</li>
+<li>Feed, smaller filesize with only a summary, gopher: <a href="sfeed_gopher.tsv">sfeed_gopher.tsv</a>.</li>
+<li>Feed with content, gopher: <a href="sfeed_content_gopher.tsv">sfeed_content_gopher.tsv</a>.</li>
</ul>
<p>Documentation:
<ul>
@@ -69,7 +78,9 @@
<h2><a href="https://www.jsonfeed.org/version/1.1/">JSONfeed 1.1</a></h2>
<ul>
<li>JSONfeed 1.1 feed, smaller filesize with only a summary: <a href="jsonfeed.json">jsonfeed.json</a>.</li>
-<li>JSONfeed 1.1 feed with content: <a href="jsonfeed_content.json">jsonfeed_content.json</a>.</li>
+<li>JSONfeed 1.1 feed with content: <a href="jsonfeed\_content.json">jsonfeed_content.json</a>.</li>
+<li>JSONfeed 1.1 feed, smaller filesize with only a summary, gopher: <a href="jsonfeed_gopher.json">jsonfeed_gopher.json</a>.</li>
+<li>JSONfeed 1.1 feed with content, gopher: <a href="jsonfeed_content_gopher.json">jsonfeed_content_gopher.json</a>.</li>
</ul>
<h2>Atom feeds for the git repositories</h2>
<p>The git <a href="/git/">repositories</a> each have an Atom feed for the commit log (atom.xml) and
DIR diff --git a/output/atom.md b/output/atom.md
@@ -1,24 +1,31 @@
## Atom
* Atom feed, smaller filesize with only a summary: [atom.xml](https://codemadness.org/atom.xml).
-* Atom feed with content: [atom_content.xml](https://codemadness.org/atom_content.xml).
+* Atom feed with content: [atom\_content.xml](https://codemadness.org/atom_content.xml).
+* Atom feed, smaller filesize with only a summary: [atom\_gopher.xml](https://codemadness.org/atom_gopher.xml).
+* Atom feed with content: [atom\_content\_gopher.xml](https://codemadness.org/atom_content_gopher.xml).
## RSS
* RSS feed, smaller filesize with only a summary: [rss.xml](https://codemadness.org/rss.xml).
-* RSS feed with content: [rss_content.xml](https://codemadness.org/rss_content.xml).
+* RSS feed with content: [rss\_content.xml](https://codemadness.org/rss_content.xml).
+* RSS feed, smaller filesize with only a summary, gopher: [rss\_gopher.xml](https://codemadness.org/rss_gopher.xml).
+* RSS feed with content, gopher: [rss\_content\_gopher.xml](https://codemadness.org/rss_content_gopher.xml).
## [Twtxt](https://twtxt.readthedocs.io/en/stable/)
-Twtxt feed: [twtxt.txt](https://codemadness.org/twtxt.txt).
+* Twtxt feed: [twtxt.txt](https://codemadness.org/twtxt.txt).
+* Twtxt feed, gopher: [twtxt\_gopher.txt](https://codemadness.org/twtxt_gopher.txt).
## [sfeed TAB-separated format](https://codemadness.org/sfeed.1.txt)
* Feed, smaller filesize with only a summary: [sfeed.tsv](https://codemadness.org/sfeed.tsv).
-* Feed with content: [sfeed_content.tsv](https://codemadness.org/sfeed_content.tsv).
+* Feed with content: [sfeed\_content.tsv](https://codemadness.org/sfeed_content.tsv).
+* Feed, smaller filesize with only a summary, gopher: [sfeed\_gopher.tsv](https://codemadness.org/sfeed_gopher.tsv).
+* Feed with content, gopher: [sfeed\_content\_gopher.tsv](https://codemadness.org/sfeed_content_gopher.tsv).
Documentation:
* [sfeed TAB-separated data format documentation](https://codemadness.org/sfeed.1.txt).
@@ -28,7 +35,9 @@ Documentation:
## [JSONfeed 1.1](https://www.jsonfeed.org/version/1.1/)
* JSONfeed 1.1 feed, smaller filesize with only a summary: [jsonfeed.json](https://codemadness.org/jsonfeed.json).
-* JSONfeed 1.1 feed with content: [jsonfeed_content.json](https://codemadness.org/jsonfeed_content.json).
+* JSONfeed 1.1 feed with content: [jsonfeed\_content.json](jsonfeed\_content.json).
+* JSONfeed 1.1 feed, smaller filesize with only a summary, gopher: [jsonfeed\_gopher.json](https://codemadness.org/jsonfeed_gopher.json).
+* JSONfeed 1.1 feed with content, gopher: [jsonfeed\_content\_gopher.json](https://codemadness.org/jsonfeed_content_gopher.json).
## Atom feeds for the git repositories
DIR diff --git a/output/atom.xml b/output/atom.xml
@@ -2,7 +2,7 @@
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
<title>Codemadness</title>
<subtitle>blog with various projects and articles about computer-related things</subtitle>
- <updated>2025-05-03T00:00:00Z</updated>
+ <updated>2025-05-16T00:00:00Z</updated>
<link rel="alternate" type="text/html" href="https://www.codemadness.org" />
<id>https://www.codemadness.org/atom.xml</id>
<link rel="self" type="application/atom+xml" href="https://www.codemadness.org/atom.xml" />
DIR diff --git a/output/atom_content.xml b/output/atom_content.xml
@@ -2,7 +2,7 @@
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
<title>Codemadness</title>
<subtitle>blog with various projects and articles about computer-related things</subtitle>
- <updated>2025-05-03T00:00:00Z</updated>
+ <updated>2025-05-16T00:00:00Z</updated>
<link rel="alternate" type="text/html" href="https://www.codemadness.org" />
<id>https://www.codemadness.org/atom_content.xml</id>
<link rel="self" type="application/atom+xml" href="https://www.codemadness.org/atom_content.xml" />
DIR diff --git a/output/atom_content_gopher.xml b/output/atom_content_gopher.xml
@@ -0,0 +1,3663 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
+ <title>Codemadness</title>
+ <subtitle>blog with various projects and articles about computer-related things</subtitle>
+ <updated>2025-05-16T00:00:00Z</updated>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org" />
+ <id>gopher://codemadness.org/0/atom_content_gopher.xml</id>
+ <link rel="self" type="application/atom+xml" href="gopher://codemadness.org/0/atom_content_gopher.xml" />
+<entry>
+ <title>Chess puzzle book generator</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/chess-puzzles" />
+ <id>gopher://codemadness.org/1/phlog/chess-puzzles</id>
+ <updated>2025-05-03T00:00:00Z</updated>
+ <published>2024-02-02T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Chess puzzle book generator</summary>
+ <content type="html"><![CDATA[<h1>Chess puzzle book generator</h1>
+ <p><strong>Last modification on </strong> <time>2025-05-03</time></p>
+ <p>This was a christmas hack for fun and non-profit.
+I wanted to write a chess puzzle book generator.
+Inspired by <a href="https://archive.org/details/1001deadlycheckm0000nunn">1001 Deadly Checkmates by John Nunn, ISBN-13: 978-1906454258</a>,
+<a href="https://www.stappenmethode.nl/en/">Steps Method workbooks</a> and other puzzle books.</p>
+<h1>Example output</h1>
+<ul>
+<li>English version: <a href="https://codemadness.org/downloads/puzzles/">https://codemadness.org/downloads/puzzles/</a></li>
+<li>Dutch version: <a href="https://hiltjo.nl/puzzles/">https://hiltjo.nl/puzzles/</a></li>
+</ul>
+<p>Terminal version:</p>
+<pre><code>curl -s 'https://codemadness.org/downloads/puzzles/index.vt' | less -R
+</code></pre>
+<p>I may or may not periodially update this page :)</p>
+<p>Time flies (since Christmas), here is a valentine edition with <a href="https://lichess.org/practice/intermediate-tactics/attraction/">attraction</a>
+puzzles (not only checkmates) using the red "love" theme.
+It is optimized for his and her pleasure:</p>
+<p><a href="https://codemadness.org/downloads/puzzles-valentine/">https://codemadness.org/downloads/puzzles-valentine/</a></p>
+<h2>Clone</h2>
+<pre><code>git clone git://git.codemadness.org/chess-puzzles
+</code></pre>
+<h2>Browse</h2>
+<p>You can browse the source-code at:</p>
+<ul>
+<li><a href="https://git.codemadness.org/chess-puzzles/">https://git.codemadness.org/chess-puzzles/</a></li>
+<li><a href="gopher://codemadness.org/1/git/chess-puzzles">gopher://codemadness.org/1/git/chess-puzzles</a></li>
+</ul>
+<h1>Quick overview of how it works</h1>
+<p>The generate.sh shellscript generates the output and files for the puzzles.</p>
+<p>The puzzles used are from the lichess.org puzzle database:
+<a href="https://database.lichess.org/#puzzles">https://database.lichess.org/#puzzles</a></p>
+<p>This database is a big CSV file containing the initial board state in the
+Forsyth-Edwards Notation (FEN) format and the moves in Universal Chess
+Interface (UCI) format. Each line contains the board state and the initial and
+solution moves.</p>
+<p>The generated index page is a HTML page, it lists the puzzles. Each puzzle on
+this page is an SVG image. This scalable image format looks good in all
+resolutions.</p>
+<h1>Open puzzle data</h1>
+<p>Lichess is an <a href="https://lichess.org/source">open-source</a> and gratis website to play on-line chess. There are
+no paid levels to unlock features. All the software hosting Lichess is
+open-source and anyone can register and play chess on it for free. Most of the
+data about the games played is also open.</p>
+<p>However, the website depends on your donations or contributions. If you can,
+<a href="https://lichess.org/about">please do so</a>.</p>
+<h1>generate.sh</h1>
+<p>Reads puzzles from the database and shuffle them. Do some rough sorting and
+categorization based on difficulty and assign score points.</p>
+<p>The random shuffling is done using a hard-coded <a href="https://en.wikipedia.org/wiki/Random_seed">random seed</a>. This means on the
+same machine with the same puzzle database it will regenerate the same sequence
+of random puzzles in a deterministic manner.</p>
+<p>It outputs HTML, with support for CSS dark mode and does not require Javascript.
+It includes a plain-text listing of the solutions in PGN notation for the
+puzzles.
+It also outputs .vt files suitable for the terminal. It uses unicode symbols
+for the chess pieces and RGB color sequence for the board theme</p>
+<h1>fen.c</h1>
+<p>This is a program written in C to read and parse the board state in FEN format
+and read the UCI moves. It can output to various formats.</p>
+<p>See the man page for detailed usage information.</p>
+<p>fen.c supports the following output formats:</p>
+<ul>
+<li>ascii - very simple ASCII mode.</li>
+<li><a href="https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation">fen</a> - output FEN of the board state (from FEN and optional played moves).</li>
+<li><a href="https://en.wikipedia.org/wiki/Portable_Game_Notation">pgn</a> - Portable Game Notation.</li>
+<li>speak - mode to output a description of the moves in words.</li>
+<li><a href="https://en.wikipedia.org/wiki/SVG">SVG</a> - Scalable Vector Graphics image.</li>
+<li>tty - Terminal output with some markup using escape codes.</li>
+</ul>
+<p>fen.c can also run in <a href="https://en.wikipedia.org/wiki/Common_Gateway_Interface">CGI</a> mode. This can be used on a HTTP server:</p>
+<p><img src="https://codemadness.org/onlyfens?fen=6k1/ppq3bp/2n2np1/5p2/2P2P2/4rBN1/PP3K1P/RQ6%20w%20-%20-%200%2023&moves=f2e3&flip=1" alt="Position from game: Rene Letelier Martner - Robert James Fischer, 1960-10-24" /></p>
+<ul>
+<li><a href="https://codemadness.org/onlyfens">https://codemadness.org/onlyfens</a></li>
+<li><a href="https://codemadness.org/onlyfens?fen=6k1/ppq3bp/2n2np1/5p2/2P2P2/4rBN1/PP3K1P/RQ6%20w%20-%20-%200%2023&moves=f2e3&flip=1">https://codemadness.org/onlyfens?fen=6k1/ppq3bp/2n2np1/5p2/2P2P2/4rBN1/PP3K1P/RQ6%20w%20-%20-%200%2023&moves=f2e3&flip=1</a></li>
+<li><a href="https://codemadness.org/onlyfens?moves=e2e4%20e7e5&flip=1&theme=green&output=svg">https://codemadness.org/onlyfens?moves=e2e4%20e7e5&flip=1&theme=green&output=svg</a></li>
+<li><a href="https://codemadness.org/onlyfens?moves=e2e4%20e7e5&output=pgn">https://codemadness.org/onlyfens?moves=e2e4%20e7e5&output=pgn</a></li>
+<li><a href="https://codemadness.org/onlyfens?moves=e2e4%20e7e5&output=speak">https://codemadness.org/onlyfens?moves=e2e4%20e7e5&output=speak</a></li>
+<li><a href="https://codemadness.org/onlyfens?moves=e2e4%20e7e5&output=ascii">https://codemadness.org/onlyfens?moves=e2e4%20e7e5&output=ascii</a></li>
+<li><a href="https://codemadness.org/onlyfens?moves=e2e4%20e7e5&output=fen">https://codemadness.org/onlyfens?moves=e2e4%20e7e5&output=fen</a></li>
+</ul>
+<p>Terminal output:</p>
+<pre><code>curl -s 'https://codemadness.org/onlyfens?moves=e2e4%20e7e5&output=tty'
+</code></pre>
+<h1>Support for Dutch notated PGN and output</h1>
+<p>For pgn and "speak mode" it has an option to output Dutch notated PGN or speech
+too.</p>
+<p>For example:</p>
+<ul>
+<li>Queen = Dame (Q -> D), translated: lady.</li>
+<li>Rook = Toren (R -> T), translated: tower.</li>
+<li>Bishop = Loper (B -> L), translated: walker.</li>
+<li>Knight = Paard (N -> P), translated: horse.</li>
+</ul>
+<h1>Example script to stream games from Lichess</h1>
+<p>There is an included example script that can stream Lichess games to the
+terminal. It uses the <a href="https://lichess.org/api">Lichess API</a>. It will display the board using terminal
+escape codes. The games are automatically annotated with PGN notation and with
+text how a human would say the notation. This can also be piped to a speech
+synthesizer like <a href="https://github.com/espeak-ng/espeak-ng/">espeak</a> as audio.</p>
+<p>pgn-extract is a useful tool to convert Portable Game Notation (PGN) to
+Universal Chess Interface (UCI) moves (or do many other useful chess related
+things!).</p>
+<h1>Example script to generate an animated gif from PGN</h1>
+<p>Theres also an example script included that can generate an animated gif from
+PGN using <a href="https://ffmpeg.org/">ffmpeg</a>.</p>
+<p>It creates an optimal color palette from the input images and generates an
+optimized animated gif. The last move (typically some checkmate) is displayed
+slightly longer.</p>
+<h1>References and chess related links</h1>
+<ul>
+<li><p>chess-puzzles source-code:<br />
+<a href="https://www.codemadness.org/git/chess-puzzles/file/README.html">https://www.codemadness.org/git/chess-puzzles/file/README.html</a></p>
+</li>
+<li><p>Lichess FEN puzzle database:<br />
+<a href="https://database.lichess.org/#puzzles">https://database.lichess.org/#puzzles</a></p>
+</li>
+<li><p>lichess.org:<br />
+<a href="https://lichess.org/">https://lichess.org/</a></p>
+</li>
+<li><p>SVG of the individual pieces used in fen.c:<br />
+<a href="https://github.com/lichess-org/lila/tree/master/public/piece/cburnett">https://github.com/lichess-org/lila/tree/master/public/piece/cburnett</a></p>
+</li>
+<li><p>pgn-extract:<br />
+A great multi-purpose PGN manipulation program with many options:<br />
+<a href="https://www.cs.kent.ac.uk/people/staff/djb/pgn-extract/">https://www.cs.kent.ac.uk/people/staff/djb/pgn-extract/</a></p>
+<p>An example to convert PGN games to UCI moves:<br />
+<code>pgn-extract --notags -Wuc</code></p>
+</li>
+<li><p>Lichess API:<br />
+<a href="https://lichess.org/api">https://lichess.org/api</a></p>
+</li>
+<li><p>Stockfish:<br />
+Strong open-source chess engine and analysis tool:<br />
+<a href="https://stockfishchess.org/">https://stockfishchess.org/</a></p>
+</li>
+</ul>
+]]></content>
+</entry>
+<entry>
+ <title>xargs: an example for parallel batch jobs</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/xargs" />
+ <id>gopher://codemadness.org/1/phlog/xargs</id>
+ <updated>2023-12-17T00:00:00Z</updated>
+ <published>2023-11-22T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>xargs: an example for parallel batch jobs</summary>
+ <content type="html"><![CDATA[<h1>xargs: an example for parallel batch jobs</h1>
+ <p><strong>Last modification on </strong> <time>2023-12-17</time></p>
+ <p>This describes a simple shellscript programming pattern to process a list of
+jobs in parallel. This script example is contained in one file.</p>
+<h1>Simple but less optimal example</h1>
+<pre><code>#!/bin/sh
+maxjobs=4
+
+# fake program for example purposes.
+someprogram() {
+ echo "Yep yep, I'm totally a real program!"
+ sleep "$1"
+}
+
+# run(arg1, arg2)
+run() {
+ echo "[$1] $2 started" >&2
+ someprogram "$1" >/dev/null
+ status="$?"
+ echo "[$1] $2 done" >&2
+ return "$status"
+}
+
+# process the jobs.
+j=1
+for f in 1 2 3 4 5 6 7 8 9 10; do
+ run "$f" "something" &
+
+ jm=$((j % maxjobs)) # shell arithmetic: modulo
+ test "$jm" = "0" && wait
+ j=$((j+1))
+done
+wait
+</code></pre>
+<h1>Why is this less optimal</h1>
+<p>This is less optimal because it waits until all jobs in the same batch are finished
+(each batch contain $maxjobs items).</p>
+<p>For example with 2 items per batch and 4 total jobs it could be:</p>
+<ul>
+<li>Job 1 is started.</li>
+<li>Job 2 is started.</li>
+<li>Job 2 is done.</li>
+<li>Job 1 is done.</li>
+<li>Wait: wait on process status of all background processes.</li>
+<li>Job 3 in new batch is started.</li>
+</ul>
+<p>This could be optimized to:</p>
+<ul>
+<li>Job 1 is started.</li>
+<li>Job 2 is started.</li>
+<li>Job 2 is done.</li>
+<li>Job 3 in new batch is started (immediately).</li>
+<li>Job 1 is done.</li>
+<li>...</li>
+</ul>
+<p>It also does not handle signals such as SIGINT (^C). However the xargs example
+below does:</p>
+<h1>Example</h1>
+<pre><code>#!/bin/sh
+maxjobs=4
+
+# fake program for example purposes.
+someprogram() {
+ echo "Yep yep, I'm totally a real program!"
+ sleep "$1"
+}
+
+# run(arg1, arg2)
+run() {
+ echo "[$1] $2 started" >&2
+ someprogram "$1" >/dev/null
+ status="$?"
+ echo "[$1] $2 done" >&2
+ return "$status"
+}
+
+# child process job.
+if test "$CHILD_MODE" = "1"; then
+ run "$1" "$2"
+ exit "$?"
+fi
+
+# generate a list of jobs for processing.
+list() {
+ for f in 1 2 3 4 5 6 7 8 9 10; do
+ printf '%s\0%s\0' "$f" "something"
+ done
+}
+
+# process jobs in parallel.
+list | CHILD_MODE="1" xargs -r -0 -P "${maxjobs}" -L 2 "$(readlink -f "$0")"
+</code></pre>
+<h1>Run and timings</h1>
+<p>Although the above example is kindof stupid, it already shows the queueing of
+jobs is more efficient.</p>
+<p>Script 1:</p>
+<pre><code>time ./script1.sh
+[...snip snip...]
+real 0m22.095s
+</code></pre>
+<p>Script 2:</p>
+<pre><code>time ./script2.sh
+[...snip snip...]
+real 0m18.120s
+</code></pre>
+<h1>How it works</h1>
+<p>The parent process:</p>
+<ul>
+<li>The parent, using xargs, handles the queue of jobs and schedules the jobs to
+execute as a child process.</li>
+<li>The list function writes the parameters to stdout. These parameters are
+separated by the NUL byte separator. The NUL byte separator is used because
+this character cannot be used in filenames (which can contain spaces or even
+newlines) and cannot be used in text (the NUL byte terminates the buffer for
+a string).</li>
+<li>The -L option must match the amount of arguments that are specified for the
+job. It will split the specified parameters per job.</li>
+<li>The expression "$(readlink -f "$0")" gets the absolute path to the
+shellscript itself. This is passed as the executable to run for xargs.</li>
+<li>xargs calls the script itself with the specified parameters it is being fed.
+The environment variable $CHILD_MODE is set to indicate to the script itself
+it is run as a child process of the script.</li>
+</ul>
+<p>The child process:</p>
+<ul>
+<li><p>The command-line arguments are passed by the parent using xargs.</p>
+</li>
+<li><p>The environment variable $CHILD_MODE is set to indicate to the script itself
+it is run as a child process of the script.</p>
+</li>
+<li><p>The script itself (ran in child-mode process) only executes the task and
+signals its status back to xargs and the parent.</p>
+</li>
+<li><p>The exit status of the child program is signaled to xargs. This could be
+handled, for example to stop on the first failure (in this example it is not).
+For example if the program is killed, stopped or the exit status is 255 then
+xargs stops running also.</p>
+</li>
+</ul>
+<h1>Description of used xargs options</h1>
+<p>From the OpenBSD man page: <a href="https://man.openbsd.org/xargs">https://man.openbsd.org/xargs</a></p>
+<pre><code>xargs - construct argument list(s) and execute utility
+</code></pre>
+<p>Options explained:</p>
+<ul>
+<li>-r: Do not run the command if there are no arguments. Normally the command
+is executed at least once even if there are no arguments.</li>
+<li>-0: Change xargs to expect NUL ('\0') characters as separators, instead of
+spaces and newlines.</li>
+<li>-P maxprocs: Parallel mode: run at most maxprocs invocations of utility
+at once.</li>
+<li>-L number: Call utility for every number of non-empty lines read. A line
+ending in unescaped white space and the next non-empty line are considered
+to form one single line. If EOF is reached and fewer than number lines have
+been read then utility will be called with the available lines.</li>
+</ul>
+<h1>xargs options -0 and -P, portability and historic context</h1>
+<p>Some of the options, like -P are as of writing (2023) non-POSIX:
+<a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/xargs.html">https://pubs.opengroup.org/onlinepubs/9699919799/utilities/xargs.html</a>.
+However many systems support this useful extension for many years now.</p>
+<p>The specification even mentions implementations which support parallel
+operations:</p>
+<p>"The version of xargs required by this volume of POSIX.1-2017 is required to
+wait for the completion of the invoked command before invoking another command.
+This was done because historical scripts using xargs assumed sequential
+execution. Implementations wanting to provide parallel operation of the invoked
+utilities are encouraged to add an option enabling parallel invocation, but
+should still wait for termination of all of the children before xargs
+terminates normally."</p>
+<p>Some historic context:</p>
+<p>The xargs -0 option was added on 1996-06-11 by Theo de Raadt, about a year
+after the NetBSD import (over 27 years ago at the time of writing):</p>
+<p><a href="http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/xargs/xargs.c?rev=1.2&content-type=text/x-cvsweb-markup">CVS log</a></p>
+<p>On OpenBSD the xargs -P option was added on 2003-12-06 by syncing the FreeBSD
+code:</p>
+<p><a href="http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/xargs/xargs.c?rev=1.14&content-type=text/x-cvsweb-markup">CVS log</a></p>
+<p>Looking at the imported git history log of GNU findutils (which has xargs), the
+very first commit already had the -0 and -P option:</p>
+<p><a href="https://savannah.gnu.org/git/?group=findutils">git log</a></p>
+<pre><code>commit c030b5ee33bbec3c93cddc3ca9ebec14c24dbe07
+Author: Kevin Dalley <kevin@seti.org>
+Date: Sun Feb 4 20:35:16 1996 +0000
+
+ Initial revision
+</code></pre>
+<h1>xargs: some incompatibilities found</h1>
+<ul>
+<li>Using the -0 option empty fields are handled differently in different
+implementations.</li>
+<li>The -n and -L option doesn't work correctly in many of the BSD implementations.
+Some count empty fields, some don't. In early implementations in FreeBSD and
+OpenBSD it only processed the first line. In OpenBSD it has been improved
+around 2017.</li>
+</ul>
+<p>Depending on what you want to do a workaround could be to use the -0 option
+with a single field and use the -n flag. Then in each child program invocation
+split the field by a separator.</p>
+<h1>References</h1>
+<ul>
+<li>xargs: <a href="https://man.openbsd.org/xargs">https://man.openbsd.org/xargs</a></li>
+<li>printf: <a href="https://man.openbsd.org/printf">https://man.openbsd.org/printf</a></li>
+<li>ksh, wait: <a href="https://man.openbsd.org/ksh#wait">https://man.openbsd.org/ksh#wait</a></li>
+<li>wait(2): <a href="https://man.openbsd.org/wait">https://man.openbsd.org/wait</a></li>
+</ul>
+]]></content>
+</entry>
+<entry>
+ <title>Improved Youtube RSS/Atom feed</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/youtube-feed" />
+ <id>gopher://codemadness.org/1/phlog/youtube-feed</id>
+ <updated>2023-11-20T00:00:00Z</updated>
+ <published>2023-11-20T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Improved Youtube Atom feed by adding video duration and filtering away shorts</summary>
+ <content type="html"><![CDATA[<h1>Improved Youtube RSS/Atom feed</h1>
+ <p><strong>Last modification on </strong> <time>2023-11-20</time></p>
+ <p>... improved at least for my preferences ;)</p>
+<p>It scrapes the channel data from Youtube and combines it with the parsed Atom
+feed from the channel on Youtube.</p>
+<p>The Atom parser is based on sfeed, with some of the code removed because it is
+not needed by this program. It scrapes the metadata of the videos from the
+channel its HTML page and uses my custom JSON parser to convert the
+Javascript/JSON structure.</p>
+<p>This parser is also used by the <a href="json2tsv.html">json2tsv</a> tool. It has few dependencies.</p>
+<h2>Features</h2>
+<ul>
+<li>Add the video duration to the title to quickly see how long the video is.</li>
+<li>Filter away Youtube shorts and upcoming videos / announcements: only videos are shown.</li>
+<li>Supports more output formats: Atom, <a href="https://www.jsonfeed.org/version/1.1/">JSON Feed</a> or
+<a href="sfeed.1.txt">sfeed</a> Tab-Separated-Value format.</li>
+<li>Easy to build and deploy: can be run as a CGI program as a static-linked
+binary in a chroot.</li>
+<li>Secure: additionally to running in a chroot it can use pledge(2) and unveil(2)
+on OpenBSD to restrict system calls and access to the filesystem.</li>
+</ul>
+<h2>How to use</h2>
+<p>There is an option to run directly from the command-line or in CGI-mode. When
+the environment variable $REQUEST_URI is set then it is automatically run in
+CGI mode.</p>
+<p>Command-line usage:</p>
+<pre><code>youtube_feed channelid atom
+youtube_feed channelid gph
+youtube_feed channelid html
+youtube_feed channelid json
+youtube_feed channelid tsv
+youtube_feed channelid txt
+</code></pre>
+<p>CGI program usage:</p>
+<p>The last basename part of the URL should be the channelid + the output format
+extension. It defaults to TSV if there is no extension.
+The CGI program can be used with a HTTPd or a Gopher daemon such as geomyidae.</p>
+<p>For example:</p>
+<pre><code>Atom XML: https://codemadness.org/yt-chan/UCrbvoMC0zUvPL8vjswhLOSw.xml
+HTML: https://codemadness.org/yt-chan/UCrbvoMC0zUvPL8vjswhLOSw.html
+JSON: https://codemadness.org/yt-chan/UCrbvoMC0zUvPL8vjswhLOSw.json
+TSV: https://codemadness.org/yt-chan/UCrbvoMC0zUvPL8vjswhLOSw.tsv
+twtxt: https://codemadness.org/yt-chan/UCrbvoMC0zUvPL8vjswhLOSw.txt
+TSV, default: https://codemadness.org/yt-chan/UCrbvoMC0zUvPL8vjswhLOSw
+
+Gopher dir: gopher://codemadness.org/1/feed.cgi/UCrbvoMC0zUvPL8vjswhLOSw.gph
+Gopher TSV: gopher://codemadness.org/0/feed.cgi/UCrbvoMC0zUvPL8vjswhLOSw
+</code></pre>
+<p>An OpenBSD httpd.conf using slowcgi as an example:</p>
+<pre><code>server "codemadness.org" {
+ location "/yt-chan/*" {
+ request strip 1
+ root "/cgi-bin/yt-chan"
+ fastcgi socket "/run/slowcgi.sock"
+ }
+}
+</code></pre>
+<h2>Using it with <a href="sfeed.html">sfeed</a></h2>
+<p>sfeedrc example of an existing Youtube RSS/Atom feed:</p>
+<pre><code># list of feeds to fetch:
+feeds() {
+ # feed <name> <feedurl> [basesiteurl] [encoding]
+ # normal Youtube Atom feed.
+ feed "yt IM" "https://www.youtube.com/feeds/videos.xml?channel_id=UCrbvoMC0zUvPL8vjswhLOSw"
+}
+</code></pre>
+<p>Use the new Atom feed directly using the CGI-mode and Atom output format:</p>
+<pre><code># list of feeds to fetch:
+feeds() {
+ # feed <name> <feedurl> [basesiteurl] [encoding]
+ # new Youtube Atom feed.
+ feed "idiotbox IM" "https://codemadness.org/yt-chan/UCrbvoMC0zUvPL8vjswhLOSw.xml"
+}
+</code></pre>
+<p>... or convert directly using a custom connector program on the local system via the command-line:</p>
+<pre><code># fetch(name, url, feedfile)
+fetch() {
+ case "$1" in
+ "connector example")
+ youtube_feed "$2";;
+ *)
+ curl -L --max-redirs 0 -H "User-Agent:" -f -s -m 15 \
+ "$2" 2>/dev/null;;
+ esac
+}
+
+# parse and convert input, by default XML to the sfeed(5) TSV format.
+# parse(name, feedurl, basesiteurl)
+parse() {
+ case "$1" in
+ "connector example")
+ cat;;
+ *)
+ sfeed "$3";;
+ esac
+}
+
+# list of feeds to fetch:
+feeds() {
+ # feed <name> <feedurl> [basesiteurl] [encoding]
+ feed "connector example" "UCrbvoMC0zUvPL8vjswhLOSw"
+}
+</code></pre>
+<h2>Screenshot using sfeed_curses</h2>
+<p><a href="downloads/screenshots/sfeed_curses_youtube.png"><img src="downloads/screenshots/sfeed_curses_youtube.png" alt="Screenshot showing the improved Youtube feed" width="480" height="270" loading="lazy" /></a></p>
+<h2>Clone</h2>
+<pre><code>git clone git://git.codemadness.org/frontends
+</code></pre>
+<h2>Browse</h2>
+<p>You can browse the source-code at:</p>
+<ul>
+<li><a href="https://git.codemadness.org/frontends/file/youtube/feed.c.html">https://git.codemadness.org/frontends/file/youtube/feed.c.html</a></li>
+<li><a href="gopher://codemadness.org/1/git/frontends/file/youtube/feed.c.gph">gopher://codemadness.org/1/git/frontends/file/youtube/feed.c.gph</a></li>
+</ul>
+<p>The program is: youtube/feed</p>
+<h2>Dependencies</h2>
+<ul>
+<li>C compiler.</li>
+<li>LibreSSL + libtls.</li>
+</ul>
+<h2>Build and install</h2>
+<pre><code>$ make
+# make install
+</code></pre>
+<h2>That's all</h2>
+<p>I hope by sharing this it is useful to someone other than me as well.</p>
+]]></content>
+</entry>
+<entry>
+ <title>webdump HTML to plain-text converter</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/webdump" />
+ <id>gopher://codemadness.org/1/phlog/webdump</id>
+ <updated>2025-04-25T00:00:00Z</updated>
+ <published>2023-11-20T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>webdump HTML to plain-text converter</summary>
+ <content type="html"><![CDATA[<h1>webdump HTML to plain-text converter</h1>
+ <p><strong>Last modification on </strong> <time>2025-04-25</time></p>
+ <p>webdump is (yet another) HTML to plain-text converter tool.</p>
+<p>It reads HTML in UTF-8 from stdin and writes plain-text to stdout.</p>
+<h2>Goals and scope</h2>
+<p>The main goal of this tool for me is to use it for converting HTML mails to
+plain-text and to convert HTML content in RSS feeds to plain-text.</p>
+<p>The tool will only convert HTML to stdout, similarly to links -dump or lynx
+-dump but simpler and more secure.</p>
+<ul>
+<li>HTML and XHTML will be supported.</li>
+<li>There will be some workarounds and quirks for broken and legacy HTML code.</li>
+<li>It will be usable and secure for reading HTML from mails and RSS/Atom feeds.</li>
+<li>No remote resources which are part of the HTML will be downloaded:
+images, video, audio, etc. But these may be visible as a link reference.</li>
+<li>Data will be written to stdout. Intended for plain-text or a text terminal.</li>
+<li>No support for Javascript, CSS, frame rendering or form processing.</li>
+<li>No HTTP or network protocol handling: HTML data is read from stdin.</li>
+<li>Listings for references and some options to extract them in a list that is
+usable for scripting. Some references are: link anchors, images, audio, video,
+HTML (i)frames, etc.</li>
+<li>Security: on OpenBSD it uses pledge("stdio", NULL).</li>
+<li>Keep the code relatively small, simple and hackable.</li>
+</ul>
+<h2>Features</h2>
+<ul>
+<li>Support for word-wrapping.</li>
+<li>A mode to enable basic markup: bold, underline, italic and blink ;)</li>
+<li>Indentation of headers, paragraphs, pre and list items.</li>
+<li>Basic support to query elements or hide them.</li>
+<li>Show link references.</li>
+<li>Show link references and resources such as img, video, audio, subtitles.</li>
+<li>Export link references and resources to a TAB-separated format.</li>
+</ul>
+<h2>Usage examples</h2>
+<pre><code>url='https://codemadness.org/sfeed.html'
+
+curl -s "$url" | webdump -r -b "$url" | less
+
+curl -s "$url" | webdump -8 -a -i -l -r -b "$url" | less -R
+
+curl -s "$url" | webdump -s 'main' -8 -a -i -l -r -b "$url" | less -R
+</code></pre>
+<p>Yes, all these option flags look ugly, a shellscript wrapper could be used :)</p>
+<h2>Practical examples</h2>
+<p>To use webdump as a HTML to text filter for example in the mutt mail client,
+change in ~/.mailcap:</p>
+<pre><code>text/html; webdump -i -l -r < %s; needsterminal; copiousoutput
+</code></pre>
+<p>In mutt you should then add:</p>
+<pre><code>auto_view text/html
+</code></pre>
+<p>Using webdump as a HTML to text filter for sfeed_curses (otherwise the default is lynx):</p>
+<pre><code>SFEED_HTMLCONV="webdump -d -8 -r -i -l -a" sfeed_curses ~/.sfeed/feeds/*
+</code></pre>
+<h1>Query/selector examples</h1>
+<p>The query syntax using the -s option is a bit inspired by CSS (but much more limited).</p>
+<p>To get the title from a HTML page:</p>
+<pre><code>url='https://codemadness.org/sfeed.html'
+
+title=$(curl -s "$url" | webdump -s 'title')
+printf '%s\n' "$title"
+</code></pre>
+<p>List audio and video-related content from a HTML page, redirect fd 3 to fd 1 (stdout):</p>
+<pre><code>url="https://media.ccc.de/v/051_Recent_features_to_OpenBSD-ntpd_and_bgpd"
+curl -s "$url" | webdump -x -s 'audio,video' -b "$url" 3>&1 >/dev/null | cut -f 2
+</code></pre>
+<h2>Clone</h2>
+<pre><code>git clone git://git.codemadness.org/webdump
+</code></pre>
+<h2>Browse</h2>
+<p>You can browse the source-code at:</p>
+<ul>
+<li><a href="https://git.codemadness.org/webdump/">https://git.codemadness.org/webdump/</a></li>
+<li><a href="gopher://codemadness.org/1/git/webdump">gopher://codemadness.org/1/git/webdump</a></li>
+</ul>
+<h2>Download releases</h2>
+<p>Releases are available at:</p>
+<ul>
+<li><a href="https://codemadness.org/releases/webdump/">https://codemadness.org/releases/webdump/</a></li>
+<li><a href="gopher://codemadness.org/1/releases/webdump">gopher://codemadness.org/1/releases/webdump</a></li>
+</ul>
+<h2>Build and install</h2>
+<pre><code>$ make
+# make install
+</code></pre>
+<h2>Dependencies</h2>
+<ul>
+<li>C compiler.</li>
+<li>libc + some BSDisms.</li>
+</ul>
+<h2>Trade-offs</h2>
+<p>All software has trade-offs.</p>
+<p>webdump processes HTML in a single-pass. It does not buffer the full DOM tree.
+Although due to the nature of HTML/XML some parts like attributes need to be
+buffered.</p>
+<p>Rendering tables in webdump is very limited. Twibright Links has really nice
+table rendering. However implementing a similar feature in the current design of
+webdump would make the code much more complex. Twibright links
+processes a full DOM tree and processes the tables in multiple passes (to
+measure the table cells) etc. Of course tables can be nested also, or HTML tables
+that are used for creating layouts (these are mostly older webpages).</p>
+<p>These trade-offs and preferences are chosen for now. It may change in the
+future. Fortunately there are the usual good suspects for HTML to plain-text
+conversion, each with their own chosen trade-offs of course:</p>
+<ul>
+<li>twibright links: <a href="http://links.twibright.com/">http://links.twibright.com/</a></li>
+<li>lynx: <a href="https://lynx.invisible-island.net/">https://lynx.invisible-island.net/</a></li>
+<li>w3m: <a href="https://w3m.sourceforge.net/">https://w3m.sourceforge.net/</a></li>
+<li>xmllint (part of libxml2): <a href="https://gitlab.gnome.org/GNOME/libxml2/-/wikis/home">https://gitlab.gnome.org/GNOME/libxml2/-/wikis/home</a></li>
+<li>xmlstarlet: <a href="https://xmlstar.sourceforge.net/">https://xmlstar.sourceforge.net/</a></li>
+</ul>
+]]></content>
+</entry>
+<entry>
+ <title>Setup your own mail paste service</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/mailservice" />
+ <id>gopher://codemadness.org/1/phlog/mailservice</id>
+ <updated>2024-02-10T00:00:00Z</updated>
+ <published>2023-10-25T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Setup your own mail paste service using mblaze</summary>
+ <content type="html"><![CDATA[<h1>Setup your own mail paste service</h1>
+ <p><strong>Last modification on </strong> <time>2024-02-10</time></p>
+ <h2>How it works</h2>
+<ul>
+<li>The user sends a mail with an attachment to a certain mail address, for
+example: paste@somehost.org</li>
+<li>The mail daemon configuration has an mail alias to pipe the raw mail to a
+shellscript.</li>
+<li>This shellscript processes the raw mail contents from stdin.</li>
+</ul>
+<h2>What it does</h2>
+<ul>
+<li>Process a mail with the attachments automatically.</li>
+<li>The script processes the attachments in the mail and stores them.</li>
+<li>It will mail (back) the URL where the file(s) are stored.</li>
+</ul>
+<p>This script is tested on OpenBSD using OpenBSD smtpd and OpenBSD httpd and the
+gopher daemon geomyidae.</p>
+<h2>Install dependencies</h2>
+<p>On OpenBSD:</p>
+<pre><code>pkg_add mblaze
+</code></pre>
+<h2>smtpd mail configuration</h2>
+<p>In your mail aliases (for example /etc/mail/aliases) put:</p>
+<pre><code>paste: |/usr/local/bin/paste-mail
+</code></pre>
+<p>This pipes the mail to the script paste-mail for processing, this script is
+described below. Copy the below contents in /usr/local/bin/paste-mail</p>
+<p>Script:</p>
+<pre><code>#!/bin/sh
+
+d="/home/www/domains/www.codemadness.org/htdocs/mailpaste"
+tmpmsg=$(mktemp)
+tmpmail=$(mktemp)
+
+cleanup() {
+ rm -f "$tmpmail" "$tmpmsg"
+}
+
+# store whole mail from stdin temporarily, on exit remove temporary file.
+trap "cleanup" EXIT
+cat > "$tmpmail"
+
+# mblaze: don't store mail sequence.
+MAILSEQ=/dev/null
+export MAILSEQ
+
+# get from address (without display name).
+from=$(maddr -a -h 'From' /dev/stdin < "$tmpmail")
+
+# check if allowed or not.
+case "$from" in
+"hiltjo@codemadness.org")
+ ;;
+*)
+ exit 0;;
+esac
+
+# prevent mail loop.
+if printf '%s' "$from" | grep -q "paste@"; then
+ exit 0
+fi
+
+echo "Thank you for using the enterprise paste service." > "$tmpmsg"
+echo "" >> "$tmpmsg"
+echo "Your file(s) are available at:" >> "$tmpmsg"
+echo "" >> "$tmpmsg"
+
+# process each attachment.
+mshow -n -q -t /dev/stdin < "$tmpmail" | sed -nE 's@.*name="(.*)".*@\1@p' | while read -r name; do
+ test "$name" = "" && continue
+
+ # extract attachment.
+ tmpfile=$(mktemp -p "$d" XXXXXXXXXXXX)
+ mshow -n -O /dev/stdin "$name" < "$tmpmail" > "$tmpfile"
+
+ # use file extension.
+ ext="${name##*/}"
+ case "$ext" in
+ *.tar.*)
+ # special case: support .tar.gz, tar.bz2, etc.
+ ext="tar.${ext##*.}";;
+ *.*)
+ ext="${ext##*.}";;
+ *)
+ ext="";;
+ esac
+ ext="${ext%%*.}"
+
+ # use file extension if it is set.
+ outputfile="$tmpfile"
+ if test "$ext" != ""; then
+ outputfile="$tmpfile.$ext"
+ fi
+ mv "$tmpfile" "$outputfile"
+ b=$(basename "$outputfile")
+
+ chmod 666 "$outputfile"
+ url="gopher://codemadness.org/9/mailpaste/$b"
+
+ echo "$name:" >> "$tmpmsg"
+ echo " Text file: gopher://codemadness.org/0/mailpaste/$b" >> "$tmpmsg"
+ echo " Image file: gopher://codemadness.org/I/mailpaste/$b" >> "$tmpmsg"
+ echo " Binary file: gopher://codemadness.org/9/mailpaste/$b" >> "$tmpmsg"
+ echo "" >> "$tmpmsg"
+done
+
+echo "" >> "$tmpmsg"
+echo "Sincerely," >> "$tmpmsg"
+echo "Your friendly paste_bot" >> "$tmpmsg"
+
+# mail back the user.
+mail -r "$from" -s "Your files" "$from" < "$tmpmsg"
+
+cleanup
+</code></pre>
+<p>The mail daemon processing the mail needs of course to be able to have
+permissions to write to the specified directory. The user who received the mail
+needs to be able to read it from a location they can access and have
+permissions for it also.</p>
+<h2>Room for improvements</h2>
+<p>This is just an example script. There is room for many improvements.
+Feel free to change it in any way you like.</p>
+<h2>References</h2>
+<ul>
+<li><a href="https://man.openbsd.org/aliases">https://man.openbsd.org/aliases</a></li>
+<li><a href="https://man.openbsd.org/smtpd">https://man.openbsd.org/smtpd</a></li>
+<li><a href="https://man.openbsd.org/httpd">https://man.openbsd.org/httpd</a></li>
+<li><a href="https://github.com/leahneukirchen/mblaze">https://github.com/leahneukirchen/mblaze</a></li>
+</ul>
+<h2>Bye bye</h2>
+<p>I hope this enterprise(tm) mail service is inspirational or something ;)</p>
+]]></content>
+</entry>
+<entry>
+ <title>A simple TODO application</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/todo" />
+ <id>gopher://codemadness.org/1/phlog/todo</id>
+ <updated>2022-07-01T00:00:00Z</updated>
+ <published>2022-07-01T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>A simple TODO application workflow</summary>
+ <content type="html"><![CDATA[<h1>A simple TODO application</h1>
+ <p><strong>Last modification on </strong> <time>2022-07-01</time></p>
+ <p>This article describes a TODO application or workflow.</p>
+<h2>Workflow</h2>
+<p>It works like this:</p>
+<ul>
+<li>Open any text editor.</li>
+<li>Edit the text.</li>
+<li>Save it in a file (probably named "TODO").</li>
+<li>Feel happy about it.</li>
+</ul>
+<h2>The text format</h2>
+<p>The text format I use is this:</p>
+<pre><code>[indendations]<symbol><SPACE><item text><NEWLINE>
+</code></pre>
+<p>Most of the time an item is just one line.
+This format is just a general guideline to keep the items somewhat structured.</p>
+<h2>Symbols</h2>
+<p>Items are prefixed with a symbol.</p>
+<ul>
+<li>- is an item which is planned to be done at some point.</li>
+<li>x is an item which is done.</li>
+<li>? is an item which I'm not (yet) sure about. It can also be an idea.</li>
+</ul>
+<p>I use an indendation with a TAB before an item to indicate item dependencies.
+The items can be nested.</p>
+<p>For the prioritization I put the most important items and sections from the top
+to the bottom. These can be reshuffled as you wish of course.</p>
+<p>To delete an item you remove the line. To archive an item you keep the line.</p>
+<h2>Sections</h2>
+<p>A section is a line which has no symbol. This is like a header to group items.</p>
+<h2>Example</h2>
+<pre><code>Checklist for releasing project 0.1:
+- Test project with different compilers and check for warnings.
+- Documentation:
+ - Proofread and make sure it matches all program behaviour.
+ - Run mandoc -Tlint on the man pages.
+ ? Copy useful examples from the README file to the man page?
+- Run testsuite and check for failures before release.
+
+
+project 0.2:
+? Investigate if feature mentioned by some user is worth adding.
+</code></pre>
+<h1>Example: secure remote cloud-encrypted edit session(tm)</h1>
+<pre><code>ssh -t host 'ed TODO'
+</code></pre>
+<h1>Example: multi-user secure remote cloud-encrypted edit session(tm)</h1>
+<pre><code>ssh host
+tmux or tmux a
+ed TODO
+</code></pre>
+<h1>Example: version-controlled multi-user secure remote cloud-encrypted edit session(tm)</h1>
+<pre><code>ssh host
+tmux or tmux a
+ed TODO
+git add TODO
+git commit -m 'TODO: update'
+</code></pre>
+<h2>Pros</h2>
+<ul>
+<li>When you open the TODO file the most important items are at the top.</li>
+<li>The items are easy to read and modify with any text editor.</li>
+<li>It is easy to extend the format and use with other text tools.</li>
+<li>The format is portable: it works on sticky-notes on your CRT monitor too!</li>
+<li>No monthly online subscription needed and full NO-money-back guarantee.</li>
+</ul>
+<h2>Cons</h2>
+<ul>
+<li>Complex lists with interconnected dependencies might not work, maybe.</li>
+<li>It's assumed there is one person maintaining the TODO file. Merging items
+from multiple people at the same time in this workflow is not recommended.</li>
+<li>It is too simple: noone will be impressed by it.</li>
+</ul>
+<p>I hope this is inspirational or something,</p>
+]]></content>
+</entry>
+<entry>
+ <title>2FA TOTP without crappy authenticator apps</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/totp" />
+ <id>gopher://codemadness.org/1/phlog/totp</id>
+ <updated>2022-10-29T00:00:00Z</updated>
+ <published>2022-03-23T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Using 2FA TOTP without crappy authenticator apps</summary>
+ <content type="html"><![CDATA[<h1>2FA TOTP without crappy authenticator apps</h1>
+ <p><strong>Last modification on </strong> <time>2022-10-29</time></p>
+ <p>This describes how to use 2FA without using crappy authenticator "apps" or a
+mobile device.</p>
+<h2>Install</h2>
+<p>On OpenBSD:</p>
+<pre><code>pkg_add oath-toolkit zbar
+</code></pre>
+<p>On Void Linux:</p>
+<pre><code>xbps-install oath-toolkit zbar
+</code></pre>
+<p>There is probably a package for your operating system.</p>
+<ul>
+<li>oath-toolkit is used to generate the digits based on the secret key.</li>
+<li>zbar is used to scan the QR barcode text from the image.</li>
+</ul>
+<h2>Steps</h2>
+<p>Save the QR code image from the authenticator app, website to an image file.
+Scan the QR code text from the image:</p>
+<pre><code>zbarimg image.png
+</code></pre>
+<p>An example QR code:</p>
+<p><img src="downloads/2fa/qr.png" alt="QR code example" /></p>
+<p>The output is typically something like:</p>
+<pre><code>QR-Code:otpauth://totp/Example:someuser@codemadness.org?secret=SECRETKEY&issuer=Codemadness
+</code></pre>
+<p>You only need to scan this QR-code for the secret key once.
+Make sure to store the secret key in a private safe place and don't show it to
+anyone else.</p>
+<p>Using the secret key the following command outputs a 6-digit code by default.
+In this example we also assume the key is base32-encoded.
+There can be other parameters and options, this is documented in the Yubico URI
+string format reference below.</p>
+<p>Command:</p>
+<pre><code>oathtool --totp -b SOMEKEY
+</code></pre>
+<ul>
+<li>The --totp option uses the time-variant TOTP mode, by default it uses HMAC SHA1.</li>
+<li>The -b option uses base32 encoding of KEY instead of hex.</li>
+</ul>
+<p>Tip: you can create a script that automatically puts the digits in the
+clipboard, for example:</p>
+<pre><code>oathtool --totp -b SOMEKEY | xclip
+</code></pre>
+<h2>References</h2>
+<ul>
+<li><a href="https://linux.die.net/man/1/zbarimg">zbarimg(1) man page</a></li>
+<li><a href="https://www.nongnu.org/oath-toolkit/man-oathtool.html">oathtool(1) man page</a></li>
+<li><a href="https://datatracker.ietf.org/doc/html/rfc6238">RFC6238 - TOTP: Time-Based One-Time Password Algorithm</a></li>
+<li><a href="https://docs.yubico.com/yesdk/users-manual/application-oath/uri-string-format.html">Yubico.com - otpauth URI string format</a></li>
+</ul>
+]]></content>
+</entry>
+<entry>
+ <title>Setup an OpenBSD RISCV64 VM in QEMU</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/openbsd-riscv64-vm" />
+ <id>gopher://codemadness.org/1/phlog/openbsd-riscv64-vm</id>
+ <updated>2021-10-26T00:00:00Z</updated>
+ <published>2021-10-23T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Setup an OpenBSD RISCV-64 VM in QEMU</summary>
+ <content type="html"><![CDATA[<h1>Setup an OpenBSD RISCV64 VM in QEMU</h1>
+ <p><strong>Last modification on </strong> <time>2021-10-26</time></p>
+ <p>This describes how to setup an OpenBSD RISCV64 VM in QEMU.</p>
+<p>The shellscript below does the following:</p>
+<ul>
+<li>Set up the disk image (raw format).</li>
+<li>Patch the disk image with the OpenBSD miniroot file for the installation.</li>
+<li>Downloads the opensbi and u-boot firmware files for qemu.</li>
+<li>Run the VM with the supported settings.</li>
+</ul>
+<p>The script is tested on the host GNU/Void Linux and OpenBSD-current.</p>
+<p><strong>IMPORTANT!: The signature and checksum for the miniroot, u-boot and opensbi
+files are not verified. If the host is OpenBSD make sure to instead install the
+packages (pkg_add u-boot-riscv64 opensbi) and adjust the firmware path for the
+qemu -bios and -kernel options. </strong></p>
+<h2>Shellscript</h2>
+<pre><code>#!/bin/sh
+# mirror list: https://www.openbsd.org/ftp.html
+mirror="https://ftp.bit.nl/pub/OpenBSD/"
+release="7.0"
+minirootname="miniroot70.img"
+
+miniroot() {
+ test -f "${minirootname}" && return # download once
+
+ url="${mirror}/${release}/riscv64/${minirootname}"
+ curl -o "${minirootname}" "${url}"
+}
+
+createrootdisk() {
+ test -f disk.raw && return # create once
+ qemu-img create disk.raw 10G # create 10 GB disk
+ dd conv=notrunc if=${minirootname} of=disk.raw # write miniroot to disk
+}
+
+opensbi() {
+ f="opensbi.tgz"
+ test -f "${f}" && return # download and extract once.
+
+ url="${mirror}/${release}/packages/amd64/opensbi-0.9p0.tgz"
+ curl -o "${f}" "${url}"
+
+ tar -xzf "${f}" share/opensbi/generic/fw_jump.bin
+}
+
+uboot() {
+ f="uboot.tgz"
+ test -f "${f}" && return # download and extract once.
+
+ url="${mirror}/${release}/packages/amd64/u-boot-riscv64-2021.07p0.tgz"
+ curl -o "${f}" "${url}"
+
+ tar -xzf "${f}" share/u-boot/qemu-riscv64_smode/u-boot.bin
+}
+
+setup() {
+ miniroot
+ createrootdisk
+ opensbi
+ uboot
+}
+
+run() {
+ qemu-system-riscv64 \
+ -machine virt \
+ -nographic \
+ -m 2048M \
+ -smp 2 \
+ -bios share/opensbi/generic/fw_jump.bin \
+ -kernel share/u-boot/qemu-riscv64_smode/u-boot.bin \
+ -drive file=disk.raw,format=raw,id=hd0 -device virtio-blk-device,drive=hd0 \
+ -netdev user,id=net0,ipv6=off -device virtio-net-device,netdev=net0
+}
+
+setup
+run
+</code></pre>
+]]></content>
+</entry>
+<entry>
+ <title>Sfeed_curses: a curses UI front-end for sfeed</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/sfeed_curses" />
+ <id>gopher://codemadness.org/1/phlog/sfeed_curses</id>
+ <updated>2022-05-08T00:00:00Z</updated>
+ <published>2020-06-25T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Sfeed_curses is a curses UI front-end for the sfeed RSS/Atom parser</summary>
+ <content type="html"><![CDATA[<h1>Sfeed_curses: a curses UI front-end for sfeed</h1>
+ <p><strong>Last modification on </strong> <time>2022-05-08</time></p>
+ <p>sfeed_curses is a curses UI front-end for <a href="sfeed.html">sfeed</a>.
+It is now part of sfeed.</p>
+<p>It shows the TAB-separated feed items in a graphical command-line UI. The
+interface has a look inspired by the <a href="http://www.mutt.org/">mutt mail client</a>. It has a sidebar
+panel for the feeds, a panel with a listing of the items and a small statusbar
+for the selected item/URL. Some functions like searching and scrolling are
+integrated in the interface itself.</p>
+<h2>Features</h2>
+<ul>
+<li>Relatively few LOC, about 2.5K lines of C.</li>
+<li>Few dependencies: a C compiler and a curses library (typically ncurses).
+It also requires a terminal (emulator) supporting UTF-8.</li>
+<li>Easy to customize by modifying the small source-code and shellscripts.</li>
+<li>Quite fast.</li>
+<li>Plumb support: open the URL or an enclosure URL directly with any program.</li>
+<li>Pipe support: pipe the selected Tab-Separated Value line to a program for
+scripting purposes. Like viewing the content in any way you like.</li>
+<li>Yank support: copy the URL or an enclosure URL to the clipboard.</li>
+<li>Familiar keybinds: supports both vi-like, emacs-like and arrow keys for
+actions.</li>
+<li>Mouse support: it supports xterm X10 and extended SGR encoding.</li>
+<li>Support two ways of managing read/unread items.
+By default sfeed_curses marks the feed items of the last day as new/bold.
+Alternatively a simple plain-text list with the read URLs can be used.</li>
+<li>UI layouts: supports vertical, horizontal and monocle (full-screen) layouts.
+Useful for different kind of screen sizes.</li>
+<li>Auto-execute keybind commands at startup to automate setting a preferred
+layout, toggle showing new items or other actions.</li>
+</ul>
+<p>Like the format programs included in sfeed you can run it by giving the feed
+files as arguments like this:</p>
+<pre><code>sfeed_curses ~/.sfeed/feeds/*
+</code></pre>
+<p>... or by reading directly from stdin:</p>
+<pre><code>sfeed_curses < ~/.sfeed/feeds/xkcd
+</code></pre>
+<p>It will show a sidebar if one or more files are specified as parameters. It
+will not show the sidebar by default when reading from stdin.</p>
+<p><a href="downloads/screenshots/sfeed_curses_screenshot.png"><img src="downloads/screenshots/sfeed_curses_screenshot.png" alt="Screenshot showing what the UI looks" width="480" height="270" loading="lazy" /></a></p>
+<p>On pressing the 'o' or ENTER keybind it will open the link URL of an item with
+the plumb program. On pressing the 'a', 'e' or '@' keybind it will open the
+enclosure URL if there is one. The default plumb program is set to <a href="https://portland.freedesktop.org/doc/xdg-open.html">xdg-open</a>,
+but can be modified by setting the environment variable $SFEED_PLUMBER. The
+plumb program receives the URL as a command-line argument.</p>
+<p>The TAB-Separated-Value line of the current selected item in the feed file can
+be piped to a program by pressing the 'c', 'p' or '|' keybind. This allows much
+flexibility to make a content formatter or write other custom actions or views.
+This line is in the exact same format as described in the sfeed(5) man page.</p>
+<p>The pipe program can be changed by setting the environment variable
+$SFEED_PIPER.</p>
+<p><a href="downloads/screenshots/sfeed_curses_pipe_screenshot.png"><img src="downloads/screenshots/sfeed_curses_pipe_screenshot.png" alt="Screenshot showing the output of the pipe content script" width="480" height="270" loading="lazy" /></a></p>
+<p>The above screenshot shows the included <a href="https://git.codemadness.org/sfeed_curses/file/sfeed_content.html">sfeed_content</a> shellscript which uses
+the <a href="https://invisible-island.net/lynx/">lynx text-browser</a> to convert HTML to plain-text. It pipes the formatted
+plain-text to the user $PAGER (or "less").</p>
+<p>Of course the script can be easily changed to use a different browser or
+HTML-to-text converter like:</p>
+<ul>
+<li><a href="https://www.dillo.org/">dillo</a></li>
+<li><a href="http://www.jikos.cz/~mikulas/links/">links</a></li>
+<li><a href="http://w3m.sourceforge.net/">w3m</a></li>
+<li><a href="https://git.codemadness.org/webdump/file/README.html">webdump</a></li>
+</ul>
+<p>It's easy to modify the color-theme by changing the macros in the source-code
+or set a predefined theme at compile-time. The README file contains information
+how to set a theme. On the left a <a href="https://templeos.org/">TempleOS</a>-like color-theme on the right a
+<a href="https://newsboat.org/">newsboat</a>-like colorscheme.</p>
+<p><a href="downloads/screenshots/sfeed_curses_theme_screenshot.png"><img src="downloads/screenshots/sfeed_curses_theme_screenshot.png" alt="Screenshot showing a custom colorscheme" width="480" height="270" loading="lazy" /></a></p>
+<p>It supports a vertical layout, horizontal and monocle (full-screen) layout.
+This can be useful for different kind of screen sizes. The keybinds '1', '2'
+and '3' can be used to switch between these layouts.</p>
+<p><a href="downloads/screenshots/sfeed_curses_horizontal_screenshot.png"><img src="downloads/screenshots/sfeed_curses_horizontal_screenshot.png" alt="Screenshot showing the horizontal layout" width="480" height="270" loading="lazy" /></a></p>
+<h2>Clone</h2>
+<pre><code>git clone git://git.codemadness.org/sfeed
+</code></pre>
+<h2>Browse</h2>
+<p>You can browse the source-code at:</p>
+<ul>
+<li><a href="https://git.codemadness.org/sfeed/">https://git.codemadness.org/sfeed/</a></li>
+<li><a href="gopher://codemadness.org/1/git/sfeed">gopher://codemadness.org/1/git/sfeed</a></li>
+</ul>
+<h2>Download releases</h2>
+<p>Releases are available at:</p>
+<ul>
+<li><a href="https://codemadness.org/releases/sfeed/">https://codemadness.org/releases/sfeed/</a></li>
+<li><a href="gopher://codemadness.org/1/releases/sfeed">gopher://codemadness.org/1/releases/sfeed</a></li>
+</ul>
+<h2>Build and install</h2>
+<pre><code>$ make
+# make install
+</code></pre>
+]]></content>
+</entry>
+<entry>
+ <title>hurl: HTTP, HTTPS and Gopher file grabber</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/hurl" />
+ <id>gopher://codemadness.org/1/phlog/hurl</id>
+ <updated>2020-07-20T00:00:00Z</updated>
+ <published>2019-11-10T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>hurl: HTTP, HTTPS and Gopher file grabber</summary>
+ <content type="html"><![CDATA[<h1>hurl: HTTP, HTTPS and Gopher file grabber</h1>
+ <p><strong>Last modification on </strong> <time>2020-07-20</time></p>
+ <p>hurl is a relatively simple HTTP, HTTPS and Gopher client/file grabber.</p>
+<h2>Why?</h2>
+<p>Sometimes (or most of the time?) you just want to fetch a file via the HTTP,
+HTTPS or Gopher protocol.</p>
+<p>The focus of this tool is only this.</p>
+<h2>Features</h2>
+<ul>
+<li>Uses OpenBSD pledge(2) and unveil(2). Allow no filesystem access (writes to
+stdout).</li>
+<li>Impose time-out and maximum size limits.</li>
+<li>Use well-defined exitcodes for reliable scripting (curl sucks at this).</li>
+<li>Send as little information as possible (no User-Agent etc by default).</li>
+</ul>
+<h2>Anti-features</h2>
+<ul>
+<li>No HTTP byte range support.</li>
+<li>No HTTP User-Agent.</li>
+<li>No HTTP If-Modified-Since/If-* support.</li>
+<li>No HTTP auth support.</li>
+<li>No HTTP/2+ support.</li>
+<li>No HTTP keep-alive.</li>
+<li>No HTTP chunked-encoding support.</li>
+<li>No HTTP redirect support.</li>
+<li>No (GZIP) compression support.</li>
+<li>No cookie-jar or cookie parsing support.</li>
+<li>No Gopher text handling (".\r\n").</li>
+<li>... etc...</li>
+</ul>
+<h2>Dependencies</h2>
+<ul>
+<li>C compiler (C99).</li>
+<li>libc + some BSD functions like err() and strlcat().</li>
+<li>LibreSSL(-portable)</li>
+<li>libtls (part of LibreSSL).</li>
+</ul>
+<h2>Optional dependencies</h2>
+<ul>
+<li>POSIX make(1) (for Makefile).</li>
+<li>mandoc for documentation: <a href="https://mdocml.bsd.lv/">https://mdocml.bsd.lv/</a></li>
+</ul>
+<h2>Clone</h2>
+<pre><code>git clone git://git.codemadness.org/hurl
+</code></pre>
+<h2>Browse</h2>
+<p>You can browse the source-code at:</p>
+<ul>
+<li><a href="https://git.codemadness.org/hurl/">https://git.codemadness.org/hurl/</a></li>
+<li><a href="gopher://codemadness.org/1/git/hurl">gopher://codemadness.org/1/git/hurl</a></li>
+</ul>
+<h2>Download releases</h2>
+<p>Releases are available at:</p>
+<ul>
+<li><a href="https://codemadness.org/releases/hurl/">https://codemadness.org/releases/hurl/</a></li>
+<li><a href="gopher://codemadness.org/1/releases/hurl">gopher://codemadness.org/1/releases/hurl</a></li>
+</ul>
+<h2>Build and install</h2>
+<pre><code>$ make
+# make install
+</code></pre>
+<h2>Examples</h2>
+<p>Fetch the Atom feed from this site using a maximum filesize limit of 1MB and
+a time-out limit of 15 seconds:</p>
+<pre><code>hurl -m 1048576 -t 15 "https://codemadness.org/atom.xml"
+</code></pre>
+<p>There is an -H option to add custom headers. This way some of the anti-features
+listed above are supported. For example some CDNs like Cloudflare are known to
+block empty or certain User-Agents.</p>
+<p>User-Agent:</p>
+<pre><code>hurl -H 'User-Agent: some browser' 'https://codemadness.org/atom.xml'
+</code></pre>
+<p>HTTP Basic Auth (base64-encoded username:password):</p>
+<pre><code>hurl -H 'Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=' \
+ 'https://codemadness.org/atom.xml'
+</code></pre>
+<p>GZIP (this assumes the served response Content-Type is gzip):</p>
+<pre><code>hurl -H 'Accept-Encoding: gzip' 'https://somesite/' | gzip -d
+</code></pre>
+]]></content>
+</entry>
+<entry>
+ <title>json2tsv: a JSON to TSV converter</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/json2tsv" />
+ <id>gopher://codemadness.org/1/phlog/json2tsv</id>
+ <updated>2021-09-25T00:00:00Z</updated>
+ <published>2019-10-13T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>json2tsv: a JSON to TAB-Separated Value converter</summary>
+ <content type="html"><![CDATA[<h1>json2tsv: a JSON to TSV converter</h1>
+ <p><strong>Last modification on </strong> <time>2021-09-25</time></p>
+ <p>Convert JSON to TSV or separated output.</p>
+<p>json2tsv reads JSON data from stdin. It outputs each JSON type to a TAB-
+Separated Value format per line by default.</p>
+<h2>TAB-Separated Value format</h2>
+<p>The output format per line is:</p>
+<pre><code>nodename<TAB>type<TAB>value<LF>
+</code></pre>
+<p>Control-characters such as a newline, TAB and backslash (\n, \t and \) are
+escaped in the nodename and value fields. Other control-characters are
+removed.</p>
+<p>The type field is a single byte and can be:</p>
+<ul>
+<li>a for array</li>
+<li>b for bool</li>
+<li>n for number</li>
+<li>o for object</li>
+<li>s for string</li>
+<li>? for null</li>
+</ul>
+<p>Filtering on the first field "nodename" is easy using awk for example.</p>
+<h2>Features</h2>
+<ul>
+<li>Accepts all <strong>valid</strong> JSON.</li>
+<li>Designed to work well with existing UNIX programs like awk and grep.</li>
+<li>Straightforward and not much lines of code: about 475 lines of C.</li>
+<li>Few dependencies: C compiler (C99), libc.</li>
+<li>No need to learn a new (meta-)language for processing data.</li>
+<li>The parser supports code point decoding and UTF-16 surrogates to UTF-8.</li>
+<li>It does not output control-characters to the terminal for security reasons by
+default (but it has a -r option if needed).</li>
+<li>On OpenBSD it supports <a href="https://man.openbsd.org/pledge">pledge(2)</a> for syscall restriction:
+pledge("stdio", NULL).</li>
+<li>Supports setting a different field separator and record separator with the -F
+and -R option.</li>
+</ul>
+<h2>Cons</h2>
+<ul>
+<li>For the tool there is additional overhead by processing and filtering data
+from stdin after parsing.</li>
+<li>The parser does not do complete validation on numbers.</li>
+<li>The parser accepts some bad input such as invalid UTF-8
+(see <a href="https://tools.ietf.org/html/rfc8259#section-8.1">RFC8259 - 8.1. Character Encoding</a>).
+json2tsv reads from stdin and does not do assumptions about a "closed
+ecosystem" as described in the RFC.</li>
+<li>The parser accepts some bad JSON input and "extensions"
+(see <a href="https://tools.ietf.org/html/rfc8259#section-9">RFC8259 - 9. Parsers</a>).</li>
+<li>Encoded NUL bytes (\u0000) in strings are ignored.
+(see <a href="https://tools.ietf.org/html/rfc8259#section-9">RFC8259 - 9. Parsers</a>).
+"An implementation may set limits on the length and character contents of
+strings."</li>
+<li>The parser is not the fastest possible JSON parser (but also not the
+slowest). For example: for ease of use, at the cost of performance all
+strings are decoded, even though they may be unused.</li>
+</ul>
+<h2>Why Yet Another JSON parser?</h2>
+<p>I wanted a tool that makes parsing JSON easier and work well from the shell,
+similar to <a href="https://stedolan.github.io/jq/">jq</a>.</p>
+<p>sed and grep often work well enough for matching some value using some regex
+pattern, but it is not good enough to parse JSON correctly or to extract all
+information: just like parsing HTML/XML using some regex is not good (enough)
+or a good idea :P.</p>
+<p>I didn't want to learn a new specific <a href="https://stedolan.github.io/jq/manual/#Builtinoperatorsandfunctions">meta-language</a> which jq has and wanted
+something simpler.</p>
+<p>While it is more efficient to embed this query language for data aggregation,
+it is also less simple. In my opinion it is simpler to separate this and use
+pattern-processing by awk or an other filtering/aggregating program.</p>
+<p>For the parser, there are many JSON parsers out there, like the efficient
+<a href="https://github.com/zserge/jsmn">jsmn parser</a>, however a few parser behaviours I want to have are:</p>
+<ul>
+<li>jsmn buffers data as tokens, which is very efficient, but also a bit
+annoying as an API as it requires another layer of code to interpret the
+tokens.</li>
+<li>jsmn does not handle decoding strings by default. Which is very efficient
+if you don't need parts of the data though.</li>
+<li>jsmn does not keep context of nested structures by default, so may require
+writing custom utility functions for nested data.</li>
+</ul>
+<p>This is why I went for a parser design that uses a single callback per "node"
+type and keeps track of the current nested structure in a single array and
+emits that.</p>
+<h2>Clone</h2>
+<pre><code>git clone git://git.codemadness.org/json2tsv
+</code></pre>
+<h2>Browse</h2>
+<p>You can browse the source-code at:</p>
+<ul>
+<li><a href="https://git.codemadness.org/json2tsv/">https://git.codemadness.org/json2tsv/</a></li>
+<li><a href="gopher://codemadness.org/1/git/json2tsv">gopher://codemadness.org/1/git/json2tsv</a></li>
+</ul>
+<h2>Download releases</h2>
+<p>Releases are available at:</p>
+<ul>
+<li><a href="https://codemadness.org/releases/json2tsv/">https://codemadness.org/releases/json2tsv/</a></li>
+<li><a href="gopher://codemadness.org/1/releases/json2tsv">gopher://codemadness.org/1/releases/json2tsv</a></li>
+</ul>
+<h2>Build and install</h2>
+<pre><code>$ make
+# make install
+</code></pre>
+<h2>Examples</h2>
+<p>An usage example to parse posts of the JSON API of <a href="https://www.reddit.com/">reddit.com</a> and format them
+to a plain-text list using awk:</p>
+<pre><code>#!/bin/sh
+curl -s -H 'User-Agent:' 'https://old.reddit.com/.json?raw_json=1&limit=100' | \
+json2tsv | \
+awk -F '\t' '
+function show() {
+ if (length(o["title"]) == 0)
+ return;
+ print n ". " o["title"] " by " o["author"] " in r/" o["subreddit"];
+ print o["url"];
+ print "";
+}
+$1 == ".data.children[].data" {
+ show();
+ n++;
+ delete o;
+}
+$1 ~ /^\.data\.children\[\]\.data\.[a-zA-Z0-9_]*$/ {
+ o[substr($1, 23)] = $3;
+}
+END {
+ show();
+}'
+</code></pre>
+<h2>References</h2>
+<ul>
+<li>Sites:
+<ul>
+<li><a href="http://seriot.ch/parsing_json.php">seriot.ch - Parsing JSON is a Minefield</a></li>
+<li><a href="https://github.com/nst/JSONTestSuite">A comprehensive test suite for RFC 8259 compliant JSON parsers</a></li>
+<li><a href="https://json.org/">json.org</a></li>
+</ul>
+</li>
+<li>Current standard:
+<ul>
+<li><a href="https://tools.ietf.org/html/rfc8259">RFC8259 - The JavaScript Object Notation (JSON) Data Interchange Format</a></li>
+<li><a href="https://www.ecma-international.org/publications/standards/Ecma-404.htm">Standard ECMA-404 - The JSON Data Interchange Syntax (2nd edition (December 2017)</a></li>
+</ul>
+</li>
+<li>Historic standards:
+<ul>
+<li><a href="https://tools.ietf.org/html/rfc7159">RFC7159 - The JavaScript Object Notation (JSON) Data Interchange Format (obsolete)</a></li>
+<li><a href="https://tools.ietf.org/html/rfc7158">RFC7158 - The JavaScript Object Notation (JSON) Data Interchange Format (obsolete)</a></li>
+<li><a href="https://tools.ietf.org/html/rfc4627">RFC4627 - The JavaScript Object Notation (JSON) Data Interchange Format (obsolete, original)</a></li>
+</ul>
+</li>
+</ul>
+]]></content>
+</entry>
+<entry>
+ <title>OpenBSD: setup a local auto-installation server</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/openbsd-autoinstall" />
+ <id>gopher://codemadness.org/1/phlog/openbsd-autoinstall</id>
+ <updated>2020-04-30T00:00:00Z</updated>
+ <published>2019-04-24T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>OpenBSD: setup a local auto-installation server</summary>
+ <content type="html"><![CDATA[<h1>OpenBSD: setup a local auto-installation server</h1>
+ <p><strong>Last modification on </strong> <time>2020-04-30</time></p>
+ <p>This guide describes how to setup a local mirror and installation/upgrade
+server that requires little or no input interaction.</p>
+<h2>Setup a local HTTP mirror</h2>
+<p>The HTTP mirror will be used to fetch the base sets and (optional) custom sets.
+In this guide we will assume <strong>192.168.0.2</strong> is the local installation server
+and mirror, the CPU architecture is amd64 and the OpenBSD release version is
+6.5. We will store the files in the directory with the structure:</p>
+<pre><code>http://192.168.0.2/pub/OpenBSD/6.5/amd64/
+</code></pre>
+<p>Create the www serve directory and fetch all sets and install files
+(if needed to save space *.iso and install65.fs can be skipped):</p>
+<pre><code>$ cd /var/www/htdocs
+$ mkdir -p pub/OpenBSD/6.5/amd64/
+$ cd pub/OpenBSD/6.5/amd64/
+$ ftp 'ftp://ftp.nluug.nl/pub/OpenBSD/6.5/amd64/*'
+</code></pre>
+<p>Verify signature and check some checksums:</p>
+<pre><code>$ signify -C -p /etc/signify/openbsd-65-base.pub -x SHA256.sig
+</code></pre>
+<p>Setup <a href="https://man.openbsd.org/httpd.8">httpd(8)</a> for simple file serving:</p>
+<pre><code># $FAVORITE_EDITOR /etc/httpd.conf
+</code></pre>
+<p>A minimal example config for <a href="https://man.openbsd.org/httpd.conf.5">httpd.conf(5)</a>:</p>
+<pre><code>server "*" {
+ listen on * port 80
+}
+</code></pre>
+<p>The default www root directory is: /var/www/htdocs/</p>
+<p>Enable the httpd daemon to start by default and start it now:</p>
+<pre><code># rcctl enable httpd
+# rcctl start httpd
+</code></pre>
+<h2>Creating an installation response/answer file</h2>
+<p>The installer supports loading responses to the installation/upgrade questions
+from a simple text file. We can do a regular installation and copy the answers
+from the saved file to make an automated version of it.</p>
+<p>Do a test installation, at the end of the installation or upgrade when asked the
+question:</p>
+<pre><code>Exit to (S)hell, (H)alt or (R)eboot?
+</code></pre>
+<p>Type S to go to the shell. Find the response file for an installation and copy
+it to some USB stick or write down the response answers:</p>
+<pre><code>cp /tmp/i/install.resp /mnt/usbstick/
+</code></pre>
+<p>A response file could be for example:</p>
+<pre><code>System hostname = testvm
+Which network interface do you wish to configure = em0
+IPv4 address for em0 = dhcp
+IPv6 address for em0 = none
+Which network interface do you wish to configure = done
+Password for root account = $2b$10$IqI43aXjgD55Q3nLbRakRO/UAG6SAClL9pyk0vIUpHZSAcLx8fWk.
+Password for user testuser = $2b$10$IqI43aXjgD55Q3nLbRakRO/UAG6SAClL9pyk0vIUpHZSAcLx8fWk.
+Start sshd(8) by default = no
+Do you expect to run the X Window System = no
+Setup a user = testuser
+Full name for user testuser = testuser
+What timezone are you in = Europe/Amsterdam
+Which disk is the root disk = wd0
+Use (W)hole disk MBR, whole disk (G)PT, (O)penBSD area or (E)dit = OpenBSD
+Use (A)uto layout, (E)dit auto layout, or create (C)ustom layout = a
+Location of sets = http
+HTTP proxy URL = none
+HTTP Server = 192.168.0.2
+Server directory = pub/OpenBSD/6.5/amd64
+Unable to connect using https. Use http instead = yes
+Location of sets = http
+Set name(s) = done
+Location of sets = done
+Exit to (S)hell, (H)alt or (R)eboot = R
+</code></pre>
+<p>Get custom encrypted password for response file:</p>
+<pre><code>$ printf '%s' 'yourpassword' | encrypt
+</code></pre>
+<h2>Changing the RAMDISK kernel disk image</h2>
+<p><a href="https://man.openbsd.org/rdsetroot.8">rdsetroot(8)</a> is publicly exposed now in base since 6.5. Before 6.5 it is
+available in the /usr/src/ tree as elfrdsetroot, see also the <a href="https://man.openbsd.org/rd.4">rd(4)</a> man page.</p>
+<pre><code>$ mkdir auto
+$ cd auto
+$ cp pubdir/bsd.rd .
+$ rdsetroot -x bsd.rd disk.fs
+# vnconfig vnd0 disk.fs
+# mkdir mount
+# mount /dev/vnd0a mount
+</code></pre>
+<p>Copy the response file (install.resp) to: mount/auto_install.conf
+(installation) <strong>or</strong> mount/auto_upgrade.conf (upgrade), but not both. In this
+guide we will do an auto-installation.</p>
+<p>Unmount, detach and patch RAMDISK:</p>
+<pre><code># umount mount
+# vnconfig -u vnd0
+$ rdsetroot bsd.rd disk.fs
+</code></pre>
+<p>To test copy bsd.rd to the root of some testmachine like /bsd.test.rd then
+(re)boot and type:</p>
+<pre><code>boot /bsd.test.rd
+</code></pre>
+<p>In the future (6.5+) it will be possible to copy to a file named "/bsd.upgrade"
+in the root of a current system and automatically load the kernel:
+<a href="https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/sys/stand/boot/boot.c?rev=1.46&content-type=text/x-cvsweb-markup">See the script bsd.upgrade in CVS.</a>
+Of course this is possible with PXE boot or some custom USB/ISO also.
+As explained in the <a href="https://man.openbsd.org/autoinstall.8">autoinstall(8)</a> man page: create either an
+auto_upgrade.conf <strong>or</strong> an auto_install.conf, but not both.</p>
+<h2>Create bootable miniroot</h2>
+<p>In this example the miniroot will boot the custom kernel, but fetch all the
+sets from the local network.</p>
+<p>We will base our miniroot of the official version: miniroot65.fs.</p>
+<p>We will create a 16MB miniroot to boot from (in this guide it is assumed the
+original miniroot is about 4MB and the modified kernel image fits in the new
+allocated space):</p>
+<pre><code>$ dd if=/dev/zero of=new.fs bs=512 count=32768
+</code></pre>
+<p>Copy first part of the original image to the new disk (no truncation):</p>
+<pre><code>$ dd conv=notrunc if=miniroot65.fs of=new.fs
+# vnconfig vnd0 new.fs
+</code></pre>
+<p>Expand disk OpenBSD boundaries:</p>
+<pre><code># disklabel -E vnd0
+> b
+Starting sector: [1024]
+Size ('*' for entire disk): [8576] *
+> r
+Total free sectors: 1168.
+> c a
+Partition a is currently 8576 sectors in size, and can have a maximum
+size of 9744 sectors.
+size: [8576] *
+> w
+> q
+</code></pre>
+<p>or:</p>
+<pre><code># printf 'b\n\n*\nc a\n*\nw\n' | disklabel -E vnd0
+</code></pre>
+<p>Grow filesystem and check it and mark as clean:</p>
+<pre><code># growfs -y /dev/vnd0a
+# fsck -y /dev/vnd0a
+</code></pre>
+<p>Mount filesystem:</p>
+<pre><code># mount /dev/vnd0a mount/
+</code></pre>
+<p>The kernel on the miniroot is GZIP compressed. Compress our modified bsd.rd and
+overwrite the original kernel:</p>
+<pre><code># gzip -c9n bsd.rd > mount/bsd
+</code></pre>
+<p>Or to save space (+- 500KB) by stripping debug symbols, taken from bsd.gz target
+<a href="https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/distrib/amd64/iso/Makefile">in this Makefile</a>.</p>
+<pre><code>$ cp bsd.rd bsd.strip
+$ strip bsd.strip
+$ strip -R .comment -R .SUNW_ctf bsd.strip
+$ gzip -c9n bsd.strip > bsd.gz
+$ cp bsd.gz mount/bsd
+</code></pre>
+<p>Now unmount and detach:</p>
+<pre><code># umount mount/
+# vnconfig -u vnd0
+</code></pre>
+<p>Now you can <a href="https://man.openbsd.org/dd.1">dd(1)</a> the image new.fs to your bootable (USB) medium.</p>
+<h2>Adding custom sets (optional)</h2>
+<p>For patching <a href="https://man.openbsd.org/rc.firsttime.8">/etc/rc.firsttime</a> and other system files it is useful to use a
+customized installation set like siteVERSION.tgz, for example: site65.tgz. The
+sets can even be specified per host/MAC address like
+siteVERSION-$(hostname -s).tgz so for example: site65-testvm.tgz</p>
+<p>When the installer checks the base sets of the mirror it looks for a file
+index.txt. To add custom sets the site entries have to be added.</p>
+<p>For example:</p>
+<pre><code>-rw-r--r-- 1 1001 0 4538975 Oct 11 13:58:26 2018 site65-testvm.tgz
+</code></pre>
+<p>The filesize, permissions etc do not matter and are not checked by the
+installer. Only the filename is matched by a regular expression.</p>
+<h2>Sign custom site* tarball sets (optional)</h2>
+<p>If you have custom sets without creating a signed custom release you will be
+prompted for the messages:</p>
+<pre><code>checksum test failed
+</code></pre>
+<p>and:</p>
+<pre><code>unverified sets: continue without verification
+</code></pre>
+<p>OpenBSD uses the program <a href="https://man.openbsd.org/signify.1">signify(1)</a> to cryptographically sign and
+verify filesets.</p>
+<p>To create a custom public/private keypair (ofcourse make sure to store the
+private key privately):</p>
+<pre><code>$ signify -G -n -c "Custom 6.5 install" -p custom-65-base.pub -s custom-65-base.sec
+</code></pre>
+<p>Create new checksum file with filelist of the current directory (except SHA256*
+files):</p>
+<pre><code>$ printf '%s\n' * | grep -v SHA256 | xargs sha256 > SHA256
+</code></pre>
+<p>Sign SHA256 and store as SHA256.sig, embed signature:</p>
+<pre><code>$ signify -S -e -s /privatedir/custom-65-base.sec -m SHA256 -x SHA256.sig
+</code></pre>
+<p>Verify the created signature and data is correct:</p>
+<pre><code>$ signify -C -p /somelocation/custom-65-base.pub -x SHA256.sig
+</code></pre>
+<p>Copy <strong>only</strong> the <strong>public</strong> key to the RAMDISK:</p>
+<pre><code>$ cp custom-65-base.pub mount/etc/signify/custom-65-base.pub
+</code></pre>
+<p>Now we have to patch the install.sub file to check our public key. If you know
+a better way without having to patch this script, please let me know.</p>
+<p>Change the variable PUB_KEY in the shellscript mount/install.sub from:</p>
+<pre><code>PUB_KEY=/etc/signify/openbsd-${VERSION}-base.pub
+</code></pre>
+<p>To:</p>
+<pre><code>PUB_KEY=/etc/signify/custom-${VERSION}-base.pub
+</code></pre>
+<p>And for upgrades from:</p>
+<pre><code>$UPGRADE_BSDRD &&
+ PUB_KEY=/mnt/etc/signify/openbsd-$((VERSION + 1))-base.pub
+</code></pre>
+<p>To:</p>
+<pre><code>$UPGRADE_BSDRD &&
+ PUB_KEY=/mnt/etc/signify/custom-$((VERSION + 1))-base.pub
+</code></pre>
+<h2>Ideas</h2>
+<ul>
+<li>Patch <a href="https://man.openbsd.org/rc.firsttime.8">rc.firsttime(8)</a>: and run syspatch, add ports, setup xenodm etc.</li>
+<li>Custom partitioning scheme, see <a href="https://man.openbsd.org/autoinstall.8">autoinstall(8)</a> "URL to autopartitioning
+template for disklabel = url".</li>
+<li>Setup <a href="https://man.openbsd.org/pxeboot.8">pxeboot(8)</a> to boot and install over the network using
+<a href="https://man.openbsd.org/dhcpd.8">dhcpd(8)</a> and
+<a href="https://man.openbsd.org/tftpd.8">tftpd(8)</a> then not even some USB stick is required.</li>
+</ul>
+<h2>References</h2>
+<ul>
+<li>Main OpenBSD installation and upgrade shellscript:
+<a href="https://cvsweb.openbsd.org/src/distrib/miniroot/install.sub">/usr/src/distrib/miniroot/install.sub</a></li>
+</ul>
+]]></content>
+</entry>
+<entry>
+ <title>Idiotbox: Youtube interface</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/idiotbox" />
+ <id>gopher://codemadness.org/1/phlog/idiotbox</id>
+ <updated>2021-12-25T00:00:00Z</updated>
+ <published>2019-02-10T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Idiotbox: Youtube interface</summary>
+ <content type="html"><![CDATA[<h1>Idiotbox: Youtube interface</h1>
+ <p><strong>Last modification on </strong> <time>2021-12-25</time></p>
+ <p>Idiotbox is a less resource-heavy Youtube interface. For viewing videos it is
+recommended to use it with <a href="https://mpv.io/">mpv</a> or
+<a href="https://mplayerhq.hu/">mplayer</a> with
+<a href="https://youtube-dl.org/">youtube-dl</a> or
+<a href="https://github.com/yt-dlp/yt-dlp">yt-dlp</a>.</p>
+<p>For more (up-to-date) information see the <a href="/git/frontends/file/youtube/README.html">README</a> file.</p>
+<h2>Why</h2>
+<p>In my opinion the standard Youtube web interface is:</p>
+<ul>
+<li>Non-intuitive, too much visual crap.</li>
+<li>Too resource-hungry, both in CPU and bandwidth.</li>
+<li>Doesn't work well on simpler (text-based) browsers such as netsurf and links.</li>
+</ul>
+<h2>Features</h2>
+<ul>
+<li>Doesn't use JavaScript.</li>
+<li>Doesn't use (tracking) cookies.</li>
+<li>CSS is optional.</li>
+<li>Multiple interfaces available: HTTP CGI, command-line, Gopher CGI (gph),
+this is a work-in-progress.</li>
+<li>Doesn't use or require the Google API.</li>
+<li>CGI interface works nice in most browsers, including text-based ones.</li>
+<li>On OpenBSD it runs "sandboxed" and it can be compiled as a static-linked
+binary with <a href="https://man.openbsd.org/pledge">pledge(2)</a>,
+<a href="https://man.openbsd.org/unveil">unveil(2)</a> in a chroot.</li>
+</ul>
+<h2>Cons</h2>
+<ul>
+<li>Order by upload date is incorrect (same as on Youtube).</li>
+<li>Some Youtube features are not supported.</li>
+<li>Uses scraping so might break at any point.</li>
+</ul>
+<h2>Clone</h2>
+<pre><code>git clone git://git.codemadness.org/frontends
+</code></pre>
+<h2>Browse</h2>
+<p>You can browse the source-code at:</p>
+<ul>
+<li><a href="https://git.codemadness.org/frontends/">https://git.codemadness.org/frontends/</a></li>
+<li><a href="gopher://codemadness.org/1/git/frontends">gopher://codemadness.org/1/git/frontends</a></li>
+</ul>
+<h2>Download releases</h2>
+<p>Releases are available at:</p>
+<ul>
+<li><a href="https://codemadness.org/releases/frontends/">https://codemadness.org/releases/frontends/</a></li>
+<li><a href="gopher://codemadness.org/1/releases/frontends">gopher://codemadness.org/1/releases/frontends</a></li>
+</ul>
+<h2>View</h2>
+<p>You can view it here: <a href="https://codemadness.org/idiotbox/">https://codemadness.org/idiotbox/</a></p>
+<p>For example you can search using the query string parameter "q":
+<a href="https://codemadness.org/idiotbox/?q=gunther+tralala">https://codemadness.org/idiotbox/?q=gunther+tralala</a></p>
+<p>The gopher version is here: <a href="gopher://codemadness.org/7/idiotbox.cgi">gopher://codemadness.org/7/idiotbox.cgi</a></p>
+]]></content>
+</entry>
+<entry>
+ <title>Gopher HTTP proxy</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/gopher-proxy" />
+ <id>gopher://codemadness.org/1/phlog/gopher-proxy</id>
+ <updated>2020-08-30T00:00:00Z</updated>
+ <published>2018-08-17T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Gopher HTTP proxy</summary>
+ <content type="html"><![CDATA[<h1>Gopher HTTP proxy</h1>
+ <p><strong>Last modification on </strong> <time>2020-08-30</time></p>
+ <p>For fun I wrote a small HTTP Gopher proxy CGI program in C. It only supports
+the basic Gopher types and has some restrictions to prevent some abuse.</p>
+<p>For your regular Gopher browsing I recommend the simple Gopher client <a href="https://git.fifth.space/sacc/">sacc</a>.</p>
+<p>For more information about Gopher check out <a href="http://gopherproject.org/">gopherproject.org</a>.</p>
+<h2>Clone</h2>
+<pre><code>git clone git://git.codemadness.org/gopherproxy-c
+</code></pre>
+<h2>Browse</h2>
+<p>You can browse the source-code at:</p>
+<ul>
+<li><a href="https://git.codemadness.org/gopherproxy-c/">https://git.codemadness.org/gopherproxy-c/</a></li>
+<li><a href="gopher://codemadness.org/1/git/gopherproxy-c">gopher://codemadness.org/1/git/gopherproxy-c</a></li>
+</ul>
+<h2>View</h2>
+<p>You can view it here:
+<a href="https://codemadness.org/gopherproxy/">https://codemadness.org/gopherproxy/</a></p>
+<p>For example you can also view my gopherhole using the proxy, the query string
+parameter "q" reads the URI:
+<a href="https://codemadness.org/gopherproxy/?q=codemadness.org">https://codemadness.org/gopherproxy/?q=codemadness.org</a></p>
+]]></content>
+</entry>
+<entry>
+ <title>Setup your own file paste service</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/paste-service" />
+ <id>gopher://codemadness.org/1/phlog/paste-service</id>
+ <updated>2018-03-10T00:00:00Z</updated>
+ <published>2018-03-10T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Howto setup your own secure file paste service</summary>
+ <content type="html"><![CDATA[<h1>Setup your own file paste service</h1>
+ <p><strong>Last modification on </strong> <time>2018-03-10</time></p>
+ <h2>Setup SSH authentication</h2>
+<p>Make sure to setup SSH public key authentication so you don't need to enter a
+password each time and have a more secure authentication.</p>
+<p>For example in the file $HOME/.ssh/config:</p>
+<pre><code>Host codemadness
+ Hostname codemadness.org
+ Port 22
+ IdentityFile ~/.ssh/codemadness/id_rsa
+</code></pre>
+<p>Of course also make sure to generate the private and public keys.</p>
+<h2>Shell alias</h2>
+<p>Make an alias or function in your shell config:</p>
+<pre><code>pastesrv() {
+ ssh user@codemadness "cat > /your/www/publicdir/paste/$1"
+ echo "https://codemadness.org/paste/$1"
+}
+</code></pre>
+<p>This function reads any data from stdin and transfers the output securely via
+SSH and writes it to a file at the specified path. This path can be visible via
+HTTP, gopher or an other protocol. Then it writes the absolute URL to stdout,
+this URL can be copied to the clipboard and pasted anywhere like to an e-mail,
+IRC etc.</p>
+<h2>Usage and examples</h2>
+<p>To use it, here are some examples:</p>
+<p>Create a patch of the last commit in the git repo and store it:</p>
+<pre><code>git format-patch --stdout HEAD^ | pastesrv 'somepatch.diff'
+</code></pre>
+<p>Create a screenshot of your current desktop and paste it:</p>
+<pre><code>xscreenshot | ff2png | pastesrv 'screenshot.png'
+</code></pre>
+<p>There are many other uses of course, use your imagination :)</p>
+]]></content>
+</entry>
+<entry>
+ <title>Setup your own git hosting service</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/setup-git-hosting" />
+ <id>gopher://codemadness.org/1/phlog/setup-git-hosting</id>
+ <updated>2022-08-07T00:00:00Z</updated>
+ <published>2018-02-25T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Howto setup your own git hosting service</summary>
+ <content type="html"><![CDATA[<h1>Setup your own git hosting service</h1>
+ <p><strong>Last modification on </strong> <time>2022-08-07</time></p>
+ <p><strong>This article assumes you use OpenBSD for the service files and OS-specific
+examples.</strong></p>
+<h2>Why</h2>
+<p>A good reason to host your own git repositories is because of having and
+keeping control over your own computing infrastructure.</p>
+<p>Some bad examples:</p>
+<ul>
+<li><a href="https://en.wikipedia.org/wiki/SourceForge#Controversies">The SourceForge ads/malware/hijack controversies. Injecting malware into projects</a>.</li>
+<li><a href="https://gitlab.com/gitlab-org/gitaly/issues/2113">As of 2019-10-23 Gitlab added telemetry to their software</a>.</li>
+<li><a href="https://about.gitlab.com/blog/2019/10/10/update-free-software-and-telemetry/">On 2019-10-24 Gitlab reverted it again because many people complained</a>.</li>
+<li><a href="https://github.blog/2020-11-16-standing-up-for-developers-youtube-dl-is-back/">On 2020-11-16 Github reinstated youtube-dl, to reverse a Digital Millennium Copyright Act (DMCA) takedown</a>.</li>
+<li><a href="https://arstechnica.com/gadgets/2021/03/critics-fume-after-github-removes-exploit-code-for-exchange-vulnerabilities/">On 2021-03-11 Github (owned by Microsoft) removes exploit code for Microsoft Exchange vulnerabilities</a>.</li>
+<li><a href="https://www.bleepingcomputer.com/news/security/github-suspends-accounts-of-russian-devs-at-sanctioned-companies/">On 2022-04-16 Russian software developers are reporting that their GitHub accounts are being suspended without warning if they work for or previously worked for companies under US sanctions</a>.</li>
+<li><a href="https://www.theregister.com/2022/08/04/gitlab_data_retention_policy/">On 2022-08-04 GitLab plans to delete dormant projects in free accounts</a>.</li>
+<li><a href="https://www.theregister.com/2022/08/05/gitlab_reverses_deletion_policy/">On 2022-08-05 GitLab U-turns on deleting dormant projects after backlash</a>.</li>
+</ul>
+<p>The same thing can happen with Github, Atlassian Bitbucket or other similar
+services. After all: they are just a company with commercial interests. These
+online services also have different pricing plans and various (arbitrary)
+restrictions. When you host it yourself the restrictions are the resource
+limits of the system and your connection, therefore it is a much more flexible
+solution.</p>
+<p>Always make sure you own the software (which is <a href="https://www.gnu.org/philosophy/free-sw.html">Free</a> or open-source) and you
+can host it yourself, so you will be in control of it.</p>
+<h2>Creating repositories</h2>
+<p>For the hosting it is recommended to use a so-called "bare" repository. A bare
+repository means no files are checked out in the folder itself. To create a
+bare repository use git init with the --bare argument:</p>
+<pre><code>$ git init --bare
+</code></pre>
+<p>I recommend to create a separate user and group for the source-code
+repositories. In the examples we will assume the user is called "src".</p>
+<p>Login as the src user and create the files. To create a directory for the
+repos, in this example /home/src/src:</p>
+<pre><code>$ mkdir -p /home/src/src
+$ cd /home/src/src
+$ git init --bare someproject
+$ $EDITOR someproject/description
+</code></pre>
+<p>Make sure the git-daemon process has access permissions to these repositories.</p>
+<h2>Install git-daemon (optional)</h2>
+<p>Using git-daemon you can clone the repositories publicly using the efficient
+git:// protocol. An alternative without having to use git-daemon is by using
+(anonymous) SSH, HTTPS or any public shared filesystem.</p>
+<p>When you use a private-only repository I recommend to just use SSH without
+git-daemon because it is secure.</p>
+<p>Install the git package. The package should contain "git daemon":</p>
+<pre><code># pkg_add git
+</code></pre>
+<p>Enable the daemon:</p>
+<pre><code># rcctl enable gitdaemon
+</code></pre>
+<p>Set the gitdaemon service flags to use the src directory and use all the
+available repositories in this directory. The command-line flags "--export-all"
+exports all repositories in the base path. Alternatively you can use the
+"git-daemon-export-ok" file (see the git-daemon man page).</p>
+<pre><code># rcctl set gitdaemon flags --export-all --base-path="/home/src/src"
+</code></pre>
+<p>To configure the service to run as the user _gitdaemon:</p>
+<pre><code># rcctl set gitdaemon user _gitdaemon
+</code></pre>
+<p>To run the daemon directly as the user _gitdaemon (without dropping privileges
+from root to the user) set the following flags in /etc/rc.d/gitdaemon:</p>
+<pre><code>daemon_flags="--user=_gitdaemon"
+</code></pre>
+<p>Which will also avoid this warning while cloning:</p>
+<pre><code>"can't access /root/.git/config"
+</code></pre>
+<p>Now start the daemon:</p>
+<pre><code># rcctl start gitdaemon
+</code></pre>
+<h2>Cloning and fetching changes</h2>
+<p>To test and clone the repository do:</p>
+<pre><code>$ git clone git://yourdomain/someproject
+</code></pre>
+<p>if you skipped the optional git-daemon installation then just clone via SSH:</p>
+<pre><code>$ git clone ssh://youraccount@yourdomain:/home/src/src/someproject
+</code></pre>
+<p>When cloning via SSH make sure to setup private/public key authentication for
+security and convenience.</p>
+<p>You should also make sure the firewall allows connections to the services like
+the git daemon, HTTPd or SSH, for example using OpenBSD pf something like this
+can be set in <a href="https://man.openbsd.org/pf.conf">/etc/pf.conf</a>:</p>
+<pre><code>tcp_services="{ ssh, gopher, http, https, git }"
+pass in on egress proto tcp from any to (egress) port $tcp_services
+</code></pre>
+<h2>Pushing changes</h2>
+<p>Add the repository as a remote:</p>
+<pre><code>$ git remote add myremote ssh://youraccount@yourdomain:/home/src/src/someproject
+</code></pre>
+<p>Then push the changes:</p>
+<pre><code>$ git push myremote master:master
+</code></pre>
+<h2>Git history web browsing (optional)</h2>
+<p>Sometimes it's nice to browse the git history log of the repository in a web
+browser or some other program without having to look at the local repository.</p>
+<ul>
+<li><a href="stagit.html">Stagit</a> is a static HTML page generator for git.</li>
+<li><a href="stagit-gopher.html">Stagit-gopher</a> is a static page generator for
+<a href="http://gopherproject.org/">gopher</a> and
+<a href="gopher://bitreich.org/1/scm/geomyidae">geomyidae</a>.</li>
+<li>cgit is a CGI-based program which shows HTML views of your repository, see
+also the page: <a href="openbsd-httpd-and-cgit.html">OpenBSD httpd, slowcgi and cgit</a>.</li>
+</ul>
+<p>It's also possible with these tools to generate an Atom feed and then use a
+RSS/Atom reader to track the git history:</p>
+<ul>
+<li>An example url from cgit: <a href="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/atom/?h=master">Linux kernel tree</a>.</li>
+<li>An example url from stagit for the <a href="/git/stagit/atom.xml">commit log</a>.</li>
+<li>An example url from stagit for the <a href="/git/stagit/tags.xml">releases</a>.</li>
+</ul>
+<p>My <a href="sfeed.html">sfeed</a> program can be used as a RSS/Atom reader.</p>
+<h2>Setting up git hooks (optional)</h2>
+<p>Using git hooks you can setup automated triggers, for example when pushing to a
+repository. Some useful examples can be:</p>
+<ul>
+<li><a href="/git/stagit/file/example_post-receive.sh.html">For stagit: update the repo files (example post-receive hook).</a></li>
+<li>Send an e-mail with the commit subject and message.</li>
+<li>Log/notify commits and changes to an IRC channel using a fifo: <a href="https://tools.suckless.org/ii/">ii</a>.</li>
+<li>Create a release tarball and checksum file on a tag push/change.</li>
+<li>Checkout files for website content.</li>
+</ul>
+]]></content>
+</entry>
+<entry>
+ <title>Setup an OpenBSD SPARC64 VM in QEMU</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/openbsd-sparc64-vm" />
+ <id>gopher://codemadness.org/1/phlog/openbsd-sparc64-vm</id>
+ <updated>2020-04-18T00:00:00Z</updated>
+ <published>2017-12-11T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Setup an OpenBSD SPARC64 VM in QEMU</summary>
+ <content type="html"><![CDATA[<h1>Setup an OpenBSD SPARC64 VM in QEMU</h1>
+ <p><strong>Last modification on </strong> <time>2020-04-18</time></p>
+ <p>This describes how to setup an OpenBSD SPARC64 VM in QEMU.</p>
+<h2>Create a disk image</h2>
+<p>To create a 5GB disk image:</p>
+<pre><code>qemu-img create -f qcow2 fs.qcow2 5G
+</code></pre>
+<h2>Install</h2>
+<p>In this guide we'll use the installation ISO to install OpenBSD. Make sure to
+download the latest (stable) OpenBSD ISO, for example install62.iso.</p>
+<ul>
+<li>Change -boot c to -boot d to boot from the CD-ROM and do a clean install.</li>
+<li>Change -cdrom install62.iso to the location of your ISO file.</li>
+<li>When the install is done type: halt -p</li>
+<li>Change -boot d back to -boot c.</li>
+</ul>
+<p>Start the VM:</p>
+<pre><code>#!/bin/sh
+LC_ALL=C QEMU_AUDIO_DRV=none \
+qemu-system-sparc64 \
+ -machine sun4u,usb=off \
+ -realtime mlock=off \
+ -smp 1,sockets=1,cores=1,threads=1 \
+ -rtc base=utc \
+ -m 1024 \
+ -boot c \
+ -drive file=fs.qcow2,if=none,id=drive-ide0-0-1,format=qcow2,cache=none \
+ -cdrom install62.iso \
+ -device ide-hd,bus=ide.0,unit=0,drive=drive-ide0-0-1,id=ide0-0-1 \
+ -msg timestamp=on \
+ -serial pty -nographic \
+ -net nic,model=ne2k_pci -net user
+</code></pre>
+<p>The VM has the following properties:</p>
+<ul>
+<li>No audio.</li>
+<li>No USB.</li>
+<li>No VGA graphics: serial console.</li>
+<li>Netdev is ne0 (Realtek 8029).</li>
+<li>1024MB memory.</li>
+</ul>
+<p>From your host connect to the serial device indicated by QEMU, for example:</p>
+<pre><code>(qemu) 2017-11-19T15:14:20.884312Z qemu-system-sparc64: -serial pty: char device redirected to /dev/ttyp0 (label serial0)
+</code></pre>
+<p>Then you can use the serial terminal emulator <strong>cu</strong> to attach:</p>
+<pre><code>cu -l /dev/ttyp0
+</code></pre>
+<p>Another option could be using the <a href="https://git.suckless.org/st/">simple terminal(st)</a> from suckless.</p>
+<pre><code>st -l /dev/ttyp0
+</code></pre>
+<p>using cu to detach the <a href="https://man.openbsd.org/cu#~^D">cu(1) man page</a> says:</p>
+<pre><code>Typed characters are normally transmitted directly to the remote machine (which
+does the echoing as well). A tilde ('~') appearing as the first character of a
+line is an escape signal; the following are recognized:
+
+ ~^D or ~. Drop the connection and exit. Only the connection is
+ the login session is not terminated.
+</code></pre>
+<p>On boot you have to type:</p>
+<pre><code>root device: wd0a
+for swap use the default (wd0b) Press enter
+</code></pre>
+<h2>Initial settings on first boot (optional)</h2>
+<p>Automatic network configuration using DHCP</p>
+<pre><code>echo "dhcp" > /etc/hostname.ne0
+</code></pre>
+<p>To bring up the interface (will be automatic on the next boot):</p>
+<pre><code>sh /etc/netstart
+</code></pre>
+<p>Add a mirror to /etc/installurl for package installation. Make sure to lookup
+the most efficient/nearby mirror site on the OpenBSD mirror page.</p>
+<pre><code>echo "https://ftp.hostserver.de/pub/OpenBSD" > /etc/installurl
+</code></pre>
+]]></content>
+</entry>
+<entry>
+ <title>Tscrape: a Twitter scraper</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/tscrape" />
+ <id>gopher://codemadness.org/1/phlog/tscrape</id>
+ <updated>2020-07-20T00:00:00Z</updated>
+ <published>2017-09-24T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Tscrape: a Twitter scraper</summary>
+ <content type="html"><![CDATA[<h1>Tscrape: a Twitter scraper</h1>
+ <p><strong>Last modification on </strong> <time>2020-07-20</time></p>
+ <p>Tscrape is a Twitter web scraper and archiver.</p>
+<p>Twitter removed the functionality to follow users using a RSS feed without
+authenticating or using their API. With this program you can format tweets in
+any way you like relatively anonymously.</p>
+<p>For more (up-to-date) information see the <a href="/git/tscrape/file/README.html">README</a> file.</p>
+<h2>Clone</h2>
+<pre><code>git clone git://git.codemadness.org/tscrape
+</code></pre>
+<h2>Browse</h2>
+<p>You can browse the source-code at:</p>
+<ul>
+<li><a href="https://git.codemadness.org/tscrape/">https://git.codemadness.org/tscrape/</a></li>
+<li><a href="gopher://codemadness.org/1/git/tscrape">gopher://codemadness.org/1/git/tscrape</a></li>
+</ul>
+<h2>Download releases</h2>
+<p>Releases are available at:</p>
+<ul>
+<li><a href="https://codemadness.org/releases/tscrape/">https://codemadness.org/releases/tscrape/</a></li>
+<li><a href="gopher://codemadness.org/1/releases/tscrape">gopher://codemadness.org/1/releases/tscrape</a></li>
+</ul>
+<h2>Examples</h2>
+<p>Output format examples:</p>
+<ul>
+<li><a href="tscrape/tscrape_html.html">tscrape_html: HTML</a></li>
+<li><a href="tscrape/tscrape_plain.txt">tscrape_plain: Text</a></li>
+</ul>
+]]></content>
+</entry>
+<entry>
+ <title>jsdatatable: a small datatable Javascript</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/datatable" />
+ <id>gopher://codemadness.org/1/phlog/datatable</id>
+ <updated>2020-07-20T00:00:00Z</updated>
+ <published>2017-09-24T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>jsdatatable: a small datatable Javascript</summary>
+ <content type="html"><![CDATA[<h1>jsdatatable: a small datatable Javascript</h1>
+ <p><strong>Last modification on </strong> <time>2020-07-20</time></p>
+ <p>This is a small datatable Javascript with no dependencies.</p>
+<h2>Features</h2>
+<ul>
+<li>Small:
+<ul>
+<li>Filesize: +- 9.1KB.</li>
+<li>Lines: +- 300, not much code, so hopefully easy to understand.</li>
+<li>No dependencies on other libraries like jQuery.</li>
+</ul>
+</li>
+<li>Sorting on columns, multi-column support with shift-click.</li>
+<li>Filtering values: case-insensitively, tokenized (separated by space).</li>
+<li>Able to add custom filtering, parsing and sorting functions.</li>
+<li>Helper function for delayed (150ms) filtering, so filtering feels more
+responsive for big datasets.</li>
+<li>Permissive ISC license, see LICENSE file.</li>
+<li>"Lazy scroll" mode:
+<ul>
+<li>fixed column headers and renders only visible rows, this allows you to
+"lazily" render millions of rows.</li>
+</ul>
+</li>
+<li>Officially supported browsers are:
+<ul>
+<li>Firefox and Firefox ESR.</li>
+<li>Chrome and most recent webkit-based browsers.</li>
+<li>IE10+.</li>
+</ul>
+</li>
+</ul>
+<h2>Why? and a comparison</h2>
+<p>It was created because all the other datatable scripts suck balls.</p>
+<p>Most Javascripts nowadays have a default dependency on jQuery, Bootstrap or
+other frameworks.</p>
+<p>jQuery adds about 97KB and Bootstrap adds about 100KB to your scripts and CSS
+as a dependency. This increases the CPU, memory and bandwidth consumption and
+latency. It also adds complexity to your scripts.</p>
+<p>jQuery was mostly used for backwards-compatibility in the Internet Explorer
+days, but is most often not needed anymore. It contains functionality to query
+the DOM using CSS-like selectors, but this is now supported with for example
+document.querySelectorAll. Functionality like a JSON parser is standard
+available now: JSON.parse().</p>
+<h3>Size comparison</h3>
+<p>All sizes are not "minified" or gzipped.</p>
+<pre><code>Name | Total | JS | CSS | Images | jQuery
+---------------------------------+---------+---------+-------+--------+-------
+jsdatatable | 12.9KB | 9.1KB | 2.5KB | 1.3KB | -
+datatables.net (without plugins) | 563.4KB | 449.3KB | 16KB | 0.8KB | 97.3KB
+jdatatable | 154.6KB | 53KB | 1KB | 3.3KB | 97.3KB
+</code></pre>
+<ul>
+<li><a href="https://datatables.net/">datatables.net</a> (without plugins).</li>
+<li><a href="https://plugins.jquery.com/jdatatable/">jdatatable</a></li>
+</ul>
+<p>Of course jsdatatable has less features (less is more!), but it does 90% of
+what's needed. Because it is so small it is also much simpler to understand and
+extend with required features if needed.</p>
+<p>See also:
+<a href="https://idlewords.com/talks/website_obesity.htm">The website obesity crisis</a></p>
+<h2>Clone</h2>
+<pre><code>git clone git://git.codemadness.org/jscancer
+</code></pre>
+<h2>Browse</h2>
+<p>You can browse the source-code at:</p>
+<ul>
+<li><a href="https://git.codemadness.org/jscancer/">https://git.codemadness.org/jscancer/</a></li>
+<li><a href="gopher://codemadness.org/1/git/jscancer">gopher://codemadness.org/1/git/jscancer</a></li>
+</ul>
+<p>It is in the datatable directory.</p>
+<h2>Download releases</h2>
+<p>Releases are available at:</p>
+<ul>
+<li><a href="https://codemadness.org/releases/jscancer/">https://codemadness.org/releases/jscancer/</a></li>
+<li><a href="gopher://codemadness.org/1/releases/jscancer">gopher://codemadness.org/1/releases/jscancer</a></li>
+</ul>
+<h2>Usage</h2>
+<h3>Examples</h3>
+<p>See example.html for an example. A stylesheet file datatable.css is also
+included, it contains the icons as embedded images.</p>
+<p>A table should have the classname "datatable" set, it must contain a <thead>
+for the column headers (<td> or <th>) and <tbody> element for the data. The
+minimal code needed for a working datatable:</p>
+<pre><code><html>
+<body>
+<input class="filter-text" /><!-- optional -->
+<table class="datatable">
+ <thead><!-- columns -->
+ <tr><td>Click me</td></tr>
+ </thead>
+ <tbody><!-- data -->
+ <tr><td>a</td></tr>
+ <tr><td>b</td></tr>
+ </tbody>
+</table>
+<script type="text/javascript" src="datatable.js"></script>
+<script type="text/javascript">var datatables = datatable_autoload();</script>
+</body>
+</html>
+</code></pre>
+<h3>Column attributes</h3>
+<p>The following column attributes are supported:</p>
+<ul>
+<li>data-filterable: if "1" or "true" specifies if the column can be filtered,
+default: "true".</li>
+<li>data-parse: specifies how to parse the values, default: "string", which is
+datatable_parse_string(). See PARSING section below.</li>
+<li>data-sort: specifies how to sort the values: default: "default", which is
+datatable_sort_default(). See SORTING section below.</li>
+<li>data-sortable: if "1" or "true" specifies if the column can be sorted,
+default: "true".</li>
+</ul>
+<h3>Parsing</h3>
+<p>By default only parsing for the types: date, float, int and string are
+supported, but other types can be easily added as a function with the name:
+datatable_parse_<typename>(). The parse functions parse the data-value
+attribute when set or else the cell content (in order). Because of this
+behaviour you can set the actual values as the data-value attribute and use the
+cell content for display. This is useful to display and properly sort
+locale-aware currency, datetimes etc.</p>
+<h3>Filtering</h3>
+<p>Filtering will be done case-insensitively on the cell content and when set also
+on the data-value attribute. The filter string is split up as tokens separated
+by space. Each token must match at least once per row to display it.</p>
+<h3>Sorting</h3>
+<p>Sorting is done on the parsed values by default with the function:
+datatable_sort_default(). To change this you can set a customname string on
+the data-sort attribute on the column which translates to the function:
+datatable_sort_<customname>().</p>
+<p>In some applications locale values are used, like for currency, decimal numbers
+datetimes. Some people also like to use icons or extended HTML elements inside
+the cell. Because jsdatatable sorts on the parsed value (see section PARSING)
+it is possible to sort on the data-value attribute values and use the cell
+content for display.</p>
+<p>For example:</p>
+<ul>
+<li>currency, decimal numbers: use data-value attribute with floating-point
+number, set data-parse column to "float".</li>
+<li>date/datetimes: use data-value attribute with UNIX timestamps (type int), set
+data-parse on column to "int" or set the data-parse attribute on column to
+"date" which is datatable_parse_date(), then make sure to use Zulu times, like:
+"2016-01-01T01:02:03Z" or other time strings that are parsable as the
+data-value attribute.</li>
+<li>icons: generally use data-value attribute with integer as weight value to
+sort on, set data-parse column to "int".</li>
+</ul>
+<h3>Dynamically update data</h3>
+<p>To update data dynamically see example-ajax.html for an example how to do this.</p>
+<h3>Caveats</h3>
+<ul>
+<li>A date, integer, float or other values must be able to parse properly, when
+the parse function returns NaN, null or undefined etc. the sorting behaviour is
+also undefined. It is recommended to always set a zero value for each type.</li>
+<li><tfoot> is not supported in datatables in "lazy" mode.</li>
+</ul>
+<h2>Demo / example</h2>
+<p><strong>For the below example to work you need to have Javascript enabled.</strong></p>
+<p><a href="datatable-example.html">datatable-example.html</a></p>
+]]></content>
+</entry>
+<entry>
+ <title>Stagit-gopher: a static git page generator for gopher</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/stagit-gopher" />
+ <id>gopher://codemadness.org/1/phlog/stagit-gopher</id>
+ <updated>2021-04-11T00:00:00Z</updated>
+ <published>2017-08-04T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>a static git page generator for gopher</summary>
+ <content type="html"><![CDATA[<h1>Stagit-gopher: a static git page generator for gopher</h1>
+ <p><strong>Last modification on </strong> <time>2021-04-11</time></p>
+ <p>stagit-gopher is a static page generator for Gopher. It creates the pages as
+static <a href="http://git.r-36.net/geomyidae/">geomyidae</a> .gph files. stagit-gopher is a modified version from the
+HTML version of stagit.</p>
+<p><a href="/git/stagit-gopher/file/README.html">Read the README for more information about it.</a></p>
+<p>I also run a gopherhole and stagit-gopher, you can see how it looks here:
+<a href="gopher://codemadness.org/1/git/">gopher://codemadness.org/1/git/</a></p>
+<p><a href="https://git.fifth.space/sacc/log.html">sacc</a> is a good Gopher client to view it.</p>
+<h2>Features</h2>
+<ul>
+<li>Log of all commits from HEAD.</li>
+<li>Log and diffstat per commit.</li>
+<li>Show file tree with line numbers.</li>
+<li>Show references: local branches and tags.</li>
+<li>Detect README and LICENSE file from HEAD and link it as a webpage.</li>
+<li>Detect submodules (.gitmodules file) from HEAD and link it as a webpage.</li>
+<li>Atom feed of the commit log (atom.xml).</li>
+<li>Atom feed of the tags/refs (tags.xml).</li>
+<li>Make index page for multiple repositories with stagit-gopher-index.</li>
+<li>After generating the pages (relatively slow) serving the files is very fast,
+simple and requires little resources (because the content is static), a
+geomyidae Gopher server is required.</li>
+<li>Security: all pages are static. No CGI or dynamic code is run for the
+interface. Using it with a secure Gopher server such as geomyidae it is
+privilege-dropped and chroot(2)'d.</li>
+<li>Simple to setup: the content generation is clearly separated from serving it.
+This makes configuration as simple as copying a few directories and scripts.</li>
+<li>Usable with Gopher clients such as lynx and <a href="https://git.fifth.space/sacc/log.html">sacc</a>.</li>
+</ul>
+<h2>Cons</h2>
+<ul>
+<li>Not suitable for large repositories (2000+ commits), because diffstats are
+an expensive operation, the cache (-c flag) is a workaround for this in
+some cases.</li>
+<li>Not suitable for large repositories with many files, because all files are
+written for each execution of stagit. This is because stagit shows the lines
+of textfiles and there is no "cache" for file metadata (this would add more
+complexity to the code).</li>
+<li>Not suitable for repositories with many branches, a quite linear history is
+assumed (from HEAD).</li>
+<li>Relatively slow to run the first time (about 3 seconds for sbase,
+1500+ commits), incremental updates are faster.</li>
+<li>Does not support some of the dynamic features cgit has (for HTTP), like:
+<ul>
+<li>Snapshot tarballs per commit.</li>
+<li>File tree per commit.</li>
+<li>History log of branches diverged from HEAD.</li>
+<li>Stats (git shortlog -s).</li>
+</ul>
+</li>
+</ul>
+<p>This is by design, just use git locally.</p>
+<h2>Clone</h2>
+<pre><code>git clone git://git.codemadness.org/stagit-gopher
+</code></pre>
+<h2>Browse</h2>
+<p>You can browse the source-code at:</p>
+<ul>
+<li><a href="https://git.codemadness.org/stagit-gopher/">https://git.codemadness.org/stagit-gopher/</a></li>
+<li><a href="gopher://codemadness.org/1/git/stagit-gopher">gopher://codemadness.org/1/git/stagit-gopher</a></li>
+</ul>
+<h2>Download releases</h2>
+<p>Releases are available at:</p>
+<ul>
+<li><a href="https://codemadness.org/releases/stagit-gopher/">https://codemadness.org/releases/stagit-gopher/</a></li>
+<li><a href="gopher://codemadness.org/1/releases/stagit-gopher">gopher://codemadness.org/1/releases/stagit-gopher</a></li>
+</ul>
+]]></content>
+</entry>
+<entry>
+ <title>Saait: a boring HTML page generator</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/saait" />
+ <id>gopher://codemadness.org/1/phlog/saait</id>
+ <updated>2020-07-20T00:00:00Z</updated>
+ <published>2017-06-10T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Saait: a boring HTML page generator</summary>
+ <content type="html"><![CDATA[<h1>Saait: a boring HTML page generator</h1>
+ <p><strong>Last modification on </strong> <time>2020-07-20</time></p>
+ <p>Saait is the most boring static HTML page generator.</p>
+<p>Meaning of saai (dutch): boring. Pronunciation: site</p>
+<p><a href="/git/saait/file/README.html">Read the README for more information about it.</a></p>
+<p>I used to use <a href="/git/static-site-scripts/files.html">shellscripts</a> to generate the static pages, but realised I
+wanted a small program that works on each platform consistently. There are
+many incompatibilities or unimplemented features in base tools across different
+platforms: Linux, UNIX, Windows.</p>
+<p>This site is created using saait.</p>
+<h2>Features</h2>
+<ul>
+<li>Single small binary that handles all the things. At run-time no dependency on
+other tools.</li>
+<li>Few lines of code (about 575 lines of C) and no dependencies except: a C
+compiler and libc.</li>
+<li>Works on most platforms: tested on Linux, *BSD, Windows.</li>
+<li>Simple template syntax.</li>
+<li>Uses HTML output by default, but can easily be modified to generate any
+textual content, like gopher pages, wiki pages or other kinds of documents.</li>
+<li>Out-of-the-box supports: creating an index page of all pages, Atom feed,
+twtxt.txt feed, sitemap.xml and urllist.txt.</li>
+</ul>
+<h2>Cons</h2>
+<ul>
+<li>Simple template syntax, but very basic. Requires C knowledge to extend it if
+needed.</li>
+<li>Only basic (no nested) template blocks supported.</li>
+</ul>
+<h2>Clone</h2>
+<pre><code>git clone git://git.codemadness.org/saait
+</code></pre>
+<h2>Browse</h2>
+<p>You can browse the source-code at:</p>
+<ul>
+<li><a href="https://git.codemadness.org/saait/">https://git.codemadness.org/saait/</a></li>
+<li><a href="gopher://codemadness.org/1/git/saait">gopher://codemadness.org/1/git/saait</a></li>
+</ul>
+<h2>Download releases</h2>
+<p>Releases are available at:</p>
+<ul>
+<li><a href="https://codemadness.org/releases/saait/">https://codemadness.org/releases/saait/</a></li>
+<li><a href="gopher://codemadness.org/1/releases/saait">gopher://codemadness.org/1/releases/saait</a></li>
+</ul>
+<h2>Documentation / man page</h2>
+<p>Below is the saait(1) man page, which includes usage examples.</p>
+<pre><code>
+SAAIT(1) General Commands Manual SAAIT(1)
+
+NAME
+ saait the most boring static page generator
+
+SYNOPSIS
+ saait [-c configfile] [-o outputdir] [-t templatesdir] pages...
+
+DESCRIPTION
+ saait writes HTML pages to the output directory.
+
+ The arguments pages are page config files, which are processed in the
+ given order.
+
+ The options are as follows:
+
+ -c configfile
+ The global configuration file, the default is "config.cfg". Each
+ page configuration file inherits variables from this file. These
+ variables can be overwritten per page.
+
+ -o outputdir
+ The output directory, the default is "output".
+
+ -t templatesdir
+ The templates directory, the default is "templates".
+
+DIRECTORY AND FILE STRUCTURE
+ A recommended directory structure for pages, although the names can be
+ anything:
+ pages/001-page.cfg
+ pages/001-page.html
+ pages/002-page.cfg
+ pages/002-page.html
+
+ The directory and file structure for templates must be:
+ templates/<templatename>/header.ext
+ templates/<templatename>/item.ext
+ templates/<templatename>/footer.ext
+
+ The following filename prefixes are detected for template blocks and
+ processed in this order:
+
+ "header."
+ Header block.
+
+ "item."
+ Item block.
+
+ "footer."
+ Footer block.
+
+ The files are saved as output/<templatename>, for example
+ templates/atom.xml/* will become: output/atom.xml. If a template block
+ file does not exist then it is treated as if it was empty.
+
+ Template directories starting with a dot (".") are ignored.
+
+ The "page" templatename is special and will be used per page.
+
+CONFIG FILE
+ A config file has a simple key=value configuration syntax, for example:
+
+ # this is a comment line.
+ filename = example.html
+ title = Example page
+ description = This is an example page
+ created = 2009-04-12
+ updated = 2009-04-14
+
+ The following variable names are special with their respective defaults:
+
+ contentfile
+ Path to the input content filename, by default this is the path
+ of the config file with the last extension replaced to ".html".
+
+ filename
+ The filename or relative file path for the output file for this
+ page. By default the value is the basename of the contentfile.
+ The path of the written output file is the value of filename
+ appended to the outputdir path.
+
+ A line starting with # is a comment and is ignored.
+
+ TABs and spaces before and after a variable name are ignored. TABs and
+ spaces before a value are ignored.
+
+TEMPLATES
+ A template (block) is text. Variables are replaced with the values set
+ in the config files.
+
+ The possible operators for variables are:
+
+ $ Escapes a XML string, for example: < to the entity &lt;.
+
+ # Literal raw string value.
+
+ % Insert contents of file of the value of the variable.
+
+ For example in a HTML item template:
+
+ <article>
+ <header>
+ <h1><a href="">${title}</a></h1>
+ <p>
+ <strong>Last modification on </strong>
+ <time datetime="${updated}">${updated}</time>
+ </p>
+ </header>
+ %{contentfile}
+ </article>
+
+EXIT STATUS
+ The saait utility exits 0 on success, and >0 if an error occurs.
+
+EXAMPLES
+ A basic usage example:
+
+ 1. Create a directory for a new site:
+
+ mkdir newsite
+
+ 2. Copy the example pages, templates, global config file and example
+ stylesheets to a directory:
+
+ cp -r pages templates config.cfg style.css print.css newsite/
+
+ 3. Change the current directory to the created directory.
+
+ cd newsite/
+
+ 4. Change the values in the global config.cfg file.
+
+ 5. If you want to modify parts of the header, like the navigation menu
+ items, you can change the following two template files:
+ templates/page/header.html
+ templates/index.html/header.html
+
+ 6. Create any new pages in the pages directory. For each config file
+ there has to be a corresponding HTML file. By default this HTML
+ file has the path of the config file, but with the last extension
+ (".cfg" in this case) replaced to ".html".
+
+ 7. Create an output directory:
+
+ mkdir -p output
+
+ 8. After any modifications the following commands can be used to
+ generate the output and process the pages in descending order:
+
+ find pages -type f -name '*.cfg' -print0 | sort -zr | xargs -0 saait
+
+ 9. Copy the modified stylesheets to the output directory also:
+
+ cp style.css print.css output/
+
+ 10. Open output/index.html locally in your webbrowser to review the
+ changes.
+
+ 11. To synchronize files, you can securely transfer them via SSH using
+ rsync:
+
+ rsync -av output/ user@somehost:/var/www/htdocs/
+
+TRIVIA
+ The most boring static page generator.
+
+ Meaning of saai (dutch): boring, pronunciation of saait: site
+
+SEE ALSO
+ find(1), sort(1), xargs(1)
+
+AUTHORS
+ Hiltjo Posthuma <hiltjo@codemadness.org>
+</code></pre>
+]]></content>
+</entry>
+<entry>
+ <title>Stagit: a static git page generator</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/stagit" />
+ <id>gopher://codemadness.org/1/phlog/stagit</id>
+ <updated>2021-04-11T00:00:00Z</updated>
+ <published>2017-05-10T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>a static git page generator</summary>
+ <content type="html"><![CDATA[<h1>Stagit: a static git page generator</h1>
+ <p><strong>Last modification on </strong> <time>2021-04-11</time></p>
+ <p>stagit is a static page generator for git.</p>
+<p><a href="/git/stagit/file/README.html">Read the README for more information about it.</a></p>
+<p>My git repository uses stagit, you can see how it looks here:
+<a href="https://codemadness.org/git/">https://codemadness.org/git/</a></p>
+<h2>Features</h2>
+<ul>
+<li>Log of all commits from HEAD.</li>
+<li>Log and diffstat per commit.</li>
+<li>Show file tree with linkable line numbers.</li>
+<li>Show references: local branches and tags.</li>
+<li>Detect README and LICENSE file from HEAD and link it as a webpage.</li>
+<li>Detect submodules (.gitmodules file) from HEAD and link it as a webpage.</li>
+<li>Atom feed of the commit log (atom.xml).</li>
+<li>Atom feed of the tags/refs (tags.xml).</li>
+<li>Make index page for multiple repositories with stagit-index.</li>
+<li>After generating the pages (relatively slow) serving the files is very fast,
+simple and requires little resources (because the content is static), only
+a HTTP file server is required.</li>
+<li>Security: all pages are static. No CGI or dynamic code is run for the
+interface. Using it with a secure httpd such as OpenBSD httpd it is
+privilege-separated, chroot(2)'d and pledge(2)'d.</li>
+<li>Simple to setup: the content generation is clearly separated from serving
+it. This makes configuration as simple as copying a few directories and
+scripts.</li>
+<li>Usable with text-browsers such as dillo, links, lynx and w3m.</li>
+</ul>
+<h2>Cons</h2>
+<ul>
+<li>Not suitable for large repositories (2000+ commits), because diffstats are
+an expensive operation, the cache (-c flag) or (-l maxlimit) is a workaround
+for this in some cases.</li>
+<li>Not suitable for large repositories with many files, because all files are
+written for each execution of stagit. This is because stagit shows the lines
+of textfiles and there is no "cache" for file metadata (this would add more
+complexity to the code).</li>
+<li>Not suitable for repositories with many branches, a quite linear history is
+assumed (from HEAD).</li>
+</ul>
+<p>In these cases it is better to use <a href="https://git.zx2c4.com/cgit/">cgit</a> or
+possibly change stagit to run as a CGI program.</p>
+<ul>
+<li>Relatively slow to run the first time (about 3 seconds for sbase,
+1500+ commits), incremental updates are faster.</li>
+<li>Does not support some of the dynamic features cgit has, like:
+<ul>
+<li>Snapshot tarballs per commit.</li>
+<li>File tree per commit.</li>
+<li>History log of branches diverged from HEAD.</li>
+<li>Stats (git shortlog -s).</li>
+</ul>
+</li>
+</ul>
+<p>This is by design, just use git locally.</p>
+<h2>Clone</h2>
+<pre><code>git clone git://git.codemadness.org/stagit
+</code></pre>
+<h2>Browse</h2>
+<p>You can browse the source-code at:</p>
+<ul>
+<li><a href="https://git.codemadness.org/stagit/">https://git.codemadness.org/stagit/</a></li>
+<li><a href="gopher://codemadness.org/1/git/stagit">gopher://codemadness.org/1/git/stagit</a></li>
+</ul>
+<h2>Download releases</h2>
+<p>Releases are available at:</p>
+<ul>
+<li><a href="https://codemadness.org/releases/stagit/">https://codemadness.org/releases/stagit/</a></li>
+<li><a href="gopher://codemadness.org/1/releases/stagit">gopher://codemadness.org/1/releases/stagit</a></li>
+</ul>
+]]></content>
+</entry>
+<entry>
+ <title>OpenBSD httpd, slowcgi and cgit</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/openbsd-httpd-and-cgit" />
+ <id>gopher://codemadness.org/1/phlog/openbsd-httpd-and-cgit</id>
+ <updated>2021-04-11T00:00:00Z</updated>
+ <published>2015-07-05T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>OpenBSD httpd, slowcgi and cgit</summary>
+ <content type="html"><![CDATA[<h1>OpenBSD httpd, slowcgi and cgit</h1>
+ <p><strong>Last modification on </strong> <time>2021-04-11</time></p>
+ <p>This is a guide to get <a href="https://git.zx2c4.com/cgit/">cgit</a> working with
+<a href="https://man.openbsd.org/httpd.8">OpenBSD httpd(8)</a> and
+<a href="https://man.openbsd.org/slowcgi.8">slowcgi(8)</a> in base. OpenBSD httpd is very simple to setup, but nevertheless
+this guide might help someone out there.</p>
+<h2>Installation</h2>
+<p>Install the cgit package:</p>
+<pre><code># pkg_add cgit
+</code></pre>
+<p>or build it from ports:</p>
+<pre><code># cd /usr/ports/www/cgit && make && make install
+</code></pre>
+<h2>Configuration</h2>
+<h3>httpd</h3>
+<p>An example of <a href="https://man.openbsd.org/httpd.conf.5">httpd.conf(5)</a>:
+<a href="downloads/openbsd-httpd/httpd.conf">httpd.conf</a>.</p>
+<h3>slowcgi</h3>
+<p>By default the slowcgi UNIX domain socket is located at:
+/var/www/run/slowcgi.sock. For this example we use the defaults.</p>
+<h3>cgit</h3>
+<p>The cgit binary should be located at: /var/www/cgi-bin/cgit.cgi (default).</p>
+<p>cgit uses the $CGIT_CONFIG environment variable to locate its config. By
+default on OpenBSD this is set to /conf/cgitrc (chroot), which is
+/var/www/conf/cgitrc. An example of the cgitrc file is here: <a href="downloads/openbsd-httpd/cgitrc">cgitrc</a>.</p>
+<p>In this example the cgit cache directory is set to /cgit/cache (chroot), which
+is /var/www/cgit/cache. Make sure to give this path read and write permissions
+for cgit (www:daemon).</p>
+<p>In the example the repository path (scan-path) is set to /htdocs/src (chroot),
+which is /var/www/htdocs/src.</p>
+<p>The footer file is set to /conf/cgit.footer. Make sure this file exists or you
+will get warnings:</p>
+<pre><code># >/var/www/conf/cgit.footer
+</code></pre>
+<p>Make sure cgit.css (stylesheet) and cgit.png (logo) are accessible, by default:
+/var/www/cgit/cgit.{css,png} (location can be changed in httpd.conf).</p>
+<p>To support .tar.gz snapshots a static gzip binary is required in the chroot
+/bin directory:</p>
+<pre><code>cd /usr/src/usr.bin/compress
+make clean && make LDFLAGS="-static -pie"
+cp obj/compress /var/www/bin/gzip
+</code></pre>
+<h2>Running the services</h2>
+<p>Enable the httpd and slowcgi services to automatically start them at boot:</p>
+<pre><code># rcctl enable httpd slowcgi
+</code></pre>
+<p>Start the services:</p>
+<pre><code># rcctl start httpd slowcgi
+</code></pre>
+]]></content>
+</entry>
+<entry>
+ <title>twitch: application to watch Twitch streams</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/twitch-interface" />
+ <id>gopher://codemadness.org/1/phlog/twitch-interface</id>
+ <updated>2020-12-14T00:00:00Z</updated>
+ <published>2014-11-23T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>twitch: application to watch Twitch streams</summary>
+ <content type="html"><![CDATA[<h1>twitch: application to watch Twitch streams</h1>
+ <p><strong>Last modification on </strong> <time>2020-12-14</time></p>
+ <p><strong>Update: as of 2020-05-06:</strong> I stopped maintaining it.
+Twitch now requires OAUTH and 2-factor authentication. It requires me to expose
+personal information such as my phone number.</p>
+<p><strong>Update: as of ~2020-01-03:</strong> I rewrote this application from Golang to C.
+The Twitch Kraken API used by the Golang version was deprecated. It was
+rewritten to use the Helix API.</p>
+<p>This program/script allows to view streams in your own video player like so the
+bloated Twitch interface is not needed. It is written in C.</p>
+<h2>Features</h2>
+<ul>
+<li>No Javascript, cookies, CSS optional.</li>
+<li>Works well in all browsers, including text-based ones.</li>
+<li>Has a HTTP CGI and Gopher CGI version.</li>
+<li>Atom feed for VODs.</li>
+</ul>
+<h2>Clone</h2>
+<pre><code>git clone git://git.codemadness.org/frontends
+</code></pre>
+<h2>Browse</h2>
+<p>You can browse the source-code at:</p>
+<ul>
+<li><a href="https://git.codemadness.org/frontends/">https://git.codemadness.org/frontends/</a></li>
+<li><a href="gopher://codemadness.org/1/git/frontends">gopher://codemadness.org/1/git/frontends</a></li>
+</ul>
+]]></content>
+</entry>
+<entry>
+ <title>Userscript: focus input field</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/userscript-focus-input-field" />
+ <id>gopher://codemadness.org/1/phlog/userscript-focus-input-field</id>
+ <updated>2014-03-02T00:00:00Z</updated>
+ <published>2014-03-02T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Userscript to focus the first input field on a page with a hotkey</summary>
+ <content type="html"><![CDATA[<h1>Userscript: focus input field</h1>
+ <p><strong>Last modification on </strong> <time>2014-03-02</time></p>
+ <p>This is an userscript I wrote a while ago which allows to focus the first input
+field on a page with ctrl+space. This is useful if a site doesn't specify the
+autofocus attribute for an input field and you don't want to switch to it using
+the mouse.</p>
+<h2>Download</h2>
+<p><a href="downloads/input_focus.user.js">Download userscript input_focus.user.js</a></p>
+]]></content>
+</entry>
+<entry>
+ <title>Userscript: Youtube circumvent age verification</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/userscript-youtube-circumvent-age-verification" />
+ <id>gopher://codemadness.org/1/phlog/userscript-youtube-circumvent-age-verification</id>
+ <updated>2020-12-27T00:00:00Z</updated>
+ <published>2013-02-21T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Userscript to circumvent Youtube age verification and redirect to the video</summary>
+ <content type="html"><![CDATA[<h1>Userscript: Youtube circumvent age verification</h1>
+ <p><strong>Last modification on </strong> <time>2020-12-27</time></p>
+ <p>This is an userscript I wrote a while ago which circumvents requiring to login
+with an account on Youtube if a video requires age verification.</p>
+<p><strong>Note: this is an old script and does not work anymore.</strong></p>
+<h2>Download</h2>
+<p><a href="downloads/youtube_circumvent_sign_in.user.js">Download userscript Youtube_circumvent_sign_in.user.js</a></p>
+]]></content>
+</entry>
+<entry>
+ <title>Userscript: block stupid fonts</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/userscript-block-stupid-fonts" />
+ <id>gopher://codemadness.org/1/phlog/userscript-block-stupid-fonts</id>
+ <updated>2020-03-10T00:00:00Z</updated>
+ <published>2012-10-21T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Userscript to whitelist your favorite fonts and block the rest</summary>
+ <content type="html"><![CDATA[<h1>Userscript: block stupid fonts</h1>
+ <p><strong>Last modification on </strong> <time>2020-03-10</time></p>
+ <p>This is an userscript I wrote a while ago which white-lists fonts I like and
+blocks the rest. The reason I made this is because I don't like the
+inconsistency of custom fonts used on a lot of websites.</p>
+<h2>Download</h2>
+<p><a href="downloads/block_stupid_fonts_v1.2.user.js">Download userscript Block_stupid_fonts_v1.2.user.js</a></p>
+<p>Old version: <a href="downloads/block_stupid_fonts.user.js">Download userscript Block_stupid_fonts.user.js</a></p>
+]]></content>
+</entry>
+<entry>
+ <title>Sfeed: simple RSS and Atom parser</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/sfeed" />
+ <id>gopher://codemadness.org/1/phlog/sfeed</id>
+ <updated>2022-11-05T00:00:00Z</updated>
+ <published>2011-04-01T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Sfeed is a simple RSS and Atom parser (and format programs to add reader functionality)</summary>
+ <content type="html"><![CDATA[<h1>Sfeed: simple RSS and Atom parser</h1>
+ <p><strong>Last modification on </strong> <time>2022-11-05</time></p>
+ <p>Sfeed is a RSS and Atom parser (and some format programs).</p>
+<p>It converts RSS or Atom feeds from XML to a TAB-separated file. There are
+formatting programs included to convert this TAB-separated format to various
+other formats. There are also some programs and scripts included to import and
+export OPML and to fetch, filter, merge and order feed items.</p>
+<p>For the most (up-to-date) information see the <a href="/git/sfeed/file/README.html">README</a>.</p>
+<h2>Clone</h2>
+<pre><code>git clone git://git.codemadness.org/sfeed
+</code></pre>
+<h2>Browse</h2>
+<p>You can browse the source-code at:</p>
+<ul>
+<li><a href="https://git.codemadness.org/sfeed/">https://git.codemadness.org/sfeed/</a></li>
+<li><a href="gopher://codemadness.org/1/git/sfeed">gopher://codemadness.org/1/git/sfeed</a></li>
+</ul>
+<h2>Download releases</h2>
+<p>Releases are available at:</p>
+<ul>
+<li><a href="https://codemadness.org/releases/sfeed/">https://codemadness.org/releases/sfeed/</a></li>
+<li><a href="gopher://codemadness.org/1/releases/sfeed">gopher://codemadness.org/1/releases/sfeed</a></li>
+</ul>
+<h2>Build and install</h2>
+<pre><code>$ make
+# make install
+</code></pre>
+<h2>Screenshot and examples</h2>
+<p><a href="downloads/screenshots/sfeed-screenshot.png"><img src="downloads/screenshots/sfeed-thumb.png" alt="Screenshot of sfeed piped to sfeed_plain using dmenu in vertical-list mode" width="400" height="232" loading="lazy" /></a></p>
+<p>The above screenshot uses the sfeed_plain format program with <a href="https://tools.suckless.org/dmenu/">dmenu</a>. This
+program outputs the feed items in a compact way per line as plain-text to
+stdout. The dmenu program reads these lines from stdin and displays them as a
+X11 list menu. When an item is selected in dmenu it prints this item to stdout.
+A simple written script can then filter for the URL in this output and do some
+action, like opening it in some browser or open a podcast in your music player.</p>
+<p>For example:</p>
+<pre><code>#!/bin/sh
+url=$(sfeed_plain "$HOME/.sfeed/feeds/"* | dmenu -l 35 -i | \
+ sed -n 's@^.* \([a-zA-Z]*://\)\(.*\)$@\1\2@p')
+test -n "${url}" && $BROWSER "${url}"
+</code></pre>
+<p>However this is just one way to format and interact with feed items.
+See also the README for other practical examples.</p>
+<p>Below are some examples of output that are supported by the included format
+programs:</p>
+<ul>
+<li><a href="downloads/sfeed/plain/feeds.txt">plain text (UTF-8)</a></li>
+<li><a href="downloads/sfeed/atom/feeds.xml">atom</a></li>
+<li>gopher</li>
+<li><a href="downloads/sfeed/html/feeds.html">HTML (CSS)</a></li>
+<li><a href="downloads/sfeed/frames/index.html">HTML frames</a></li>
+<li><a href="jsonfeed_content.json">JSON Feed</a></li>
+<li><a href="downloads/sfeed/mbox/feeds.mbox">mbox</a></li>
+<li><a href="downloads/sfeed/twtxt/twtxt.txt">twtxt</a></li>
+</ul>
+<p>There is also a curses UI front-end, see the page <a href="sfeed_curses.html">sfeed_curses</a>.
+It is now part of sfeed.</p>
+<h2>Videos</h2>
+<p>Here are some videos of other people showcasing some of the functionalities of
+sfeed, sfeed_plain and sfeed_curses. To the creators: thanks for making these!</p>
+<ul>
+<li><a href="https://www.youtube.com/watch?v=RnuY32DP9jU">sfeed: RSS/Atom Feeds without the Suck (Youtube)</a><br />
+by <a href="https://www.youtube.com/channel/UCQQB104oMOos758GTOdx_kQ">noocsharp</a>
+<a href="downloads/sfeed/videos/sfeed_without_the_suck.mp4">(mirror)</a><br />
+Video published on March 8 2020.</li>
+<li><a href="https://www.youtube.com/watch?v=ok8k639GoRU">Sfeed - news in the terminal with minimalism (Youtube)</a><br />
+by <a href="https://www.youtube.com/channel/UCJetJ7nDNLlEzDLXv7KIo0w">Gavin Freeborn</a>
+<a href="downloads/sfeed/videos/sfeed_news_in_terminal.mp4">(mirror)</a><br />
+Video published on January 15 2021.</li>
+<li><a href="https://www.youtube.com/watch?v=xMkW4iJzot0">Sfeed - Peak Minimal RSS Feed Reader (Youtube)</a><br />
+by <a href="https://www.youtube.com/channel/UCld68syR8Wi-GY_n4CaoJGA">Brodie Robertson</a>
+<a href="downloads/sfeed/videos/sfeed_minimalism.mp4">(mirror)</a><br />
+Video published on February 23 2021.</li>
+<li><a href="https://www.youtube.com/watch?v=O8x0MAyqvt0">RSS with sfeed, fdm, and mblaze! (Youtube)</a><br />
+by <a href="https://www.youtube.com/channel/UCz_u0h4usMbnFsIHSVdjUQw">Joseph Choe</a>
+<a href="downloads/sfeed/videos/rss_with_sfeed_fdm_and_mblaze.mp4">(mirror)</a><br />
+Website: <a href="https://josephchoe.com/rss-terminal">https://josephchoe.com/rss-terminal</a><br />
+Video published on 4 November 2022.</li>
+</ul>
+]]></content>
+</entry>
+<entry>
+ <title>Vim theme: relaxed</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/vim-theme-relaxed" />
+ <id>gopher://codemadness.org/1/phlog/vim-theme-relaxed</id>
+ <updated>2011-01-07T00:00:00Z</updated>
+ <published>2011-01-07T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>a dark VIM theme I made and use on a daily basis</summary>
+ <content type="html"><![CDATA[<h1>Vim theme: relaxed</h1>
+ <p><strong>Last modification on </strong> <time>2011-01-07</time></p>
+ <p>This is a dark theme I made for <a href="https://www.vim.org/">vim</a>. This is a theme I personally used for
+quite a while now and over time tweaked to my liking. It is made for gvim, but
+also works for 16-colour terminals (with small visual differences). The
+relaxed.vim file also has my .Xdefaults file colours listed at the top for
+16+-colour terminals on X11.</p>
+<p>It is inspired by the "desert" theme available at
+<a href="https://www.vim.org/scripts/script.php?script_id=105">https://www.vim.org/scripts/script.php?script_id=105</a>, although I removed the
+cursive and bold styles and changed some colours I didn't like.</p>
+<h2>Download</h2>
+<p><a href="downloads/themes/vim/relaxed.vim">relaxed.vim</a></p>
+<h2>Screenshot</h2>
+<p><a href="downloads/themes/vim/vim_relaxed_theme.png"><img src="downloads/themes/vim/vim_relaxed_theme_thumb.png" alt="Screenshot of VIM theme relaxed on the left is gvim (GUI), on the right is vim in urxvt (terminal)" width="480" height="300" loading="lazy" /></a></p>
+]]></content>
+</entry>
+<entry>
+ <title>Seturgent: set urgency hints for X applications</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/seturgent" />
+ <id>gopher://codemadness.org/1/phlog/seturgent</id>
+ <updated>2020-07-20T00:00:00Z</updated>
+ <published>2010-10-31T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Seturgent is a small utility to set an application it's urgency hint</summary>
+ <content type="html"><![CDATA[<h1>Seturgent: set urgency hints for X applications</h1>
+ <p><strong>Last modification on </strong> <time>2020-07-20</time></p>
+ <p>Seturgent is a small utility to set an application its urgency hint. For most
+windowmanager's and panel applications this will highlight the application and
+will allow special actions.</p>
+<h2>Clone</h2>
+<pre><code> git clone git://git.codemadness.org/seturgent
+</code></pre>
+<h2>Browse</h2>
+<p>You can browse the source-code at:</p>
+<ul>
+<li><a href="https://git.codemadness.org/seturgent/">https://git.codemadness.org/seturgent/</a></li>
+<li><a href="gopher://codemadness.org/1/git/seturgent">gopher://codemadness.org/1/git/seturgent</a></li>
+</ul>
+<h2>Download releases</h2>
+<p>Releases are available at:</p>
+<ul>
+<li><a href="https://codemadness.org/releases/seturgent/">https://codemadness.org/releases/seturgent/</a></li>
+<li><a href="gopher://codemadness.org/1/releases/seturgent">gopher://codemadness.org/1/releases/seturgent</a></li>
+</ul>
+]]></content>
+</entry>
+<entry>
+ <title>DWM-hiltjo: my windowmanager configuration</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/dwm" />
+ <id>gopher://codemadness.org/1/phlog/dwm</id>
+ <updated>2020-07-20T00:00:00Z</updated>
+ <published>2010-08-12T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>My DWM configuration; a few added features to suit my needs</summary>
+ <content type="html"><![CDATA[<h1>DWM-hiltjo: my windowmanager configuration</h1>
+ <p><strong>Last modification on </strong> <time>2020-07-20</time></p>
+ <p><a href="https://dwm.suckless.org/">DWM</a> is a very minimal windowmanager. It has the most essential features I
+need, everything else is "do-it-yourself" or extending it with the many
+available <a href="https://dwm.suckless.org/patches/">patches</a>. The vanilla version is less than 2000 SLOC. This makes it
+easy to understand and modify it.</p>
+<p>I really like my configuration at the moment and want to share my changes. Some
+of the features listed below are patches from suckless.org I applied, but there
+are also some changes I made.</p>
+<p>This configuration is entirely tailored for my preferences of course.</p>
+<h2>Features</h2>
+<ul>
+<li>Titlebar:
+<ul>
+<li>Shows all clients of the selected / active tags.</li>
+<li>Divide application titlebars evenly among available space.</li>
+<li>Colour urgent clients in the taskbar on active tags.</li>
+<li>Left-click focuses clicked client.</li>
+<li>Right-click toggles monocle layout.</li>
+<li>Middle-click kills the clicked client.</li>
+</ul>
+</li>
+<li>Tagbar:
+<ul>
+<li>Only show active tags.</li>
+<li>Colour inactive tags with urgent clients.</li>
+</ul>
+</li>
+<li>Layouts:
+<ul>
+<li>Cycle layouts with Modkey + Space (next) and Modkey + Control + Space
+(previous).</li>
+<li>Fullscreen layout (hides topbar and removes borders).</li>
+</ul>
+</li>
+<li>Other:
+<ul>
+<li>Move tiled clients around with the mouse (drag-move), awesomewm-like.</li>
+<li>Add some keybinds for multimedia keyboards (audio play / pause, mute, www,
+volume buttons, etc).</li>
+</ul>
+</li>
+<li>... and more ;) ...</li>
+</ul>
+<h2>Clone</h2>
+<pre><code>git clone -b hiltjo git://git.codemadness.org/dwm
+</code></pre>
+<h2>Screenshot</h2>
+<p><a href="downloads/screenshots/dwm-screenshot.png"><img src="downloads/screenshots/dwm-screenshot-thumb.png" alt="Screenshot showing what dwm-hiltjo looks like" width="480" height="300" loading="lazy" /></a></p>
+]]></content>
+</entry>
+<entry>
+ <title>Query unused CSS rules on current document state</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/query-unused-css-rules-on-current-document-state" />
+ <id>gopher://codemadness.org/1/phlog/query-unused-css-rules-on-current-document-state</id>
+ <updated>2010-04-21T00:00:00Z</updated>
+ <published>2010-04-21T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>How to see all the rules in a stylesheet (CSS) that are not used for the current document</summary>
+ <content type="html"><![CDATA[<h1>Query unused CSS rules on current document state</h1>
+ <p><strong>Last modification on </strong> <time>2010-04-21</time></p>
+ <p>Today I was doing some web development and wanted to see all the rules in a
+stylesheet (CSS) that were not used for the current document. I wrote the
+following Javascript code which you can paste in the Firebug console and run:</p>
+<pre><code>(function() {
+ for (var i=0;i<document.styleSheets.length;i++) {
+ var rules = document.styleSheets[i].cssRules || [];
+ var sheethref = document.styleSheets[i].href || 'inline';
+ for (var r=0;r<rules.length;r++)
+ if (!document.querySelectorAll(rules[r].selectorText).length)
+ console.log(sheethref + ': "' + rules[r].selectorText + '" not found.');
+ }
+})();
+</code></pre>
+<p>This will output all the (currently) unused CSS rules per selector, the output can be for example:</p>
+<pre><code>http://www.codemadness.nl/blog/wp-content/themes/codemadness/style.css: "fieldset, a img" not found.
+http://www.codemadness.nl/blog/wp-content/themes/codemadness/style.css: "#headerimg" not found.
+http://www.codemadness.nl/blog/wp-content/themes/codemadness/style.css: "a:hover" not found.
+http://www.codemadness.nl/blog/wp-content/themes/codemadness/style.css: "h2 a:hover, h3 a:hover" not found.
+http://www.codemadness.nl/blog/wp-content/themes/codemadness/style.css: ".postmetadata-center" not found.
+http://www.codemadness.nl/blog/wp-content/themes/codemadness/style.css: ".thread-alt" not found.
+</code></pre>
+<p>Just a trick I wanted to share, I hope someone finds this useful :)</p>
+<p>For webkit-based browsers you can use "Developer Tools" and use "Audits" under
+"Web Page Performance" it says "Remove unused CSS rules". For Firefox there is
+also Google Page Speed: <a href="https://code.google.com/speed/page-speed/">https://code.google.com/speed/page-speed/</a> this adds
+an extra section under Firebug.</p>
+<p>Tested on Chrome and Firefox.</p>
+]]></content>
+</entry>
+<entry>
+ <title>Driconf: enabling S3 texture compression on Linux</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/driconf" />
+ <id>gopher://codemadness.org/1/phlog/driconf</id>
+ <updated>2020-08-21T00:00:00Z</updated>
+ <published>2009-07-05T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>driconf: enabling S3 texture compression</summary>
+ <content type="html"><![CDATA[<h1>Driconf: enabling S3 texture compression on Linux</h1>
+ <p><strong>Last modification on </strong> <time>2020-08-21</time></p>
+ <p><strong>Update: the DXTC patent expired on 2018-03-16, many distros enable this by
+default now.</strong></p>
+<p>S3TC (also known as DXTn or DXTC) is a patented lossy texture compression
+algorithm. See: <a href="https://en.wikipedia.org/wiki/S3TC">https://en.wikipedia.org/wiki/S3TC</a> for more detailed
+information. Many games use S3TC and if you use Wine to play games you
+definitely want to enable it if your graphics card supports it.</p>
+<p>Because this algorithm was <a href="https://dri.freedesktop.org/wiki/S3TC/">patented it is disabled by default on many Linux
+distributions</a>.</p>
+<p>To enable it you can install the library "libtxc" if your favorite OS has not
+installed it already.</p>
+<p>For easy configuration you can install the optional utility DRIconf, which you
+can find at: <a href="https://dri.freedesktop.org/wiki/DriConf">https://dri.freedesktop.org/wiki/DriConf</a>. DriConf can safely be
+removed after configuration.</p>
+<h2>Steps to enable it</h2>
+<p>Install libtxc_dxtn:</p>
+<p>ArchLinux:
+<pre><code># pacman -S libtxc_dxtn
+</code></pre>
+<p>Debian:
+<pre><code># aptitude install libtxc-dxtn-s2tc0
+</code></pre>
+</p>
+</p>
+<p>Install driconf (optional):</p>
+<p>ArchLinux:</p>
+<pre><code># pacman -S driconf
+</code></pre>
+<p>Debian:</p>
+<pre><code># aptitude install driconf
+</code></pre>
+<p>Run driconf and enable S3TC:</p>
+<p><a href="downloads/screenshots/driconf.png"><img src="downloads/screenshots/driconf-thumb.png" alt="Screenshot of DRIconf window and its options" width="300" height="266" loading="lazy" /></a></p>
+<h2>Additional links</h2>
+<ul>
+<li>S3TC: <a href="https://dri.freedesktop.org/wiki/S3TC/">https://dri.freedesktop.org/wiki/S3TC/</a></li>
+<li>DriConf: <a href="https://dri.freedesktop.org/wiki/DriConf">https://dri.freedesktop.org/wiki/DriConf</a></li>
+</ul>
+]]></content>
+</entry>
+<entry>
+ <title>Getting the USB-powerline bridge to work on Linux</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/getting-the-usb-powerline-bridge-to-work-on-linux" />
+ <id>gopher://codemadness.org/1/phlog/getting-the-usb-powerline-bridge-to-work-on-linux</id>
+ <updated>2019-12-06T00:00:00Z</updated>
+ <published>2009-04-13T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>A guide to get a USB-powerline bridge with the Intellon 51x1 chipset working on Linux</summary>
+ <content type="html"><![CDATA[<h1>Getting the USB-powerline bridge to work on Linux</h1>
+ <p><strong>Last modification on </strong> <time>2019-12-06</time></p>
+ <p><strong>NOTE: this guide is obsolete, a working driver is now included in the Linux
+kernel tree (<a href="https://lkml.org/lkml/2009/4/18/121">since Linux 2.6.31</a>)</strong></p>
+<h2>Introduction</h2>
+<p>A USB to powerline bridge is a network device that instead of using an ordinary
+Ethernet cable (CAT5 for example) or wireless LAN it uses the powerlines as a
+network to communicate with similar devices. A more comprehensive explanation
+of what it is and how it works you can find here:
+<a href="https://en.wikipedia.org/wiki/IEEE_1901">https://en.wikipedia.org/wiki/IEEE_1901</a>.</p>
+<p>Known products that use the Intellon 51x1 chipset:</p>
+<ul>
+<li>MicroLink dLAN USB</li>
+<li>"Digitus network"</li>
+<li>Intellon USB Ethernet powerline adapter</li>
+<li>Lots of other USB-powerline adapters...</li>
+</ul>
+<p>To check if your device is supported:</p>
+<pre><code>$ lsusb | grep -i 09e1
+Bus 001 Device 003: ID 09e1:5121 Intellon Corp.
+</code></pre>
+<p>If the vendor (09e1) and product (5121) ID match then it's probably supported.</p>
+<h2>Installation</h2>
+<p>Get drivers from the official site:
+<a href="http://www.devolo.co.uk/consumer/downloads-44-microlink-dlan-usb.html?l=en">http://www.devolo.co.uk/consumer/downloads-44-microlink-dlan-usb.html?l=en</a> or
+<a href="downloads/int51x1/dLAN-linux-package-v4.tar.gz">mirrored here</a>.
+The drivers from the official site were/are more up-to-date.</p>
+<p>Extract them:</p>
+<pre><code>$ tar -xzvf dLAN-linux-package-v4.tar.gz
+</code></pre>
+<p>Go to the extracted directory and compile them:</p>
+<pre><code>$ ./configure
+$ make
+</code></pre>
+<p>Depending on the errors you got you might need to <a href="downloads/int51x1/int51x1.patch">download</a> and apply
+my patch:</p>
+<pre><code>$ cd dLAN-linux-package-v4/ (or other path to the source code)
+$ patch < int51x1.patch
+</code></pre>
+<p>Try again:</p>
+<pre><code>$ ./configure
+$ make
+</code></pre>
+<p>If that failed try:</p>
+<pre><code>$ ./configure
+$ KBUILD_NOPEDANTIC=1 make
+</code></pre>
+<p>If that went OK install the drivers (as root):</p>
+<pre><code># make install
+</code></pre>
+<p>Check if the "devolo_usb" module is loaded:</p>
+<pre><code>$ lsmod | grep -i devolo_usb
+</code></pre>
+<p>If it shows up then it's loaded. Now check if the interface is added:</p>
+<pre><code>$ ifconfig -a | grep -i dlanusb
+dlanusb0 Link encap:Ethernet HWaddr 00:12:34:56:78:9A
+</code></pre>
+<h2>Configuration</h2>
+<p>It is assumed you use a static IP, otherwise you can just use your DHCP client
+to get an unused IP address from your DHCP server. Setting up the interface is
+done like this (change the IP address and netmask accordingly if it's
+different):</p>
+<pre><code># ifconfig dlanusb0 192.168.2.12 netmask 255.255.255.0
+</code></pre>
+<h2>Checking if the network works</h2>
+<p>Try to ping an IP address on your network to test for a working connection:</p>
+<pre><code>$ ping 192.168.2.1
+PING 192.168.2.1 (192.168.2.1) 56(84) bytes of data.
+64 bytes from 192.168.2.1: icmp_seq=1 ttl=30 time=2.49 ms
+64 bytes from 192.168.2.1: icmp_seq=2 ttl=30 time=3.37 ms
+64 bytes from 192.168.2.1: icmp_seq=3 ttl=30 time=2.80 ms
+--- 192.168.2.1 ping statistics ---
+3 packets transmitted, 3 received, 0% packet loss, time 2005ms
+rtt min/avg/max/mdev = 2.497/2.891/3.374/0.368 ms
+</code></pre>
+<p>You can now set up a network connection like you normally do with any Ethernet
+device. The route can be added like this for example:</p>
+<pre><code># route add -net 0.0.0.0 netmask 0.0.0.0 gw 192.168.2.1 dlanusb0
+</code></pre>
+<p>Change the IP address of your local gateway accordingly. Also make sure your
+nameserver is set in /etc/resolv.conf, something like:</p>
+<pre><code>nameserver 192.168.2.1
+</code></pre>
+<p>Test your internet connection by doing for example:</p>
+<pre><code>$ ping codemadness.org
+PING codemadness.org (64.13.232.151) 56(84) bytes of data.
+64 bytes from acmkoieeei.gs02.gridserver.com (64.13.232.151): icmp_seq=1 ttl=52 time=156 ms
+64 bytes from acmkoieeei.gs02.gridserver.com (64.13.232.151): icmp_seq=2 ttl=52 time=156 ms
+64 bytes from acmkoieeei.gs02.gridserver.com (64.13.232.151): icmp_seq=3 ttl=52 time=155 ms
+--- codemadness.org ping statistics ---
+3 packets transmitted, 3 received, 0% packet loss, time 1999ms
+rtt min/avg/max/mdev = 155.986/156.312/156.731/0.552 ms
+</code></pre>
+<p>If this command failed you probably have not setup your DNS/gateway properly.
+If it worked then good for you :)</p>
+<h2>References</h2>
+<ul>
+<li><a href="http://www.devolo.co.uk/consumer/downloads-44-microlink-dlan-usb.html?l=en">Devolo download page with drivers (USB version).</a></li>
+<li><a href="downloads/int51x1/dLAN-linux-package-v4.tar.gz">dLAN-linux-package-v4.tar.gz</a></li>
+<li><a href="downloads/int51x1/int51x1.patch">Patch for recent 2.6.x kernels</a></li>
+<li><a href="downloads/int51x1/INT51X1_datasheet.pdf">INT51X1 datasheet</a></li>
+</ul>
+]]></content>
+</entry>
+<entry>
+ <title>Gothic 1 game guide</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/gothic" />
+ <id>gopher://codemadness.org/1/phlog/gothic</id>
+ <updated>2025-01-05T00:00:00Z</updated>
+ <published>2009-04-12T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Gothic 1 game guide with some useful tips</summary>
+ <content type="html"><![CDATA[<h1>Gothic 1 game guide</h1>
+ <p><strong>Last modification on </strong> <time>2025-01-05</time></p>
+ <p><strong>Disclaimer:</strong>
+Some (including myself) may find some of these hints/exploits cheating. This
+guide is just for educational and fun purposes. Some of these hints/tips apply
+to Gothic 2 as well. I got the meat exploit from a guide somewhere on the
+internet I can't recall where, anyway kudos to that person. Some of the
+exploits I discovered myself.</p>
+<h2>Configuration</h2>
+<h3>Widescreen resolution</h3>
+<p>Gothic supports widescreen resolutions with a small tweak, add the following
+text string as a command-line argument:</p>
+<pre><code>-zRes:1920,1200,32
+</code></pre>
+<p>This also works for Gothic 2. Here 1920 is the width, 1200 the height and 32
+the bits per pixel, change this to your preferred resolution.</p>
+<h3>Fix crash with Steam version</h3>
+<p>Disable steam overlay. If that doesn't work rename GameOverlayRenderer.dll in
+your steam folder to _GameOverlayRenderer.dll. I strongly recommend to buy the
+better version from <a href="https://www.gog.com/game/gothic">GOG.com</a>. The GOG version has no DRM and allows easier
+modding, it also allows playing in most published languages: German, English,
+Polish, furthermore it has some original artwork and soundtrack included.</p>
+<h3>Upgrade Steam version to stand-alone version and remove Steam DRM (Gothic 1 and 2)</h3>
+<p>You can install the Gothic playerkit and patches to remove the Steam DRM.</p>
+<p><a href="https://www.worldofgothic.de/">WorldOfGothic</a> playerkit patches:</p>
+<ul>
+<li>Gothic 1 (EN): <a href="https://www.worldofgothic.com/dl/?go=dlfile&fileid=28">https://www.worldofgothic.com/dl/?go=dlfile&fileid=28</a></li>
+<li>Gothic 1 (DE): <a href="https://www.worldofgothic.de/dl/download_34.htm">https://www.worldofgothic.de/dl/download_34.htm</a></li>
+<li>Gothic 2 (EN/DE): <a href="https://www.worldofgothic.de/dl/download_168.htm">https://www.worldofgothic.de/dl/download_168.htm</a></li>
+</ul>
+<h3>Play Gothic in a different language with English subtitles</h3>
+<p>If you're like me and have played the English version many times, but would
+like to hear the (original) German voice audio or if you would like to play
+with different audio than you're used to, then you can copy the speech.vdf file
+of your preferred version to your game files. Optionally turn on subtitles.
+I've used this to play the English version of Gothic with the original German
+voice audio and English subtitles.
+This works best with the version from GOG as it allows easier modding.</p>
+<h2>Easy money/weapons/armour/other items</h2>
+<h3>Steal from Huno</h3>
+<p>At night attack Huno the smith in the Old Camp and steal all his steel. Then
+make some weapons and sell them with a merchant. When you ask Huno about
+blacksmith equipment it will respawn with 5 of each kind of steel. This is also
+a fairly good starting weapon (requires 20 strength). Also his chest located
+near the sharpening stone and fire contains some steel as well, lock-pick it.
+The combination is: RRLRLL. The chest contains at least 20 raw steel, forge it
+to get 20 crude swords which you can sell for 50 ore each to a merchant. This
+will generate some nice starting money (1000+ ore) :)</p>
+<h3>Steal weapons from the castle in the Old Camp</h3>
+<p>This tip is useful for getting pretty good starting weapons.</p>
+<p>Before entering the castle itself drop your ore (Left control + down for me)
+in front of it. This will ensure when you get caught (and you probably will ;))
+no ore will get stolen by the guards. Now use the "slip past guard" technique
+described below and you should be able to get into Gomez his castle. Run to the
+left where some weapons are stored. Now make sure you at least steal the best
+weapon (battle sword) and steal as much as you can until you get whacked. I
+usually stand in the corner since that's where the best weapons are (battle
+sword, judgement sword, etc). You'll now have some nice starting weapon(s) and
+the good thing is they require very little attributes (about 13 strength).</p>
+<p>Location: <a href="downloads/gothic1/old_camp_swords.png">screenshot</a></p>
+<h3>Free scraper armour the New Camp</h3>
+<p>In the New Camp go to the mine and talk to Swiney at the bottom of "The
+Hollow". Ask who he is and then ask to join the scrapers. He will give you a
+"Diggers dress" worth 250 ore. It has the following stats: + 10 against
+weapons. + 5 against fire. This will also give you free entrance to the bar in
+the New Camp.</p>
+<h3>Unlimited water bottles in the New Camp</h3>
+<p>In the quest from Lefty you will be assigned to get water bottles from the
+rice lord. He will give you infinite amounts of water bottles, in batches of
+12.</p>
+<h3>Armour amulet and increase HP potion</h3>
+<p>In the Old Camp in the main castle there are at least 3 chests with valuable
+items that don't require a key:</p>
+<ul>
+<li><p>Middle right side (looking from the entrance), 1 chest:
+<ul>
+<li>lock combination: LLLLRLRL</li>
+<li>loot:
+<ul>
+<li>+15 against weapons, +15 against arrows (amulet of stone skin)
+(worth: 1000 ore)</li>
+</ul>
+</li>
+<li>additionally there are 2 locked doors at the right side in this room. In
+the final room there are 3 floors with lots of chests.<br />
+<a href="downloads/gothic1/video/amulet.mp4">Video of the location</a></li>
+</ul>
+</p>
+</li>
+<li><p>Left side, 1 chest:
+<ul>
+<li>lock combination: RLLLLLRR</li>
+<li>loot:
+<ul>
+<li>+8 mana amulet (worth: 600 ore)</li>
+<li>2 potions (+70 hp)</li>
+<li>dreamcall (weed)</li>
+<li>120 coins (worth: nothing)</li>
+</ul>
+</li>
+</ul>
+</p>
+</li>
+<li><p>Right side, 2 chests with:
+<ul>
+<li>lock combination: RLLLRLLR</li>
+<li>loot:
+<ul>
+<li>armour amulets, +15 against weapons (worth: 600 ore)</li>
+<li>maximum life potion, +10 maximum life (worth: 1000 ore)</li>
+<li>speed potion (1 minute duration)</li>
+<li>4 potions (+70 hp)</li>
+</ul>
+</li>
+</ul>
+</p>
+</li>
+</ul>
+<h3>Swamp/Sect Camp harvest twice</h3>
+<p>In the swamp-weed harvest quest you must get swamp-weed for a guru. After this
+quest you can get the harvest again, but you can keep the harvest without
+consequences.</p>
+<h2>Exploits</h2>
+<h3>Slip past guards</h3>
+<p>This exploit is really simple, just draw your weapon before you're "targeted"
+by the guard and run past them, this bypasses the dialog sequence. When you're
+just out of their range holster your weapon again, so the people around won't
+get pissed off.</p>
+<p>Works really well on the guards in front of the Old camp's castle, Y'Berrion
+templars and New Camp mercenaries near the Water magicians, just to name a few.</p>
+<p><a href="downloads/gothic1/video/amulet.mp4">Video</a></p>
+<h3>Meat duplication</h3>
+<p>Go to a pan and focus / target it so it says "frying pan" or similar. Now open
+your inventory and select the meat. Now cook the meat (for me Left Control +
+Arrow up). The inventory should remain open. You'll now have twice as much meat
+as you had before. Do this a few times and you'll have a lot of meat, easy for
+trading with ore/other items as well. This exploit does not work with the
+community patch applied.</p>
+<h3>Glitch through (locked) doors and walls</h3>
+<p>You can glitch through walls by strafing into them. Then when the player is
+partially collided into a door or wall you can jump forward to glitch through
+it.</p>
+<p><a href="downloads/gothic1/video/bloodsword.mp4">Video</a></p>
+<h3>Fall from great heights</h3>
+<p>When you fall or jump from where you usually get fall damage you can do the
+following trick: slightly before the ground use left or right strafe. This
+works because it resets the falling animation. There are also other ways to
+achieve the same thing cancelling the falling animation, such as attacking with
+a weapon in the air.</p>
+<p><a href="downloads/gothic1/video/fall.mp4">Video</a></p>
+<h2>Experience / level up tips</h2>
+<h3>Test of faith (extra exp)</h3>
+<p>You get an additional 750 exp (from Lares) when you forge the letter in the new
+camp and then give it to Diego. You can still join both camps after this.</p>
+<h3>Fighting skeleton mages and their skeletons</h3>
+<p>An easy way to get more experience is to let the skeleton mages summon as much
+skeletons as they can, instead of rushing to kill the summoner immediately.
+After you have defeated all of them: kill the skeleton mage.</p>
+<h3>Permanent str/dex/mana/hp potions/items and teachers</h3>
+<p>When you want to get the maximum power at the end of the game you should save
+up the items that give you a permanent boost. Teachers of strength, dexterity
+and mana won't train over 100 of each skill. However using potions and quest
+rewards you can increase this over 100.</p>
+<p>You should also look out for the following:</p>
+<ul>
+<li><p>Learn to get extra force into your punch from Horatio (strength +5, this
+can't be done after level 100 strength). Talking to Jeremiah in the New Camp
+bar unlocks the dialog option to train strength at Horatio.</p>
+</li>
+<li><p>Smoke the strongest non-quest joint (+2 mana).</p>
+</li>
+</ul>
+<h3>Permanent potions in Sleeper temple</h3>
+<p>This one is really obvious, but I would like to point out the mummy's on each
+side where Xardas is located have lots and I mean lots of permanent potions.
+This will give you a nice boost before the end battle.</p>
+<p>Location, left and right corridor in the Sleeper temple: <a href="downloads/gothic1/sleeper_temple_potions.png">screenshot</a><br />
+Mummies, you can loot them: <a href="downloads/gothic1/sleeper_temple_potions_mummies.png">screenshot</a><br /> </p>
+<h3>Permanent potions as reward in quests</h3>
+<p>Always pick the permanent potion as a reward for quests when you can, for
+example the quest for delivering the message to the High Fire magicians (mana
+potion) or the one for fetching the almanac for the Sect Camp. Don't forget to
+pick up the potions from Riordian the water magician when you're doing the
+focus stones quest, it contains a strength and dexterity potion (+3).</p>
+<h3>Improve ancient ore armour further</h3>
+<p>In the last chapters the blacksmith Stone from the Old Camp is captured If you
+save him from the prison cell in the Old Camp the reward will have a few
+options. One of the options is improving the Ancient Ore armour.</p>
+<h2>Good early game weapons available in chapter 1</h2>
+<h3>Orc Hammer</h3>
+<p>Location: in a cave near bloodhounds near the mountain fort.<br />
+It can be reached from a path from the swamp camp up to the mountain.
+Watch out for the bloodhounds. They can instantly kill you in the early game.</p>
+<p>Location: <a href="downloads/gothic1/early_weapon_old_orc_hammer_location.png">screenshot</a><br />
+Stats: <a href="downloads/gothic1/early_weapon_old_orc_hammer_stats.png">screenshot</a><br /> </p>
+<p>Stats:
+<ul>
+<li>Type: one-handed</li>
+<li>Damage: 50</li>
+<li>Required strength: 22</li>
+<li>Worth: 1000 ore</li>
+</ul>
+</p>
+<p>It has very low strength stat requirement and has high damage for the early
+game chapters. A downside is the lower weapon swing range.
+It is also a decent weapon against stone golems.</p>
+<h3>Old Battle Axe</h3>
+<p>Location: near Xardas his tower.<br />
+Watch out for a group of Biters lurking there.</p>
+<p>Location: <a href="downloads/gothic1/early_weapon_old_battle_axe_location.png">screenshot</a><br />
+Stats: <a href="downloads/gothic1/early_weapon_old_battle_axe_stats.png">screenshot</a><br /> </p>
+<p>Stats:
+<ul>
+<li>Type: two-handed</li>
+<li>Damage: 67</li>
+<li>Required strength: 36</li>
+<li>Worth: 1800 ore</li>
+</ul>
+</p>
+<p>It has a relatively low strength requirements and is available in game chapter
+1 or could be sold for a decent amount.</p>
+<h3>Random/beginner tips</h3>
+<ul>
+<li><p>If you want to talk to a NPC, but some animation of them takes too long (like
+eating, drinking, smoking) you can sometimes force them out of it by quickly
+unsheathing/sheathing your weapon.</p>
+</li>
+<li><p>When in the Old Camp: Baal Parvez can take you to the Sect Camp, he can be
+found near the campfire near Fisk and Dexter.
+Mordrag can take you to the New Camp, he can be found near the south gate,
+slightly after the campfire near Baal Parvez.</p>
+<p>When you follow them and when they kill monsters then you also get the
+experience.</p>
+</li>
+<li><p>The NPC Wolf in the New Camp sells "The Bloodflies" book for 150 ore. When
+you read this book you learn how to remove bloodflies parts (without having to
+spend learning points). After you read the book and learned its skill then you
+can sell the book back for 75 ore. This investment quickly pays back: Per
+bloodfly: sting: 25 ore (unsold value), 2x wings (15 ore each unsold value).</p>
+</li>
+<li><p>The templar Gor Na Drak (usually near the old mine and walks around with
+another templar): talking to him teaches you how to learn to get secretion from
+minecrawlers for free.</p>
+</li>
+<li><p>The spell scroll "Transform into bloodfly" is very useful:
+<ul>
+<li>A bloodfly is very fast.</li>
+<li>Can also fly over water.</li>
+<li>The scroll costs 100 ore. Its the same price as a potion of speed, but it
+has no duration (just until you transform back).</li>
+<li>You have no fall damage.</li>
+<li>You can climb some steep mountains this way.</li>
+<li>Some monsters won't attack you, but some NPCs will attack you.</li>
+<li>Your attribute stats will temporary change.</li>
+<li>It requires 10 mana to cast (low requirement).</li>
+</ul>
+</p>
+</li>
+<li><p>Almost all mummies that are lootable in the game (Orc temple and The Sleeper
+temple) have really good loot: permanent and regular potions and amulets and
+rings.<br /> </p>
+</li>
+<li><p>Skill investments:
+<ul>
+<li>For melee skills:
+<ul>
+<li>Strength</li>
+<li>One-handed weapons have a bit lower weapon damage but are less clunky and
+faster. You can also interrupt enemy attacks.</li>
+<li>Two-handed weapons have the highest damage, but are slower.</li>
+<li>Get at least the first tier of one-handed training. It will change the
+combat animations and make combat less slow and clunky.</li>
+</ul>
+</li>
+<li>For ranged skills:
+<ul>
+<li>Dexterity</li>
+<li>Cross-bows have high damage and are very good.
+<ul>
+<li>Cross-bow: the path for cross-bow training is easier in the old camp.
+When you become the Old Camp guard Scorpio can train you. Later in the
+game in chapter 4 after some story progression he will train everyone.</li>
+</ul>
+</li>
+</ul>
+</li>
+<li>For mage characters:
+<ul>
+<li>Investing a little bit into strength, lets say 30 STR is OK.</li>
+<li>Magic skills are powerful but are a bit clunky and slow.</li>
+<li>Joining the Old Camp (fire mage) or New Camp (water mage) for the magician
+path is probably easier.</li>
+</ul>
+</li>
+<li>Harvest animals:
+<ul>
+<li>Early investments of a few skill points into getting skins, teeth and claws
+from animals is OK (it is easy to get a lot of ore if you loot everything
+though).</li>
+</ul>
+</li>
+<li>Lockpicking: training in lockpicking only reduces the chance to break locks
+when you fail the combination. Investing in it is OK but not necessary.
+A small cheat: the lock pick combination stays the same, you can save and
+reload the game to avoid losing lockpicks.</li>
+<li>Bad skill investments to avoid:
+<ul>
+<li>Sneak and pickpocket are nearly useless.</li>
+</ul>
+</li>
+</ul>
+</p>
+</li>
+</ul>
+<p>Overall recommendation: I'd recommend a hybrid of melee/magic or melee/range.
+Early game for melee: get max strength to 100 and get at least the first tier
+of one-handed training.<br />
+In the later game focus more on ranged combat or learning the magic circles.</p>
+<h1>Side-quest Chromanin / The Stranger</h1>
+<p>This describes an interesting side quest in the Gothic 1 game, which is not too
+obvious to find and may be overlooked.</p>
+<p>The first Chromanin book is found by defeating the skeleton mage in the Fog
+Tower. On its bones you can find the Chromanin book. Reading the book starts
+the Chromanin / The Stranger quest. The books contain some typos, being
+demonicly possesed could be an excuse for that :)</p>
+<p>Note that the Old books only spawn in a specific order after reading each found
+book. So they have to be done in this specific order.</p>
+<p><a href="downloads/gothic1/chromanin/0_mage_fog_tower.png">Fog tower mage</a><br />
+<a href="downloads/gothic1/chromanin/0_fog_tower.png">Location</a><br />
+<a href="downloads/gothic1/chromanin/0_map.png">Map</a><br /> </p>
+<p>Text:</p>
+<pre><code>"He who is willing to
+renounce all depravity
+and wanders on the path
+of righteousness, shall
+know where the source
+of my power lies
+hidden. So that he might
+use it to break the chains
+of this world and prove
+worthy to receive Chromanin."
+
+"The Wise One sees to
+having a general overview before he
+dedicates himself to his
+next mission."
+</code></pre>
+<h2>Chromanin</h2>
+<p>The clue is in the words "general overview" on the second page.
+One of the highest points on the map is the tower where you find and free the orc Ur-Shak
+from being attacked by other orcs.</p>
+<p>The Wise One sees to having a general overview before he dedicates himself to
+his next mission".<br />
+Location: on top of the tower near where the orc Ur-Shak was.<br />
+Item: Old Book.</p>
+<p><a href="downloads/gothic1/chromanin/1_book.png">Item</a><br />
+<a href="downloads/gothic1/chromanin/1_tower.png">Location</a><br />
+<a href="downloads/gothic1/chromanin/1_map.png">Map</a><br /> </p>
+<h2>Chromanin 2</h2>
+<p>Text:</p>
+<pre><code>"Carried from the tides
+of time, Chromanin's
+visions have opened my
+eyes. No price could be
+high enough to ever
+renounce my faith in
+them, for it touched my
+heart too insensely."
+
+"What is devided will be
+reunited, after being
+massively separated for
+a short time."
+</code></pre>
+<p>Clue: "What is devided (sic) will be reunited, after being massively separated for a short time".
+Location: small island near the (divided) river near the Old Camp.</p>
+<p><a href="downloads/gothic1/chromanin/2_book.png">Item</a><br />
+<a href="downloads/gothic1/chromanin/2_river.png">Location</a><br />
+<a href="downloads/gothic1/chromanin/2_map.png">Map</a><br /> </p>
+<h2>Chromanin 3</h2>
+<p>Text:</p>
+<pre><code>"Oh, Ancient Gods. How
+can it be that a man like
+me, simple and unworthy,
+may receive such great a
+legacy. I feel great
+fear to lose all of it
+again by a slight
+faltering in word or
+deed."
+
+"The wise fisherman
+occasionally tries to get
+lucky on the other side
+of the lake."
+</code></pre>
+<p>Clue: a fisherman lake and (partially sunken hut) can be found close the the entrance of the New Camp.
+At the other side is the Old Book.</p>
+<p><a href="downloads/gothic1/chromanin/3_book.png">Item</a><br />
+<a href="downloads/gothic1/chromanin/3_lake_new_camp.png">Location</a><br />
+<a href="downloads/gothic1/chromanin/3_map.png">Map</a><br /> </p>
+<h2>Chromanin 4</h2>
+<p>Text:</p>
+<pre><code>"I dare not to be in
+the presence of
+Chromanin one day. Gone
+are the days of wasting
+and wailing. So easy it
+will be to acheive
+absolute perfection. I'm
+not far from it!"
+
+"Long forgotten are the
+deeds of those who once
+were aboard."
+</code></pre>
+<p>Clue: "Long forgotten are the deeds of those who once were aboard."
+A broken ship can be found near the beach at the entrance of the Fog Tower.</p>
+<p><a href="downloads/gothic1/chromanin/4_book.png">Item</a><br />
+<a href="downloads/gothic1/chromanin/4_aboard.png">Location</a><br />
+<a href="downloads/gothic1/chromanin/4_map.png">Map</a><br /> </p>
+<h2>Chromanin 5</h2>
+<p>Text:</p>
+<pre><code>"But I shall not walk this
+path alone. This honor is
+mine. I must accept to
+share the power within
+myself with the worthy
+ones who are to come and
+find me. I hope they're
+coming soon..."
+
+"You will find me where it all began."
+</code></pre>
+<p>Clue: "You will find me where it all began."
+Very obvious it is the same location as were the first book was found.</p>
+<p><a href="downloads/gothic1/chromanin/5_book.png">Item</a><br />
+<a href="downloads/gothic1/chromanin/5_begin.png">Location</a><br />
+<a href="downloads/gothic1/chromanin/5_map.png">Map</a><br /> </p>
+<h2>Chromanin 6</h2>
+<p>Text:</p>
+<pre><code>"Empty pages"
+</code></pre>
+<p><a href="downloads/gothic1/chromanin/6_book.png">Item</a></p>
+<p>On the corpse is the last chromanin book.
+When reading this last book the book is empty.
+Then there is evil laugh and 2 skeleton mages and skeleton minions will spawn.</p>
+<h2>Chromanin quest log</h2>
+<p>Here are the texts in the quest log:</p>
+<p><a href="downloads/gothic1/chromanin/quest_log_part_1.png">Quest log part 1</a><br />
+<a href="downloads/gothic1/chromanin/quest_log_part_2.png">Quest log part 2</a><br /> </p>
+<h3>The End</h3>
+<p>When you use the tips described above Gothic should be an easier game and you
+should be able to get at a high(er) level with lots of mana/strength/hp.</p>
+<p>Have fun!</p>
+]]></content>
+</entry>
+</feed>
DIR diff --git a/output/atom_gopher.xml b/output/atom_gopher.xml
@@ -0,0 +1,429 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
+ <title>Codemadness</title>
+ <subtitle>blog with various projects and articles about computer-related things</subtitle>
+ <updated>2025-05-16T00:00:00Z</updated>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org" />
+ <id>gopher://codemadness.org/0/atom_gopher.xml</id>
+ <link rel="self" type="application/atom+xml" href="gopher://codemadness.org/0/atom_gopher.xml" />
+<entry>
+ <title>Chess puzzle book generator</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/chess-puzzles" />
+ <id>gopher://codemadness.org/1/phlog/chess-puzzles</id>
+ <updated>2025-05-03T00:00:00Z</updated>
+ <published>2024-02-02T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Chess puzzle book generator</summary>
+</entry>
+<entry>
+ <title>xargs: an example for parallel batch jobs</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/xargs" />
+ <id>gopher://codemadness.org/1/phlog/xargs</id>
+ <updated>2023-12-17T00:00:00Z</updated>
+ <published>2023-11-22T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>xargs: an example for parallel batch jobs</summary>
+</entry>
+<entry>
+ <title>Improved Youtube RSS/Atom feed</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/youtube-feed" />
+ <id>gopher://codemadness.org/1/phlog/youtube-feed</id>
+ <updated>2023-11-20T00:00:00Z</updated>
+ <published>2023-11-20T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Improved Youtube Atom feed by adding video duration and filtering away shorts</summary>
+</entry>
+<entry>
+ <title>webdump HTML to plain-text converter</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/webdump" />
+ <id>gopher://codemadness.org/1/phlog/webdump</id>
+ <updated>2025-04-25T00:00:00Z</updated>
+ <published>2023-11-20T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>webdump HTML to plain-text converter</summary>
+</entry>
+<entry>
+ <title>Setup your own mail paste service</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/mailservice" />
+ <id>gopher://codemadness.org/1/phlog/mailservice</id>
+ <updated>2024-02-10T00:00:00Z</updated>
+ <published>2023-10-25T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Setup your own mail paste service using mblaze</summary>
+</entry>
+<entry>
+ <title>A simple TODO application</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/todo" />
+ <id>gopher://codemadness.org/1/phlog/todo</id>
+ <updated>2022-07-01T00:00:00Z</updated>
+ <published>2022-07-01T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>A simple TODO application workflow</summary>
+</entry>
+<entry>
+ <title>2FA TOTP without crappy authenticator apps</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/totp" />
+ <id>gopher://codemadness.org/1/phlog/totp</id>
+ <updated>2022-10-29T00:00:00Z</updated>
+ <published>2022-03-23T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Using 2FA TOTP without crappy authenticator apps</summary>
+</entry>
+<entry>
+ <title>Setup an OpenBSD RISCV64 VM in QEMU</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/openbsd-riscv64-vm" />
+ <id>gopher://codemadness.org/1/phlog/openbsd-riscv64-vm</id>
+ <updated>2021-10-26T00:00:00Z</updated>
+ <published>2021-10-23T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Setup an OpenBSD RISCV-64 VM in QEMU</summary>
+</entry>
+<entry>
+ <title>Sfeed_curses: a curses UI front-end for sfeed</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/sfeed_curses" />
+ <id>gopher://codemadness.org/1/phlog/sfeed_curses</id>
+ <updated>2022-05-08T00:00:00Z</updated>
+ <published>2020-06-25T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Sfeed_curses is a curses UI front-end for the sfeed RSS/Atom parser</summary>
+</entry>
+<entry>
+ <title>hurl: HTTP, HTTPS and Gopher file grabber</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/hurl" />
+ <id>gopher://codemadness.org/1/phlog/hurl</id>
+ <updated>2020-07-20T00:00:00Z</updated>
+ <published>2019-11-10T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>hurl: HTTP, HTTPS and Gopher file grabber</summary>
+</entry>
+<entry>
+ <title>json2tsv: a JSON to TSV converter</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/json2tsv" />
+ <id>gopher://codemadness.org/1/phlog/json2tsv</id>
+ <updated>2021-09-25T00:00:00Z</updated>
+ <published>2019-10-13T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>json2tsv: a JSON to TAB-Separated Value converter</summary>
+</entry>
+<entry>
+ <title>OpenBSD: setup a local auto-installation server</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/openbsd-autoinstall" />
+ <id>gopher://codemadness.org/1/phlog/openbsd-autoinstall</id>
+ <updated>2020-04-30T00:00:00Z</updated>
+ <published>2019-04-24T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>OpenBSD: setup a local auto-installation server</summary>
+</entry>
+<entry>
+ <title>Idiotbox: Youtube interface</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/idiotbox" />
+ <id>gopher://codemadness.org/1/phlog/idiotbox</id>
+ <updated>2021-12-25T00:00:00Z</updated>
+ <published>2019-02-10T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Idiotbox: Youtube interface</summary>
+</entry>
+<entry>
+ <title>Gopher HTTP proxy</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/gopher-proxy" />
+ <id>gopher://codemadness.org/1/phlog/gopher-proxy</id>
+ <updated>2020-08-30T00:00:00Z</updated>
+ <published>2018-08-17T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Gopher HTTP proxy</summary>
+</entry>
+<entry>
+ <title>Setup your own file paste service</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/paste-service" />
+ <id>gopher://codemadness.org/1/phlog/paste-service</id>
+ <updated>2018-03-10T00:00:00Z</updated>
+ <published>2018-03-10T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Howto setup your own secure file paste service</summary>
+</entry>
+<entry>
+ <title>Setup your own git hosting service</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/setup-git-hosting" />
+ <id>gopher://codemadness.org/1/phlog/setup-git-hosting</id>
+ <updated>2022-08-07T00:00:00Z</updated>
+ <published>2018-02-25T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Howto setup your own git hosting service</summary>
+</entry>
+<entry>
+ <title>Setup an OpenBSD SPARC64 VM in QEMU</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/openbsd-sparc64-vm" />
+ <id>gopher://codemadness.org/1/phlog/openbsd-sparc64-vm</id>
+ <updated>2020-04-18T00:00:00Z</updated>
+ <published>2017-12-11T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Setup an OpenBSD SPARC64 VM in QEMU</summary>
+</entry>
+<entry>
+ <title>Tscrape: a Twitter scraper</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/tscrape" />
+ <id>gopher://codemadness.org/1/phlog/tscrape</id>
+ <updated>2020-07-20T00:00:00Z</updated>
+ <published>2017-09-24T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Tscrape: a Twitter scraper</summary>
+</entry>
+<entry>
+ <title>jsdatatable: a small datatable Javascript</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/datatable" />
+ <id>gopher://codemadness.org/1/phlog/datatable</id>
+ <updated>2020-07-20T00:00:00Z</updated>
+ <published>2017-09-24T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>jsdatatable: a small datatable Javascript</summary>
+</entry>
+<entry>
+ <title>Stagit-gopher: a static git page generator for gopher</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/stagit-gopher" />
+ <id>gopher://codemadness.org/1/phlog/stagit-gopher</id>
+ <updated>2021-04-11T00:00:00Z</updated>
+ <published>2017-08-04T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>a static git page generator for gopher</summary>
+</entry>
+<entry>
+ <title>Saait: a boring HTML page generator</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/saait" />
+ <id>gopher://codemadness.org/1/phlog/saait</id>
+ <updated>2020-07-20T00:00:00Z</updated>
+ <published>2017-06-10T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Saait: a boring HTML page generator</summary>
+</entry>
+<entry>
+ <title>Stagit: a static git page generator</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/stagit" />
+ <id>gopher://codemadness.org/1/phlog/stagit</id>
+ <updated>2021-04-11T00:00:00Z</updated>
+ <published>2017-05-10T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>a static git page generator</summary>
+</entry>
+<entry>
+ <title>OpenBSD httpd, slowcgi and cgit</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/openbsd-httpd-and-cgit" />
+ <id>gopher://codemadness.org/1/phlog/openbsd-httpd-and-cgit</id>
+ <updated>2021-04-11T00:00:00Z</updated>
+ <published>2015-07-05T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>OpenBSD httpd, slowcgi and cgit</summary>
+</entry>
+<entry>
+ <title>twitch: application to watch Twitch streams</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/twitch-interface" />
+ <id>gopher://codemadness.org/1/phlog/twitch-interface</id>
+ <updated>2020-12-14T00:00:00Z</updated>
+ <published>2014-11-23T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>twitch: application to watch Twitch streams</summary>
+</entry>
+<entry>
+ <title>Userscript: focus input field</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/userscript-focus-input-field" />
+ <id>gopher://codemadness.org/1/phlog/userscript-focus-input-field</id>
+ <updated>2014-03-02T00:00:00Z</updated>
+ <published>2014-03-02T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Userscript to focus the first input field on a page with a hotkey</summary>
+</entry>
+<entry>
+ <title>Userscript: Youtube circumvent age verification</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/userscript-youtube-circumvent-age-verification" />
+ <id>gopher://codemadness.org/1/phlog/userscript-youtube-circumvent-age-verification</id>
+ <updated>2020-12-27T00:00:00Z</updated>
+ <published>2013-02-21T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Userscript to circumvent Youtube age verification and redirect to the video</summary>
+</entry>
+<entry>
+ <title>Userscript: block stupid fonts</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/userscript-block-stupid-fonts" />
+ <id>gopher://codemadness.org/1/phlog/userscript-block-stupid-fonts</id>
+ <updated>2020-03-10T00:00:00Z</updated>
+ <published>2012-10-21T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Userscript to whitelist your favorite fonts and block the rest</summary>
+</entry>
+<entry>
+ <title>Sfeed: simple RSS and Atom parser</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/sfeed" />
+ <id>gopher://codemadness.org/1/phlog/sfeed</id>
+ <updated>2022-11-05T00:00:00Z</updated>
+ <published>2011-04-01T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Sfeed is a simple RSS and Atom parser (and format programs to add reader functionality)</summary>
+</entry>
+<entry>
+ <title>Vim theme: relaxed</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/vim-theme-relaxed" />
+ <id>gopher://codemadness.org/1/phlog/vim-theme-relaxed</id>
+ <updated>2011-01-07T00:00:00Z</updated>
+ <published>2011-01-07T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>a dark VIM theme I made and use on a daily basis</summary>
+</entry>
+<entry>
+ <title>Seturgent: set urgency hints for X applications</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/seturgent" />
+ <id>gopher://codemadness.org/1/phlog/seturgent</id>
+ <updated>2020-07-20T00:00:00Z</updated>
+ <published>2010-10-31T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Seturgent is a small utility to set an application it's urgency hint</summary>
+</entry>
+<entry>
+ <title>DWM-hiltjo: my windowmanager configuration</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/dwm" />
+ <id>gopher://codemadness.org/1/phlog/dwm</id>
+ <updated>2020-07-20T00:00:00Z</updated>
+ <published>2010-08-12T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>My DWM configuration; a few added features to suit my needs</summary>
+</entry>
+<entry>
+ <title>Query unused CSS rules on current document state</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/query-unused-css-rules-on-current-document-state" />
+ <id>gopher://codemadness.org/1/phlog/query-unused-css-rules-on-current-document-state</id>
+ <updated>2010-04-21T00:00:00Z</updated>
+ <published>2010-04-21T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>How to see all the rules in a stylesheet (CSS) that are not used for the current document</summary>
+</entry>
+<entry>
+ <title>Driconf: enabling S3 texture compression on Linux</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/driconf" />
+ <id>gopher://codemadness.org/1/phlog/driconf</id>
+ <updated>2020-08-21T00:00:00Z</updated>
+ <published>2009-07-05T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>driconf: enabling S3 texture compression</summary>
+</entry>
+<entry>
+ <title>Getting the USB-powerline bridge to work on Linux</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/getting-the-usb-powerline-bridge-to-work-on-linux" />
+ <id>gopher://codemadness.org/1/phlog/getting-the-usb-powerline-bridge-to-work-on-linux</id>
+ <updated>2019-12-06T00:00:00Z</updated>
+ <published>2009-04-13T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>A guide to get a USB-powerline bridge with the Intellon 51x1 chipset working on Linux</summary>
+</entry>
+<entry>
+ <title>Gothic 1 game guide</title>
+ <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/gothic" />
+ <id>gopher://codemadness.org/1/phlog/gothic</id>
+ <updated>2025-01-05T00:00:00Z</updated>
+ <published>2009-04-12T00:00:00Z</published>
+ <author>
+ <name>Hiltjo</name>
+ <uri>gopher://codemadness.org</uri>
+ </author>
+ <summary>Gothic 1 game guide with some useful tips</summary>
+</entry>
+</feed>
DIR diff --git a/output/jsonfeed.json b/output/jsonfeed.json
@@ -0,0 +1,285 @@
+{
+"version": "https://jsonfeed.org/version/1.1",
+"title": "Newsfeed",
+"items": [
+{
+ "id": "https://www.codemadness.org/chess-puzzles.html",
+ "date_published": "2024-02-02T00:00:00Z",
+ "title": "Chess puzzle book generator",
+ "url": "https://www.codemadness.org/chess-puzzles.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "Chess puzzle book generator"
+},
+{
+ "id": "https://www.codemadness.org/xargs.html",
+ "date_published": "2023-11-22T00:00:00Z",
+ "title": "xargs: an example for parallel batch jobs",
+ "url": "https://www.codemadness.org/xargs.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "xargs: an example for parallel batch jobs"
+},
+{
+ "id": "https://www.codemadness.org/youtube-feed.html",
+ "date_published": "2023-11-20T00:00:00Z",
+ "title": "Improved Youtube RSS/Atom feed",
+ "url": "https://www.codemadness.org/youtube-feed.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "Improved Youtube Atom feed by adding video duration and filtering away shorts"
+},
+{
+ "id": "https://www.codemadness.org/webdump.html",
+ "date_published": "2023-11-20T00:00:00Z",
+ "title": "webdump HTML to plain-text converter",
+ "url": "https://www.codemadness.org/webdump.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "webdump HTML to plain-text converter"
+},
+{
+ "id": "https://www.codemadness.org/mailservice.html",
+ "date_published": "2023-10-25T00:00:00Z",
+ "title": "Setup your own mail paste service",
+ "url": "https://www.codemadness.org/mailservice.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "Setup your own mail paste service using mblaze"
+},
+{
+ "id": "https://www.codemadness.org/todo-application.html",
+ "date_published": "2022-07-01T00:00:00Z",
+ "title": "A simple TODO application",
+ "url": "https://www.codemadness.org/todo-application.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "A simple TODO application workflow"
+},
+{
+ "id": "https://www.codemadness.org/totp.html",
+ "date_published": "2022-03-23T00:00:00Z",
+ "title": "2FA TOTP without crappy authenticator apps",
+ "url": "https://www.codemadness.org/totp.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "Using 2FA TOTP without crappy authenticator apps"
+},
+{
+ "id": "https://www.codemadness.org/openbsd-riscv64-vm.html",
+ "date_published": "2021-10-23T00:00:00Z",
+ "title": "Setup an OpenBSD RISCV64 VM in QEMU",
+ "url": "https://www.codemadness.org/openbsd-riscv64-vm.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "Setup an OpenBSD RISCV-64 VM in QEMU"
+},
+{
+ "id": "https://www.codemadness.org/sfeed_curses-ui.html",
+ "date_published": "2020-06-25T00:00:00Z",
+ "title": "Sfeed_curses: a curses UI front-end for sfeed",
+ "url": "https://www.codemadness.org/sfeed_curses-ui.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "Sfeed_curses is a curses UI front-end for the sfeed RSS/Atom parser"
+},
+{
+ "id": "https://www.codemadness.org/hurl.html",
+ "date_published": "2019-11-10T00:00:00Z",
+ "title": "hurl: HTTP, HTTPS and Gopher file grabber",
+ "url": "https://www.codemadness.org/hurl.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "hurl: HTTP, HTTPS and Gopher file grabber"
+},
+{
+ "id": "https://www.codemadness.org/json2tsv.html",
+ "date_published": "2019-10-13T00:00:00Z",
+ "title": "json2tsv: a JSON to TSV converter",
+ "url": "https://www.codemadness.org/json2tsv.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "json2tsv: a JSON to TAB-Separated Value converter"
+},
+{
+ "id": "https://www.codemadness.org/openbsd-autoinstall.html",
+ "date_published": "2019-04-24T00:00:00Z",
+ "title": "OpenBSD: setup a local auto-installation server",
+ "url": "https://www.codemadness.org/openbsd-autoinstall.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "OpenBSD: setup a local auto-installation server"
+},
+{
+ "id": "https://www.codemadness.org/idiotbox.html",
+ "date_published": "2019-02-10T00:00:00Z",
+ "title": "Idiotbox: Youtube interface",
+ "url": "https://www.codemadness.org/idiotbox.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "Idiotbox: Youtube interface"
+},
+{
+ "id": "https://www.codemadness.org/gopher-proxy.html",
+ "date_published": "2018-08-17T00:00:00Z",
+ "title": "Gopher HTTP proxy",
+ "url": "https://www.codemadness.org/gopher-proxy.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "Gopher HTTP proxy"
+},
+{
+ "id": "https://www.codemadness.org/paste-service.html",
+ "date_published": "2018-03-10T00:00:00Z",
+ "title": "Setup your own file paste service",
+ "url": "https://www.codemadness.org/paste-service.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "Howto setup your own secure file paste service"
+},
+{
+ "id": "https://www.codemadness.org/setup-git-hosting.html",
+ "date_published": "2018-02-25T00:00:00Z",
+ "title": "Setup your own git hosting service",
+ "url": "https://www.codemadness.org/setup-git-hosting.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "Howto setup your own git hosting service"
+},
+{
+ "id": "https://www.codemadness.org/openbsd-sparc64-vm.html",
+ "date_published": "2017-12-11T00:00:00Z",
+ "title": "Setup an OpenBSD SPARC64 VM in QEMU",
+ "url": "https://www.codemadness.org/openbsd-sparc64-vm.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "Setup an OpenBSD SPARC64 VM in QEMU"
+},
+{
+ "id": "https://www.codemadness.org/tscrape.html",
+ "date_published": "2017-09-24T00:00:00Z",
+ "title": "Tscrape: a Twitter scraper",
+ "url": "https://www.codemadness.org/tscrape.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "Tscrape: a Twitter scraper"
+},
+{
+ "id": "https://www.codemadness.org/datatable.html",
+ "date_published": "2017-09-24T00:00:00Z",
+ "title": "jsdatatable: a small datatable Javascript",
+ "url": "https://www.codemadness.org/datatable.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "jsdatatable: a small datatable Javascript"
+},
+{
+ "id": "https://www.codemadness.org/stagit-gopher.html",
+ "date_published": "2017-08-04T00:00:00Z",
+ "title": "Stagit-gopher: a static git page generator for gopher",
+ "url": "https://www.codemadness.org/stagit-gopher.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "a static git page generator for gopher"
+},
+{
+ "id": "https://www.codemadness.org/saait.html",
+ "date_published": "2017-06-10T00:00:00Z",
+ "title": "Saait: a boring HTML page generator",
+ "url": "https://www.codemadness.org/saait.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "Saait: a boring HTML page generator"
+},
+{
+ "id": "https://www.codemadness.org/stagit.html",
+ "date_published": "2017-05-10T00:00:00Z",
+ "title": "Stagit: a static git page generator",
+ "url": "https://www.codemadness.org/stagit.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "a static git page generator"
+},
+{
+ "id": "https://www.codemadness.org/openbsd-httpd-and-cgit.html",
+ "date_published": "2015-07-05T00:00:00Z",
+ "title": "OpenBSD httpd, slowcgi and cgit",
+ "url": "https://www.codemadness.org/openbsd-httpd-and-cgit.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "OpenBSD httpd, slowcgi and cgit"
+},
+{
+ "id": "https://www.codemadness.org/twitch-interface.html",
+ "date_published": "2014-11-23T00:00:00Z",
+ "title": "twitch: application to watch Twitch streams",
+ "url": "https://www.codemadness.org/twitch-interface.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "twitch: application to watch Twitch streams"
+},
+{
+ "id": "https://www.codemadness.org/userscript-focus-input-field.html",
+ "date_published": "2014-03-02T00:00:00Z",
+ "title": "Userscript: focus input field",
+ "url": "https://www.codemadness.org/userscript-focus-input-field.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "Userscript to focus the first input field on a page with a hotkey"
+},
+{
+ "id": "https://www.codemadness.org/userscript-youtube-circumvent-age-verification.html",
+ "date_published": "2013-02-21T00:00:00Z",
+ "title": "Userscript: Youtube circumvent age verification",
+ "url": "https://www.codemadness.org/userscript-youtube-circumvent-age-verification.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "Userscript to circumvent Youtube age verification and redirect to the video"
+},
+{
+ "id": "https://www.codemadness.org/userscript-block-stupid-fonts.html",
+ "date_published": "2012-10-21T00:00:00Z",
+ "title": "Userscript: block stupid fonts",
+ "url": "https://www.codemadness.org/userscript-block-stupid-fonts.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "Userscript to whitelist your favorite fonts and block the rest"
+},
+{
+ "id": "https://www.codemadness.org/sfeed-simple-feed-parser.html",
+ "date_published": "2011-04-01T00:00:00Z",
+ "title": "Sfeed: simple RSS and Atom parser",
+ "url": "https://www.codemadness.org/sfeed-simple-feed-parser.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "Sfeed is a simple RSS and Atom parser (and format programs to add reader functionality)"
+},
+{
+ "id": "https://www.codemadness.org/vim-theme-relaxed.html",
+ "date_published": "2011-01-07T00:00:00Z",
+ "title": "Vim theme: relaxed",
+ "url": "https://www.codemadness.org/vim-theme-relaxed.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "a dark VIM theme I made and use on a daily basis"
+},
+{
+ "id": "https://www.codemadness.org/seturgent-set-urgency-hints-for-x-applications.html",
+ "date_published": "2010-10-31T00:00:00Z",
+ "title": "Seturgent: set urgency hints for X applications",
+ "url": "https://www.codemadness.org/seturgent-set-urgency-hints-for-x-applications.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "Seturgent is a small utility to set an application it's urgency hint"
+},
+{
+ "id": "https://www.codemadness.org/dwm-hiltjo-my-windowmanager-configuration.html",
+ "date_published": "2010-08-12T00:00:00Z",
+ "title": "DWM-hiltjo: my windowmanager configuration",
+ "url": "https://www.codemadness.org/dwm-hiltjo-my-windowmanager-configuration.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "My DWM configuration; a few added features to suit my needs"
+},
+{
+ "id": "https://www.codemadness.org/query-unused-css-rules-on-current-document-state.html",
+ "date_published": "2010-04-21T00:00:00Z",
+ "title": "Query unused CSS rules on current document state",
+ "url": "https://www.codemadness.org/query-unused-css-rules-on-current-document-state.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "How to see all the rules in a stylesheet (CSS) that are not used for the current document"
+},
+{
+ "id": "https://www.codemadness.org/driconf-enabling-s3-texture-compression-on-linux.html",
+ "date_published": "2009-07-05T00:00:00Z",
+ "title": "Driconf: enabling S3 texture compression on Linux",
+ "url": "https://www.codemadness.org/driconf-enabling-s3-texture-compression-on-linux.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "driconf: enabling S3 texture compression"
+},
+{
+ "id": "https://www.codemadness.org/getting-the-usb-powerline-bridge-to-work-on-linux.html",
+ "date_published": "2009-04-13T00:00:00Z",
+ "title": "Getting the USB-powerline bridge to work on Linux",
+ "url": "https://www.codemadness.org/getting-the-usb-powerline-bridge-to-work-on-linux.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "A guide to get a USB-powerline bridge with the Intellon 51x1 chipset working on Linux"
+},
+{
+ "id": "https://www.codemadness.org/gothic-1-guide.html",
+ "date_published": "2009-04-12T00:00:00Z",
+ "title": "Gothic 1 game guide",
+ "url": "https://www.codemadness.org/gothic-1-guide.html",
+ "authors": [{"name": "Hiltjo"}],
+ "content_text": "Gothic 1 game guide with some useful tips"
+}]
+}
DIR diff --git a/output/jsonfeed_content.json b/output/jsonfeed_content.json
@@ -0,0 +1,285 @@
+{
+"version": "https://jsonfeed.org/version/1.1",
+"title": "Newsfeed",
+"items": [
+{
+ "id": "https://www.codemadness.org/chess-puzzles.html",
+ "date_published": "2024-02-02T00:00:00Z",
+ "title": "Chess puzzle book generator",
+ "url": "https://www.codemadness.org/chess-puzzles.html",
+ "authors": [{"name": "Hiltjo"}],
codemadness.org:70 /git/www.codemadness.org/commit/aa794d3b1909cb60c010583a2da9a3582cdc6b16.gph:4651: line too long