URI: 
       atom_content_gopher.xml - 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
       ---
       atom_content_gopher.xml (186171B)
       ---
            1 <?xml version="1.0" encoding="UTF-8"?>
            2 <feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
            3         <title>Codemadness</title>
            4         <subtitle>blog with various projects and articles about computer-related things</subtitle>
            5         <updated>2026-05-31T00:00:00Z</updated>
            6         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org" />
            7         <id>gopher://codemadness.org/0/atom_content_gopher.xml</id>
            8         <link rel="self" type="application/atom+xml" href="gopher://codemadness.org/0/atom_content_gopher.xml" />
            9 <entry>
           10         <title>Wireguard on OpenBSD for use as a mobile VPN</title>
           11         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/wireguard" />
           12         <id>gopher://codemadness.org/1/phlog/wireguard</id>
           13         <updated>2026-05-30T00:00:00Z</updated>
           14         <published>2026-03-27T00:00:00Z</published>
           15         <author>
           16                 <name>Hiltjo</name>
           17                 <uri>gopher://codemadness.org</uri>
           18         </author>
           19         <summary>Guide to setup a Wireguard endpoint on OpenBSD to use as a (mobile) VPN</summary>
           20         <content type="html"><![CDATA[<h1>Wireguard on OpenBSD for use as a mobile VPN</h1>
           21         <p><strong>Last modification on </strong> <time>2026-05-30</time></p>
           22         <p>Wireguard is a fast, modern and secure VPN tunnel.</p>
           23 <p>Below is a guide to setup <a href="https://www.wireguard.com/">Wireguard</a> on the OpenBSD
           24 operating system intended for use as a mobile VPN.</p>
           25 <p>It describes using the OpenBSD Wireguard wg(4) kernel driver using ifconfig,
           26 not the userland application, and will focus on setting up a IPv4 tunnel.</p>
           27 <p>It is however recommended to install wireguard-tools, because it contains
           28 useful tools to generate a private and public key (wg genkey, wg pubkey).</p>
           29 <p>To install the wireguard-tools package on OpenBSD:</p>
           30 <pre><code># pkg_add wireguard-tools
           31 </code></pre>
           32 <h2>Enable IPv4 traffic forwarding</h2>
           33 <p>To enable traffic forwarding for IPv4 run:</p>
           34 <pre><code># sysctl net.inet.ip.forwarding=1
           35 </code></pre>
           36 <p>To make it persistent add the above lines to the file /etc/sysctl.conf.  These
           37 sysctl lines are loaded on boot time.</p>
           38 <h2>Server config: /etc/hostname.wg0</h2>
           39 <p>This is an example config for the wg0 network interface.  It is stored at
           40 /etc/hostname.wg0:</p>
           41 <pre><code>wgport 51820 wgkey 'private_key_here'
           42 inet 10.1.2.1/24
           43 up
           44 
           45 # peer: phone
           46 wgpeer 'pubkey_here' wgaip 10.1.2.2/32 wgdescr 'phone' wgpsk 'psk_here'
           47 </code></pre>
           48 <h2>Generating a private key</h2>
           49 <p>Using wireguard-tools wg command:</p>
           50 <pre><code>$ wg genkey
           51 </code></pre>
           52 <p>Replace private_key_here with the generated text.</p>
           53 <p>To generate both a private and public key to the files private.key and
           54 public.key:</p>
           55 <pre><code>$ wg genkey | tee private.key | wg pubkey &gt; public.key
           56 </code></pre>
           57 <p><strong>!!! Keep the private key secure. Do not share it with anyone!!!</strong></p>
           58 <h2>Generate a separate preshared key (PSK).</h2>
           59 <p>Using a preshared key (PSK) is optional, but recommended. This is used in the
           60 handshake to guard against future compromise of the peers' encrypted tunnel if
           61 a quantum-computational attack on their Diffie-Hellman exchange becomes
           62 feasible.</p>
           63 <p>Using wireguard-tools wg command:</p>
           64 <pre><code>$ wg genpsk
           65 </code></pre>
           66 <p>The PSK can be shared with a known client when configuring the clients. <strong>Make sure
           67 to share it via a safe channel</strong>.</p>
           68 <p>To configure or restart the wg0 interface using the configuration in
           69 /etc/hostname.wg0:</p>
           70 <pre><code># sh /etc/netstart wg0
           71 </code></pre>
           72 <p>To show general info of the interface run the command (requires root
           73 permissions to view all information):</p>
           74 <pre><code># ifconfig wg0
           75 </code></pre>
           76 <p>In the ifconfig wg0 output it should list the server public key as:</p>
           77 <pre><code>wgpubkey server_pubkey_here
           78 </code></pre>
           79 <h2>Full example of a client config: wg-client.conf</h2>
           80 <pre><code>[Interface]
           81 Address = 10.1.2.2/32
           82 DNS = 10.1.2.1
           83 PrivateKey = CHBzstIHCi7+YOOa2MN0RXhkPAmJwIXQW0e6/n6+Pno=
           84 
           85 [Peer]
           86 AllowedIPs = 0.0.0.0/0
           87 Endpoint = example.org:51820
           88 PreSharedKey = 8ao/EMExyPAHrT3ShX+lnA0u7jUmo7MhrT0GjDcrIJA=
           89 PublicKey = Rny+AW4EPqPPxfO+8O+QdlkIrWbZRGQ6u6Fje5pUOFM=
           90 </code></pre>
           91 <p><strong>Of course do not copy-paste this private key and PSK. Generate your own ;)</strong></p>
           92 <h2>pf(4) firewall rules</h2>
           93 <p>Below is a fragment of the firewall rules required for Wireguard.
           94 These rules assume a simple VPS with a vio network interface connected to the
           95 interwebs (no double NAT or other weird complex things ;)).</p>
           96 <p>pf.conf:</p>
           97 <pre><code># wireguard
           98 pass out quick on egress inet from (wg0:network) nat-to (vio0:0)
           99 pass in quick on wg0 from any to any
          100 pass in quick on wg0 proto udp from any to any port 51820
          101 # allow all on wireguard
          102 pass quick on wg0
          103 </code></pre>
          104 <h2>Mobile VPN application</h2>
          105 <p>For Android download the APK from <a href="https://www.wireguard.com/install/">https://www.wireguard.com/install/</a>.
          106 There are also other versions available on the page.</p>
          107 <h2>Android Wireguard settings</h2>
          108 <h2>Adding a tunnel</h2>
          109 <p>In the Wireguard application press the plus (+) button in the bottom left of
          110 the screen to add a tunnel.</p>
          111 <h2>Option: "Scan from QR code"</h2>
          112 <h3>Generate a QR code image from a client config</h3>
          113 <p>Install the libqrencode package for the qrencode program:</p>
          114 <pre><code># pkg_add libqrencode
          115 </code></pre>
          116 <p>Generate a QR code PNG image from a client config:</p>
          117 <pre><code>$ qrencode -o qr.png &lt; wg-client.conf
          118 </code></pre>
          119 <p>This QR code simply contains the full text from the wg-client.conf. It can be
          120 scanned from the Android Wireguard application.  If it contains sensitive
          121 information such as the private key make sure to share the image in a safe way
          122 and/or destroy it immediately.</p>
          123 <p><img src="downloads/openbsd-wg/client-example-qr.png" alt="QR code image" /></p>
          124 <p>If the QR code contains a private key, make sure to destroy it "Inspector Gadget"-style.</p>
          125 <p><img src="downloads/openbsd-wg/inspector_gadget.jpg" alt="inspector Gadget reading self-destruct message" width="320" height="240" loading="lazy" /></p>
          126 <p><a href="downloads/openbsd-wg/inspector_gadget.webm">Inspector Gadget, self-destruct video clip</a></p>
          127 <p>Now scan the generated image to import the config.</p>
          128 <h3>Option: "Import from file or archive"</h3>
          129 <p>Import a text .conf file or archive (ZIP) file containing one or more configs.</p>
          130 <p>Example conf file: <a href="downloads/openbsd-wg/client-example.conf">client-example.conf</a>.<br />  
          131 Example ZIP file: <a href="downloads/openbsd-wg/client-example.zip">client-example.zip</a>.</p>
          132 <h3>Option: "Create from scratch"</h3>
          133 <p>Generating the private key on the device itself and sharing the <strong>public</strong> key
          134 and PSK is probably the safest option.  Although sharing the public key text
          135 from a mobile device can be a bit annoying.</p>
          136 <h2>Android settings</h2>
          137 <p>Only allow connections and DNS using VPN:</p>
          138 <ul>
          139 <li>Settings -&gt; VPN -&gt; Network &amp; Internet:
          140 Make sure Wireguard is set and enabled under VPN.</li>
          141 </ul>
          142 <p>VPN settings, open Wireguard cogwheel:
          143 <ul>
          144 <li>Enable: Always on VPN option, with the description: "Stay connected to VPN at all times".</li>
          145 <li>Enable: Block connections without VPN.</li>
          146 </ul>
          147 </p>
          148 <p>Other recommendations:</p>
          149 <ul>
          150 <li>Under Wi-Fi -&gt; Privacy.
          151 <ul>
          152 <li>Use randomized MAC.</li>
          153 <li>Disable "Send device name".</li>
          154 </ul>
          155 </li>
          156 <li>Set a secure and privacy-respecting DNS server.</li>
          157 </ul>
          158 <h2>Wireguard persistent keepalive setting</h2>
          159 <p>If the interface very rarely sends traffic, but it might at anytime receive
          160 traffic from a peer, and it is behind NAT, the interface might benefit from
          161 having a persistent keepalive interval of 25 seconds.</p>
          162 <p>If it is not needed, then it is recommended to not enable it, which is the
          163 default.</p>
          164 <p>This option is called PersistentKeepalive in Wireguard conf and is called
          165 wgpka for OpenBSD ifconfig, see the ifconfig(8) man page WIREGUARD section.</p>
          166 <h2>Debugging tips</h2>
          167 <p>For the Wireguard Android application you can find a textual log:</p>
          168 <ul>
          169 <li>Open the Wireguard application.</li>
          170 <li>At the top right select the 3 dots settings thingy.</li>
          171 <li>Select the menu labeled "View application log".</li>
          172 </ul>
          173 <p>On the OpenBSD server you can run enable run-time debugging on the wg0 interface:</p>
          174 <pre><code># ifconfig wg0 debug
          175 </code></pre>
          176 <h2>Bonus: example using wg-quick from wg-tools</h2>
          177 <p>Using the wg-quick program from wg-tools you can also quickly setup a client.
          178 This will setup the DNS, routing and interface. It can setup and restore the
          179 DNS and routing settings easily.</p>
          180 <p>As root, to setup the interface:</p>
          181 <pre><code># wg-quick up absolute/path/to/config/wg-client.conf
          182 </code></pre>
          183 <p>As root, to restore the interface:</p>
          184 <pre><code># wg-quick down absolute/path/to/config/wg-client.conf
          185 </code></pre>
          186 <h2>Bonus: generating a private key using only OpenSSL commands</h2>
          187 <p>Generate a private key:</p>
          188 <pre><code>$ openssl genpkey -algorithm X25519 -outform DER -out private.der
          189 </code></pre>
          190 <p>Now extract the last 32 bytes which has part of the actual private key (the
          191 first ASN.1 DER encoded bytes contain metadata information). Convert the actual
          192 key data to base64.</p>
          193 <p>Run:</p>
          194 <pre><code>$ tail -c 32 private.der | openssl enc -a -A &gt; private.key
          195 </code></pre>
          196 <p>Derive public key:</p>
          197 <pre><code>$ openssl pkey -inform DER -in private.der -pubout -outform DER -out public.der
          198 </code></pre>
          199 <p>Convert public key to Wireguard format:</p>
          200 <pre><code>$ tail -c 32 public.der | openssl enc -a -A &gt; public.key
          201 </code></pre>
          202 <p>Or even the magical voodoo commands:</p>
          203 <pre><code>$ openssl rand -base64 32 &gt; private.key
          204 $ (printf '\060\056\002\001\000\060\005\006\003\053\145\156\004\042\004\040';openssl enc -d -a -A &lt; private.key) | \
          205   openssl pkey -inform DER -pubout -outform DER | \
          206         tail -c 32 | \
          207         openssl enc -a -A &gt; public.key
          208 </code></pre>
          209 <h2>References</h2>
          210 <ul>
          211 <li><a href="https://www.wireguard.com/">Wireguard</a>:
          212 <ul>
          213 <li><a href="https://www.wireguard.com/quickstart/">Wireguard quickstart page</a>:
          214 This uses the userland Wireguard programs and config. But it contains
          215 helpful information.<br />  </li>
          216 <li><a href="https://www.man7.org/linux/man-pages/man8/wg.8.html">wg(8) man page</a>.</li>
          217 <li><a href="https://www.man7.org/linux/man-pages/man8/wg-quick.8.html">wg-quick(8) man page</a>.</li>
          218 </ul>
          219 </li>
          220 <li><a href="https://www.openbsd.org/">OpenBSD operating system</a>:
          221 <ul>
          222 <li><a href="https://man.openbsd.org/wg">wg(4) driver man page</a>.</li>
          223 <li><a href="https://man.openbsd.org/ifconfig.8#WIREGUARD">ifconfig(8) man page WIREGUARD section</a>.</li>
          224 <li><a href="https://man.openbsd.org/pf.conf.5">pf.conf(5) file format</a>.</li>
          225 </ul>
          226 </li>
          227 </ul>
          228 ]]></content>
          229 </entry>
          230 <entry>
          231         <title>susmb: unprivileged mounting of SMB/CIFS shares via FUSE</title>
          232         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/susmb" />
          233         <id>gopher://codemadness.org/1/phlog/susmb</id>
          234         <updated>2026-03-06T00:00:00Z</updated>
          235         <published>2026-03-06T00:00:00Z</published>
          236         <author>
          237                 <name>Hiltjo</name>
          238                 <uri>gopher://codemadness.org</uri>
          239         </author>
          240         <summary>susmb: unprivileged mounting of SMB/CIFS shares via FUSE</summary>
          241         <content type="html"><![CDATA[<h1>susmb: unprivileged mounting of SMB/CIFS shares via FUSE</h1>
          242         <p><strong>Last modification on </strong> <time>2026-03-06</time></p>
          243         <p>usmb is a program to mount SMB shares from userland via FUSE.
          244 Under-the-hood it uses Samba and exposes the network share as a regular local filesystem.
          245 Programs can just access the filesystem and for example do not need to add code for SMB support or link against Samba.
          246 It is more convenient than scripting around smbclient in some cases.<br />  
          247 smbclient(1): <a href="https://www.samba.org/samba/docs/current/man-html/smbclient.1.html">https://www.samba.org/samba/docs/current/man-html/smbclient.1.html</a></p>
          248 <p>susmb is a fork of usmb from 2013-02-04.
          249 <a href="http://repo.or.cz/w/usmb.git/snapshot/aa94e132c12faf1a00f547ea4a96b5728612dea6.tar.gz">http://repo.or.cz/w/usmb.git/snapshot/aa94e132c12faf1a00f547ea4a96b5728612dea6.tar.gz</a>
          250 (git commit aa94e132c12faf1a00f547ea4a96b5728612dea6)</p>
          251 <p>usmb has been unmaintained since 2013. Sometimes programs are finished and so
          252 being unmaintained is not so bad. I think the general idea of the code was good
          253 and it is still a useful program for some systems, probably mostly BSD systems.
          254 Linux has a SMB/CIFS driver anyway.</p>
          255 <p>The two main reasons I forked usmb were performance issues with it on OpenBSD
          256 (because of hardcoded options and assumptions for FUSE for Linux) and issues by
          257 using it in non-interactive mode.</p>
          258 <h2>Clone</h2>
          259 <pre><code>git clone git://git.codemadness.org/susmb
          260 </code></pre>
          261 <h2>Browse</h2>
          262 <p>You can browse the source-code at:</p>
          263 <ul>
          264 <li><a href="https://git.codemadness.org/susmb/">https://git.codemadness.org/susmb/</a></li>
          265 <li><a href="gopher://codemadness.org/1/git/susmb">gopher://codemadness.org/1/git/susmb</a></li>
          266 </ul>
          267 <h2>Download releases</h2>
          268 <p>Releases are available at:</p>
          269 <ul>
          270 <li><a href="https://codemadness.org/releases/susmb/">https://codemadness.org/releases/susmb/</a></li>
          271 <li><a href="gopher://codemadness.org/1/releases/susmb">gopher://codemadness.org/1/releases/susmb</a></li>
          272 </ul>
          273 <h2>Build and install</h2>
          274 <pre><code>$ make
          275 # make install
          276 </code></pre>
          277 <h2>Dependencies</h2>
          278 <ul>
          279 <li>C compiler.</li>
          280 <li>libc + BSD extensions (libbsd on Linux).</li>
          281 <li>FUSE 2.6 or later (and probably version &lt;3).</li>
          282 <li>Samba / libsmbclient 4.20+.</li>
          283 </ul>
          284 <h2>Usage example</h2>
          285 <pre><code>susmb \
          286         -u hiltjo \
          287         -f \
          288         -o 'uid=1000,gid=1000,allow_other' \
          289         "smb://domain\someuser@192.168.1.1/Storage" \
          290         /mnt/share
          291 </code></pre>
          292 <h2>Changes</h2>
          293 <p>susmb has the patches applied from OpenBSD ports 7.6:
          294 <a href="https://cvsweb.openbsd.org/ports/sysutils/usmb/patches">https://cvsweb.openbsd.org/ports/sysutils/usmb/patches</a></p>
          295 <p>Below is a summary of the most important changes:</p>
          296 <h2>Performance</h2>
          297 <ul>
          298 <li>Set struct stat st.st_blksiz to a higher number for more efficient buffering
          299 for programs using the standard FILE* stdio interfaces. Huge improvement for
          300 reads and writes.<br />  
          301 On OpenBSD the default block size for FUSE is 512 bytes.
          302 This crippled network performance.<br />  
          303 On OpenBSD there is no FUSE caching layer (at time of writing 2025-09-07), so
          304 each read call had more overhead.</li>
          305 <li>Remove the hardcoded FUSE mount option max_read=N.<br />  
          306 This crippled network performance.</li>
          307 </ul>
          308 <h2>Security</h2>
          309 <ul>
          310 <li>Many code simplifications and deletions (reducing attack surface and makes it
          311 easier to review).</li>
          312 <li>Use unveil(2) syscall to lock down much of the filesystem except the
          313 mountpoint and required FUSE devices.</li>
          314 <li>Optional privilege dropping support: on OpenBSD FUSE would need to run as root:
          315 (sysctl kern.usermount was removed around July 2016, around
          316 commit 65c8a8a0394483b41de8f02c862e65fb529cf538).<br />  
          317 After mounting the filesystem and acquiring access to the FUSE driver
          318 privileges are dropped. This is not perfect, but at least now the Samba smbclient
          319 code runs as a user again.</li>
          320 <li>Reading the password from the terminal in a secure way using readpassphrase(3).</li>
          321 </ul>
          322 <h2>Cleanups</h2>
          323 <ul>
          324 <li>Merge everything into one C file for easier code review.</li>
          325 <li>Remove Samba &lt; 3.3 compatibility layer and code. This is hard to test
          326 nowadays anyway.</li>
          327 <li>Use getopt for option parsing: remove dependencies on glib which was used for
          328 option parsing only.</li>
          329 <li>Remove long option support.</li>
          330 <li>Remove libxml2 dependency and configuration via XML. Configuration is now
          331 done via a simpler syntax as a URI from the command-line. This was also
          332 listed in the man page under the BUGS section as a wanted feature.</li>
          333 <li>Remove autoconf and Debian-specific packaging files. Use a simple Makefile.</li>
          334 <li>Man page rewritten from roff to mandoc.</li>
          335 </ul>
          336 <h2>OpenBSD port added as sysutils/susmb</h2>
          337 <p>An OpenBSD port was added to sysutils/susmb (thanks to Pascal Stumpf!).</p>
          338 <p>Mailinglist thread: <a href="https://marc.info/?l=openbsd-ports&amp;m=177169411929407&amp;w=2">https://marc.info/?l=openbsd-ports&amp;m=177169411929407&amp;w=2</a></p>
          339 ]]></content>
          340 </entry>
          341 <entry>
          342         <title>Chess puzzle book generator</title>
          343         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/chess-puzzles" />
          344         <id>gopher://codemadness.org/1/phlog/chess-puzzles</id>
          345         <updated>2026-03-28T00:00:00Z</updated>
          346         <published>2024-02-02T00:00:00Z</published>
          347         <author>
          348                 <name>Hiltjo</name>
          349                 <uri>gopher://codemadness.org</uri>
          350         </author>
          351         <summary>Chess puzzle book generator</summary>
          352         <content type="html"><![CDATA[<h1>Chess puzzle book generator</h1>
          353         <p><strong>Last modification on </strong> <time>2026-03-28</time></p>
          354         <p>This was a christmas hack for fun and non-profit.
          355 I wanted to write a chess puzzle book generator.
          356 Inspired by <a href="https://archive.org/details/1001deadlycheckm0000nunn">1001 Deadly Checkmates by John Nunn, ISBN-13: 978-1906454258</a>,
          357 <a href="https://www.stappenmethode.nl/en/">Steps Method workbooks</a> and other puzzle books.</p>
          358 <h1>Example output</h1>
          359 <ul>
          360 <li>English version: <a href="https://codemadness.org/downloads/puzzles/">https://codemadness.org/downloads/puzzles/</a></li>
          361 <li>Dutch version: <a href="https://hiltjo.nl/puzzles/">https://hiltjo.nl/puzzles/</a></li>
          362 </ul>
          363 <p>Terminal version:</p>
          364 <pre><code>curl -s 'https://codemadness.org/downloads/puzzles/index.vt' | less -R
          365 </code></pre>
          366 <p>I may or may not periodially update this page :)</p>
          367 <p>Time flies (since Christmas), here is a valentine edition with <a href="https://lichess.org/practice/intermediate-tactics/attraction/">attraction</a>
          368 puzzles (not only checkmates) using the red "love" theme.
          369 It is optimized for his and her pleasure:</p>
          370 <p><a href="https://codemadness.org/downloads/puzzles-valentine/">https://codemadness.org/downloads/puzzles-valentine/</a></p>
          371 <h2>Clone</h2>
          372 <pre><code>git clone git://git.codemadness.org/chess-puzzles
          373 </code></pre>
          374 <h2>Browse</h2>
          375 <p>You can browse the source-code at:</p>
          376 <ul>
          377 <li><a href="https://git.codemadness.org/chess-puzzles/">https://git.codemadness.org/chess-puzzles/</a></li>
          378 <li><a href="gopher://codemadness.org/1/git/chess-puzzles">gopher://codemadness.org/1/git/chess-puzzles</a></li>
          379 </ul>
          380 <h1>Quick overview of how it works</h1>
          381 <p>The generate.sh shellscript generates the output and files for the puzzles.</p>
          382 <p>The puzzles used are from the lichess.org puzzle database:
          383 <a href="https://database.lichess.org/#puzzles">https://database.lichess.org/#puzzles</a></p>
          384 <p>This database is a big CSV file containing the initial board state in the
          385 Forsyth-Edwards Notation (FEN) format and the moves in Universal Chess
          386 Interface (UCI) format. Each line contains the board state and the initial and
          387 solution moves.</p>
          388 <p>The generated index page is a HTML page, it lists the puzzles.  Each puzzle on
          389 this page is an SVG image. This scalable image format looks good in all
          390 resolutions.</p>
          391 <h1>Open puzzle data</h1>
          392 <p>Lichess is an <a href="https://lichess.org/source">open-source</a> and gratis website to play on-line chess. There are
          393 no paid levels to unlock features.  All the software hosting Lichess is
          394 open-source and anyone can register and play chess on it for free. Most of the
          395 data about the games played is also open.</p>
          396 <p>However, the website depends on your donations or contributions. If you can,
          397 <a href="https://lichess.org/about">please do so</a>.</p>
          398 <h1>generate.sh</h1>
          399 <p>Reads puzzles from the database and shuffle them. Do some rough sorting and
          400 categorization based on difficulty and assign score points.</p>
          401 <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
          402 same machine with the same puzzle database it will regenerate the same sequence
          403 of random puzzles in a deterministic manner.</p>
          404 <p>It outputs HTML, with support for CSS dark mode and does not require Javascript.
          405 It includes a plain-text listing of the solutions in PGN notation for the
          406 puzzles.
          407 It also outputs .vt files suitable for the terminal. It uses unicode symbols
          408 for the chess pieces and RGB color sequence for the board theme.</p>
          409 <h1>fen.c</h1>
          410 <p>This is a program written in C to read and parse the board state in FEN format
          411 and read the UCI moves. It can output to various formats.</p>
          412 <p>See the man page for detailed usage information.</p>
          413 <p>fen.c supports the following output formats:</p>
          414 <ul>
          415 <li>ascii - very simple ASCII mode.</li>
          416 <li>describe - describes current pieces positions as text, line by line (useful for blindfold or visualization practise).</li>
          417 <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>
          418 <li><a href="https://en.wikipedia.org/wiki/Portable_Game_Notation">pgn</a> - Portable Game Notation.</li>
          419 <li>speak - mode to output a description of the moves in words.</li>
          420 <li><a href="https://en.wikipedia.org/wiki/SVG">SVG</a> - Scalable Vector Graphics image.</li>
          421 <li>tty - Terminal output with some markup using escape codes.</li>
          422 </ul>
          423 <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>
          424 <p><img src="https://codemadness.org/onlyfens?fen=6k1/ppq3bp/2n2np1/5p2/2P2P2/4rBN1/PP3K1P/RQ6%20w%20-%20-%200%2023&amp;moves=f2e3&amp;flip=1" alt="Position from game: Rene Letelier Martner - Robert James Fischer, 1960-10-24" /></p>
          425 <ul>
          426 <li><a href="https://codemadness.org/onlyfens">https://codemadness.org/onlyfens</a></li>
          427 <li><a href="https://codemadness.org/onlyfens?fen=6k1/ppq3bp/2n2np1/5p2/2P2P2/4rBN1/PP3K1P/RQ6%20w%20-%20-%200%2023&amp;moves=f2e3&amp;flip=1">https://codemadness.org/onlyfens?fen=6k1/ppq3bp/2n2np1/5p2/2P2P2/4rBN1/PP3K1P/RQ6%20w%20-%20-%200%2023&amp;moves=f2e3&amp;flip=1</a></li>
          428 <li><a href="https://codemadness.org/onlyfens?moves=e2e4%20e7e5&amp;flip=1&amp;theme=green&amp;output=svg">https://codemadness.org/onlyfens?moves=e2e4%20e7e5&amp;flip=1&amp;theme=green&amp;output=svg</a></li>
          429 <li><a href="https://codemadness.org/onlyfens?moves=e2e4%20e7e5&amp;output=pgn">https://codemadness.org/onlyfens?moves=e2e4%20e7e5&amp;output=pgn</a></li>
          430 <li><a href="https://codemadness.org/onlyfens?moves=e2e4%20e7e5&amp;output=speak">https://codemadness.org/onlyfens?moves=e2e4%20e7e5&amp;output=speak</a></li>
          431 <li><a href="https://codemadness.org/onlyfens?moves=e2e4%20e7e5&amp;output=ascii">https://codemadness.org/onlyfens?moves=e2e4%20e7e5&amp;output=ascii</a></li>
          432 <li><a href="https://codemadness.org/onlyfens?moves=e2e4%20e7e5&amp;output=fen">https://codemadness.org/onlyfens?moves=e2e4%20e7e5&amp;output=fen</a></li>
          433 </ul>
          434 <p>Terminal output:</p>
          435 <pre><code>curl -s 'https://codemadness.org/onlyfens?moves=e2e4%20e7e5&amp;output=tty'
          436 </code></pre>
          437 <h1>Support for Dutch notated PGN and output</h1>
          438 <p>For pgn and "speak mode" it has an option to output Dutch notated PGN or speech
          439 too.</p>
          440 <p>For example:</p>
          441 <ul>
          442 <li>Queen = Dame (Q -&gt; D), translated: lady.</li>
          443 <li>Rook = Toren (R -&gt; T), translated: tower.</li>
          444 <li>Bishop = Loper (B -&gt; L), translated: walker.</li>
          445 <li>Knight = Paard (N -&gt; P), translated: horse.</li>
          446 </ul>
          447 <h1>Example script to stream games from Lichess</h1>
          448 <p>There is an included example script that can stream Lichess games to the
          449 terminal. It uses the <a href="https://lichess.org/api">Lichess API</a>.  It will display the board using terminal
          450 escape codes. The games are automatically annotated with PGN notation and with
          451 text how a human would say the notation. This can also be piped to a speech
          452 synthesizer like <a href="https://github.com/espeak-ng/espeak-ng/">espeak</a> as audio.</p>
          453 <p>pgn-extract is a useful tool to convert Portable Game Notation (PGN) to
          454 Universal Chess Interface (UCI) moves (or do many other useful chess related
          455 things!).</p>
          456 <h1>Example script to generate an animated gif from PGN</h1>
          457 <p>Theres also an example script included that can generate an animated gif from
          458 PGN using <a href="https://ffmpeg.org/">ffmpeg</a>.</p>
          459 <p>It creates an optimal color palette from the input images and generates an
          460 optimized animated gif. The last move (typically some checkmate) is displayed
          461 slightly longer.</p>
          462 <h1>References and chess related links</h1>
          463 <ul>
          464 <li><p>chess-puzzles source-code:<br />  
          465 <a href="https://www.codemadness.org/git/chess-puzzles/file/README.html">https://www.codemadness.org/git/chess-puzzles/file/README.html</a></p>
          466 </li>
          467 <li><p>Lichess FEN puzzle database:<br />  
          468 <a href="https://database.lichess.org/#puzzles">https://database.lichess.org/#puzzles</a></p>
          469 </li>
          470 <li><p>lichess.org:<br />  
          471 <a href="https://lichess.org/">https://lichess.org/</a></p>
          472 </li>
          473 <li><p>SVG of the individual pieces used in fen.c:<br />  
          474 <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>
          475 </li>
          476 <li><p>pgn-extract:<br />  
          477 A great multi-purpose PGN manipulation program with many options:<br />  
          478 <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>
          479 <p>An example to convert PGN games to UCI moves:<br />  
          480 <code>pgn-extract --notags -Wuc</code></p>
          481 </li>
          482 <li><p>Lichess API:<br />  
          483 <a href="https://lichess.org/api">https://lichess.org/api</a></p>
          484 </li>
          485 <li><p>Stockfish:<br />  
          486 Strong open-source chess engine and analysis tool:<br />  
          487 <a href="https://stockfishchess.org/">https://stockfishchess.org/</a></p>
          488 </li>
          489 </ul>
          490 ]]></content>
          491 </entry>
          492 <entry>
          493         <title>xargs: an example for parallel batch jobs</title>
          494         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/xargs" />
          495         <id>gopher://codemadness.org/1/phlog/xargs</id>
          496         <updated>2023-12-17T00:00:00Z</updated>
          497         <published>2023-11-22T00:00:00Z</published>
          498         <author>
          499                 <name>Hiltjo</name>
          500                 <uri>gopher://codemadness.org</uri>
          501         </author>
          502         <summary>xargs: an example for parallel batch jobs</summary>
          503         <content type="html"><![CDATA[<h1>xargs: an example for parallel batch jobs</h1>
          504         <p><strong>Last modification on </strong> <time>2023-12-17</time></p>
          505         <p>This describes a simple shellscript programming pattern to process a list of
          506 jobs in parallel. This script example is contained in one file.</p>
          507 <h2>Simple but less optimal example</h2>
          508 <pre><code>#!/bin/sh
          509 maxjobs=4
          510 
          511 # fake program for example purposes.
          512 someprogram() {
          513         echo "Yep yep, I'm totally a real program!"
          514         sleep "$1"
          515 }
          516 
          517 # run(arg1, arg2)
          518 run() {
          519         echo "[$1] $2 started" &gt;&amp;2
          520         someprogram "$1" &gt;/dev/null
          521         status="$?"
          522         echo "[$1] $2 done" &gt;&amp;2
          523         return "$status"
          524 }
          525 
          526 # process the jobs.
          527 j=1
          528 for f in 1 2 3 4 5 6 7 8 9 10; do
          529         run "$f" "something" &amp;
          530 
          531         jm=$((j % maxjobs)) # shell arithmetic: modulo
          532         test "$jm" = "0" &amp;&amp; wait
          533         j=$((j+1))
          534 done
          535 wait
          536 </code></pre>
          537 <h2>Why is this less optimal</h2>
          538 <p>This is less optimal because it waits until all jobs in the same batch are finished
          539 (each batch contain $maxjobs items).</p>
          540 <p>For example with 2 items per batch and 4 total jobs it could be:</p>
          541 <ul>
          542 <li>Job 1 is started.</li>
          543 <li>Job 2 is started.</li>
          544 <li>Job 2 is done.</li>
          545 <li>Job 1 is done.</li>
          546 <li>Wait: wait on process status of all background processes.</li>
          547 <li>Job 3 in new batch is started.</li>
          548 </ul>
          549 <p>This could be optimized to:</p>
          550 <ul>
          551 <li>Job 1 is started.</li>
          552 <li>Job 2 is started.</li>
          553 <li>Job 2 is done.</li>
          554 <li>Job 3 in new batch is started (immediately).</li>
          555 <li>Job 1 is done.</li>
          556 <li>...</li>
          557 </ul>
          558 <p>It also does not handle signals such as SIGINT (^C). However the xargs example
          559 below does:</p>
          560 <h2>Example</h2>
          561 <pre><code>#!/bin/sh
          562 maxjobs=4
          563 
          564 # fake program for example purposes.
          565 someprogram() {
          566         echo "Yep yep, I'm totally a real program!"
          567         sleep "$1"
          568 }
          569 
          570 # run(arg1, arg2)
          571 run() {
          572         echo "[$1] $2 started" &gt;&amp;2
          573         someprogram "$1" &gt;/dev/null
          574         status="$?"
          575         echo "[$1] $2 done" &gt;&amp;2
          576         return "$status"
          577 }
          578 
          579 # child process job.
          580 if test "$CHILD_MODE" = "1"; then
          581         run "$1" "$2"
          582         exit "$?"
          583 fi
          584 
          585 # generate a list of jobs for processing.
          586 list() {
          587         for f in 1 2 3 4 5 6 7 8 9 10; do
          588                 printf '%s\0%s\0' "$f" "something"
          589         done
          590 }
          591 
          592 # process jobs in parallel.
          593 list | CHILD_MODE="1" xargs -r -0 -P "${maxjobs}" -L 2 "$(readlink -f "$0")"
          594 </code></pre>
          595 <h2>Run and timings</h2>
          596 <p>Although the above example is kindof stupid, it already shows the queueing of
          597 jobs is more efficient.</p>
          598 <p>Script 1:</p>
          599 <pre><code>time ./script1.sh
          600 [...snip snip...]
          601 real    0m22.095s
          602 </code></pre>
          603 <p>Script 2:</p>
          604 <pre><code>time ./script2.sh
          605 [...snip snip...]
          606 real    0m18.120s
          607 </code></pre>
          608 <h2>How it works</h2>
          609 <p>The parent process:</p>
          610 <ul>
          611 <li>The parent, using xargs, handles the queue of jobs and schedules the jobs to
          612 execute as a child process.</li>
          613 <li>The list function writes the parameters to stdout. These parameters are
          614 separated by the NUL byte separator. The NUL byte separator is used because
          615 this character cannot be used in filenames (which can contain spaces or even
          616 newlines) and cannot be used in text (the NUL byte terminates the buffer for
          617 a string).</li>
          618 <li>The -L option must match the amount of arguments that are specified for the
          619 job. It will split the specified parameters per job.</li>
          620 <li>The expression "$(readlink -f "$0")" gets the absolute path to the
          621 shellscript itself. This is passed as the executable to run for xargs.</li>
          622 <li>xargs calls the script itself with the specified parameters it is being fed.
          623 The environment variable $CHILD_MODE is set to indicate to the script itself
          624 it is run as a child process of the script.</li>
          625 </ul>
          626 <p>The child process:</p>
          627 <ul>
          628 <li><p>The command-line arguments are passed by the parent using xargs.</p>
          629 </li>
          630 <li><p>The environment variable $CHILD_MODE is set to indicate to the script itself
          631 it is run as a child process of the script.</p>
          632 </li>
          633 <li><p>The script itself (ran in child-mode process) only executes the task and
          634 signals its status back to xargs and the parent.</p>
          635 </li>
          636 <li><p>The exit status of the child program is signaled to xargs. This could be
          637 handled, for example to stop on the first failure (in this example it is not).
          638 For example if the program is killed, stopped or the exit status is 255 then
          639 xargs stops running also.</p>
          640 </li>
          641 </ul>
          642 <h2>Description of used xargs options</h2>
          643 <p>From the OpenBSD man page: <a href="https://man.openbsd.org/xargs">https://man.openbsd.org/xargs</a></p>
          644 <pre><code>xargs - construct argument list(s) and execute utility
          645 </code></pre>
          646 <p>Options explained:</p>
          647 <ul>
          648 <li>-r: Do not run the command if there are no arguments. Normally the command
          649 is executed at least once even if there are no arguments.</li>
          650 <li>-0: Change xargs to expect NUL ('\0') characters as separators, instead of
          651 spaces and newlines.</li>
          652 <li>-P maxprocs: Parallel mode: run at most maxprocs invocations of utility
          653 at once.</li>
          654 <li>-L number: Call utility for every number of non-empty lines read. A line
          655 ending in unescaped white space and the next non-empty line are considered
          656 to form one single line. If EOF is reached and fewer than number lines have
          657 been read then utility will be called with the available lines.</li>
          658 </ul>
          659 <h2>xargs options -0 and -P, portability and historic context</h2>
          660 <p>Some of the options, like -P are as of writing (2023) non-POSIX:
          661 <a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/xargs.html">https://pubs.opengroup.org/onlinepubs/9699919799/utilities/xargs.html</a>.
          662 However many systems support this useful extension for many years now.</p>
          663 <p>The specification even mentions implementations which support parallel
          664 operations:</p>
          665 <p>"The version of xargs required by this volume of POSIX.1-2017 is required to
          666 wait for the completion of the invoked command before invoking another command.
          667 This was done because historical scripts using xargs assumed sequential
          668 execution. Implementations wanting to provide parallel operation of the invoked
          669 utilities are encouraged to add an option enabling parallel invocation, but
          670 should still wait for termination of all of the children before xargs
          671 terminates normally."</p>
          672 <p>Some historic context:</p>
          673 <p>The xargs -0 option was added on 1996-06-11 by Theo de Raadt, about a year
          674 after the NetBSD import (over 27 years ago at the time of writing):</p>
          675 <p><a href="http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/xargs/xargs.c?rev=1.2&amp;content-type=text/x-cvsweb-markup">CVS log</a></p>
          676 <p>On OpenBSD the xargs -P option was added on 2003-12-06 by syncing the FreeBSD
          677 code:</p>
          678 <p><a href="http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/xargs/xargs.c?rev=1.14&amp;content-type=text/x-cvsweb-markup">CVS log</a></p>
          679 <p>Looking at the imported git history log of GNU findutils (which has xargs), the
          680 very first commit already had the -0 and -P option:</p>
          681 <p><a href="https://savannah.gnu.org/git/?group=findutils">git log</a></p>
          682 <pre><code>commit c030b5ee33bbec3c93cddc3ca9ebec14c24dbe07
          683 Author: Kevin Dalley &lt;kevin@seti.org&gt;
          684 Date:   Sun Feb 4 20:35:16 1996 +0000
          685 
          686     Initial revision
          687 </code></pre>
          688 <h2>xargs: some incompatibilities found</h2>
          689 <ul>
          690 <li>Using the -0 option empty fields are handled differently in different
          691 implementations.</li>
          692 <li>The -n and -L option doesn't work correctly in many of the BSD implementations.
          693 Some count empty fields, some don't.  In early implementations in FreeBSD and
          694 OpenBSD it only processed the first line.  In OpenBSD it has been improved
          695 around 2017.</li>
          696 </ul>
          697 <p>Depending on what you want to do a workaround could be to use the -0 option
          698 with a single field and use the -n flag.  Then in each child program invocation
          699 split the field by a separator.</p>
          700 <h2>References</h2>
          701 <ul>
          702 <li>xargs: <a href="https://man.openbsd.org/xargs">https://man.openbsd.org/xargs</a></li>
          703 <li>printf: <a href="https://man.openbsd.org/printf">https://man.openbsd.org/printf</a></li>
          704 <li>ksh, wait: <a href="https://man.openbsd.org/ksh#wait">https://man.openbsd.org/ksh#wait</a></li>
          705 <li>wait(2): <a href="https://man.openbsd.org/wait">https://man.openbsd.org/wait</a></li>
          706 </ul>
          707 ]]></content>
          708 </entry>
          709 <entry>
          710         <title>Improved Youtube RSS/Atom feed</title>
          711         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/youtube-feed" />
          712         <id>gopher://codemadness.org/1/phlog/youtube-feed</id>
          713         <updated>2023-11-20T00:00:00Z</updated>
          714         <published>2023-11-20T00:00:00Z</published>
          715         <author>
          716                 <name>Hiltjo</name>
          717                 <uri>gopher://codemadness.org</uri>
          718         </author>
          719         <summary>Improved Youtube Atom feed by adding video duration and filtering away shorts</summary>
          720         <content type="html"><![CDATA[<h1>Improved Youtube RSS/Atom feed</h1>
          721         <p><strong>Last modification on </strong> <time>2023-11-20</time></p>
          722         <p>... improved at least for my preferences ;)</p>
          723 <p>It scrapes the channel data from Youtube and combines it with the parsed Atom
          724 feed from the channel on Youtube.</p>
          725 <p>The Atom parser is based on sfeed, with some of the code removed because it is
          726 not needed by this program.  It scrapes the metadata of the videos from the
          727 channel its HTML page and uses my custom JSON parser to convert the
          728 Javascript/JSON structure.</p>
          729 <p>This parser is also used by the <a href="json2tsv.html">json2tsv</a> tool. It has few dependencies.</p>
          730 <h2>Features</h2>
          731 <ul>
          732 <li>Add the video duration to the title to quickly see how long the video is.</li>
          733 <li>Filter away Youtube shorts and upcoming videos / announcements: only videos are shown.</li>
          734 <li>Supports more output formats: Atom, <a href="https://www.jsonfeed.org/version/1.1/">JSON Feed</a> or
          735 <a href="sfeed.1.txt">sfeed</a> Tab-Separated-Value format.</li>
          736 <li>Easy to build and deploy: can be run as a CGI program as a static-linked
          737 binary in a chroot.</li>
          738 <li>Secure: additionally to running in a chroot it can use pledge(2) and unveil(2)
          739 on OpenBSD to restrict system calls and access to the filesystem.</li>
          740 </ul>
          741 <h2>How to use</h2>
          742 <p>There is an option to run directly from the command-line or in CGI-mode.  When
          743 the environment variable $REQUEST_URI is set then it is automatically run in
          744 CGI mode.</p>
          745 <p>Command-line usage:</p>
          746 <pre><code>youtube_feed channelid atom
          747 youtube_feed channelid gph
          748 youtube_feed channelid html
          749 youtube_feed channelid json
          750 youtube_feed channelid tsv
          751 youtube_feed channelid txt
          752 </code></pre>
          753 <p>CGI program usage:</p>
          754 <p>The last basename part of the URL should be the channelid + the output format
          755 extension. It defaults to TSV if there is no extension.
          756 The CGI program can be used with a HTTPd or a Gopher daemon such as geomyidae.</p>
          757 <p>For example:</p>
          758 <pre><code>Atom XML:     https://codemadness.org/yt-chan/UCrbvoMC0zUvPL8vjswhLOSw.xml
          759 HTML:         https://codemadness.org/yt-chan/UCrbvoMC0zUvPL8vjswhLOSw.html
          760 JSON:         https://codemadness.org/yt-chan/UCrbvoMC0zUvPL8vjswhLOSw.json
          761 TSV:          https://codemadness.org/yt-chan/UCrbvoMC0zUvPL8vjswhLOSw.tsv
          762 twtxt:        https://codemadness.org/yt-chan/UCrbvoMC0zUvPL8vjswhLOSw.txt
          763 TSV, default: https://codemadness.org/yt-chan/UCrbvoMC0zUvPL8vjswhLOSw
          764 
          765 Gopher dir:   gopher://codemadness.org/1/feed.cgi/UCrbvoMC0zUvPL8vjswhLOSw.gph
          766 Gopher TSV:   gopher://codemadness.org/0/feed.cgi/UCrbvoMC0zUvPL8vjswhLOSw
          767 </code></pre>
          768 <p>An OpenBSD httpd.conf using slowcgi as an example:</p>
          769 <pre><code>server "codemadness.org" {
          770         location "/yt-chan/*" {
          771                 request strip 1
          772                 root "/cgi-bin/yt-chan"
          773                 fastcgi socket "/run/slowcgi.sock"
          774         }
          775 }
          776 </code></pre>
          777 <h2>Using it with <a href="sfeed.html">sfeed</a></h2>
          778 <p>sfeedrc example of an existing Youtube RSS/Atom feed:</p>
          779 <pre><code># list of feeds to fetch:
          780 feeds() {
          781         # feed &lt;name&gt; &lt;feedurl&gt; [basesiteurl] [encoding]
          782         # normal Youtube Atom feed.
          783         feed "yt IM" "https://www.youtube.com/feeds/videos.xml?channel_id=UCrbvoMC0zUvPL8vjswhLOSw"
          784 }
          785 </code></pre>
          786 <p>Use the new Atom feed directly using the CGI-mode and Atom output format:</p>
          787 <pre><code># list of feeds to fetch:
          788 feeds() {
          789         # feed &lt;name&gt; &lt;feedurl&gt; [basesiteurl] [encoding]
          790         # new Youtube Atom feed.
          791         feed "idiotbox IM" "https://codemadness.org/yt-chan/UCrbvoMC0zUvPL8vjswhLOSw.xml"
          792 }
          793 </code></pre>
          794 <p>... or convert directly using a custom connector program on the local system via the command-line:</p>
          795 <pre><code># fetch(name, url, feedfile)
          796 fetch() {
          797         case "$1" in
          798         "connector example")
          799                 youtube_feed "$2";;
          800         *)
          801                 curl -L --max-redirs 0 -H "User-Agent:" -f -s -m 15 \
          802                         "$2" 2&gt;/dev/null;;
          803         esac
          804 }
          805 
          806 # parse and convert input, by default XML to the sfeed(5) TSV format.
          807 # parse(name, feedurl, basesiteurl)
          808 parse() {
          809         case "$1" in
          810         "connector example")
          811                 cat;;
          812         *)
          813                 sfeed "$3";;
          814         esac
          815 }
          816 
          817 # list of feeds to fetch:
          818 feeds() {
          819         # feed &lt;name&gt; &lt;feedurl&gt; [basesiteurl] [encoding]
          820         feed "connector example" "UCrbvoMC0zUvPL8vjswhLOSw"
          821 }
          822 </code></pre>
          823 <h2>Screenshot using sfeed_curses</h2>
          824 <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>
          825 <h2>Clone</h2>
          826 <pre><code>git clone git://git.codemadness.org/frontends
          827 </code></pre>
          828 <h2>Browse</h2>
          829 <p>You can browse the source-code at:</p>
          830 <ul>
          831 <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>
          832 <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>
          833 </ul>
          834 <p>The program is: youtube/feed</p>
          835 <h2>Dependencies</h2>
          836 <ul>
          837 <li>C compiler.</li>
          838 <li>LibreSSL + libtls.</li>
          839 </ul>
          840 <h2>Build and install</h2>
          841 <pre><code>$ make
          842 # make install
          843 </code></pre>
          844 <h2>That's all</h2>
          845 <p>I hope by sharing this it is useful to someone other than me as well.</p>
          846 ]]></content>
          847 </entry>
          848 <entry>
          849         <title>webdump HTML to plain-text converter</title>
          850         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/webdump" />
          851         <id>gopher://codemadness.org/1/phlog/webdump</id>
          852         <updated>2025-04-25T00:00:00Z</updated>
          853         <published>2023-11-20T00:00:00Z</published>
          854         <author>
          855                 <name>Hiltjo</name>
          856                 <uri>gopher://codemadness.org</uri>
          857         </author>
          858         <summary>webdump HTML to plain-text converter</summary>
          859         <content type="html"><![CDATA[<h1>webdump HTML to plain-text converter</h1>
          860         <p><strong>Last modification on </strong> <time>2025-04-25</time></p>
          861         <p>webdump is (yet another) HTML to plain-text converter tool.</p>
          862 <p>It reads HTML in UTF-8 from stdin and writes plain-text to stdout.</p>
          863 <h2>Goals and scope</h2>
          864 <p>The main goal of this tool for me is to use it for converting HTML mails to
          865 plain-text and to convert HTML content in RSS feeds to plain-text.</p>
          866 <p>The tool will only convert HTML to stdout, similarly to links -dump or lynx
          867 -dump but simpler and more secure.</p>
          868 <ul>
          869 <li>HTML and XHTML will be supported.</li>
          870 <li>There will be some workarounds and quirks for broken and legacy HTML code.</li>
          871 <li>It will be usable and secure for reading HTML from mails and RSS/Atom feeds.</li>
          872 <li>No remote resources which are part of the HTML will be downloaded:
          873 images, video, audio, etc. But these may be visible as a link reference.</li>
          874 <li>Data will be written to stdout. Intended for plain-text or a text terminal.</li>
          875 <li>No support for Javascript, CSS, frame rendering or form processing.</li>
          876 <li>No HTTP or network protocol handling: HTML data is read from stdin.</li>
          877 <li>Listings for references and some options to extract them in a list that is
          878 usable for scripting. Some references are: link anchors, images, audio, video,
          879 HTML (i)frames, etc.</li>
          880 <li>Security: on OpenBSD it uses pledge("stdio", NULL).</li>
          881 <li>Keep the code relatively small, simple and hackable.</li>
          882 </ul>
          883 <h2>Features</h2>
          884 <ul>
          885 <li>Support for word-wrapping.</li>
          886 <li>A mode to enable basic markup: bold, underline, italic and blink ;)</li>
          887 <li>Indentation of headers, paragraphs, pre and list items.</li>
          888 <li>Basic support to query elements or hide them.</li>
          889 <li>Show link references.</li>
          890 <li>Show link references and resources such as img, video, audio, subtitles.</li>
          891 <li>Export link references and resources to a TAB-separated format.</li>
          892 </ul>
          893 <h2>Usage examples</h2>
          894 <pre><code>url='https://codemadness.org/sfeed.html'
          895 
          896 curl -s "$url" | webdump -r -b "$url" | less
          897 
          898 curl -s "$url" | webdump -8 -a -i -l -r -b "$url" | less -R
          899 
          900 curl -s "$url" | webdump -s 'main' -8 -a -i -l -r -b "$url" | less -R
          901 </code></pre>
          902 <p>Yes, all these option flags look ugly, a shellscript wrapper could be used :)</p>
          903 <h2>Practical examples</h2>
          904 <p>To use webdump as a HTML to text filter for example in the mutt mail client,
          905 change in ~/.mailcap:</p>
          906 <pre><code>text/html; webdump -i -l -r &lt; %s; needsterminal; copiousoutput
          907 </code></pre>
          908 <p>In mutt you should then add:</p>
          909 <pre><code>auto_view text/html
          910 </code></pre>
          911 <p>Using webdump as a HTML to text filter for sfeed_curses (otherwise the default is lynx):</p>
          912 <pre><code>SFEED_HTMLCONV="webdump -d -8 -r -i -l -a" sfeed_curses ~/.sfeed/feeds/*
          913 </code></pre>
          914 <h1>Query/selector examples</h1>
          915 <p>The query syntax using the -s option is a bit inspired by CSS (but much more limited).</p>
          916 <p>To get the title from a HTML page:</p>
          917 <pre><code>url='https://codemadness.org/sfeed.html'
          918 
          919 title=$(curl -s "$url" | webdump -s 'title')
          920 printf '%s\n' "$title"
          921 </code></pre>
          922 <p>List audio and video-related content from a HTML page, redirect fd 3 to fd 1 (stdout):</p>
          923 <pre><code>url="https://media.ccc.de/v/051_Recent_features_to_OpenBSD-ntpd_and_bgpd"
          924 curl -s "$url" | webdump -x -s 'audio,video' -b "$url" 3&gt;&amp;1 &gt;/dev/null | cut -f 2
          925 </code></pre>
          926 <h2>Clone</h2>
          927 <pre><code>git clone git://git.codemadness.org/webdump
          928 </code></pre>
          929 <h2>Browse</h2>
          930 <p>You can browse the source-code at:</p>
          931 <ul>
          932 <li><a href="https://git.codemadness.org/webdump/">https://git.codemadness.org/webdump/</a></li>
          933 <li><a href="gopher://codemadness.org/1/git/webdump">gopher://codemadness.org/1/git/webdump</a></li>
          934 </ul>
          935 <h2>Download releases</h2>
          936 <p>Releases are available at:</p>
          937 <ul>
          938 <li><a href="https://codemadness.org/releases/webdump/">https://codemadness.org/releases/webdump/</a></li>
          939 <li><a href="gopher://codemadness.org/1/releases/webdump">gopher://codemadness.org/1/releases/webdump</a></li>
          940 </ul>
          941 <h2>Build and install</h2>
          942 <pre><code>$ make
          943 # make install
          944 </code></pre>
          945 <h2>Dependencies</h2>
          946 <ul>
          947 <li>C compiler.</li>
          948 <li>libc + some BSDisms.</li>
          949 </ul>
          950 <h2>Trade-offs</h2>
          951 <p>All software has trade-offs.</p>
          952 <p>webdump processes HTML in a single-pass. It does not buffer the full DOM tree.
          953 Although due to the nature of HTML/XML some parts like attributes need to be
          954 buffered.</p>
          955 <p>Rendering tables in webdump is very limited. Twibright Links has really nice
          956 table rendering. However implementing a similar feature in the current design of
          957 webdump would make the code much more complex. Twibright links
          958 processes a full DOM tree and processes the tables in multiple passes (to
          959 measure the table cells) etc.  Of course tables can be nested also, or HTML tables
          960 that are used for creating layouts (these are mostly older webpages).</p>
          961 <p>These trade-offs and preferences are chosen for now. It may change in the
          962 future.  Fortunately there are the usual good suspects for HTML to plain-text
          963 conversion, each with their own chosen trade-offs of course:</p>
          964 <ul>
          965 <li>twibright links: <a href="http://links.twibright.com/">http://links.twibright.com/</a></li>
          966 <li>lynx: <a href="https://lynx.invisible-island.net/">https://lynx.invisible-island.net/</a></li>
          967 <li>w3m: <a href="https://w3m.sourceforge.net/">https://w3m.sourceforge.net/</a></li>
          968 <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>
          969 <li>xmlstarlet: <a href="https://xmlstar.sourceforge.net/">https://xmlstar.sourceforge.net/</a></li>
          970 </ul>
          971 ]]></content>
          972 </entry>
          973 <entry>
          974         <title>Setup your own mail paste service</title>
          975         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/mailservice" />
          976         <id>gopher://codemadness.org/1/phlog/mailservice</id>
          977         <updated>2024-02-10T00:00:00Z</updated>
          978         <published>2023-10-25T00:00:00Z</published>
          979         <author>
          980                 <name>Hiltjo</name>
          981                 <uri>gopher://codemadness.org</uri>
          982         </author>
          983         <summary>Setup your own mail paste service using mblaze</summary>
          984         <content type="html"><![CDATA[<h1>Setup your own mail paste service</h1>
          985         <p><strong>Last modification on </strong> <time>2024-02-10</time></p>
          986         <h2>How it works</h2>
          987 <ul>
          988 <li>The user sends a mail with an attachment to a certain mail address, for
          989 example: paste@somehost.org</li>
          990 <li>The mail daemon configuration has an mail alias to pipe the raw mail to a
          991 shellscript.</li>
          992 <li>This shellscript processes the raw mail contents from stdin.</li>
          993 </ul>
          994 <h2>What it does</h2>
          995 <ul>
          996 <li>Process a mail with the attachments automatically.</li>
          997 <li>The script processes the attachments in the mail and stores them.</li>
          998 <li>It will mail (back) the URL where the file(s) are stored.</li>
          999 </ul>
         1000 <p>This script is tested on OpenBSD using OpenBSD smtpd and OpenBSD httpd and the
         1001 gopher daemon geomyidae.</p>
         1002 <h2>Install dependencies</h2>
         1003 <p>On OpenBSD:</p>
         1004 <pre><code>pkg_add mblaze
         1005 </code></pre>
         1006 <h2>smtpd mail configuration</h2>
         1007 <p>In your mail aliases (for example /etc/mail/aliases) put:</p>
         1008 <pre><code>paste: |/usr/local/bin/paste-mail
         1009 </code></pre>
         1010 <p>This pipes the mail to the script paste-mail for processing, this script is
         1011 described below. Copy the below contents in /usr/local/bin/paste-mail</p>
         1012 <p>Script:</p>
         1013 <pre><code>#!/bin/sh
         1014 
         1015 d="/home/www/domains/www.codemadness.org/htdocs/mailpaste"
         1016 tmpmsg=$(mktemp)
         1017 tmpmail=$(mktemp)
         1018 
         1019 cleanup() {
         1020         rm -f "$tmpmail" "$tmpmsg"
         1021 }
         1022 
         1023 # store whole mail from stdin temporarily, on exit remove temporary file.
         1024 trap "cleanup" EXIT
         1025 cat &gt; "$tmpmail"
         1026 
         1027 # mblaze: don't store mail sequence.
         1028 MAILSEQ=/dev/null
         1029 export MAILSEQ
         1030 
         1031 # get from address (without display name).
         1032 from=$(maddr -a -h 'From' /dev/stdin &lt; "$tmpmail")
         1033 
         1034 # check if allowed or not.
         1035 case "$from" in
         1036 "hiltjo@codemadness.org")
         1037         ;;
         1038 *)
         1039         exit 0;;
         1040 esac
         1041 
         1042 # prevent mail loop.
         1043 if printf '%s' "$from" | grep -q "paste@"; then
         1044         exit 0
         1045 fi
         1046 
         1047 echo "Thank you for using the enterprise paste service." &gt; "$tmpmsg"
         1048 echo "" &gt;&gt; "$tmpmsg"
         1049 echo "Your file(s) are available at:" &gt;&gt; "$tmpmsg"
         1050 echo "" &gt;&gt; "$tmpmsg"
         1051 
         1052 # process each attachment.
         1053 mshow -n -q -t /dev/stdin &lt; "$tmpmail" | sed -nE 's@.*name="(.*)".*@\1@p' | while read -r name; do
         1054         test "$name" = "" &amp;&amp; continue
         1055 
         1056         # extract attachment.
         1057         tmpfile=$(mktemp -p "$d" XXXXXXXXXXXX)
         1058         mshow -n -O /dev/stdin "$name" &lt; "$tmpmail" &gt; "$tmpfile"
         1059 
         1060         # use file extension.
         1061         ext="${name##*/}"
         1062         case "$ext" in
         1063         *.tar.*)
         1064                 # special case: support .tar.gz, tar.bz2, etc.
         1065                 ext="tar.${ext##*.}";;
         1066         *.*)
         1067                 ext="${ext##*.}";;
         1068         *)
         1069                 ext="";;
         1070         esac
         1071         ext="${ext%%*.}"
         1072 
         1073         # use file extension if it is set.
         1074         outputfile="$tmpfile"
         1075         if test "$ext" != ""; then
         1076                 outputfile="$tmpfile.$ext"
         1077         fi
         1078         mv "$tmpfile" "$outputfile"
         1079         b=$(basename "$outputfile")
         1080 
         1081         chmod 666 "$outputfile"
         1082         url="gopher://codemadness.org/9/mailpaste/$b"
         1083 
         1084         echo "$name:" &gt;&gt; "$tmpmsg"
         1085         echo "        Text   file: gopher://codemadness.org/0/mailpaste/$b" &gt;&gt; "$tmpmsg"
         1086         echo "        Image  file: gopher://codemadness.org/I/mailpaste/$b" &gt;&gt; "$tmpmsg"
         1087         echo "        Binary file: gopher://codemadness.org/9/mailpaste/$b" &gt;&gt; "$tmpmsg"
         1088         echo "" &gt;&gt; "$tmpmsg"
         1089 done
         1090 
         1091 echo "" &gt;&gt; "$tmpmsg"
         1092 echo "Sincerely," &gt;&gt; "$tmpmsg"
         1093 echo "Your friendly paste_bot" &gt;&gt; "$tmpmsg"
         1094 
         1095 # mail back the user.
         1096 mail -r "$from" -s "Your files" "$from" &lt; "$tmpmsg"
         1097 
         1098 cleanup
         1099 </code></pre>
         1100 <p>The mail daemon processing the mail needs of course to be able to have
         1101 permissions to write to the specified directory. The user who received the mail
         1102 needs to be able to read it from a location they can access and have
         1103 permissions for it also.</p>
         1104 <h2>Room for improvements</h2>
         1105 <p>This is just an example script. There is room for many improvements.
         1106 Feel free to change it in any way you like.</p>
         1107 <h2>References</h2>
         1108 <ul>
         1109 <li><a href="https://man.openbsd.org/aliases">https://man.openbsd.org/aliases</a></li>
         1110 <li><a href="https://man.openbsd.org/smtpd">https://man.openbsd.org/smtpd</a></li>
         1111 <li><a href="https://man.openbsd.org/httpd">https://man.openbsd.org/httpd</a></li>
         1112 <li><a href="https://github.com/leahneukirchen/mblaze">https://github.com/leahneukirchen/mblaze</a></li>
         1113 </ul>
         1114 <h2>Bye bye</h2>
         1115 <p>I hope this enterprise(tm) mail service is inspirational or something ;)</p>
         1116 ]]></content>
         1117 </entry>
         1118 <entry>
         1119         <title>A simple TODO application</title>
         1120         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/todo" />
         1121         <id>gopher://codemadness.org/1/phlog/todo</id>
         1122         <updated>2022-07-01T00:00:00Z</updated>
         1123         <published>2022-07-01T00:00:00Z</published>
         1124         <author>
         1125                 <name>Hiltjo</name>
         1126                 <uri>gopher://codemadness.org</uri>
         1127         </author>
         1128         <summary>A simple TODO application workflow</summary>
         1129         <content type="html"><![CDATA[<h1>A simple TODO application</h1>
         1130         <p><strong>Last modification on </strong> <time>2022-07-01</time></p>
         1131         <p>This article describes a TODO application or workflow.</p>
         1132 <h2>Workflow</h2>
         1133 <p>It works like this:</p>
         1134 <ul>
         1135 <li>Open any text editor.</li>
         1136 <li>Edit the text.</li>
         1137 <li>Save it in a file (probably named "TODO").</li>
         1138 <li>Feel happy about it.</li>
         1139 </ul>
         1140 <h2>The text format</h2>
         1141 <p>The text format I use is this:</p>
         1142 <pre><code>[indendations]&lt;symbol&gt;&lt;SPACE&gt;&lt;item text&gt;&lt;NEWLINE&gt;
         1143 </code></pre>
         1144 <p>Most of the time an item is just one line.
         1145 This format is just a general guideline to keep the items somewhat structured.</p>
         1146 <h2>Symbols</h2>
         1147 <p>Items are prefixed with a symbol.</p>
         1148 <ul>
         1149 <li>- is an item which is planned to be done at some point.</li>
         1150 <li>x is an item which is done.</li>
         1151 <li>? is an item which I'm not (yet) sure about. It can also be an idea.</li>
         1152 </ul>
         1153 <p>I use an indendation with a TAB before an item to indicate item dependencies.
         1154 The items can be nested.</p>
         1155 <p>For the prioritization I put the most important items and sections from the top
         1156 to the bottom. These can be reshuffled as you wish of course.</p>
         1157 <p>To delete an item you remove the line. To archive an item you keep the line.</p>
         1158 <h2>Sections</h2>
         1159 <p>A section is a line which has no symbol. This is like a header to group items.</p>
         1160 <h2>Example</h2>
         1161 <pre><code>Checklist for releasing project 0.1:
         1162 - Test project with different compilers and check for warnings.
         1163 - Documentation:
         1164         - Proofread and make sure it matches all program behaviour.
         1165         - Run mandoc -Tlint on the man pages.
         1166         ? Copy useful examples from the README file to the man page?
         1167 - Run testsuite and check for failures before release.
         1168 
         1169 
         1170 project 0.2:
         1171 ? Investigate if feature mentioned by some user is worth adding.
         1172 </code></pre>
         1173 <h1>Example: secure remote cloud-encrypted edit session(tm)</h1>
         1174 <pre><code>ssh -t host 'ed TODO'
         1175 </code></pre>
         1176 <h1>Example: multi-user secure remote cloud-encrypted edit session(tm)</h1>
         1177 <pre><code>ssh host
         1178 tmux or tmux a
         1179 ed TODO
         1180 </code></pre>
         1181 <h1>Example: version-controlled multi-user secure remote cloud-encrypted edit session(tm)</h1>
         1182 <pre><code>ssh host
         1183 tmux or tmux a
         1184 ed TODO
         1185 git add TODO
         1186 git commit -m 'TODO: update'
         1187 </code></pre>
         1188 <h2>Pros</h2>
         1189 <ul>
         1190 <li>When you open the TODO file the most important items are at the top.</li>
         1191 <li>The items are easy to read and modify with any text editor.</li>
         1192 <li>It is easy to extend the format and use with other text tools.</li>
         1193 <li>The format is portable: it works on sticky-notes on your CRT monitor too!</li>
         1194 <li>No monthly online subscription needed and full NO-money-back guarantee.</li>
         1195 </ul>
         1196 <h2>Cons</h2>
         1197 <ul>
         1198 <li>Complex lists with interconnected dependencies might not work, maybe.</li>
         1199 <li>It's assumed there is one person maintaining the TODO file. Merging items
         1200 from multiple people at the same time in this workflow is not recommended.</li>
         1201 <li>It is too simple: noone will be impressed by it.</li>
         1202 </ul>
         1203 <p>I hope this is inspirational or something,</p>
         1204 ]]></content>
         1205 </entry>
         1206 <entry>
         1207         <title>2FA TOTP without crappy authenticator apps</title>
         1208         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/totp" />
         1209         <id>gopher://codemadness.org/1/phlog/totp</id>
         1210         <updated>2022-10-29T00:00:00Z</updated>
         1211         <published>2022-03-23T00:00:00Z</published>
         1212         <author>
         1213                 <name>Hiltjo</name>
         1214                 <uri>gopher://codemadness.org</uri>
         1215         </author>
         1216         <summary>Using 2FA TOTP without crappy authenticator apps</summary>
         1217         <content type="html"><![CDATA[<h1>2FA TOTP without crappy authenticator apps</h1>
         1218         <p><strong>Last modification on </strong> <time>2022-10-29</time></p>
         1219         <p>This describes how to use 2FA without using crappy authenticator "apps" or a
         1220 mobile device.</p>
         1221 <h2>Install</h2>
         1222 <p>On OpenBSD:</p>
         1223 <pre><code>pkg_add oath-toolkit zbar
         1224 </code></pre>
         1225 <p>On Void Linux:</p>
         1226 <pre><code>xbps-install oath-toolkit zbar
         1227 </code></pre>
         1228 <p>There is probably a package for your operating system.</p>
         1229 <ul>
         1230 <li>oath-toolkit is used to generate the digits based on the secret key.</li>
         1231 <li>zbar is used to scan the QR barcode text from the image.</li>
         1232 </ul>
         1233 <h2>Steps</h2>
         1234 <p>Save the QR code image from the authenticator app, website to an image file.
         1235 Scan the QR code text from the image:</p>
         1236 <pre><code>zbarimg image.png
         1237 </code></pre>
         1238 <p>An example QR code:</p>
         1239 <p><img src="downloads/2fa/qr.png" alt="QR code example" /></p>
         1240 <p>The output is typically something like:</p>
         1241 <pre><code>QR-Code:otpauth://totp/Example:someuser@codemadness.org?secret=SECRETKEY&amp;issuer=Codemadness
         1242 </code></pre>
         1243 <p>You only need to scan this QR-code for the secret key once.
         1244 Make sure to store the secret key in a private safe place and don't show it to
         1245 anyone else.</p>
         1246 <p>Using the secret key the following command outputs a 6-digit code by default.
         1247 In this example we also assume the key is base32-encoded.
         1248 There can be other parameters and options, this is documented in the Yubico URI
         1249 string format reference below.</p>
         1250 <p>Command:</p>
         1251 <pre><code>oathtool --totp -b SOMEKEY
         1252 </code></pre>
         1253 <ul>
         1254 <li>The --totp option uses the time-variant TOTP mode, by default it uses HMAC SHA1.</li>
         1255 <li>The -b option uses base32 encoding of KEY instead of hex.</li>
         1256 </ul>
         1257 <p>Tip: you can create a script that automatically puts the digits in the
         1258 clipboard, for example:</p>
         1259 <pre><code>oathtool --totp -b SOMEKEY | xclip
         1260 </code></pre>
         1261 <h2>References</h2>
         1262 <ul>
         1263 <li><a href="https://linux.die.net/man/1/zbarimg">zbarimg(1) man page</a></li>
         1264 <li><a href="https://www.nongnu.org/oath-toolkit/man-oathtool.html">oathtool(1) man page</a></li>
         1265 <li><a href="https://datatracker.ietf.org/doc/html/rfc6238">RFC6238 - TOTP: Time-Based One-Time Password Algorithm</a></li>
         1266 <li><a href="https://docs.yubico.com/yesdk/users-manual/application-oath/uri-string-format.html">Yubico.com - otpauth URI string format</a></li>
         1267 </ul>
         1268 ]]></content>
         1269 </entry>
         1270 <entry>
         1271         <title>Setup an OpenBSD RISCV64 VM in QEMU</title>
         1272         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/openbsd-riscv64-vm" />
         1273         <id>gopher://codemadness.org/1/phlog/openbsd-riscv64-vm</id>
         1274         <updated>2021-10-26T00:00:00Z</updated>
         1275         <published>2021-10-23T00:00:00Z</published>
         1276         <author>
         1277                 <name>Hiltjo</name>
         1278                 <uri>gopher://codemadness.org</uri>
         1279         </author>
         1280         <summary>Setup an OpenBSD RISCV-64 VM in QEMU</summary>
         1281         <content type="html"><![CDATA[<h1>Setup an OpenBSD RISCV64 VM in QEMU</h1>
         1282         <p><strong>Last modification on </strong> <time>2021-10-26</time></p>
         1283         <p>This describes how to setup an OpenBSD RISCV64 VM in QEMU.</p>
         1284 <p>The shellscript below does the following:</p>
         1285 <ul>
         1286 <li>Set up the disk image (raw format).</li>
         1287 <li>Patch the disk image with the OpenBSD miniroot file for the installation.</li>
         1288 <li>Downloads the opensbi and u-boot firmware files for qemu.</li>
         1289 <li>Run the VM with the supported settings.</li>
         1290 </ul>
         1291 <p>The script is tested on the host GNU/Void Linux and OpenBSD-current.</p>
         1292 <p><strong>IMPORTANT!: The signature and checksum for the miniroot, u-boot and opensbi
         1293 files are not verified. If the host is OpenBSD make sure to instead install the
         1294 packages (pkg_add u-boot-riscv64 opensbi) and adjust the firmware path for the
         1295 qemu -bios and -kernel options. </strong></p>
         1296 <h2>Shellscript</h2>
         1297 <pre><code>#!/bin/sh
         1298 # mirror list: https://www.openbsd.org/ftp.html
         1299 mirror="https://ftp.bit.nl/pub/OpenBSD/"
         1300 release="7.0"
         1301 minirootname="miniroot70.img"
         1302 
         1303 miniroot() {
         1304         test -f "${minirootname}" &amp;&amp; return # download once
         1305 
         1306         url="${mirror}/${release}/riscv64/${minirootname}"
         1307         curl -o "${minirootname}" "${url}"
         1308 }
         1309 
         1310 createrootdisk() {
         1311         test -f disk.raw &amp;&amp; return # create once
         1312         qemu-img create disk.raw 10G # create 10 GB disk
         1313         dd conv=notrunc if=${minirootname} of=disk.raw # write miniroot to disk
         1314 }
         1315 
         1316 opensbi() {
         1317         f="opensbi.tgz"
         1318         test -f "${f}" &amp;&amp; return # download and extract once.
         1319 
         1320         url="${mirror}/${release}/packages/amd64/opensbi-0.9p0.tgz"
         1321         curl -o "${f}" "${url}"
         1322 
         1323         tar -xzf "${f}" share/opensbi/generic/fw_jump.bin
         1324 }
         1325 
         1326 uboot() {
         1327         f="uboot.tgz"
         1328         test -f "${f}" &amp;&amp; return # download and extract once.
         1329 
         1330         url="${mirror}/${release}/packages/amd64/u-boot-riscv64-2021.07p0.tgz"
         1331         curl -o "${f}" "${url}"
         1332 
         1333         tar -xzf "${f}" share/u-boot/qemu-riscv64_smode/u-boot.bin
         1334 }
         1335 
         1336 setup() {
         1337         miniroot
         1338         createrootdisk
         1339         opensbi
         1340         uboot
         1341 }
         1342 
         1343 run() {
         1344         qemu-system-riscv64 \
         1345                 -machine virt \
         1346                 -nographic \
         1347                 -m 2048M \
         1348                 -smp 2 \
         1349                 -bios share/opensbi/generic/fw_jump.bin \
         1350                 -kernel share/u-boot/qemu-riscv64_smode/u-boot.bin \
         1351                 -drive file=disk.raw,format=raw,id=hd0 -device virtio-blk-device,drive=hd0 \
         1352                 -netdev user,id=net0,ipv6=off -device virtio-net-device,netdev=net0
         1353 }
         1354 
         1355 setup
         1356 run
         1357 </code></pre>
         1358 ]]></content>
         1359 </entry>
         1360 <entry>
         1361         <title>Sfeed_curses: a curses UI front-end for sfeed</title>
         1362         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/sfeed_curses" />
         1363         <id>gopher://codemadness.org/1/phlog/sfeed_curses</id>
         1364         <updated>2025-07-24T00:00:00Z</updated>
         1365         <published>2020-06-25T00:00:00Z</published>
         1366         <author>
         1367                 <name>Hiltjo</name>
         1368                 <uri>gopher://codemadness.org</uri>
         1369         </author>
         1370         <summary>Sfeed_curses is a curses UI front-end for the sfeed RSS/Atom parser</summary>
         1371         <content type="html"><![CDATA[<h1>Sfeed_curses: a curses UI front-end for sfeed</h1>
         1372         <p><strong>Last modification on </strong> <time>2025-07-24</time></p>
         1373         <p>sfeed_curses is a curses UI front-end for <a href="sfeed.html">sfeed</a>.
         1374 It is now part of sfeed.</p>
         1375 <p>It shows the TAB-separated feed items in a graphical command-line UI.  The
         1376 interface has a look inspired by the <a href="http://www.mutt.org/">mutt mail client</a>. It has a sidebar
         1377 panel for the feeds, a panel with a listing of the items and a small statusbar
         1378 for the selected item/URL. Some functions like searching and scrolling are
         1379 integrated in the interface itself.</p>
         1380 <h2>Features</h2>
         1381 <ul>
         1382 <li>Relatively few LOC, about 2.5K lines of C.</li>
         1383 <li>Few dependencies: a C compiler and a curses library (typically ncurses).
         1384 It also requires a terminal (emulator) which supports UTF-8.
         1385 <ul>
         1386 <li>xterm-compatible shim <a href="https://git.codemadness.org/sfeed/file/minicurses.h.html">minicurses.h</a></li>
         1387 </ul>
         1388 </li>
         1389 <li>Easy to customize by modifying the small source-code and shellscripts.</li>
         1390 <li>Plumb support: open the URL or an enclosure URL directly with any program.</li>
         1391 <li>Pipe support: pipe the selected Tab-Separated Value line to a program for
         1392 scripting purposes. Like viewing the content in any way you like.</li>
         1393 <li>Yank support: copy the URL or an enclosure URL to the clipboard.</li>
         1394 <li>Familiar keybinds: supports both vi-like, emacs-like and arrow keys for
         1395 actions.</li>
         1396 <li>Mouse support: it supports xterm X10 and extended SGR encoding.</li>
         1397 <li>Support two ways of managing read/unread items.
         1398 By default sfeed_curses marks the feed items of the last day as new/bold.
         1399 Alternatively a simple plain-text list with the read URLs can be used.</li>
         1400 <li>UI layouts: supports vertical, horizontal and monocle (full-screen) layouts.
         1401 Useful for different kind of screen sizes.</li>
         1402 <li>Auto-execute keybind commands at startup to automate setting a preferred
         1403 layout, toggle showing new items or other actions.</li>
         1404 </ul>
         1405 <p>Like the format programs included in sfeed you can run it by giving the feed
         1406 files as arguments like this:</p>
         1407 <pre><code>sfeed_curses ~/.sfeed/feeds/*
         1408 </code></pre>
         1409 <p>... or by reading directly from stdin:</p>
         1410 <pre><code>sfeed_curses &lt; ~/.sfeed/feeds/xkcd
         1411 </code></pre>
         1412 <p>It will show a sidebar if one or more files are specified as parameters. It
         1413 will not show the sidebar by default when reading from stdin.</p>
         1414 <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>
         1415 <p>On pressing the 'o' or ENTER keybind it will open the link URL of an item with
         1416 the plumb program.  On pressing the 'a', 'e' or '@' keybind it will open the
         1417 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>,
         1418 but can be modified by setting the environment variable $SFEED_PLUMBER.  The
         1419 plumb program receives the URL as a command-line argument.</p>
         1420 <p>The TAB-Separated-Value line of the current selected item in the feed file can
         1421 be piped to a program by pressing the 'c', 'p' or '|' keybind. This allows much
         1422 flexibility to make a content formatter or write other custom actions or views.
         1423 This line is in the exact same format as described in the sfeed(5) man page.</p>
         1424 <p>The pipe program can be changed by setting the environment variable
         1425 $SFEED_PIPER.</p>
         1426 <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>
         1427 <p>The above screenshot shows the included <a href="https://git.codemadness.org/sfeed/file/sfeed_content.html">sfeed_content</a> shellscript which uses
         1428 the <a href="https://invisible-island.net/lynx/">lynx text-browser</a> to convert HTML to plain-text.  It pipes the formatted
         1429 plain-text to the user $PAGER (or "less").</p>
         1430 <p>Of course the script can be easily changed to use a different browser or
         1431 HTML-to-text converter like:</p>
         1432 <ul>
         1433 <li><a href="https://www.dillo.org/">dillo</a></li>
         1434 <li><a href="http://www.jikos.cz/~mikulas/links/">links</a></li>
         1435 <li><a href="http://w3m.sourceforge.net/">w3m</a></li>
         1436 <li><a href="https://git.codemadness.org/webdump/file/README.html">webdump</a></li>
         1437 </ul>
         1438 <p>It's easy to modify the color-theme by changing the macros in the source-code
         1439 or set a predefined theme at compile-time. The README file contains information
         1440 how to set a theme.  On the left a <a href="https://templeos.org/">TempleOS</a>-like color-theme on the right a
         1441 <a href="https://newsboat.org/">newsboat</a>-like colorscheme.</p>
         1442 <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>
         1443 <p>It supports a vertical layout, horizontal and monocle (full-screen) layout.
         1444 This can be useful for different kind of screen sizes.  The keybinds '1', '2'
         1445 and '3' can be used to switch between these layouts.</p>
         1446 <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>
         1447 <h2>Clone</h2>
         1448 <pre><code>git clone git://git.codemadness.org/sfeed
         1449 </code></pre>
         1450 <h2>Browse</h2>
         1451 <p>You can browse the source-code at:</p>
         1452 <ul>
         1453 <li><a href="https://git.codemadness.org/sfeed/">https://git.codemadness.org/sfeed/</a></li>
         1454 <li><a href="gopher://codemadness.org/1/git/sfeed">gopher://codemadness.org/1/git/sfeed</a></li>
         1455 </ul>
         1456 <h2>Download releases</h2>
         1457 <p>Releases are available at:</p>
         1458 <ul>
         1459 <li><a href="https://codemadness.org/releases/sfeed/">https://codemadness.org/releases/sfeed/</a></li>
         1460 <li><a href="gopher://codemadness.org/1/releases/sfeed">gopher://codemadness.org/1/releases/sfeed</a></li>
         1461 </ul>
         1462 <h2>Build and install</h2>
         1463 <pre><code>$ make
         1464 # make install
         1465 </code></pre>
         1466 ]]></content>
         1467 </entry>
         1468 <entry>
         1469         <title>hurl: HTTP, HTTPS and Gopher file grabber</title>
         1470         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/hurl" />
         1471         <id>gopher://codemadness.org/1/phlog/hurl</id>
         1472         <updated>2020-07-20T00:00:00Z</updated>
         1473         <published>2019-11-10T00:00:00Z</published>
         1474         <author>
         1475                 <name>Hiltjo</name>
         1476                 <uri>gopher://codemadness.org</uri>
         1477         </author>
         1478         <summary>hurl: HTTP, HTTPS and Gopher file grabber</summary>
         1479         <content type="html"><![CDATA[<h1>hurl: HTTP, HTTPS and Gopher file grabber</h1>
         1480         <p><strong>Last modification on </strong> <time>2020-07-20</time></p>
         1481         <p>hurl is a relatively simple HTTP, HTTPS and Gopher client/file grabber.</p>
         1482 <h2>Why?</h2>
         1483 <p>Sometimes (or most of the time?) you just want to fetch a file via the HTTP,
         1484 HTTPS or Gopher protocol.</p>
         1485 <p>The focus of this tool is only this.</p>
         1486 <h2>Features</h2>
         1487 <ul>
         1488 <li>Uses OpenBSD pledge(2) and unveil(2). Allow no filesystem access (writes to
         1489 stdout).</li>
         1490 <li>Impose time-out and maximum size limits.</li>
         1491 <li>Use well-defined exitcodes for reliable scripting (curl sucks at this).</li>
         1492 <li>Send as little information as possible (no User-Agent etc by default).</li>
         1493 </ul>
         1494 <h2>Anti-features</h2>
         1495 <ul>
         1496 <li>No HTTP byte range support.</li>
         1497 <li>No HTTP User-Agent.</li>
         1498 <li>No HTTP If-Modified-Since/If-* support.</li>
         1499 <li>No HTTP auth support.</li>
         1500 <li>No HTTP/2+ support.</li>
         1501 <li>No HTTP keep-alive.</li>
         1502 <li>No HTTP chunked-encoding support.</li>
         1503 <li>No HTTP redirect support.</li>
         1504 <li>No (GZIP) compression support.</li>
         1505 <li>No cookie-jar or cookie parsing support.</li>
         1506 <li>No Gopher text handling (".\r\n").</li>
         1507 <li>... etc...</li>
         1508 </ul>
         1509 <h2>Dependencies</h2>
         1510 <ul>
         1511 <li>C compiler (C99).</li>
         1512 <li>libc + some BSD functions like err() and strlcat().</li>
         1513 <li>LibreSSL(-portable)</li>
         1514 <li>libtls (part of LibreSSL).</li>
         1515 </ul>
         1516 <h2>Optional dependencies</h2>
         1517 <ul>
         1518 <li>POSIX make(1) (for Makefile).</li>
         1519 <li>mandoc for documentation: <a href="https://mdocml.bsd.lv/">https://mdocml.bsd.lv/</a></li>
         1520 </ul>
         1521 <h2>Clone</h2>
         1522 <pre><code>git clone git://git.codemadness.org/hurl
         1523 </code></pre>
         1524 <h2>Browse</h2>
         1525 <p>You can browse the source-code at:</p>
         1526 <ul>
         1527 <li><a href="https://git.codemadness.org/hurl/">https://git.codemadness.org/hurl/</a></li>
         1528 <li><a href="gopher://codemadness.org/1/git/hurl">gopher://codemadness.org/1/git/hurl</a></li>
         1529 </ul>
         1530 <h2>Download releases</h2>
         1531 <p>Releases are available at:</p>
         1532 <ul>
         1533 <li><a href="https://codemadness.org/releases/hurl/">https://codemadness.org/releases/hurl/</a></li>
         1534 <li><a href="gopher://codemadness.org/1/releases/hurl">gopher://codemadness.org/1/releases/hurl</a></li>
         1535 </ul>
         1536 <h2>Build and install</h2>
         1537 <pre><code>$ make
         1538 # make install
         1539 </code></pre>
         1540 <h2>Examples</h2>
         1541 <p>Fetch the Atom feed from this site using a maximum filesize limit of 1MB and
         1542 a time-out limit of 15 seconds:</p>
         1543 <pre><code>hurl -m 1048576 -t 15 "https://codemadness.org/atom.xml"
         1544 </code></pre>
         1545 <p>There is an -H option to add custom headers. This way some of the anti-features
         1546 listed above are supported. For example some CDNs like Cloudflare are known to
         1547 block empty or certain User-Agents.</p>
         1548 <p>User-Agent:</p>
         1549 <pre><code>hurl -H 'User-Agent: some browser' 'https://codemadness.org/atom.xml'
         1550 </code></pre>
         1551 <p>HTTP Basic Auth (base64-encoded username:password):</p>
         1552 <pre><code>hurl -H 'Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=' \
         1553         'https://codemadness.org/atom.xml'
         1554 </code></pre>
         1555 <p>GZIP (this assumes the served response Content-Type is gzip):</p>
         1556 <pre><code>hurl -H 'Accept-Encoding: gzip' 'https://somesite/' | gzip -d
         1557 </code></pre>
         1558 ]]></content>
         1559 </entry>
         1560 <entry>
         1561         <title>json2tsv: a JSON to TSV converter</title>
         1562         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/json2tsv" />
         1563         <id>gopher://codemadness.org/1/phlog/json2tsv</id>
         1564         <updated>2021-09-25T00:00:00Z</updated>
         1565         <published>2019-10-13T00:00:00Z</published>
         1566         <author>
         1567                 <name>Hiltjo</name>
         1568                 <uri>gopher://codemadness.org</uri>
         1569         </author>
         1570         <summary>json2tsv: a JSON to TAB-Separated Value converter</summary>
         1571         <content type="html"><![CDATA[<h1>json2tsv: a JSON to TSV converter</h1>
         1572         <p><strong>Last modification on </strong> <time>2021-09-25</time></p>
         1573         <p>Convert JSON to TSV or separated output.</p>
         1574 <p>json2tsv reads JSON data from stdin.  It outputs each JSON type to a TAB-
         1575 Separated Value format per line by default.</p>
         1576 <h2>TAB-Separated Value format</h2>
         1577 <p>The output format per line is:</p>
         1578 <pre><code>nodename&lt;TAB&gt;type&lt;TAB&gt;value&lt;LF&gt;
         1579 </code></pre>
         1580 <p>Control-characters such as a newline, TAB and backslash (\n, \t and \) are
         1581 escaped in the nodename and value fields.  Other control-characters are
         1582 removed.</p>
         1583 <p>The type field is a single byte and can be:</p>
         1584 <ul>
         1585 <li>a for array</li>
         1586 <li>b for bool</li>
         1587 <li>n for number</li>
         1588 <li>o for object</li>
         1589 <li>s for string</li>
         1590 <li>? for null</li>
         1591 </ul>
         1592 <p>Filtering on the first field "nodename" is easy using awk for example.</p>
         1593 <h2>Features</h2>
         1594 <ul>
         1595 <li>Accepts all <strong>valid</strong> JSON.</li>
         1596 <li>Designed to work well with existing UNIX programs like awk and grep.</li>
         1597 <li>Straightforward and not much lines of code: about 475 lines of C.</li>
         1598 <li>Few dependencies: C compiler (C99), libc.</li>
         1599 <li>No need to learn a new (meta-)language for processing data.</li>
         1600 <li>The parser supports code point decoding and UTF-16 surrogates to UTF-8.</li>
         1601 <li>It does not output control-characters to the terminal for security reasons by
         1602 default (but it has a -r option if needed).</li>
         1603 <li>On OpenBSD it supports <a href="https://man.openbsd.org/pledge">pledge(2)</a> for syscall restriction:
         1604 pledge("stdio", NULL).</li>
         1605 <li>Supports setting a different field separator and record separator with the -F
         1606 and -R option.</li>
         1607 </ul>
         1608 <h2>Cons</h2>
         1609 <ul>
         1610 <li>For the tool there is additional overhead by processing and filtering data
         1611 from stdin after parsing.</li>
         1612 <li>The parser does not do complete validation on numbers.</li>
         1613 <li>The parser accepts some bad input such as invalid UTF-8
         1614 (see <a href="https://tools.ietf.org/html/rfc8259#section-8.1">RFC8259 - 8.1. Character Encoding</a>).
         1615 json2tsv reads from stdin and does not do assumptions about a "closed
         1616 ecosystem" as described in the RFC.</li>
         1617 <li>The parser accepts some bad JSON input and "extensions"
         1618 (see <a href="https://tools.ietf.org/html/rfc8259#section-9">RFC8259 - 9. Parsers</a>).</li>
         1619 <li>Encoded NUL bytes (\u0000) in strings are ignored.
         1620 (see <a href="https://tools.ietf.org/html/rfc8259#section-9">RFC8259 - 9. Parsers</a>).
         1621 "An implementation may set limits on the length and character contents of
         1622 strings."</li>
         1623 <li>The parser is not the fastest possible JSON parser (but also not the
         1624 slowest).  For example: for ease of use, at the cost of performance all
         1625 strings are decoded, even though they may be unused.</li>
         1626 </ul>
         1627 <h2>Why Yet Another JSON parser?</h2>
         1628 <p>I wanted a tool that makes parsing JSON easier and work well from the shell,
         1629 similar to <a href="https://stedolan.github.io/jq/">jq</a>.</p>
         1630 <p>sed and grep often work well enough for matching some value using some regex
         1631 pattern, but it is not good enough to parse JSON correctly or to extract all
         1632 information: just like parsing HTML/XML using some regex is not good (enough)
         1633 or a good idea :P.</p>
         1634 <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
         1635 something simpler.</p>
         1636 <p>While it is more efficient to embed this query language for data aggregation,
         1637 it is also less simple. In my opinion it is simpler to separate this and use
         1638 pattern-processing by awk or an other filtering/aggregating program.</p>
         1639 <p>For the parser, there are many JSON parsers out there, like the efficient
         1640 <a href="https://github.com/zserge/jsmn">jsmn parser</a>, however a few parser behaviours I want to have are:</p>
         1641 <ul>
         1642 <li>jsmn buffers data as tokens, which is very efficient, but also a bit
         1643 annoying as an API as it requires another layer of code to interpret the
         1644 tokens.</li>
         1645 <li>jsmn does not handle decoding strings by default. Which is very efficient
         1646 if you don't need parts of the data though.</li>
         1647 <li>jsmn does not keep context of nested structures by default, so may require
         1648 writing custom utility functions for nested data.</li>
         1649 </ul>
         1650 <p>This is why I went for a parser design that uses a single callback per "node"
         1651 type and keeps track of the current nested structure in a single array and
         1652 emits that.</p>
         1653 <h2>Clone</h2>
         1654 <pre><code>git clone git://git.codemadness.org/json2tsv
         1655 </code></pre>
         1656 <h2>Browse</h2>
         1657 <p>You can browse the source-code at:</p>
         1658 <ul>
         1659 <li><a href="https://git.codemadness.org/json2tsv/">https://git.codemadness.org/json2tsv/</a></li>
         1660 <li><a href="gopher://codemadness.org/1/git/json2tsv">gopher://codemadness.org/1/git/json2tsv</a></li>
         1661 </ul>
         1662 <h2>Download releases</h2>
         1663 <p>Releases are available at:</p>
         1664 <ul>
         1665 <li><a href="https://codemadness.org/releases/json2tsv/">https://codemadness.org/releases/json2tsv/</a></li>
         1666 <li><a href="gopher://codemadness.org/1/releases/json2tsv">gopher://codemadness.org/1/releases/json2tsv</a></li>
         1667 </ul>
         1668 <h2>Build and install</h2>
         1669 <pre><code>$ make
         1670 # make install
         1671 </code></pre>
         1672 <h2>Examples</h2>
         1673 <p>An usage example to parse posts of the JSON API of <a href="https://www.reddit.com/">reddit.com</a> and format them
         1674 to a plain-text list using awk:</p>
         1675 <pre><code>#!/bin/sh
         1676 curl -s -H 'User-Agent:' 'https://old.reddit.com/.json?raw_json=1&amp;limit=100' | \
         1677 json2tsv | \
         1678 awk -F '\t' '
         1679 function show() {
         1680         if (length(o["title"]) == 0)
         1681                 return;
         1682         print n ". " o["title"] " by " o["author"] " in r/" o["subreddit"];
         1683         print o["url"];
         1684         print "";
         1685 }
         1686 $1 == ".data.children[].data" {
         1687         show();
         1688         n++;
         1689         delete o;
         1690 }
         1691 $1 ~ /^\.data\.children\[\]\.data\.[a-zA-Z0-9_]*$/ {
         1692         o[substr($1, 23)] = $3;
         1693 }
         1694 END {
         1695         show();
         1696 }'
         1697 </code></pre>
         1698 <h2>References</h2>
         1699 <ul>
         1700 <li>Sites:
         1701 <ul>
         1702 <li><a href="http://seriot.ch/parsing_json.php">seriot.ch - Parsing JSON is a Minefield</a></li>
         1703 <li><a href="https://github.com/nst/JSONTestSuite">A comprehensive test suite for RFC 8259 compliant JSON parsers</a></li>
         1704 <li><a href="https://json.org/">json.org</a></li>
         1705 </ul>
         1706 </li>
         1707 <li>Current standard:
         1708 <ul>
         1709 <li><a href="https://tools.ietf.org/html/rfc8259">RFC8259 - The JavaScript Object Notation (JSON) Data Interchange Format</a></li>
         1710 <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>
         1711 </ul>
         1712 </li>
         1713 <li>Historic standards:
         1714 <ul>
         1715 <li><a href="https://tools.ietf.org/html/rfc7159">RFC7159 - The JavaScript Object Notation (JSON) Data Interchange Format (obsolete)</a></li>
         1716 <li><a href="https://tools.ietf.org/html/rfc7158">RFC7158 - The JavaScript Object Notation (JSON) Data Interchange Format (obsolete)</a></li>
         1717 <li><a href="https://tools.ietf.org/html/rfc4627">RFC4627 - The JavaScript Object Notation (JSON) Data Interchange Format (obsolete, original)</a></li>
         1718 </ul>
         1719 </li>
         1720 </ul>
         1721 ]]></content>
         1722 </entry>
         1723 <entry>
         1724         <title>OpenBSD: setup a local auto-installation server</title>
         1725         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/openbsd-autoinstall" />
         1726         <id>gopher://codemadness.org/1/phlog/openbsd-autoinstall</id>
         1727         <updated>2020-04-30T00:00:00Z</updated>
         1728         <published>2019-04-24T00:00:00Z</published>
         1729         <author>
         1730                 <name>Hiltjo</name>
         1731                 <uri>gopher://codemadness.org</uri>
         1732         </author>
         1733         <summary>OpenBSD: setup a local auto-installation server</summary>
         1734         <content type="html"><![CDATA[<h1>OpenBSD: setup a local auto-installation server</h1>
         1735         <p><strong>Last modification on </strong> <time>2020-04-30</time></p>
         1736         <p>This guide describes how to setup a local mirror and installation/upgrade
         1737 server that requires little or no input interaction.</p>
         1738 <h2>Setup a local HTTP mirror</h2>
         1739 <p>The HTTP mirror will be used to fetch the base sets and (optional) custom sets.
         1740 In this guide we will assume <strong>192.168.0.2</strong> is the local installation server
         1741 and mirror, the CPU architecture is amd64 and the OpenBSD release version is
         1742 6.5.  We will store the files in the directory with the structure:</p>
         1743 <pre><code>http://192.168.0.2/pub/OpenBSD/6.5/amd64/
         1744 </code></pre>
         1745 <p>Create the www serve directory and fetch all sets and install files
         1746 (if needed to save space *.iso and install65.fs can be skipped):</p>
         1747 <pre><code>$ cd /var/www/htdocs
         1748 $ mkdir -p pub/OpenBSD/6.5/amd64/
         1749 $ cd pub/OpenBSD/6.5/amd64/
         1750 $ ftp 'ftp://ftp.nluug.nl/pub/OpenBSD/6.5/amd64/*'
         1751 </code></pre>
         1752 <p>Verify signature and check some checksums:</p>
         1753 <pre><code>$ signify -C -p /etc/signify/openbsd-65-base.pub -x SHA256.sig
         1754 </code></pre>
         1755 <p>Setup <a href="https://man.openbsd.org/httpd.8">httpd(8)</a> for simple file serving:</p>
         1756 <pre><code># $FAVORITE_EDITOR /etc/httpd.conf
         1757 </code></pre>
         1758 <p>A minimal example config for <a href="https://man.openbsd.org/httpd.conf.5">httpd.conf(5)</a>:</p>
         1759 <pre><code>server "*" {
         1760         listen on * port 80
         1761 }
         1762 </code></pre>
         1763 <p>The default www root directory is: /var/www/htdocs/</p>
         1764 <p>Enable the httpd daemon to start by default and start it now:</p>
         1765 <pre><code># rcctl enable httpd
         1766 # rcctl start httpd
         1767 </code></pre>
         1768 <h2>Creating an installation response/answer file</h2>
         1769 <p>The installer supports loading responses to the installation/upgrade questions
         1770 from a simple text file. We can do a regular installation and copy the answers
         1771 from the saved file to make an automated version of it.</p>
         1772 <p>Do a test installation, at the end of the installation or upgrade when asked the
         1773 question:</p>
         1774 <pre><code>Exit to (S)hell, (H)alt or (R)eboot?
         1775 </code></pre>
         1776 <p>Type S to go to the shell. Find the response file for an installation and copy
         1777 it to some USB stick or write down the response answers:</p>
         1778 <pre><code>cp /tmp/i/install.resp /mnt/usbstick/
         1779 </code></pre>
         1780 <p>A response file could be for example:</p>
         1781 <pre><code>System hostname = testvm
         1782 Which network interface do you wish to configure = em0
         1783 IPv4 address for em0 = dhcp
         1784 IPv6 address for em0 = none
         1785 Which network interface do you wish to configure = done
         1786 Password for root account = $2b$10$IqI43aXjgD55Q3nLbRakRO/UAG6SAClL9pyk0vIUpHZSAcLx8fWk.
         1787 Password for user testuser = $2b$10$IqI43aXjgD55Q3nLbRakRO/UAG6SAClL9pyk0vIUpHZSAcLx8fWk.
         1788 Start sshd(8) by default = no
         1789 Do you expect to run the X Window System = no
         1790 Setup a user = testuser
         1791 Full name for user testuser = testuser
         1792 What timezone are you in = Europe/Amsterdam
         1793 Which disk is the root disk = wd0
         1794 Use (W)hole disk MBR, whole disk (G)PT, (O)penBSD area or (E)dit = OpenBSD
         1795 Use (A)uto layout, (E)dit auto layout, or create (C)ustom layout = a
         1796 Location of sets = http
         1797 HTTP proxy URL = none
         1798 HTTP Server = 192.168.0.2
         1799 Server directory = pub/OpenBSD/6.5/amd64
         1800 Unable to connect using https. Use http instead = yes
         1801 Location of sets = http
         1802 Set name(s) = done
         1803 Location of sets = done
         1804 Exit to (S)hell, (H)alt or (R)eboot = R
         1805 </code></pre>
         1806 <p>Get custom encrypted password for response file:</p>
         1807 <pre><code>$ printf '%s' 'yourpassword' | encrypt
         1808 </code></pre>
         1809 <h2>Changing the RAMDISK kernel disk image</h2>
         1810 <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
         1811 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>
         1812 <pre><code>$ mkdir auto
         1813 $ cd auto
         1814 $ cp pubdir/bsd.rd .
         1815 $ rdsetroot -x bsd.rd disk.fs
         1816 # vnconfig vnd0 disk.fs
         1817 # mkdir mount
         1818 # mount /dev/vnd0a mount
         1819 </code></pre>
         1820 <p>Copy the response file (install.resp) to: mount/auto_install.conf
         1821 (installation) <strong>or</strong> mount/auto_upgrade.conf (upgrade), but not both. In this
         1822 guide we will do an auto-installation.</p>
         1823 <p>Unmount, detach and patch RAMDISK:</p>
         1824 <pre><code># umount mount
         1825 # vnconfig -u vnd0
         1826 $ rdsetroot bsd.rd disk.fs
         1827 </code></pre>
         1828 <p>To test copy bsd.rd to the root of some testmachine like /bsd.test.rd then
         1829 (re)boot and type:</p>
         1830 <pre><code>boot /bsd.test.rd
         1831 </code></pre>
         1832 <p>In the future (6.5+) it will be possible to copy to a file named "/bsd.upgrade"
         1833 in the root of a current system and automatically load the kernel:
         1834 <a href="https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/sys/stand/boot/boot.c?rev=1.46&amp;content-type=text/x-cvsweb-markup">See the script bsd.upgrade in CVS.</a>
         1835 Of course this is possible with PXE boot or some custom USB/ISO also.
         1836 As explained in the <a href="https://man.openbsd.org/autoinstall.8">autoinstall(8)</a> man page: create either an
         1837 auto_upgrade.conf <strong>or</strong> an auto_install.conf, but not both.</p>
         1838 <h2>Create bootable miniroot</h2>
         1839 <p>In this example the miniroot will boot the custom kernel, but fetch all the
         1840 sets from the local network.</p>
         1841 <p>We will base our miniroot of the official version: miniroot65.fs.</p>
         1842 <p>We will create a 16MB miniroot to boot from (in this guide it is assumed the
         1843 original miniroot is about 4MB and the modified kernel image fits in the new
         1844 allocated space):</p>
         1845 <pre><code>$ dd if=/dev/zero of=new.fs bs=512 count=32768
         1846 </code></pre>
         1847 <p>Copy first part of the original image to the new disk (no truncation):</p>
         1848 <pre><code>$ dd conv=notrunc if=miniroot65.fs of=new.fs
         1849 # vnconfig vnd0 new.fs
         1850 </code></pre>
         1851 <p>Expand disk OpenBSD boundaries:</p>
         1852 <pre><code># disklabel -E vnd0
         1853 &gt; b
         1854 Starting sector: [1024]
         1855 Size ('*' for entire disk): [8576] *
         1856 &gt; r
         1857 Total free sectors: 1168.
         1858 &gt; c a
         1859 Partition a is currently 8576 sectors in size, and can have a maximum
         1860 size of 9744 sectors.
         1861 size: [8576] *
         1862 &gt; w
         1863 &gt; q
         1864 </code></pre>
         1865 <p>or:</p>
         1866 <pre><code># printf 'b\n\n*\nc a\n*\nw\n' | disklabel -E vnd0
         1867 </code></pre>
         1868 <p>Grow filesystem and check it and mark as clean:</p>
         1869 <pre><code># growfs -y /dev/vnd0a
         1870 # fsck -y /dev/vnd0a
         1871 </code></pre>
         1872 <p>Mount filesystem:</p>
         1873 <pre><code># mount /dev/vnd0a mount/
         1874 </code></pre>
         1875 <p>The kernel on the miniroot is GZIP compressed. Compress our modified bsd.rd and
         1876 overwrite the original kernel:</p>
         1877 <pre><code># gzip -c9n bsd.rd &gt; mount/bsd
         1878 </code></pre>
         1879 <p>Or to save space (+- 500KB) by stripping debug symbols, taken from bsd.gz target
         1880 <a href="https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/distrib/amd64/iso/Makefile">in this Makefile</a>.</p>
         1881 <pre><code>$ cp bsd.rd bsd.strip
         1882 $ strip bsd.strip
         1883 $ strip -R .comment -R .SUNW_ctf bsd.strip
         1884 $ gzip -c9n bsd.strip &gt; bsd.gz
         1885 $ cp bsd.gz mount/bsd
         1886 </code></pre>
         1887 <p>Now unmount and detach:</p>
         1888 <pre><code># umount mount/
         1889 # vnconfig -u vnd0
         1890 </code></pre>
         1891 <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>
         1892 <h2>Adding custom sets (optional)</h2>
         1893 <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
         1894 customized installation set like siteVERSION.tgz, for example: site65.tgz.  The
         1895 sets can even be specified per host/MAC address like
         1896 siteVERSION-$(hostname -s).tgz so for example: site65-testvm.tgz</p>
         1897 <p>When the installer checks the base sets of the mirror it looks for a file
         1898 index.txt.  To add custom sets the site entries have to be added.</p>
         1899 <p>For example:</p>
         1900 <pre><code>-rw-r--r--  1 1001  0    4538975 Oct 11 13:58:26 2018 site65-testvm.tgz
         1901 </code></pre>
         1902 <p>The filesize, permissions etc do not matter and are not checked by the
         1903 installer.  Only the filename is matched by a regular expression.</p>
         1904 <h2>Sign custom site* tarball sets (optional)</h2>
         1905 <p>If you have custom sets without creating a signed custom release you will be
         1906 prompted for the messages:</p>
         1907 <pre><code>checksum test failed
         1908 </code></pre>
         1909 <p>and:</p>
         1910 <pre><code>unverified sets: continue without verification
         1911 </code></pre>
         1912 <p>OpenBSD uses the program <a href="https://man.openbsd.org/signify.1">signify(1)</a> to cryptographically sign and
         1913 verify filesets.</p>
         1914 <p>To create a custom public/private keypair (ofcourse make sure to store the
         1915 private key privately):</p>
         1916 <pre><code>$ signify -G -n -c "Custom 6.5 install" -p custom-65-base.pub -s custom-65-base.sec
         1917 </code></pre>
         1918 <p>Create new checksum file with filelist of the current directory (except SHA256*
         1919 files):</p>
         1920 <pre><code>$ printf '%s\n' * | grep -v SHA256 | xargs sha256 &gt; SHA256
         1921 </code></pre>
         1922 <p>Sign SHA256 and store as SHA256.sig, embed signature:</p>
         1923 <pre><code>$ signify -S -e -s /privatedir/custom-65-base.sec -m SHA256 -x SHA256.sig
         1924 </code></pre>
         1925 <p>Verify the created signature and data is correct:</p>
         1926 <pre><code>$ signify -C -p /somelocation/custom-65-base.pub -x SHA256.sig
         1927 </code></pre>
         1928 <p>Copy <strong>only</strong> the <strong>public</strong> key to the RAMDISK:</p>
         1929 <pre><code>$ cp custom-65-base.pub mount/etc/signify/custom-65-base.pub
         1930 </code></pre>
         1931 <p>Now we have to patch the install.sub file to check our public key.  If you know
         1932 a better way without having to patch this script, please let me know.</p>
         1933 <p>Change the variable PUB_KEY in the shellscript mount/install.sub from:</p>
         1934 <pre><code>PUB_KEY=/etc/signify/openbsd-${VERSION}-base.pub
         1935 </code></pre>
         1936 <p>To:</p>
         1937 <pre><code>PUB_KEY=/etc/signify/custom-${VERSION}-base.pub
         1938 </code></pre>
         1939 <p>And for upgrades from:</p>
         1940 <pre><code>$UPGRADE_BSDRD &amp;&amp;
         1941         PUB_KEY=/mnt/etc/signify/openbsd-$((VERSION + 1))-base.pub
         1942 </code></pre>
         1943 <p>To:</p>
         1944 <pre><code>$UPGRADE_BSDRD &amp;&amp;
         1945         PUB_KEY=/mnt/etc/signify/custom-$((VERSION + 1))-base.pub
         1946 </code></pre>
         1947 <h2>Ideas</h2>
         1948 <ul>
         1949 <li>Patch <a href="https://man.openbsd.org/rc.firsttime.8">rc.firsttime(8)</a>: and run syspatch, add ports, setup xenodm etc.</li>
         1950 <li>Custom partitioning scheme, see <a href="https://man.openbsd.org/autoinstall.8">autoinstall(8)</a> "URL to autopartitioning
         1951 template for disklabel = url".</li>
         1952 <li>Setup <a href="https://man.openbsd.org/pxeboot.8">pxeboot(8)</a> to boot and install over the network using
         1953 <a href="https://man.openbsd.org/dhcpd.8">dhcpd(8)</a> and
         1954 <a href="https://man.openbsd.org/tftpd.8">tftpd(8)</a> then not even some USB stick is required.</li>
         1955 </ul>
         1956 <h2>References</h2>
         1957 <ul>
         1958 <li>Main OpenBSD installation and upgrade shellscript:
         1959 <a href="https://cvsweb.openbsd.org/src/distrib/miniroot/install.sub">/usr/src/distrib/miniroot/install.sub</a></li>
         1960 </ul>
         1961 ]]></content>
         1962 </entry>
         1963 <entry>
         1964         <title>Idiotbox: Youtube interface</title>
         1965         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/idiotbox" />
         1966         <id>gopher://codemadness.org/1/phlog/idiotbox</id>
         1967         <updated>2021-12-25T00:00:00Z</updated>
         1968         <published>2019-02-10T00:00:00Z</published>
         1969         <author>
         1970                 <name>Hiltjo</name>
         1971                 <uri>gopher://codemadness.org</uri>
         1972         </author>
         1973         <summary>Idiotbox: Youtube interface</summary>
         1974         <content type="html"><![CDATA[<h1>Idiotbox: Youtube interface</h1>
         1975         <p><strong>Last modification on </strong> <time>2021-12-25</time></p>
         1976         <p>Idiotbox is a less resource-heavy Youtube interface.  For viewing videos it is
         1977 recommended to use it with <a href="https://mpv.io/">mpv</a> or
         1978 <a href="https://mplayerhq.hu/">mplayer</a> with
         1979 <a href="https://youtube-dl.org/">youtube-dl</a> or
         1980 <a href="https://github.com/yt-dlp/yt-dlp">yt-dlp</a>.</p>
         1981 <p>For more (up-to-date) information see the <a href="/git/frontends/file/youtube/README.html">README</a> file.</p>
         1982 <h2>Why</h2>
         1983 <p>In my opinion the standard Youtube web interface is:</p>
         1984 <ul>
         1985 <li>Non-intuitive, too much visual crap.</li>
         1986 <li>Too resource-hungry, both in CPU and bandwidth.</li>
         1987 <li>Doesn't work well on simpler (text-based) browsers such as netsurf and links.</li>
         1988 </ul>
         1989 <h2>Features</h2>
         1990 <ul>
         1991 <li>Doesn't use JavaScript.</li>
         1992 <li>Doesn't use (tracking) cookies.</li>
         1993 <li>CSS is optional.</li>
         1994 <li>Multiple interfaces available: HTTP CGI, command-line, Gopher CGI (gph),
         1995 this is a work-in-progress.</li>
         1996 <li>Doesn't use or require the Google API.</li>
         1997 <li>CGI interface works nice in most browsers, including text-based ones.</li>
         1998 <li>On OpenBSD it runs "sandboxed" and it can be compiled as a static-linked
         1999 binary with <a href="https://man.openbsd.org/pledge">pledge(2)</a>,
         2000 <a href="https://man.openbsd.org/unveil">unveil(2)</a> in a chroot.</li>
         2001 </ul>
         2002 <h2>Cons</h2>
         2003 <ul>
         2004 <li>Order by upload date is incorrect (same as on Youtube).</li>
         2005 <li>Some Youtube features are not supported.</li>
         2006 <li>Uses scraping so might break at any point.</li>
         2007 </ul>
         2008 <h2>Clone</h2>
         2009 <pre><code>git clone git://git.codemadness.org/frontends
         2010 </code></pre>
         2011 <h2>Browse</h2>
         2012 <p>You can browse the source-code at:</p>
         2013 <ul>
         2014 <li><a href="https://git.codemadness.org/frontends/">https://git.codemadness.org/frontends/</a></li>
         2015 <li><a href="gopher://codemadness.org/1/git/frontends">gopher://codemadness.org/1/git/frontends</a></li>
         2016 </ul>
         2017 <h2>Download releases</h2>
         2018 <p>Releases are available at:</p>
         2019 <ul>
         2020 <li><a href="https://codemadness.org/releases/frontends/">https://codemadness.org/releases/frontends/</a></li>
         2021 <li><a href="gopher://codemadness.org/1/releases/frontends">gopher://codemadness.org/1/releases/frontends</a></li>
         2022 </ul>
         2023 <h2>View</h2>
         2024 <p>You can view it here: <a href="https://codemadness.org/idiotbox/">https://codemadness.org/idiotbox/</a></p>
         2025 <p>For example you can search using the query string parameter "q":
         2026 <a href="https://codemadness.org/idiotbox/?q=gunther+tralala">https://codemadness.org/idiotbox/?q=gunther+tralala</a></p>
         2027 <p>The gopher version is here: <a href="gopher://codemadness.org/7/idiotbox.cgi">gopher://codemadness.org/7/idiotbox.cgi</a></p>
         2028 ]]></content>
         2029 </entry>
         2030 <entry>
         2031         <title>Gopher HTTP proxy</title>
         2032         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/gopher-proxy" />
         2033         <id>gopher://codemadness.org/1/phlog/gopher-proxy</id>
         2034         <updated>2020-08-30T00:00:00Z</updated>
         2035         <published>2018-08-17T00:00:00Z</published>
         2036         <author>
         2037                 <name>Hiltjo</name>
         2038                 <uri>gopher://codemadness.org</uri>
         2039         </author>
         2040         <summary>Gopher HTTP proxy</summary>
         2041         <content type="html"><![CDATA[<h1>Gopher HTTP proxy</h1>
         2042         <p><strong>Last modification on </strong> <time>2020-08-30</time></p>
         2043         <p>For fun I wrote a small HTTP Gopher proxy CGI program in C. It only supports
         2044 the basic Gopher types and has some restrictions to prevent some abuse.</p>
         2045 <p>For your regular Gopher browsing I recommend the simple Gopher client <a href="https://git.fifth.space/sacc/">sacc</a>.</p>
         2046 <p>For more information about Gopher check out <a href="http://gopherproject.org/">gopherproject.org</a>.</p>
         2047 <h2>Clone</h2>
         2048 <pre><code>git clone git://git.codemadness.org/gopherproxy-c
         2049 </code></pre>
         2050 <h2>Browse</h2>
         2051 <p>You can browse the source-code at:</p>
         2052 <ul>
         2053 <li><a href="https://git.codemadness.org/gopherproxy-c/">https://git.codemadness.org/gopherproxy-c/</a></li>
         2054 <li><a href="gopher://codemadness.org/1/git/gopherproxy-c">gopher://codemadness.org/1/git/gopherproxy-c</a></li>
         2055 </ul>
         2056 <h2>View</h2>
         2057 <p>You can view it here:
         2058 <a href="https://codemadness.org/gopherproxy/">https://codemadness.org/gopherproxy/</a></p>
         2059 <p>For example you can also view my gopherhole using the proxy, the query string
         2060 parameter "q" reads the URI:
         2061 <a href="https://codemadness.org/gopherproxy/?q=codemadness.org">https://codemadness.org/gopherproxy/?q=codemadness.org</a></p>
         2062 <p><strong>Due to abuse this service is (temporary) disabled, but of course you can self-host it</strong></p>
         2063 <p><strong>For authors writing crawler bots: please respect robots.txt, HTTP status codes and test your code properly</strong></p>
         2064 ]]></content>
         2065 </entry>
         2066 <entry>
         2067         <title>Setup your own file paste service</title>
         2068         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/paste-service" />
         2069         <id>gopher://codemadness.org/1/phlog/paste-service</id>
         2070         <updated>2018-03-10T00:00:00Z</updated>
         2071         <published>2018-03-10T00:00:00Z</published>
         2072         <author>
         2073                 <name>Hiltjo</name>
         2074                 <uri>gopher://codemadness.org</uri>
         2075         </author>
         2076         <summary>Howto setup your own secure file paste service</summary>
         2077         <content type="html"><![CDATA[<h1>Setup your own file paste service</h1>
         2078         <p><strong>Last modification on </strong> <time>2018-03-10</time></p>
         2079         <h2>Setup SSH authentication</h2>
         2080 <p>Make sure to setup SSH public key authentication so you don't need to enter a
         2081 password each time and have a more secure authentication.</p>
         2082 <p>For example in the file $HOME/.ssh/config:</p>
         2083 <pre><code>Host codemadness
         2084         Hostname codemadness.org
         2085         Port 22
         2086         IdentityFile ~/.ssh/codemadness/id_rsa
         2087 </code></pre>
         2088 <p>Of course also make sure to generate the private and public keys.</p>
         2089 <h2>Shell alias</h2>
         2090 <p>Make an alias or function in your shell config:</p>
         2091 <pre><code>pastesrv() {
         2092         ssh user@codemadness "cat &gt; /your/www/publicdir/paste/$1"
         2093         echo "https://codemadness.org/paste/$1"
         2094 }
         2095 </code></pre>
         2096 <p>This function reads any data from stdin and transfers the output securely via
         2097 SSH and writes it to a file at the specified path. This path can be visible via
         2098 HTTP, gopher or an other protocol. Then it writes the absolute URL to stdout,
         2099 this URL can be copied to the clipboard and pasted anywhere like to an e-mail,
         2100 IRC etc.</p>
         2101 <h2>Usage and examples</h2>
         2102 <p>To use it, here are some examples:</p>
         2103 <p>Create a patch of the last commit in the git repo and store it:</p>
         2104 <pre><code>git format-patch --stdout HEAD^ | pastesrv 'somepatch.diff'
         2105 </code></pre>
         2106 <p>Create a screenshot of your current desktop and paste it:</p>
         2107 <pre><code>xscreenshot | ff2png | pastesrv 'screenshot.png'
         2108 </code></pre>
         2109 <p>There are many other uses of course, use your imagination :)</p>
         2110 ]]></content>
         2111 </entry>
         2112 <entry>
         2113         <title>Setup your own git hosting service</title>
         2114         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/setup-git-hosting" />
         2115         <id>gopher://codemadness.org/1/phlog/setup-git-hosting</id>
         2116         <updated>2022-08-07T00:00:00Z</updated>
         2117         <published>2018-02-25T00:00:00Z</published>
         2118         <author>
         2119                 <name>Hiltjo</name>
         2120                 <uri>gopher://codemadness.org</uri>
         2121         </author>
         2122         <summary>Howto setup your own git hosting service</summary>
         2123         <content type="html"><![CDATA[<h1>Setup your own git hosting service</h1>
         2124         <p><strong>Last modification on </strong> <time>2022-08-07</time></p>
         2125         <p><strong>This article assumes you use OpenBSD for the service files and OS-specific
         2126 examples.</strong></p>
         2127 <h2>Why</h2>
         2128 <p>A good reason to host your own git repositories is because of having and
         2129 keeping control over your own computing infrastructure.</p>
         2130 <p>Some bad examples:</p>
         2131 <ul>
         2132 <li><a href="https://en.wikipedia.org/wiki/SourceForge#Controversies">The SourceForge ads/malware/hijack controversies. Injecting malware into projects</a>.</li>
         2133 <li><a href="https://gitlab.com/gitlab-org/gitaly/issues/2113">As of 2019-10-23 Gitlab added telemetry to their software</a>.</li>
         2134 <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>
         2135 <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>
         2136 <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>
         2137 <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>
         2138 <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>
         2139 <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>
         2140 </ul>
         2141 <p>The same thing can happen with Github, Atlassian Bitbucket or other similar
         2142 services.  After all: they are just a company with commercial interests.  These
         2143 online services also have different pricing plans and various (arbitrary)
         2144 restrictions.  When you host it yourself the restrictions are the resource
         2145 limits of the system and your connection, therefore it is a much more flexible
         2146 solution.</p>
         2147 <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
         2148 can host it yourself, so you will be in control of it.</p>
         2149 <h2>Creating repositories</h2>
         2150 <p>For the hosting it is recommended to use a so-called "bare" repository.  A bare
         2151 repository means no files are checked out in the folder itself.  To create a
         2152 bare repository use git init with the --bare argument:</p>
         2153 <pre><code>$ git init --bare
         2154 </code></pre>
         2155 <p>I recommend to create a separate user and group for the source-code
         2156 repositories.  In the examples we will assume the user is called "src".</p>
         2157 <p>Login as the src user and create the files. To create a directory for the
         2158 repos, in this example /home/src/src:</p>
         2159 <pre><code>$ mkdir -p /home/src/src
         2160 $ cd /home/src/src
         2161 $ git init --bare someproject
         2162 $ $EDITOR someproject/description
         2163 </code></pre>
         2164 <p>Make sure the git-daemon process has access permissions to these repositories.</p>
         2165 <h2>Install git-daemon (optional)</h2>
         2166 <p>Using git-daemon you can clone the repositories publicly using the efficient
         2167 git:// protocol. An alternative without having to use git-daemon is by using
         2168 (anonymous) SSH, HTTPS or any public shared filesystem.</p>
         2169 <p>When you use a private-only repository I recommend to just use SSH without
         2170 git-daemon because it is secure.</p>
         2171 <p>Install the git package. The package should contain "git daemon":</p>
         2172 <pre><code># pkg_add git
         2173 </code></pre>
         2174 <p>Enable the daemon:</p>
         2175 <pre><code># rcctl enable gitdaemon
         2176 </code></pre>
         2177 <p>Set the gitdaemon service flags to use the src directory and use all the
         2178 available repositories in this directory. The command-line flags "--export-all"
         2179 exports all repositories in the base path. Alternatively you can use the
         2180 "git-daemon-export-ok" file (see the git-daemon man page).</p>
         2181 <pre><code># rcctl set gitdaemon flags --export-all --base-path="/home/src/src"
         2182 </code></pre>
         2183 <p>To configure the service to run as the user _gitdaemon:</p>
         2184 <pre><code># rcctl set gitdaemon user _gitdaemon
         2185 </code></pre>
         2186 <p>To run the daemon directly as the user _gitdaemon (without dropping privileges
         2187 from root to the user) set the following flags in /etc/rc.d/gitdaemon:</p>
         2188 <pre><code>daemon_flags="--user=_gitdaemon"
         2189 </code></pre>
         2190 <p>Which will also avoid this warning while cloning:</p>
         2191 <pre><code>"can't access /root/.git/config"
         2192 </code></pre>
         2193 <p>Now start the daemon:</p>
         2194 <pre><code># rcctl start gitdaemon
         2195 </code></pre>
         2196 <h2>Cloning and fetching changes</h2>
         2197 <p>To test and clone the repository do:</p>
         2198 <pre><code>$ git clone git://yourdomain/someproject
         2199 </code></pre>
         2200 <p>if you skipped the optional git-daemon installation then just clone via SSH:</p>
         2201 <pre><code>$ git clone ssh://youraccount@yourdomain:/home/src/src/someproject
         2202 </code></pre>
         2203 <p>When cloning via SSH make sure to setup private/public key authentication for
         2204 security and convenience.</p>
         2205 <p>You should also make sure the firewall allows connections to the services like
         2206 the git daemon, HTTPd or SSH, for example using OpenBSD pf something like this
         2207 can be set in <a href="https://man.openbsd.org/pf.conf">/etc/pf.conf</a>:</p>
         2208 <pre><code>tcp_services="{ ssh, gopher, http, https, git }"
         2209 pass in on egress proto tcp from any to (egress) port $tcp_services
         2210 </code></pre>
         2211 <h2>Pushing changes</h2>
         2212 <p>Add the repository as a remote:</p>
         2213 <pre><code>$ git remote add myremote ssh://youraccount@yourdomain:/home/src/src/someproject
         2214 </code></pre>
         2215 <p>Then push the changes:</p>
         2216 <pre><code>$ git push myremote master:master
         2217 </code></pre>
         2218 <h2>Git history web browsing (optional)</h2>
         2219 <p>Sometimes it's nice to browse the git history log of the repository in a web
         2220 browser or some other program without having to look at the local repository.</p>
         2221 <ul>
         2222 <li><a href="stagit.html">Stagit</a> is a static HTML page generator for git.</li>
         2223 <li><a href="stagit-gopher.html">Stagit-gopher</a> is a static page generator for
         2224 <a href="http://gopherproject.org/">gopher</a> and
         2225 <a href="gopher://bitreich.org/1/scm/geomyidae">geomyidae</a>.</li>
         2226 <li>cgit is a CGI-based program which shows HTML views of your repository, see
         2227 also the page: <a href="openbsd-httpd-and-cgit.html">OpenBSD httpd, slowcgi and cgit</a>.</li>
         2228 </ul>
         2229 <p>It's also possible with these tools to generate an Atom feed and then use a
         2230 RSS/Atom reader to track the git history:</p>
         2231 <ul>
         2232 <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>
         2233 <li>An example url from stagit for the <a href="/git/stagit/atom.xml">commit log</a>.</li>
         2234 <li>An example url from stagit for the <a href="/git/stagit/tags.xml">releases</a>.</li>
         2235 </ul>
         2236 <p>My <a href="sfeed.html">sfeed</a> program can be used as a RSS/Atom reader.</p>
         2237 <h2>Setting up git hooks (optional)</h2>
         2238 <p>Using git hooks you can setup automated triggers, for example when pushing to a
         2239 repository.  Some useful examples can be:</p>
         2240 <ul>
         2241 <li><a href="/git/stagit/file/example_post-receive.sh.html">For stagit: update the repo files (example post-receive hook).</a></li>
         2242 <li>Send an e-mail with the commit subject and message.</li>
         2243 <li>Log/notify commits and changes to an IRC channel using a fifo: <a href="https://tools.suckless.org/ii/">ii</a>.</li>
         2244 <li>Create a release tarball and checksum file on a tag push/change.</li>
         2245 <li>Checkout files for website content.</li>
         2246 </ul>
         2247 ]]></content>
         2248 </entry>
         2249 <entry>
         2250         <title>Setup an OpenBSD SPARC64 VM in QEMU</title>
         2251         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/openbsd-sparc64-vm" />
         2252         <id>gopher://codemadness.org/1/phlog/openbsd-sparc64-vm</id>
         2253         <updated>2020-04-18T00:00:00Z</updated>
         2254         <published>2017-12-11T00:00:00Z</published>
         2255         <author>
         2256                 <name>Hiltjo</name>
         2257                 <uri>gopher://codemadness.org</uri>
         2258         </author>
         2259         <summary>Setup an OpenBSD SPARC64 VM in QEMU</summary>
         2260         <content type="html"><![CDATA[<h1>Setup an OpenBSD SPARC64 VM in QEMU</h1>
         2261         <p><strong>Last modification on </strong> <time>2020-04-18</time></p>
         2262         <p>This describes how to setup an OpenBSD SPARC64 VM in QEMU.</p>
         2263 <h2>Create a disk image</h2>
         2264 <p>To create a 5GB disk image:</p>
         2265 <pre><code>qemu-img create -f qcow2 fs.qcow2 5G
         2266 </code></pre>
         2267 <h2>Install</h2>
         2268 <p>In this guide we'll use the installation ISO to install OpenBSD. Make sure to
         2269 download the latest (stable) OpenBSD ISO, for example install62.iso.</p>
         2270 <ul>
         2271 <li>Change -boot c to -boot d to boot from the CD-ROM and do a clean install.</li>
         2272 <li>Change -cdrom install62.iso to the location of your ISO file.</li>
         2273 <li>When the install is done type: halt -p</li>
         2274 <li>Change -boot d back to -boot c.</li>
         2275 </ul>
         2276 <p>Start the VM:</p>
         2277 <pre><code>#!/bin/sh
         2278 LC_ALL=C QEMU_AUDIO_DRV=none \
         2279 qemu-system-sparc64 \
         2280         -machine sun4u,usb=off \
         2281         -realtime mlock=off \
         2282         -smp 1,sockets=1,cores=1,threads=1 \
         2283         -rtc base=utc \
         2284         -m 1024 \
         2285         -boot c \
         2286         -drive file=fs.qcow2,if=none,id=drive-ide0-0-1,format=qcow2,cache=none \
         2287         -cdrom install62.iso \
         2288         -device ide-hd,bus=ide.0,unit=0,drive=drive-ide0-0-1,id=ide0-0-1 \
         2289         -msg timestamp=on \
         2290         -serial pty -nographic \
         2291         -net nic,model=ne2k_pci -net user
         2292 </code></pre>
         2293 <p>The VM has the following properties:</p>
         2294 <ul>
         2295 <li>No audio.</li>
         2296 <li>No USB.</li>
         2297 <li>No VGA graphics: serial console.</li>
         2298 <li>Netdev is ne0 (Realtek 8029).</li>
         2299 <li>1024MB memory.</li>
         2300 </ul>
         2301 <p>From your host connect to the serial device indicated by QEMU, for example:</p>
         2302 <pre><code>(qemu) 2017-11-19T15:14:20.884312Z qemu-system-sparc64: -serial pty: char device redirected to /dev/ttyp0 (label serial0)
         2303 </code></pre>
         2304 <p>Then you can use the serial terminal emulator <strong>cu</strong> to attach:</p>
         2305 <pre><code>cu -l /dev/ttyp0
         2306 </code></pre>
         2307 <p>Another option could be using the <a href="https://git.suckless.org/st/">simple terminal(st)</a> from suckless.</p>
         2308 <pre><code>st -l /dev/ttyp0
         2309 </code></pre>
         2310 <p>using cu to detach the <a href="https://man.openbsd.org/cu#~^D">cu(1) man page</a> says:</p>
         2311 <pre><code>Typed characters are normally transmitted directly to the remote machine (which
         2312 does the echoing as well).  A tilde ('~') appearing as the first character of a
         2313 line is an escape signal; the following are recognized:
         2314 
         2315     ~^D or ~.  Drop the connection and exit.  Only the connection is
         2316                the login session is not terminated.
         2317 </code></pre>
         2318 <p>On boot you have to type:</p>
         2319 <pre><code>root device: wd0a
         2320 for swap use the default (wd0b) Press enter
         2321 </code></pre>
         2322 <h2>Initial settings on first boot (optional)</h2>
         2323 <p>Automatic network configuration using DHCP</p>
         2324 <pre><code>echo "dhcp" &gt; /etc/hostname.ne0
         2325 </code></pre>
         2326 <p>To bring up the interface (will be automatic on the next boot):</p>
         2327 <pre><code>sh /etc/netstart
         2328 </code></pre>
         2329 <p>Add a mirror to /etc/installurl for package installation. Make sure to lookup
         2330 the most efficient/nearby mirror site on the OpenBSD mirror page.</p>
         2331 <pre><code>echo "https://ftp.hostserver.de/pub/OpenBSD" &gt; /etc/installurl
         2332 </code></pre>
         2333 ]]></content>
         2334 </entry>
         2335 <entry>
         2336         <title>Tscrape: a Twitter scraper</title>
         2337         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/tscrape" />
         2338         <id>gopher://codemadness.org/1/phlog/tscrape</id>
         2339         <updated>2020-07-20T00:00:00Z</updated>
         2340         <published>2017-09-24T00:00:00Z</published>
         2341         <author>
         2342                 <name>Hiltjo</name>
         2343                 <uri>gopher://codemadness.org</uri>
         2344         </author>
         2345         <summary>Tscrape: a Twitter scraper</summary>
         2346         <content type="html"><![CDATA[<h1>Tscrape: a Twitter scraper</h1>
         2347         <p><strong>Last modification on </strong> <time>2020-07-20</time></p>
         2348         <p>Tscrape is a Twitter web scraper and archiver.</p>
         2349 <p>Twitter removed the functionality to follow users using a RSS feed without
         2350 authenticating or using their API. With this program you can format tweets in
         2351 any way you like relatively anonymously.</p>
         2352 <p>For more (up-to-date) information see the <a href="/git/tscrape/file/README.html">README</a> file.</p>
         2353 <h2>Clone</h2>
         2354 <pre><code>git clone git://git.codemadness.org/tscrape
         2355 </code></pre>
         2356 <h2>Browse</h2>
         2357 <p>You can browse the source-code at:</p>
         2358 <ul>
         2359 <li><a href="https://git.codemadness.org/tscrape/">https://git.codemadness.org/tscrape/</a></li>
         2360 <li><a href="gopher://codemadness.org/1/git/tscrape">gopher://codemadness.org/1/git/tscrape</a></li>
         2361 </ul>
         2362 <h2>Download releases</h2>
         2363 <p>Releases are available at:</p>
         2364 <ul>
         2365 <li><a href="https://codemadness.org/releases/tscrape/">https://codemadness.org/releases/tscrape/</a></li>
         2366 <li><a href="gopher://codemadness.org/1/releases/tscrape">gopher://codemadness.org/1/releases/tscrape</a></li>
         2367 </ul>
         2368 <h2>Examples</h2>
         2369 <p>Output format examples:</p>
         2370 <ul>
         2371 <li><a href="tscrape/tscrape_html.html">tscrape_html: HTML</a></li>
         2372 <li><a href="tscrape/tscrape_plain.txt">tscrape_plain: Text</a></li>
         2373 </ul>
         2374 ]]></content>
         2375 </entry>
         2376 <entry>
         2377         <title>jsdatatable: a small datatable Javascript</title>
         2378         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/datatable" />
         2379         <id>gopher://codemadness.org/1/phlog/datatable</id>
         2380         <updated>2020-07-20T00:00:00Z</updated>
         2381         <published>2017-09-24T00:00:00Z</published>
         2382         <author>
         2383                 <name>Hiltjo</name>
         2384                 <uri>gopher://codemadness.org</uri>
         2385         </author>
         2386         <summary>jsdatatable: a small datatable Javascript</summary>
         2387         <content type="html"><![CDATA[<h1>jsdatatable: a small datatable Javascript</h1>
         2388         <p><strong>Last modification on </strong> <time>2020-07-20</time></p>
         2389         <p>This is a small datatable Javascript with no dependencies.</p>
         2390 <h2>Features</h2>
         2391 <ul>
         2392 <li>Small:
         2393 <ul>
         2394 <li>Filesize: +- 9.1KB.</li>
         2395 <li>Lines: +- 300, not much code, so hopefully easy to understand.</li>
         2396 <li>No dependencies on other libraries like jQuery.</li>
         2397 </ul>
         2398 </li>
         2399 <li>Sorting on columns, multi-column support with shift-click.</li>
         2400 <li>Filtering values: case-insensitively, tokenized (separated by space).</li>
         2401 <li>Able to add custom filtering, parsing and sorting functions.</li>
         2402 <li>Helper function for delayed (150ms) filtering, so filtering feels more
         2403 responsive for big datasets.</li>
         2404 <li>Permissive ISC license, see LICENSE file.</li>
         2405 <li>"Lazy scroll" mode:
         2406 <ul>
         2407 <li>fixed column headers and renders only visible rows, this allows you to
         2408 "lazily" render millions of rows.</li>
         2409 </ul>
         2410 </li>
         2411 <li>Officially supported browsers are:
         2412 <ul>
         2413 <li>Firefox and Firefox ESR.</li>
         2414 <li>Chrome and most recent webkit-based browsers.</li>
         2415 <li>IE10+.</li>
         2416 </ul>
         2417 </li>
         2418 </ul>
         2419 <h2>Why? and a comparison</h2>
         2420 <p>It was created because all the other datatable scripts suck balls.</p>
         2421 <p>Most Javascripts nowadays have a default dependency on jQuery, Bootstrap or
         2422 other frameworks.</p>
         2423 <p>jQuery adds about 97KB and Bootstrap adds about 100KB to your scripts and CSS
         2424 as a dependency.  This increases the CPU, memory and bandwidth consumption and
         2425 latency. It also adds complexity to your scripts.</p>
         2426 <p>jQuery was mostly used for backwards-compatibility in the Internet Explorer
         2427 days, but is most often not needed anymore. It contains functionality to query
         2428 the DOM using CSS-like selectors, but this is now supported with for example
         2429 document.querySelectorAll.  Functionality like a JSON parser is standard
         2430 available now: JSON.parse().</p>
         2431 <h3>Size comparison</h3>
         2432 <p>All sizes are not "minified" or gzipped.</p>
         2433 <pre><code>Name                             |   Total |      JS |   CSS | Images | jQuery
         2434 ---------------------------------+---------+---------+-------+--------+-------
         2435 jsdatatable                      |  12.9KB |   9.1KB | 2.5KB |  1.3KB |      -
         2436 datatables.net (without plugins) | 563.4KB | 449.3KB |  16KB |  0.8KB | 97.3KB
         2437 jdatatable                       | 154.6KB |    53KB |   1KB |  3.3KB | 97.3KB
         2438 </code></pre>
         2439 <ul>
         2440 <li><a href="https://datatables.net/">datatables.net</a> (without plugins).</li>
         2441 <li><a href="https://plugins.jquery.com/jdatatable/">jdatatable</a></li>
         2442 </ul>
         2443 <p>Of course jsdatatable has less features (less is more!), but it does 90% of
         2444 what's needed.  Because it is so small it is also much simpler to understand and
         2445 extend with required features if needed.</p>
         2446 <p>See also:
         2447 <a href="https://idlewords.com/talks/website_obesity.htm">The website obesity crisis</a></p>
         2448 <h2>Clone</h2>
         2449 <pre><code>git clone git://git.codemadness.org/jscancer
         2450 </code></pre>
         2451 <h2>Browse</h2>
         2452 <p>You can browse the source-code at:</p>
         2453 <ul>
         2454 <li><a href="https://git.codemadness.org/jscancer/">https://git.codemadness.org/jscancer/</a></li>
         2455 <li><a href="gopher://codemadness.org/1/git/jscancer">gopher://codemadness.org/1/git/jscancer</a></li>
         2456 </ul>
         2457 <p>It is in the datatable directory.</p>
         2458 <h2>Download releases</h2>
         2459 <p>Releases are available at:</p>
         2460 <ul>
         2461 <li><a href="https://codemadness.org/releases/jscancer/">https://codemadness.org/releases/jscancer/</a></li>
         2462 <li><a href="gopher://codemadness.org/1/releases/jscancer">gopher://codemadness.org/1/releases/jscancer</a></li>
         2463 </ul>
         2464 <h2>Usage</h2>
         2465 <h3>Examples</h3>
         2466 <p>See example.html for an example. A stylesheet file datatable.css is also
         2467 included, it contains the icons as embedded images.</p>
         2468 <p>A table should have the classname "datatable" set, it must contain a &lt;thead&gt;
         2469 for the column headers (&lt;td&gt; or &lt;th&gt;) and &lt;tbody&gt; element for the data. The
         2470 minimal code needed for a working datatable:</p>
         2471 <pre><code>&lt;html&gt;
         2472 &lt;body&gt;
         2473 &lt;input class="filter-text" /&gt;&lt;!-- optional --&gt;
         2474 &lt;table class="datatable"&gt;
         2475         &lt;thead&gt;&lt;!-- columns --&gt;
         2476                 &lt;tr&gt;&lt;td&gt;Click me&lt;/td&gt;&lt;/tr&gt;
         2477         &lt;/thead&gt;
         2478         &lt;tbody&gt;&lt;!-- data --&gt;
         2479                 &lt;tr&gt;&lt;td&gt;a&lt;/td&gt;&lt;/tr&gt;
         2480                 &lt;tr&gt;&lt;td&gt;b&lt;/td&gt;&lt;/tr&gt;
         2481         &lt;/tbody&gt;
         2482 &lt;/table&gt;
         2483 &lt;script type="text/javascript" src="datatable.js"&gt;&lt;/script&gt;
         2484 &lt;script type="text/javascript"&gt;var datatables = datatable_autoload();&lt;/script&gt;
         2485 &lt;/body&gt;
         2486 &lt;/html&gt;
         2487 </code></pre>
         2488 <h3>Column attributes</h3>
         2489 <p>The following column attributes are supported:</p>
         2490 <ul>
         2491 <li>data-filterable: if "1" or "true" specifies if the column can be filtered,
         2492 default: "true".</li>
         2493 <li>data-parse: specifies how to parse the values, default: "string", which is
         2494 datatable_parse_string(). See PARSING section below.</li>
         2495 <li>data-sort: specifies how to sort the values: default: "default", which is
         2496 datatable_sort_default(). See SORTING section below.</li>
         2497 <li>data-sortable: if "1" or "true" specifies if the column can be sorted,
         2498 default: "true".</li>
         2499 </ul>
         2500 <h3>Parsing</h3>
         2501 <p>By default only parsing for the types: date, float, int and string are
         2502 supported, but other types can be easily added as a function with the name:
         2503 datatable_parse_&lt;typename&gt;(). The parse functions parse the data-value
         2504 attribute when set or else the cell content (in order). Because of this
         2505 behaviour you can set the actual values as the data-value attribute and use the
         2506 cell content for display. This is useful to display and properly sort
         2507 locale-aware currency, datetimes etc.</p>
         2508 <h3>Filtering</h3>
         2509 <p>Filtering will be done case-insensitively on the cell content and when set also
         2510 on the data-value attribute. The filter string is split up as tokens separated
         2511 by space. Each token must match at least once per row to display it.</p>
         2512 <h3>Sorting</h3>
         2513 <p>Sorting is done on the parsed values by default with the function:
         2514 datatable_sort_default(). To change this you can set a customname string on
         2515 the data-sort attribute on the column which translates to the function:
         2516 datatable_sort_&lt;customname&gt;().</p>
         2517 <p>In some applications locale values are used, like for currency, decimal numbers
         2518 datetimes. Some people also like to use icons or extended HTML elements inside
         2519 the cell. Because jsdatatable sorts on the parsed value (see section PARSING)
         2520 it is possible to sort on the data-value attribute values and use the cell
         2521 content for display.</p>
         2522 <p>For example:</p>
         2523 <ul>
         2524 <li>currency, decimal numbers: use data-value attribute with floating-point
         2525 number, set data-parse column to "float".</li>
         2526 <li>date/datetimes: use data-value attribute with UNIX timestamps (type int), set
         2527 data-parse on column to "int" or set the data-parse attribute on column to
         2528 "date" which is datatable_parse_date(), then make sure to use Zulu times, like:
         2529 "2016-01-01T01:02:03Z" or other time strings that are parsable as the
         2530 data-value attribute.</li>
         2531 <li>icons: generally use data-value attribute with integer as weight value to
         2532 sort on, set data-parse column to "int".</li>
         2533 </ul>
         2534 <h3>Dynamically update data</h3>
         2535 <p>To update data dynamically see example-ajax.html for an example how to do this.</p>
         2536 <h3>Caveats</h3>
         2537 <ul>
         2538 <li>A date, integer, float or other values must be able to parse properly, when
         2539 the parse function returns NaN, null or undefined etc. the sorting behaviour is
         2540 also undefined. It is recommended to always set a zero value for each type.</li>
         2541 <li>&lt;tfoot&gt; is not supported in datatables in "lazy" mode.</li>
         2542 </ul>
         2543 <h2>Demo / example</h2>
         2544 <p><strong>For the below example to work you need to have Javascript enabled.</strong></p>
         2545 <p><a href="datatable-example.html">datatable-example.html</a></p>
         2546 ]]></content>
         2547 </entry>
         2548 <entry>
         2549         <title>Stagit-gopher: a static git page generator for gopher</title>
         2550         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/stagit-gopher" />
         2551         <id>gopher://codemadness.org/1/phlog/stagit-gopher</id>
         2552         <updated>2021-04-11T00:00:00Z</updated>
         2553         <published>2017-08-04T00:00:00Z</published>
         2554         <author>
         2555                 <name>Hiltjo</name>
         2556                 <uri>gopher://codemadness.org</uri>
         2557         </author>
         2558         <summary>a static git page generator for gopher</summary>
         2559         <content type="html"><![CDATA[<h1>Stagit-gopher: a static git page generator for gopher</h1>
         2560         <p><strong>Last modification on </strong> <time>2021-04-11</time></p>
         2561         <p>stagit-gopher is a static page generator for Gopher.  It creates the pages as
         2562 static <a href="http://git.r-36.net/geomyidae/">geomyidae</a> .gph files.  stagit-gopher is a modified version from the
         2563 HTML version of stagit.</p>
         2564 <p><a href="/git/stagit-gopher/file/README.html">Read the README for more information about it.</a></p>
         2565 <p>I also run a gopherhole and stagit-gopher, you can see how it looks here:
         2566 <a href="gopher://codemadness.org/1/git/">gopher://codemadness.org/1/git/</a></p>
         2567 <p><a href="https://git.fifth.space/sacc/log.html">sacc</a> is a good Gopher client to view it.</p>
         2568 <h2>Features</h2>
         2569 <ul>
         2570 <li>Log of all commits from HEAD.</li>
         2571 <li>Log and diffstat per commit.</li>
         2572 <li>Show file tree with line numbers.</li>
         2573 <li>Show references: local branches and tags.</li>
         2574 <li>Detect README and LICENSE file from HEAD and link it as a webpage.</li>
         2575 <li>Detect submodules (.gitmodules file) from HEAD and link it as a webpage.</li>
         2576 <li>Atom feed of the commit log (atom.xml).</li>
         2577 <li>Atom feed of the tags/refs (tags.xml).</li>
         2578 <li>Make index page for multiple repositories with stagit-gopher-index.</li>
         2579 <li>After generating the pages (relatively slow) serving the files is very fast,
         2580 simple and requires little resources (because the content is static), a
         2581 geomyidae Gopher server is required.</li>
         2582 <li>Security: all pages are static. No CGI or dynamic code is run for the
         2583 interface.  Using it with a secure Gopher server such as geomyidae it is
         2584 privilege-dropped and chroot(2)'d.</li>
         2585 <li>Simple to setup: the content generation is clearly separated from serving it.
         2586 This makes configuration as simple as copying a few directories and scripts.</li>
         2587 <li>Usable with Gopher clients such as lynx and <a href="https://git.fifth.space/sacc/log.html">sacc</a>.</li>
         2588 </ul>
         2589 <h2>Cons</h2>
         2590 <ul>
         2591 <li>Not suitable for large repositories (2000+ commits), because diffstats are
         2592 an expensive operation, the cache (-c flag) is a workaround for this in
         2593 some cases.</li>
         2594 <li>Not suitable for large repositories with many files, because all files are
         2595 written for each execution of stagit. This is because stagit shows the lines
         2596 of textfiles and there is no "cache" for file metadata (this would add more
         2597 complexity to the code).</li>
         2598 <li>Not suitable for repositories with many branches, a quite linear history is
         2599 assumed (from HEAD).</li>
         2600 <li>Relatively slow to run the first time (about 3 seconds for sbase,
         2601 1500+ commits), incremental updates are faster.</li>
         2602 <li>Does not support some of the dynamic features cgit has (for HTTP), like:
         2603 <ul>
         2604 <li>Snapshot tarballs per commit.</li>
         2605 <li>File tree per commit.</li>
         2606 <li>History log of branches diverged from HEAD.</li>
         2607 <li>Stats (git shortlog -s).</li>
         2608 </ul>
         2609 </li>
         2610 </ul>
         2611 <p>This is by design, just use git locally.</p>
         2612 <h2>Clone</h2>
         2613 <pre><code>git clone git://git.codemadness.org/stagit-gopher
         2614 </code></pre>
         2615 <h2>Browse</h2>
         2616 <p>You can browse the source-code at:</p>
         2617 <ul>
         2618 <li><a href="https://git.codemadness.org/stagit-gopher/">https://git.codemadness.org/stagit-gopher/</a></li>
         2619 <li><a href="gopher://codemadness.org/1/git/stagit-gopher">gopher://codemadness.org/1/git/stagit-gopher</a></li>
         2620 </ul>
         2621 <h2>Download releases</h2>
         2622 <p>Releases are available at:</p>
         2623 <ul>
         2624 <li><a href="https://codemadness.org/releases/stagit-gopher/">https://codemadness.org/releases/stagit-gopher/</a></li>
         2625 <li><a href="gopher://codemadness.org/1/releases/stagit-gopher">gopher://codemadness.org/1/releases/stagit-gopher</a></li>
         2626 </ul>
         2627 ]]></content>
         2628 </entry>
         2629 <entry>
         2630         <title>Saait: a boring HTML page generator</title>
         2631         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/saait" />
         2632         <id>gopher://codemadness.org/1/phlog/saait</id>
         2633         <updated>2020-07-20T00:00:00Z</updated>
         2634         <published>2017-06-10T00:00:00Z</published>
         2635         <author>
         2636                 <name>Hiltjo</name>
         2637                 <uri>gopher://codemadness.org</uri>
         2638         </author>
         2639         <summary>Saait: a boring HTML page generator</summary>
         2640         <content type="html"><![CDATA[<h1>Saait: a boring HTML page generator</h1>
         2641         <p><strong>Last modification on </strong> <time>2020-07-20</time></p>
         2642         <p>Saait is the most boring static HTML page generator.</p>
         2643 <p>Meaning of saai (dutch): boring. Pronunciation: site</p>
         2644 <p><a href="/git/saait/file/README.html">Read the README for more information about it.</a></p>
         2645 <p>I used to use <a href="/git/static-site-scripts/files.html">shellscripts</a> to generate the static pages, but realised I
         2646 wanted a small program that works on each platform consistently.  There are
         2647 many incompatibilities or unimplemented features in base tools across different
         2648 platforms: Linux, UNIX, Windows.</p>
         2649 <p>This site is created using saait.</p>
         2650 <h2>Features</h2>
         2651 <ul>
         2652 <li>Single small binary that handles all the things. At run-time no dependency on
         2653 other tools.</li>
         2654 <li>Few lines of code (about 575 lines of C) and no dependencies except: a C
         2655 compiler and libc.</li>
         2656 <li>Works on most platforms: tested on Linux, *BSD, Windows.</li>
         2657 <li>Simple template syntax.</li>
         2658 <li>Uses HTML output by default, but can easily be modified to generate any
         2659 textual content, like gopher pages, wiki pages or other kinds of documents.</li>
         2660 <li>Out-of-the-box supports: creating an index page of all pages, Atom feed,
         2661 twtxt.txt feed, sitemap.xml and urllist.txt.</li>
         2662 </ul>
         2663 <h2>Cons</h2>
         2664 <ul>
         2665 <li>Simple template syntax, but very basic. Requires C knowledge to extend it if
         2666 needed.</li>
         2667 <li>Only basic (no nested) template blocks supported.</li>
         2668 </ul>
         2669 <h2>Clone</h2>
         2670 <pre><code>git clone git://git.codemadness.org/saait
         2671 </code></pre>
         2672 <h2>Browse</h2>
         2673 <p>You can browse the source-code at:</p>
         2674 <ul>
         2675 <li><a href="https://git.codemadness.org/saait/">https://git.codemadness.org/saait/</a></li>
         2676 <li><a href="gopher://codemadness.org/1/git/saait">gopher://codemadness.org/1/git/saait</a></li>
         2677 </ul>
         2678 <h2>Download releases</h2>
         2679 <p>Releases are available at:</p>
         2680 <ul>
         2681 <li><a href="https://codemadness.org/releases/saait/">https://codemadness.org/releases/saait/</a></li>
         2682 <li><a href="gopher://codemadness.org/1/releases/saait">gopher://codemadness.org/1/releases/saait</a></li>
         2683 </ul>
         2684 <h2>Documentation / man page</h2>
         2685 <p>Below is the saait(1) man page, which includes usage examples.</p>
         2686 <pre><code>
         2687 SAAIT(1)                    General Commands Manual                      SAAIT(1)
         2688 
         2689 NAME
         2690      saait  the most boring static page generator
         2691 
         2692 SYNOPSIS
         2693      saait [-c configfile] [-o outputdir] [-t templatesdir] pages...
         2694 
         2695 DESCRIPTION
         2696      saait writes HTML pages to the output directory.
         2697 
         2698      The arguments pages are page config files, which are processed in the
         2699      given order.
         2700 
         2701      The options are as follows:
         2702 
         2703      -c configfile
         2704              The global configuration file, the default is "config.cfg". Each
         2705              page configuration file inherits variables from this file. These
         2706              variables can be overwritten per page.
         2707 
         2708      -o outputdir
         2709              The output directory, the default is "output".
         2710 
         2711      -t templatesdir
         2712              The templates directory, the default is "templates".
         2713 
         2714 DIRECTORY AND FILE STRUCTURE
         2715      A recommended directory structure for pages, although the names can be
         2716      anything:
         2717      pages/001-page.cfg
         2718      pages/001-page.html
         2719      pages/002-page.cfg
         2720      pages/002-page.html
         2721 
         2722      The directory and file structure for templates must be:
         2723      templates/&lt;templatename&gt;/header.ext
         2724      templates/&lt;templatename&gt;/item.ext
         2725      templates/&lt;templatename&gt;/footer.ext
         2726 
         2727      The following filename prefixes are detected for template blocks and
         2728      processed in this order:
         2729 
         2730      "header."
         2731              Header block.
         2732 
         2733      "item."
         2734              Item block.
         2735 
         2736      "footer."
         2737              Footer block.
         2738 
         2739      The files are saved as output/&lt;templatename&gt;, for example
         2740      templates/atom.xml/* will become: output/atom.xml. If a template block
         2741      file does not exist then it is treated as if it was empty.
         2742 
         2743      Template directories starting with a dot (".") are ignored.
         2744 
         2745      The "page" templatename is special and will be used per page.
         2746 
         2747 CONFIG FILE
         2748      A config file has a simple key=value configuration syntax, for example:
         2749 
         2750      # this is a comment line.
         2751      filename = example.html
         2752      title = Example page
         2753      description = This is an example page
         2754      created = 2009-04-12
         2755      updated = 2009-04-14
         2756 
         2757      The following variable names are special with their respective defaults:
         2758 
         2759      contentfile
         2760              Path to the input content filename, by default this is the path
         2761              of the config file with the last extension replaced to ".html".
         2762 
         2763      filename
         2764              The filename or relative file path for the output file for this
         2765              page.  By default the value is the basename of the contentfile.
         2766              The path of the written output file is the value of filename
         2767              appended to the outputdir path.
         2768 
         2769      A line starting with # is a comment and is ignored.
         2770 
         2771      TABs and spaces before and after a variable name are ignored.  TABs and
         2772      spaces before a value are ignored.
         2773 
         2774 TEMPLATES
         2775      A template (block) is text.  Variables are replaced with the values set
         2776      in the config files.
         2777 
         2778      The possible operators for variables are:
         2779 
         2780      $             Escapes a XML string, for example: &lt; to the entity &amp;lt;.
         2781 
         2782      #             Literal raw string value.
         2783 
         2784      %             Insert contents of file of the value of the variable.
         2785 
         2786      For example in a HTML item template:
         2787 
         2788      &lt;article&gt;
         2789              &lt;header&gt;
         2790                      &lt;h1&gt;&lt;a href=""&gt;${title}&lt;/a&gt;&lt;/h1&gt;
         2791                      &lt;p&gt;
         2792                              &lt;strong&gt;Last modification on &lt;/strong&gt;
         2793                              &lt;time datetime="${updated}"&gt;${updated}&lt;/time&gt;
         2794                      &lt;/p&gt;
         2795              &lt;/header&gt;
         2796              %{contentfile}
         2797      &lt;/article&gt;
         2798 
         2799 EXIT STATUS
         2800      The saait utility exits 0 on success, and &gt;0 if an error occurs.
         2801 
         2802 EXAMPLES
         2803      A basic usage example:
         2804 
         2805      1.   Create a directory for a new site:
         2806 
         2807           mkdir newsite
         2808 
         2809      2.   Copy the example pages, templates, global config file and example
         2810           stylesheets to a directory:
         2811 
         2812           cp -r pages templates config.cfg style.css print.css newsite/
         2813 
         2814      3.   Change the current directory to the created directory.
         2815 
         2816           cd newsite/
         2817 
         2818      4.   Change the values in the global config.cfg file.
         2819 
         2820      5.   If you want to modify parts of the header, like the navigation menu
         2821           items, you can change the following two template files:
         2822           templates/page/header.html
         2823           templates/index.html/header.html
         2824 
         2825      6.   Create any new pages in the pages directory. For each config file
         2826           there has to be a corresponding HTML file.  By default this HTML
         2827           file has the path of the config file, but with the last extension
         2828           (".cfg" in this case) replaced to ".html".
         2829 
         2830      7.   Create an output directory:
         2831 
         2832           mkdir -p output
         2833 
         2834      8.   After any modifications the following commands can be used to
         2835           generate the output and process the pages in descending order:
         2836 
         2837           find pages -type f -name '*.cfg' -print0 | sort -zr | xargs -0 saait
         2838 
         2839      9.   Copy the modified stylesheets to the output directory also:
         2840 
         2841           cp style.css print.css output/
         2842 
         2843      10.  Open output/index.html locally in your webbrowser to review the
         2844           changes.
         2845 
         2846      11.  To synchronize files, you can securely transfer them via SSH using
         2847           rsync:
         2848 
         2849           rsync -av output/ user@somehost:/var/www/htdocs/
         2850 
         2851 TRIVIA
         2852      The most boring static page generator.
         2853 
         2854      Meaning of saai (dutch): boring, pronunciation of saait: site
         2855 
         2856 SEE ALSO
         2857      find(1), sort(1), xargs(1)
         2858 
         2859 AUTHORS
         2860      Hiltjo Posthuma &lt;hiltjo@codemadness.org&gt;
         2861 </code></pre>
         2862 ]]></content>
         2863 </entry>
         2864 <entry>
         2865         <title>Stagit: a static git page generator</title>
         2866         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/stagit" />
         2867         <id>gopher://codemadness.org/1/phlog/stagit</id>
         2868         <updated>2021-04-11T00:00:00Z</updated>
         2869         <published>2017-05-10T00:00:00Z</published>
         2870         <author>
         2871                 <name>Hiltjo</name>
         2872                 <uri>gopher://codemadness.org</uri>
         2873         </author>
         2874         <summary>a static git page generator</summary>
         2875         <content type="html"><![CDATA[<h1>Stagit: a static git page generator</h1>
         2876         <p><strong>Last modification on </strong> <time>2021-04-11</time></p>
         2877         <p>stagit is a static page generator for git.</p>
         2878 <p><a href="/git/stagit/file/README.html">Read the README for more information about it.</a></p>
         2879 <p>My git repository uses stagit, you can see how it looks here:
         2880 <a href="https://codemadness.org/git/">https://codemadness.org/git/</a></p>
         2881 <h2>Features</h2>
         2882 <ul>
         2883 <li>Log of all commits from HEAD.</li>
         2884 <li>Log and diffstat per commit.</li>
         2885 <li>Show file tree with linkable line numbers.</li>
         2886 <li>Show references: local branches and tags.</li>
         2887 <li>Detect README and LICENSE file from HEAD and link it as a webpage.</li>
         2888 <li>Detect submodules (.gitmodules file) from HEAD and link it as a webpage.</li>
         2889 <li>Atom feed of the commit log (atom.xml).</li>
         2890 <li>Atom feed of the tags/refs (tags.xml).</li>
         2891 <li>Make index page for multiple repositories with stagit-index.</li>
         2892 <li>After generating the pages (relatively slow) serving the files is very fast,
         2893 simple and requires little resources (because the content is static), only
         2894 a HTTP file server is required.</li>
         2895 <li>Security: all pages are static. No CGI or dynamic code is run for the
         2896 interface. Using it with a secure httpd such as OpenBSD httpd it is
         2897 privilege-separated, chroot(2)'d and pledge(2)'d.</li>
         2898 <li>Simple to setup: the content generation is clearly separated from serving
         2899 it. This makes configuration as simple as copying a few directories and
         2900 scripts.</li>
         2901 <li>Usable with text-browsers such as dillo, links, lynx and w3m.</li>
         2902 </ul>
         2903 <h2>Cons</h2>
         2904 <ul>
         2905 <li>Not suitable for large repositories (2000+ commits), because diffstats are
         2906 an expensive operation, the cache (-c flag) or (-l maxlimit) is a workaround
         2907 for this in some cases.</li>
         2908 <li>Not suitable for large repositories with many files, because all files are
         2909 written for each execution of stagit. This is because stagit shows the lines
         2910 of textfiles and there is no "cache" for file metadata (this would add more
         2911 complexity to the code).</li>
         2912 <li>Not suitable for repositories with many branches, a quite linear history is
         2913 assumed (from HEAD).</li>
         2914 </ul>
         2915 <p>In these cases it is better to use <a href="https://git.zx2c4.com/cgit/">cgit</a> or
         2916 possibly change stagit to run as a CGI program.</p>
         2917 <ul>
         2918 <li>Relatively slow to run the first time (about 3 seconds for sbase,
         2919 1500+ commits), incremental updates are faster.</li>
         2920 <li>Does not support some of the dynamic features cgit has, like:
         2921 <ul>
         2922 <li>Snapshot tarballs per commit.</li>
         2923 <li>File tree per commit.</li>
         2924 <li>History log of branches diverged from HEAD.</li>
         2925 <li>Stats (git shortlog -s).</li>
         2926 </ul>
         2927 </li>
         2928 </ul>
         2929 <p>This is by design, just use git locally.</p>
         2930 <h2>Clone</h2>
         2931 <pre><code>git clone git://git.codemadness.org/stagit
         2932 </code></pre>
         2933 <h2>Browse</h2>
         2934 <p>You can browse the source-code at:</p>
         2935 <ul>
         2936 <li><a href="https://git.codemadness.org/stagit/">https://git.codemadness.org/stagit/</a></li>
         2937 <li><a href="gopher://codemadness.org/1/git/stagit">gopher://codemadness.org/1/git/stagit</a></li>
         2938 </ul>
         2939 <h2>Download releases</h2>
         2940 <p>Releases are available at:</p>
         2941 <ul>
         2942 <li><a href="https://codemadness.org/releases/stagit/">https://codemadness.org/releases/stagit/</a></li>
         2943 <li><a href="gopher://codemadness.org/1/releases/stagit">gopher://codemadness.org/1/releases/stagit</a></li>
         2944 </ul>
         2945 ]]></content>
         2946 </entry>
         2947 <entry>
         2948         <title>OpenBSD httpd, slowcgi and cgit</title>
         2949         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/openbsd-httpd-and-cgit" />
         2950         <id>gopher://codemadness.org/1/phlog/openbsd-httpd-and-cgit</id>
         2951         <updated>2021-04-11T00:00:00Z</updated>
         2952         <published>2015-07-05T00:00:00Z</published>
         2953         <author>
         2954                 <name>Hiltjo</name>
         2955                 <uri>gopher://codemadness.org</uri>
         2956         </author>
         2957         <summary>OpenBSD httpd, slowcgi and cgit</summary>
         2958         <content type="html"><![CDATA[<h1>OpenBSD httpd, slowcgi and cgit</h1>
         2959         <p><strong>Last modification on </strong> <time>2021-04-11</time></p>
         2960         <p>This is a guide to get <a href="https://git.zx2c4.com/cgit/">cgit</a> working with
         2961 <a href="https://man.openbsd.org/httpd.8">OpenBSD httpd(8)</a> and
         2962 <a href="https://man.openbsd.org/slowcgi.8">slowcgi(8)</a> in base.  OpenBSD httpd is very simple to setup, but nevertheless
         2963 this guide might help someone out there.</p>
         2964 <h2>Installation</h2>
         2965 <p>Install the cgit package:</p>
         2966 <pre><code># pkg_add cgit
         2967 </code></pre>
         2968 <p>or build it from ports:</p>
         2969 <pre><code># cd /usr/ports/www/cgit &amp;&amp; make &amp;&amp; make install
         2970 </code></pre>
         2971 <h2>Configuration</h2>
         2972 <h3>httpd</h3>
         2973 <p>An example of <a href="https://man.openbsd.org/httpd.conf.5">httpd.conf(5)</a>:
         2974 <a href="downloads/openbsd-httpd/httpd.conf">httpd.conf</a>.</p>
         2975 <h3>slowcgi</h3>
         2976 <p>By default the slowcgi UNIX domain socket is located at:
         2977 /var/www/run/slowcgi.sock.  For this example we use the defaults.</p>
         2978 <h3>cgit</h3>
         2979 <p>The cgit binary should be located at: /var/www/cgi-bin/cgit.cgi (default).</p>
         2980 <p>cgit uses the $CGIT_CONFIG environment variable to locate its config.  By
         2981 default on OpenBSD this is set to /conf/cgitrc (chroot), which is
         2982 /var/www/conf/cgitrc.  An example of the cgitrc file is here: <a href="downloads/openbsd-httpd/cgitrc">cgitrc</a>.</p>
         2983 <p>In this example the cgit cache directory is set to /cgit/cache (chroot), which
         2984 is /var/www/cgit/cache.  Make sure to give this path read and write permissions
         2985 for cgit (www:daemon).</p>
         2986 <p>In the example the repository path (scan-path) is set to /htdocs/src (chroot),
         2987 which is /var/www/htdocs/src.</p>
         2988 <p>The footer file is set to /conf/cgit.footer. Make sure this file exists or you
         2989 will get warnings:</p>
         2990 <pre><code># &gt;/var/www/conf/cgit.footer
         2991 </code></pre>
         2992 <p>Make sure cgit.css (stylesheet) and cgit.png (logo) are accessible, by default:
         2993 /var/www/cgit/cgit.{css,png} (location can be changed in httpd.conf).</p>
         2994 <p>To support .tar.gz snapshots a static gzip binary is required in the chroot
         2995 /bin directory:</p>
         2996 <pre><code>cd /usr/src/usr.bin/compress
         2997 make clean &amp;&amp; make LDFLAGS="-static -pie"
         2998 cp obj/compress /var/www/bin/gzip
         2999 </code></pre>
         3000 <h2>Running the services</h2>
         3001 <p>Enable the httpd and slowcgi services to automatically start them at boot:</p>
         3002 <pre><code># rcctl enable httpd slowcgi
         3003 </code></pre>
         3004 <p>Start the services:</p>
         3005 <pre><code># rcctl start httpd slowcgi
         3006 </code></pre>
         3007 ]]></content>
         3008 </entry>
         3009 <entry>
         3010         <title>twitch: application to watch Twitch streams</title>
         3011         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/twitch-interface" />
         3012         <id>gopher://codemadness.org/1/phlog/twitch-interface</id>
         3013         <updated>2020-12-14T00:00:00Z</updated>
         3014         <published>2014-11-23T00:00:00Z</published>
         3015         <author>
         3016                 <name>Hiltjo</name>
         3017                 <uri>gopher://codemadness.org</uri>
         3018         </author>
         3019         <summary>twitch: application to watch Twitch streams</summary>
         3020         <content type="html"><![CDATA[<h1>twitch: application to watch Twitch streams</h1>
         3021         <p><strong>Last modification on </strong> <time>2020-12-14</time></p>
         3022         <p><strong>Update: as of 2020-05-06:</strong> I stopped maintaining it.
         3023 Twitch now requires OAUTH and 2-factor authentication. It requires me to expose
         3024 personal information such as my phone number.</p>
         3025 <p><strong>Update: as of ~2020-01-03:</strong> I rewrote this application from Golang to C.
         3026 The Twitch Kraken API used by the Golang version was deprecated.  It was
         3027 rewritten to use the Helix API.</p>
         3028 <p>This program/script allows to view streams in your own video player like so the
         3029 bloated Twitch interface is not needed.  It is written in C.</p>
         3030 <h2>Features</h2>
         3031 <ul>
         3032 <li>No Javascript, cookies, CSS optional.</li>
         3033 <li>Works well in all browsers, including text-based ones.</li>
         3034 <li>Has a HTTP CGI and Gopher CGI version.</li>
         3035 <li>Atom feed for VODs.</li>
         3036 </ul>
         3037 <h2>Clone</h2>
         3038 <pre><code>git clone git://git.codemadness.org/frontends
         3039 </code></pre>
         3040 <h2>Browse</h2>
         3041 <p>You can browse the source-code at:</p>
         3042 <ul>
         3043 <li><a href="https://git.codemadness.org/frontends/">https://git.codemadness.org/frontends/</a></li>
         3044 <li><a href="gopher://codemadness.org/1/git/frontends">gopher://codemadness.org/1/git/frontends</a></li>
         3045 </ul>
         3046 ]]></content>
         3047 </entry>
         3048 <entry>
         3049         <title>Userscript: focus input field</title>
         3050         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/userscript-focus-input-field" />
         3051         <id>gopher://codemadness.org/1/phlog/userscript-focus-input-field</id>
         3052         <updated>2014-03-02T00:00:00Z</updated>
         3053         <published>2014-03-02T00:00:00Z</published>
         3054         <author>
         3055                 <name>Hiltjo</name>
         3056                 <uri>gopher://codemadness.org</uri>
         3057         </author>
         3058         <summary>Userscript to focus the first input field on a page with a hotkey</summary>
         3059         <content type="html"><![CDATA[<h1>Userscript: focus input field</h1>
         3060         <p><strong>Last modification on </strong> <time>2014-03-02</time></p>
         3061         <p>This is an userscript I wrote a while ago which allows to focus the first input
         3062 field on a page with ctrl+space.  This is useful if a site doesn't specify the
         3063 autofocus attribute for an input field and you don't want to switch to it using
         3064 the mouse.</p>
         3065 <h2>Download</h2>
         3066 <p><a href="downloads/input_focus.user.js">Download userscript input_focus.user.js</a></p>
         3067 ]]></content>
         3068 </entry>
         3069 <entry>
         3070         <title>Userscript: Youtube circumvent age verification</title>
         3071         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/userscript-youtube-circumvent-age-verification" />
         3072         <id>gopher://codemadness.org/1/phlog/userscript-youtube-circumvent-age-verification</id>
         3073         <updated>2020-12-27T00:00:00Z</updated>
         3074         <published>2013-02-21T00:00:00Z</published>
         3075         <author>
         3076                 <name>Hiltjo</name>
         3077                 <uri>gopher://codemadness.org</uri>
         3078         </author>
         3079         <summary>Userscript to circumvent Youtube age verification and redirect to the video</summary>
         3080         <content type="html"><![CDATA[<h1>Userscript: Youtube circumvent age verification</h1>
         3081         <p><strong>Last modification on </strong> <time>2020-12-27</time></p>
         3082         <p>This is an userscript I wrote a while ago which circumvents requiring to login
         3083 with an account on Youtube if a video requires age verification.</p>
         3084 <p><strong>Note: this is an old script and does not work anymore.</strong></p>
         3085 <h2>Download</h2>
         3086 <p><a href="downloads/youtube_circumvent_sign_in.user.js">Download userscript Youtube_circumvent_sign_in.user.js</a></p>
         3087 ]]></content>
         3088 </entry>
         3089 <entry>
         3090         <title>Userscript: block stupid fonts</title>
         3091         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/userscript-block-stupid-fonts" />
         3092         <id>gopher://codemadness.org/1/phlog/userscript-block-stupid-fonts</id>
         3093         <updated>2020-03-10T00:00:00Z</updated>
         3094         <published>2012-10-21T00:00:00Z</published>
         3095         <author>
         3096                 <name>Hiltjo</name>
         3097                 <uri>gopher://codemadness.org</uri>
         3098         </author>
         3099         <summary>Userscript to whitelist your favorite fonts and block the rest</summary>
         3100         <content type="html"><![CDATA[<h1>Userscript: block stupid fonts</h1>
         3101         <p><strong>Last modification on </strong> <time>2020-03-10</time></p>
         3102         <p>This is an userscript I wrote a while ago which white-lists fonts I like and
         3103 blocks the rest.  The reason I made this is because I don't like the
         3104 inconsistency of custom fonts used on a lot of websites.</p>
         3105 <h2>Download</h2>
         3106 <p><a href="downloads/block_stupid_fonts_v1.2.user.js">Download userscript Block_stupid_fonts_v1.2.user.js</a></p>
         3107 <p>Old version: <a href="downloads/block_stupid_fonts.user.js">Download userscript Block_stupid_fonts.user.js</a></p>
         3108 ]]></content>
         3109 </entry>
         3110 <entry>
         3111         <title>Sfeed: simple RSS and Atom parser</title>
         3112         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/sfeed" />
         3113         <id>gopher://codemadness.org/1/phlog/sfeed</id>
         3114         <updated>2022-11-05T00:00:00Z</updated>
         3115         <published>2011-04-01T00:00:00Z</published>
         3116         <author>
         3117                 <name>Hiltjo</name>
         3118                 <uri>gopher://codemadness.org</uri>
         3119         </author>
         3120         <summary>Sfeed is a simple RSS and Atom parser (and format programs to add reader functionality)</summary>
         3121         <content type="html"><![CDATA[<h1>Sfeed: simple RSS and Atom parser</h1>
         3122         <p><strong>Last modification on </strong> <time>2022-11-05</time></p>
         3123         <p>Sfeed is a RSS and Atom parser (and some format programs).</p>
         3124 <p>It converts RSS or Atom feeds from XML to a TAB-separated file. There are
         3125 formatting programs included to convert this TAB-separated format to various
         3126 other formats. There are also some programs and scripts included to import and
         3127 export OPML and to fetch, filter, merge and order feed items.</p>
         3128 <p>For the most (up-to-date) information see the <a href="/git/sfeed/file/README.html">README</a>.</p>
         3129 <h2>Clone</h2>
         3130 <pre><code>git clone git://git.codemadness.org/sfeed
         3131 </code></pre>
         3132 <h2>Browse</h2>
         3133 <p>You can browse the source-code at:</p>
         3134 <ul>
         3135 <li><a href="https://git.codemadness.org/sfeed/">https://git.codemadness.org/sfeed/</a></li>
         3136 <li><a href="gopher://codemadness.org/1/git/sfeed">gopher://codemadness.org/1/git/sfeed</a></li>
         3137 </ul>
         3138 <h2>Download releases</h2>
         3139 <p>Releases are available at:</p>
         3140 <ul>
         3141 <li><a href="https://codemadness.org/releases/sfeed/">https://codemadness.org/releases/sfeed/</a></li>
         3142 <li><a href="gopher://codemadness.org/1/releases/sfeed">gopher://codemadness.org/1/releases/sfeed</a></li>
         3143 </ul>
         3144 <h2>Build and install</h2>
         3145 <pre><code>$ make
         3146 # make install
         3147 </code></pre>
         3148 <h2>Screenshot and examples</h2>
         3149 <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>
         3150 <p>The above screenshot uses the sfeed_plain format program with <a href="https://tools.suckless.org/dmenu/">dmenu</a>.  This
         3151 program outputs the feed items in a compact way per line as plain-text to
         3152 stdout.  The dmenu program reads these lines from stdin and displays them as a
         3153 X11 list menu. When an item is selected in dmenu it prints this item to stdout.
         3154 A simple written script can then filter for the URL in this output and do some
         3155 action, like opening it in some browser or open a podcast in your music player.</p>
         3156 <p>For example:</p>
         3157 <pre><code>#!/bin/sh
         3158 url=$(sfeed_plain "$HOME/.sfeed/feeds/"* | dmenu -l 35 -i | \
         3159         sed -n 's@^.* \([a-zA-Z]*://\)\(.*\)$@\1\2@p')
         3160 test -n "${url}" &amp;&amp; $BROWSER "${url}"
         3161 </code></pre>
         3162 <p>However this is just one way to format and interact with feed items.
         3163 See also the README for other practical examples.</p>
         3164 <p>Below are some examples of output that are supported by the included format
         3165 programs:</p>
         3166 <ul>
         3167 <li><a href="downloads/sfeed/plain/feeds.txt">plain text (UTF-8)</a></li>
         3168 <li><a href="downloads/sfeed/atom/feeds.xml">atom</a></li>
         3169 <li>gopher</li>
         3170 <li><a href="downloads/sfeed/html/feeds.html">HTML (CSS)</a></li>
         3171 <li><a href="downloads/sfeed/frames/index.html">HTML frames</a></li>
         3172 <li><a href="jsonfeed_content.json">JSON Feed</a></li>
         3173 <li><a href="downloads/sfeed/mbox/feeds.mbox">mbox</a></li>
         3174 <li><a href="downloads/sfeed/twtxt/twtxt.txt">twtxt</a></li>
         3175 </ul>
         3176 <p>There is also a curses UI front-end, see the page <a href="sfeed_curses.html">sfeed_curses</a>.
         3177 It is now part of sfeed.</p>
         3178 <h2>Videos</h2>
         3179 <p>Here are some videos of other people showcasing some of the functionalities of
         3180 sfeed, sfeed_plain and sfeed_curses.  To the creators: thanks for making these!</p>
         3181 <ul>
         3182 <li><a href="https://www.youtube.com/watch?v=RnuY32DP9jU">sfeed: RSS/Atom Feeds without the Suck (Youtube)</a><br />  
         3183 by <a href="https://www.youtube.com/channel/UCQQB104oMOos758GTOdx_kQ">noocsharp</a>
         3184 <a href="downloads/sfeed/videos/sfeed_without_the_suck.mp4">(mirror)</a><br />  
         3185 Video published on March 8 2020.</li>
         3186 <li><a href="https://www.youtube.com/watch?v=ok8k639GoRU">Sfeed - news in the terminal with minimalism (Youtube)</a><br />  
         3187 by <a href="https://www.youtube.com/channel/UCJetJ7nDNLlEzDLXv7KIo0w">Gavin Freeborn</a>
         3188 <a href="downloads/sfeed/videos/sfeed_news_in_terminal.mp4">(mirror)</a><br />  
         3189 Video published on January 15 2021.</li>
         3190 <li><a href="https://www.youtube.com/watch?v=xMkW4iJzot0">Sfeed - Peak Minimal RSS Feed Reader (Youtube)</a><br />  
         3191 by <a href="https://www.youtube.com/channel/UCld68syR8Wi-GY_n4CaoJGA">Brodie Robertson</a>
         3192 <a href="downloads/sfeed/videos/sfeed_minimalism.mp4">(mirror)</a><br />  
         3193 Video published on February 23 2021.</li>
         3194 <li><a href="https://www.youtube.com/watch?v=O8x0MAyqvt0">RSS with sfeed, fdm, and mblaze! (Youtube)</a><br />  
         3195 by <a href="https://www.youtube.com/channel/UCz_u0h4usMbnFsIHSVdjUQw">Joseph Choe</a>
         3196 <a href="downloads/sfeed/videos/rss_with_sfeed_fdm_and_mblaze.mp4">(mirror)</a><br />  
         3197 Website: <a href="https://josephchoe.com/rss-terminal">https://josephchoe.com/rss-terminal</a><br />  
         3198 Video published on 4 November 2022.</li>
         3199 </ul>
         3200 ]]></content>
         3201 </entry>
         3202 <entry>
         3203         <title>Vim theme: relaxed</title>
         3204         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/vim-theme-relaxed" />
         3205         <id>gopher://codemadness.org/1/phlog/vim-theme-relaxed</id>
         3206         <updated>2011-01-07T00:00:00Z</updated>
         3207         <published>2011-01-07T00:00:00Z</published>
         3208         <author>
         3209                 <name>Hiltjo</name>
         3210                 <uri>gopher://codemadness.org</uri>
         3211         </author>
         3212         <summary>a dark VIM theme I made and use on a daily basis</summary>
         3213         <content type="html"><![CDATA[<h1>Vim theme: relaxed</h1>
         3214         <p><strong>Last modification on </strong> <time>2011-01-07</time></p>
         3215         <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
         3216 quite a while now and over time tweaked to my liking.  It is made for gvim, but
         3217 also works for 16-colour terminals (with small visual differences).  The
         3218 relaxed.vim file also has my .Xdefaults file colours listed at the top for
         3219 16+-colour terminals on X11.</p>
         3220 <p>It is inspired by the "desert" theme available at
         3221 <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
         3222 cursive and bold styles and changed some colours I didn't like.</p>
         3223 <h2>Download</h2>
         3224 <p><a href="downloads/themes/vim/relaxed.vim">relaxed.vim</a></p>
         3225 <h2>Screenshot</h2>
         3226 <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>
         3227 ]]></content>
         3228 </entry>
         3229 <entry>
         3230         <title>Seturgent: set urgency hints for X applications</title>
         3231         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/seturgent" />
         3232         <id>gopher://codemadness.org/1/phlog/seturgent</id>
         3233         <updated>2020-07-20T00:00:00Z</updated>
         3234         <published>2010-10-31T00:00:00Z</published>
         3235         <author>
         3236                 <name>Hiltjo</name>
         3237                 <uri>gopher://codemadness.org</uri>
         3238         </author>
         3239         <summary>Seturgent is a small utility to set an application it&#39;s urgency hint</summary>
         3240         <content type="html"><![CDATA[<h1>Seturgent: set urgency hints for X applications</h1>
         3241         <p><strong>Last modification on </strong> <time>2020-07-20</time></p>
         3242         <p>Seturgent is a small utility to set an application its urgency hint.  For most
         3243 windowmanager's and panel applications this will highlight the application and
         3244 will allow special actions.</p>
         3245 <h2>Clone</h2>
         3246 <pre><code>    git clone git://git.codemadness.org/seturgent
         3247 </code></pre>
         3248 <h2>Browse</h2>
         3249 <p>You can browse the source-code at:</p>
         3250 <ul>
         3251 <li><a href="https://git.codemadness.org/seturgent/">https://git.codemadness.org/seturgent/</a></li>
         3252 <li><a href="gopher://codemadness.org/1/git/seturgent">gopher://codemadness.org/1/git/seturgent</a></li>
         3253 </ul>
         3254 <h2>Download releases</h2>
         3255 <p>Releases are available at:</p>
         3256 <ul>
         3257 <li><a href="https://codemadness.org/releases/seturgent/">https://codemadness.org/releases/seturgent/</a></li>
         3258 <li><a href="gopher://codemadness.org/1/releases/seturgent">gopher://codemadness.org/1/releases/seturgent</a></li>
         3259 </ul>
         3260 ]]></content>
         3261 </entry>
         3262 <entry>
         3263         <title>DWM-hiltjo: my windowmanager configuration</title>
         3264         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/dwm" />
         3265         <id>gopher://codemadness.org/1/phlog/dwm</id>
         3266         <updated>2020-07-20T00:00:00Z</updated>
         3267         <published>2010-08-12T00:00:00Z</published>
         3268         <author>
         3269                 <name>Hiltjo</name>
         3270                 <uri>gopher://codemadness.org</uri>
         3271         </author>
         3272         <summary>My DWM configuration; a few added features to suit my needs</summary>
         3273         <content type="html"><![CDATA[<h1>DWM-hiltjo: my windowmanager configuration</h1>
         3274         <p><strong>Last modification on </strong> <time>2020-07-20</time></p>
         3275         <p><a href="https://dwm.suckless.org/">DWM</a> is a very minimal windowmanager. It has the most essential features I
         3276 need, everything else is "do-it-yourself" or extending it with the many
         3277 available <a href="https://dwm.suckless.org/patches/">patches</a>. The vanilla version is less than 2000 SLOC. This makes it
         3278 easy to understand and modify it.</p>
         3279 <p>I really like my configuration at the moment and want to share my changes. Some
         3280 of the features listed below are patches from suckless.org I applied, but there
         3281 are also some changes I made.</p>
         3282 <p>This configuration is entirely tailored for my preferences of course.</p>
         3283 <h2>Features</h2>
         3284 <ul>
         3285 <li>Titlebar:
         3286 <ul>
         3287 <li>Shows all clients of the selected / active tags.</li>
         3288 <li>Divide application titlebars evenly among available space.</li>
         3289 <li>Colour urgent clients in the taskbar on active tags.</li>
         3290 <li>Left-click focuses clicked client.</li>
         3291 <li>Right-click toggles monocle layout.</li>
         3292 <li>Middle-click kills the clicked client.</li>
         3293 </ul>
         3294 </li>
         3295 <li>Tagbar:
         3296 <ul>
         3297 <li>Only show active tags.</li>
         3298 <li>Colour inactive tags with urgent clients.</li>
         3299 </ul>
         3300 </li>
         3301 <li>Layouts:
         3302 <ul>
         3303 <li>Cycle layouts with Modkey + Space (next) and Modkey + Control + Space
         3304 (previous).</li>
         3305 <li>Fullscreen layout (hides topbar and removes borders).</li>
         3306 </ul>
         3307 </li>
         3308 <li>Other:
         3309 <ul>
         3310 <li>Move tiled clients around with the mouse (drag-move), awesomewm-like.</li>
         3311 <li>Add some keybinds for multimedia keyboards (audio play / pause, mute, www,
         3312 volume buttons, etc).</li>
         3313 </ul>
         3314 </li>
         3315 <li>... and more ;) ...</li>
         3316 </ul>
         3317 <h2>Clone</h2>
         3318 <pre><code>git clone -b hiltjo git://git.codemadness.org/dwm
         3319 </code></pre>
         3320 <h2>Screenshot</h2>
         3321 <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>
         3322 ]]></content>
         3323 </entry>
         3324 <entry>
         3325         <title>Query unused CSS rules on current document state</title>
         3326         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/query-unused-css-rules-on-current-document-state" />
         3327         <id>gopher://codemadness.org/1/phlog/query-unused-css-rules-on-current-document-state</id>
         3328         <updated>2010-04-21T00:00:00Z</updated>
         3329         <published>2010-04-21T00:00:00Z</published>
         3330         <author>
         3331                 <name>Hiltjo</name>
         3332                 <uri>gopher://codemadness.org</uri>
         3333         </author>
         3334         <summary>How to see all the rules in a stylesheet (CSS) that are not used for the current document</summary>
         3335         <content type="html"><![CDATA[<h1>Query unused CSS rules on current document state</h1>
         3336         <p><strong>Last modification on </strong> <time>2010-04-21</time></p>
         3337         <p>Today I was doing some web development and wanted to see all the rules in a
         3338 stylesheet (CSS) that were not used for the current document. I wrote the
         3339 following Javascript code which you can paste in the Firebug console and run:</p>
         3340 <pre><code>(function() {
         3341         for (var i=0;i&lt;document.styleSheets.length;i++) {
         3342                 var rules = document.styleSheets[i].cssRules || [];
         3343                 var sheethref = document.styleSheets[i].href || 'inline';
         3344                 for (var r=0;r&lt;rules.length;r++)
         3345                         if (!document.querySelectorAll(rules[r].selectorText).length)
         3346                                 console.log(sheethref + ': "' + rules[r].selectorText + '" not found.');
         3347         }
         3348 })();
         3349 </code></pre>
         3350 <p>This will output all the (currently) unused CSS rules per selector, the output can be for example:</p>
         3351 <pre><code>http://www.codemadness.nl/blog/wp-content/themes/codemadness/style.css: "fieldset, a img" not found.
         3352 http://www.codemadness.nl/blog/wp-content/themes/codemadness/style.css: "#headerimg" not found.
         3353 http://www.codemadness.nl/blog/wp-content/themes/codemadness/style.css: "a:hover" not found.
         3354 http://www.codemadness.nl/blog/wp-content/themes/codemadness/style.css: "h2 a:hover, h3 a:hover" not found.
         3355 http://www.codemadness.nl/blog/wp-content/themes/codemadness/style.css: ".postmetadata-center" not found.
         3356 http://www.codemadness.nl/blog/wp-content/themes/codemadness/style.css: ".thread-alt" not found.
         3357 </code></pre>
         3358 <p>Just a trick I wanted to share, I hope someone finds this useful :)</p>
         3359 <p>For webkit-based browsers you can use "Developer Tools" and use "Audits" under
         3360 "Web Page Performance" it says "Remove unused CSS rules". For Firefox there is
         3361 also Google Page Speed: <a href="https://code.google.com/speed/page-speed/">https://code.google.com/speed/page-speed/</a> this adds
         3362 an extra section under Firebug.</p>
         3363 <p>Tested on Chrome and Firefox.</p>
         3364 ]]></content>
         3365 </entry>
         3366 <entry>
         3367         <title>Driconf: enabling S3 texture compression on Linux</title>
         3368         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/driconf" />
         3369         <id>gopher://codemadness.org/1/phlog/driconf</id>
         3370         <updated>2020-08-21T00:00:00Z</updated>
         3371         <published>2009-07-05T00:00:00Z</published>
         3372         <author>
         3373                 <name>Hiltjo</name>
         3374                 <uri>gopher://codemadness.org</uri>
         3375         </author>
         3376         <summary>driconf: enabling S3 texture compression</summary>
         3377         <content type="html"><![CDATA[<h1>Driconf: enabling S3 texture compression on Linux</h1>
         3378         <p><strong>Last modification on </strong> <time>2020-08-21</time></p>
         3379         <p><strong>Update: the DXTC patent expired on 2018-03-16, many distros enable this by
         3380 default now.</strong></p>
         3381 <p>S3TC (also known as DXTn or DXTC) is a patented lossy texture compression
         3382 algorithm.  See: <a href="https://en.wikipedia.org/wiki/S3TC">https://en.wikipedia.org/wiki/S3TC</a> for more detailed
         3383 information.  Many games use S3TC and if you use Wine to play games you
         3384 definitely want to enable it if your graphics card supports it.</p>
         3385 <p>Because this algorithm was <a href="https://dri.freedesktop.org/wiki/S3TC/">patented it is disabled by default on many Linux
         3386 distributions</a>.</p>
         3387 <p>To enable it you can install the library "libtxc" if your favorite OS has not
         3388 installed it already.</p>
         3389 <p>For easy configuration you can install the optional utility DRIconf, which you
         3390 can find at: <a href="https://dri.freedesktop.org/wiki/DriConf">https://dri.freedesktop.org/wiki/DriConf</a>.  DriConf can safely be
         3391 removed after configuration.</p>
         3392 <h2>Steps to enable it</h2>
         3393 <p>Install libtxc_dxtn:</p>
         3394 <p>ArchLinux:
         3395 <pre><code># pacman -S libtxc_dxtn
         3396 </code></pre>
         3397 <p>Debian:
         3398 <pre><code># aptitude install libtxc-dxtn-s2tc0
         3399 </code></pre>
         3400 </p>
         3401 </p>
         3402 <p>Install driconf (optional):</p>
         3403 <p>ArchLinux:</p>
         3404 <pre><code># pacman -S driconf
         3405 </code></pre>
         3406 <p>Debian:</p>
         3407 <pre><code># aptitude install driconf
         3408 </code></pre>
         3409 <p>Run driconf and enable S3TC:</p>
         3410 <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>
         3411 <h2>Additional links</h2>
         3412 <ul>
         3413 <li>S3TC: <a href="https://dri.freedesktop.org/wiki/S3TC/">https://dri.freedesktop.org/wiki/S3TC/</a></li>
         3414 <li>DriConf: <a href="https://dri.freedesktop.org/wiki/DriConf">https://dri.freedesktop.org/wiki/DriConf</a></li>
         3415 </ul>
         3416 ]]></content>
         3417 </entry>
         3418 <entry>
         3419         <title>Getting the USB-powerline bridge to work on Linux</title>
         3420         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/getting-the-usb-powerline-bridge-to-work-on-linux" />
         3421         <id>gopher://codemadness.org/1/phlog/getting-the-usb-powerline-bridge-to-work-on-linux</id>
         3422         <updated>2019-12-06T00:00:00Z</updated>
         3423         <published>2009-04-13T00:00:00Z</published>
         3424         <author>
         3425                 <name>Hiltjo</name>
         3426                 <uri>gopher://codemadness.org</uri>
         3427         </author>
         3428         <summary>A guide to get a USB-powerline bridge with the Intellon 51x1 chipset working on Linux</summary>
         3429         <content type="html"><![CDATA[<h1>Getting the USB-powerline bridge to work on Linux</h1>
         3430         <p><strong>Last modification on </strong> <time>2019-12-06</time></p>
         3431         <p><strong>NOTE: this guide is obsolete, a working driver is now included in the Linux
         3432 kernel tree (<a href="https://lkml.org/lkml/2009/4/18/121">since Linux 2.6.31</a>)</strong></p>
         3433 <h2>Introduction</h2>
         3434 <p>A USB to powerline bridge is a network device that instead of using an ordinary
         3435 Ethernet cable (CAT5 for example) or wireless LAN it uses the powerlines as a
         3436 network to communicate with similar devices.  A more comprehensive explanation
         3437 of what it is and how it works you can find here:
         3438 <a href="https://en.wikipedia.org/wiki/IEEE_1901">https://en.wikipedia.org/wiki/IEEE_1901</a>.</p>
         3439 <p>Known products that use the Intellon 51x1 chipset:</p>
         3440 <ul>
         3441 <li>MicroLink dLAN USB</li>
         3442 <li>"Digitus network"</li>
         3443 <li>Intellon USB Ethernet powerline adapter</li>
         3444 <li>Lots of other USB-powerline adapters...</li>
         3445 </ul>
         3446 <p>To check if your device is supported:</p>
         3447 <pre><code>$ lsusb | grep -i 09e1
         3448 Bus 001 Device 003: ID 09e1:5121 Intellon Corp.
         3449 </code></pre>
         3450 <p>If the vendor (09e1) and product (5121) ID match then it's probably supported.</p>
         3451 <h2>Installation</h2>
         3452 <p>Get drivers from the official site:
         3453 <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
         3454 <a href="downloads/int51x1/dLAN-linux-package-v4.tar.gz">mirrored here</a>.
         3455 The drivers from the official site were/are more up-to-date.</p>
         3456 <p>Extract them:</p>
         3457 <pre><code>$ tar -xzvf dLAN-linux-package-v4.tar.gz
         3458 </code></pre>
         3459 <p>Go to the extracted directory and compile them:</p>
         3460 <pre><code>$ ./configure
         3461 $ make
         3462 </code></pre>
         3463 <p>Depending on the errors you got you might need to <a href="downloads/int51x1/int51x1.patch">download</a> and apply
         3464 my patch:</p>
         3465 <pre><code>$ cd dLAN-linux-package-v4/     (or other path to the source code)
         3466 $ patch &lt; int51x1.patch
         3467 </code></pre>
         3468 <p>Try again:</p>
         3469 <pre><code>$ ./configure
         3470 $ make
         3471 </code></pre>
         3472 <p>If that failed try:</p>
         3473 <pre><code>$ ./configure
         3474 $ KBUILD_NOPEDANTIC=1 make
         3475 </code></pre>
         3476 <p>If that went OK install the drivers (as root):</p>
         3477 <pre><code># make install
         3478 </code></pre>
         3479 <p>Check if the "devolo_usb" module is loaded:</p>
         3480 <pre><code>$ lsmod | grep -i devolo_usb
         3481 </code></pre>
         3482 <p>If it shows up then it's loaded. Now check if the interface is added:</p>
         3483 <pre><code>$ ifconfig -a | grep -i dlanusb
         3484 dlanusb0 Link encap:Ethernet HWaddr 00:12:34:56:78:9A
         3485 </code></pre>
         3486 <h2>Configuration</h2>
         3487 <p>It is assumed you use a static IP, otherwise you can just use your DHCP client
         3488 to get an unused IP address from your DHCP server. Setting up the interface is
         3489 done like this (change the IP address and netmask accordingly if it's
         3490 different):</p>
         3491 <pre><code># ifconfig dlanusb0 192.168.2.12 netmask 255.255.255.0
         3492 </code></pre>
         3493 <h2>Checking if the network works</h2>
         3494 <p>Try to ping an IP address on your network to test for a working connection:</p>
         3495 <pre><code>$ ping 192.168.2.1
         3496 PING 192.168.2.1 (192.168.2.1) 56(84) bytes of data.
         3497 64 bytes from 192.168.2.1: icmp_seq=1 ttl=30 time=2.49 ms
         3498 64 bytes from 192.168.2.1: icmp_seq=2 ttl=30 time=3.37 ms
         3499 64 bytes from 192.168.2.1: icmp_seq=3 ttl=30 time=2.80 ms
         3500 --- 192.168.2.1 ping statistics ---
         3501 3 packets transmitted, 3 received, 0% packet loss, time 2005ms
         3502 rtt min/avg/max/mdev = 2.497/2.891/3.374/0.368 ms
         3503 </code></pre>
         3504 <p>You can now set up a network connection like you normally do with any Ethernet
         3505 device.  The route can be added like this for example:</p>
         3506 <pre><code># route add -net 0.0.0.0 netmask 0.0.0.0 gw 192.168.2.1 dlanusb0
         3507 </code></pre>
         3508 <p>Change the IP address of your local gateway accordingly. Also make sure your
         3509 nameserver is set in /etc/resolv.conf, something like:</p>
         3510 <pre><code>nameserver 192.168.2.1
         3511 </code></pre>
         3512 <p>Test your internet connection by doing for example:</p>
         3513 <pre><code>$ ping codemadness.org
         3514 PING codemadness.org (64.13.232.151) 56(84) bytes of data.
         3515 64 bytes from acmkoieeei.gs02.gridserver.com (64.13.232.151): icmp_seq=1 ttl=52 time=156 ms
         3516 64 bytes from acmkoieeei.gs02.gridserver.com (64.13.232.151): icmp_seq=2 ttl=52 time=156 ms
         3517 64 bytes from acmkoieeei.gs02.gridserver.com (64.13.232.151): icmp_seq=3 ttl=52 time=155 ms
         3518 --- codemadness.org ping statistics ---
         3519 3 packets transmitted, 3 received, 0% packet loss, time 1999ms
         3520 rtt min/avg/max/mdev = 155.986/156.312/156.731/0.552 ms
         3521 </code></pre>
         3522 <p>If this command failed you probably have not setup your DNS/gateway properly.
         3523 If it worked then good for you :)</p>
         3524 <h2>References</h2>
         3525 <ul>
         3526 <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>
         3527 <li><a href="downloads/int51x1/dLAN-linux-package-v4.tar.gz">dLAN-linux-package-v4.tar.gz</a></li>
         3528 <li><a href="downloads/int51x1/int51x1.patch">Patch for recent 2.6.x kernels</a></li>
         3529 <li><a href="downloads/int51x1/INT51X1_datasheet.pdf">INT51X1 datasheet</a></li>
         3530 </ul>
         3531 ]]></content>
         3532 </entry>
         3533 <entry>
         3534         <title>Gothic 1 game guide</title>
         3535         <link rel="alternate" type="text/gopher" href="gopher://codemadness.org/1/phlog/gothic" />
         3536         <id>gopher://codemadness.org/1/phlog/gothic</id>
         3537         <updated>2026-05-31T00:00:00Z</updated>
         3538         <published>2009-04-12T00:00:00Z</published>
         3539         <author>
         3540                 <name>Hiltjo</name>
         3541                 <uri>gopher://codemadness.org</uri>
         3542         </author>
         3543         <summary>Gothic 1 game guide with some useful tips</summary>
         3544         <content type="html"><![CDATA[<h1>Gothic 1 game guide</h1>
         3545         <p><strong>Last modification on </strong> <time>2026-05-31</time></p>
         3546         <p><strong>Disclaimer:</strong>
         3547 Some (including myself) may find some of these hints/exploits cheating. This
         3548 guide is just for educational and fun purposes. Some of these hints/tips apply
         3549 to Gothic 2 as well. I got the meat exploit from a guide somewhere on the
         3550 internet I can't recall where, anyway kudos to that person. Some of the
         3551 exploits I discovered myself.</p>
         3552 <h2>Configuration</h2>
         3553 <h3>Widescreen resolution</h3>
         3554 <p>Gothic supports widescreen resolutions with a small tweak, add the following
         3555 text string as a command-line argument:</p>
         3556 <pre><code>-zRes:1920,1200,32
         3557 </code></pre>
         3558 <p>This also works for Gothic 2. Here 1920 is the width, 1200 the height and 32
         3559 the bits per pixel, change this to your preferred resolution.</p>
         3560 <h3>Fix crash with Steam version</h3>
         3561 <p>Disable steam overlay. If that doesn't work rename GameOverlayRenderer.dll in
         3562 your steam folder to _GameOverlayRenderer.dll.  I strongly recommend to buy the
         3563 better version from <a href="https://www.gog.com/game/gothic">GOG.com</a>.  The GOG version has no DRM and allows easier
         3564 modding, it also allows playing in most published languages: German, English,
         3565 Polish, furthermore it has some original artwork and soundtrack included.</p>
         3566 <h3>Upgrade Steam version to stand-alone version and remove Steam DRM (Gothic 1 and 2)</h3>
         3567 <p>You can install the Gothic playerkit and patches to remove the Steam DRM.</p>
         3568 <p><a href="https://www.worldofgothic.de/">WorldOfGothic</a> playerkit patches:</p>
         3569 <ul>
         3570 <li>Gothic 1 (EN):    <a href="https://www.worldofgothic.com/dl/?go=dlfile&amp;fileid=28">https://www.worldofgothic.com/dl/?go=dlfile&amp;fileid=28</a></li>
         3571 <li>Gothic 1 (DE):    <a href="https://www.worldofgothic.de/dl/download_34.htm">https://www.worldofgothic.de/dl/download_34.htm</a></li>
         3572 <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>
         3573 </ul>
         3574 <h3>Play Gothic in a different language with English subtitles</h3>
         3575 <p>If you're like me and have played the English version many times, but would
         3576 like to hear the (original) German voice audio or if you would like to play
         3577 with different audio than you're used to, then you can copy the speech.vdf file
         3578 of your preferred version to your game files. Optionally turn on subtitles.
         3579 I've used this to play the English version of Gothic with the original German
         3580 voice audio and English subtitles.
         3581 This works best with the version from GOG as it allows easier modding.</p>
         3582 <h2>Easy money/weapons/armour/other items</h2>
         3583 <h3>Steal from Huno</h3>
         3584 <p>At night attack Huno the smith in the Old Camp and steal all his steel. Then
         3585 make some weapons and sell them with a merchant.  When you ask Huno about
         3586 blacksmith equipment it will respawn with 5 of each kind of steel. This is also
         3587 a fairly good starting weapon (requires 20 strength).  Also his chest located
         3588 near the sharpening stone and fire contains some steel as well, lock-pick it.
         3589 The combination is: RRLRLL. The chest contains at least 20 raw steel, forge it
         3590 to get 20 crude swords which you can sell for 50 ore each to a merchant.  This
         3591 will generate some nice starting money (1000+ ore) :)</p>
         3592 <h3>Steal weapons from the castle in the Old Camp</h3>
         3593 <p>This tip is useful for getting pretty good starting weapons.</p>
         3594 <p>Before entering the castle itself drop your ore (Left control + down for me)
         3595 in front of it. This will ensure when you get caught (and you probably will ;))
         3596 no ore will get stolen by the guards. Now use the "slip past guard" technique
         3597 described below and you should be able to get into Gomez his castle. Run to the
         3598 left where some weapons are stored. Now make sure you at least steal the best
         3599 weapon (battle sword) and steal as much as you can until you get whacked.  I
         3600 usually stand in the corner since that's where the best weapons are (battle
         3601 sword, judgement sword, etc). You'll now have some nice starting weapon(s) and
         3602 the good thing is they require very little attributes (about 13 strength).</p>
         3603 <p>Location: <a href="downloads/gothic1/old_camp_swords.png">screenshot</a></p>
         3604 <h3>Free scraper armour the New Camp</h3>
         3605 <p>In the New Camp go to the mine and talk to Swiney at the bottom of "The
         3606 Hollow". Ask who he is and then ask to join the scrapers.  He will give you a
         3607 "Diggers dress" worth 250 ore. It has the following stats: + 10 against
         3608 weapons. + 5 against fire.  This will also give you free entrance to the bar in
         3609 the New Camp.</p>
         3610 <h3>Unlimited water bottles in the New Camp</h3>
         3611 <p>In the quest from Lefty you will be assigned to get water bottles from the
         3612 rice lord.  He will give you infinite amounts of water bottles, in batches of
         3613 12.</p>
         3614 <h3>Armour amulet and increase HP potion</h3>
         3615 <p>In the Old Camp in the main castle there are at least 3 chests with valuable
         3616 items that don't require a key:</p>
         3617 <ul>
         3618 <li><p>Middle right side (looking from the entrance), 1 chest:
         3619 <ul>
         3620 <li>lock combination: LLLLRLRL</li>
         3621 <li>loot:
         3622 <ul>
         3623 <li>+15 against weapons, +15 against arrows (amulet of stone skin)
         3624 (worth: 1000 ore)</li>
         3625 </ul>
         3626 </li>
         3627 <li>additionally there are 2 locked doors at the right side in this room. In
         3628 the final room there are 3 floors with lots of chests.<br />  
         3629 <a href="downloads/gothic1/video/amulet.mp4">Video of the location</a></li>
         3630 </ul>
         3631 </p>
         3632 </li>
         3633 <li><p>Left side, 1 chest:
         3634 <ul>
         3635 <li>lock combination: RLLLLLRR</li>
         3636 <li>loot:
         3637 <ul>
         3638 <li>+8 mana amulet (worth: 600 ore)</li>
         3639 <li>2 potions (+70 hp)</li>
         3640 <li>dreamcall (weed)</li>
         3641 <li>120 coins (worth: nothing)</li>
         3642 </ul>
         3643 </li>
         3644 </ul>
         3645 </p>
         3646 </li>
         3647 <li><p>Right side, 2 chests with:
         3648 <ul>
         3649 <li>lock combination: RLLLRLLR</li>
         3650 <li>loot:
         3651 <ul>
         3652 <li>armour amulets, +15 against weapons (worth: 600 ore)</li>
         3653 <li>maximum life potion, +10 maximum life (worth: 1000 ore)</li>
         3654 <li>speed potion (1 minute duration)</li>
         3655 <li>4 potions (+70 hp)</li>
         3656 </ul>
         3657 </li>
         3658 </ul>
         3659 </p>
         3660 </li>
         3661 </ul>
         3662 <h3>Swamp/Sect Camp harvest twice</h3>
         3663 <p>In the swamp-weed harvest quest you must get swamp-weed for a guru. After this
         3664 quest you can get the harvest again, but you can keep the harvest without
         3665 consequences.</p>
         3666 <h2>Exploits</h2>
         3667 <h3>Slip past guards</h3>
         3668 <p>This exploit is really simple, just draw your weapon before you're "targeted"
         3669 by the guard and run past them, this bypasses the dialog sequence.  When you're
         3670 just out of their range holster your weapon again, so the people around won't
         3671 get pissed off.</p>
         3672 <p>Works really well on the guards in front of the Old camp's castle, Y'Berrion
         3673 templars and New Camp mercenaries near the Water magicians, just to name a few.</p>
         3674 <p><a href="downloads/gothic1/video/amulet.mp4">Video</a></p>
         3675 <h3>Meat duplication</h3>
         3676 <p>Go to a pan and focus / target it so it says "frying pan" or similar. Now open
         3677 your inventory and select the meat. Now cook the meat (for me Left Control +
         3678 Arrow up). The inventory should remain open. You'll now have twice as much meat
         3679 as you had before. Do this a few times and you'll have a lot of meat, easy for
         3680 trading with ore/other items as well. This exploit does not work with the
         3681 community patch applied.</p>
         3682 <h3>Glitch through (locked) doors and walls</h3>
         3683 <p>You can glitch through walls by strafing into them. Then when the player is
         3684 partially collided into a door or wall you can jump forward to glitch through
         3685 it.</p>
         3686 <p><a href="downloads/gothic1/video/bloodsword.mp4">Video</a></p>
         3687 <h3>Fall from great heights</h3>
         3688 <p>When you fall or jump from where you usually get fall damage you can do the
         3689 following trick: slightly before the ground use left or right strafe.  This
         3690 works because it resets the falling animation. There are also other ways to
         3691 achieve the same thing cancelling the falling animation, such as attacking with
         3692 a weapon in the air.</p>
         3693 <p><a href="downloads/gothic1/video/fall.mp4">Video</a></p>
         3694 <h2>Experience / level up tips</h2>
         3695 <h3>Test of faith (extra exp)</h3>
         3696 <p>You get an additional 750 exp (from Lares) when you forge the letter in the new
         3697 camp and then give it to Diego. You can still join both camps after this.</p>
         3698 <h3>Fighting skeleton mages and their skeletons</h3>
         3699 <p>An easy way to get more experience is to let the skeleton mages summon as much
         3700 skeletons as they can, instead of rushing to kill the summoner immediately.
         3701 After you have defeated all of them: kill the skeleton mage.</p>
         3702 <h3>Permanent str/dex/mana/hp potions/items and teachers</h3>
         3703 <p>When you want to get the maximum power at the end of the game you should save
         3704 up the items that give you a permanent boost. Teachers of strength, dexterity
         3705 and mana won't train over 100 of each skill.  However using potions and quest
         3706 rewards you can increase this over 100.</p>
         3707 <p>You should also look out for the following:</p>
         3708 <ul>
         3709 <li><p>Learn to get extra force into your punch from Horatio (strength +5, this
         3710 can't be done after level 100 strength). Talking to Jeremiah in the New Camp
         3711 bar unlocks the dialog option to train strength at Horatio.</p>
         3712 </li>
         3713 <li><p>Smoke the strongest non-quest joint (+2 mana).</p>
         3714 </li>
         3715 </ul>
         3716 <h3>Permanent potions in Sleeper temple</h3>
         3717 <p>This one is really obvious, but I would like to point out the mummies on each
         3718 side where Xardas is located have lots and I mean lots of permanent potions.
         3719 This will give you a nice boost before the end battle.</p>
         3720 <p>Location, left and right corridor in the Sleeper temple: <a href="downloads/gothic1/sleeper_temple_potions.png">screenshot</a><br />  
         3721 Mummies, you can loot them: <a href="downloads/gothic1/sleeper_temple_potions_mummies.png">screenshot</a><br />  </p>
         3722 <h3>Permanent potions as reward in quests</h3>
         3723 <p>Always pick the permanent potion as a reward for quests when you can, for
         3724 example the quest for delivering the message to the High Fire magicians (mana
         3725 potion) or the one for fetching the almanac for the Sect Camp.  Don't forget to
         3726 pick up the potions from Riordian the water magician when you're doing the
         3727 focus stones quest, it contains a strength and dexterity potion (+3).</p>
         3728 <h3>Improve ancient ore armour further</h3>
         3729 <p>In the last chapters the blacksmith Stone from the Old Camp is captured If you
         3730 save him from the prison cell in the Old Camp the reward will have a few
         3731 options.  One of the options is improving the Ancient Ore armour.</p>
         3732 <h2>Good early game weapons available in chapter 1</h2>
         3733 <h3>Orc Hammer</h3>
         3734 <p>Location: in a cave near bloodhounds near the mountain fort.<br />  
         3735 It can be reached from a path from the swamp camp up to the mountain.
         3736 Watch out for the bloodhounds. They can instantly kill you in the early game.</p>
         3737 <p>Location: <a href="downloads/gothic1/early_weapon_old_orc_hammer_location.png">screenshot</a><br />  
         3738 Stats: <a href="downloads/gothic1/early_weapon_old_orc_hammer_stats.png">screenshot</a><br />  </p>
         3739 <p>Stats:
         3740 <ul>
         3741 <li>Type: one-handed</li>
         3742 <li>Damage: 50</li>
         3743 <li>Required strength: 22</li>
         3744 <li>Worth: 1000 ore</li>
         3745 </ul>
         3746 </p>
         3747 <p>It has very low strength stat requirement and has high damage for the early
         3748 game chapters.  A downside is the lower weapon swing range.
         3749 It is also a decent weapon against stone golems.</p>
         3750 <h3>Old Battle Axe</h3>
         3751 <p>Location: near Xardas his tower.<br />  
         3752 Watch out for a group of Biters lurking there.</p>
         3753 <p>Location: <a href="downloads/gothic1/early_weapon_old_battle_axe_location.png">screenshot</a><br />  
         3754 Stats: <a href="downloads/gothic1/early_weapon_old_battle_axe_stats.png">screenshot</a><br />  </p>
         3755 <p>Stats:
         3756 <ul>
         3757 <li>Type: two-handed</li>
         3758 <li>Damage: 67</li>
         3759 <li>Required strength: 36</li>
         3760 <li>Worth: 1800 ore</li>
         3761 </ul>
         3762 </p>
         3763 <p>It has a relatively low strength requirements and is available in game chapter
         3764 1 or could be sold for a decent amount.</p>
         3765 <h3>Random/beginner tips</h3>
         3766 <ul>
         3767 <li><p>If you want to talk to a NPC, but some animation of them takes too long (like
         3768 eating, drinking, smoking) you can sometimes force them out of it by quickly
         3769 unsheathing/sheathing your weapon.</p>
         3770 </li>
         3771 <li><p>When in the Old Camp: Baal Parvez can take you to the Sect Camp, he can be
         3772 found near the campfire near Fisk and Dexter.
         3773 Mordrag can take you to the New Camp, he can be found near the south gate,
         3774 slightly after the campfire near Baal Parvez.</p>
         3775 <p>When you follow them and when they kill monsters then you also get the
         3776 experience.</p>
         3777 </li>
         3778 <li><p>If you do no damage to rock golems or skeletons: use a different type of
         3779 weapon than a sword: use a hammer (like the Orc Hammer or God's Hammer).  For
         3780 the elemental golems (fire and ice) use the elemental counter, like Ice Bolt or
         3781 Fire Bolt.</p>
         3782 </li>
         3783 <li><p>The NPC Wolf in the New Camp sells "The Bloodflies" book for 150 ore. When
         3784 you read this book you learn how to remove bloodflies parts (without having to
         3785 spend learning points). After you read the book and learned its skill then you
         3786 can sell the book back for 75 ore. This investment quickly pays back: Per
         3787 bloodfly: sting: 25 ore (unsold value), 2x wings (15 ore each unsold value).</p>
         3788 </li>
         3789 <li><p>The templar Gor Na Drak (usually near the old mine and walks around with
         3790 another templar): talking to him teaches you how to learn to get secretion from
         3791 minecrawlers for free.</p>
         3792 </li>
         3793 <li><p>The spell scroll "Transform into bloodfly" is very useful:
         3794 <ul>
         3795 <li>A bloodfly is very fast.</li>
         3796 <li>Can also fly over water.</li>
         3797 <li>The scroll costs 100 ore. Its the same price as a potion of speed, but it
         3798 has no duration (just until you transform back).</li>
         3799 <li>You have no fall damage.</li>
         3800 <li>You can climb some steep mountains this way.</li>
         3801 <li>Some monsters won't attack you, but some NPCs will attack you.</li>
         3802 <li>Your attribute stats will temporary change.</li>
         3803 <li>It requires 10 mana to cast (low requirement).</li>
         3804 </ul>
         3805 </p>
         3806 </li>
         3807 <li><p>Almost all mummies that are lootable in the game (Orc temple and The Sleeper
         3808 temple) have really good loot: permanent and regular potions and amulets and
         3809 rings.<br />  </p>
         3810 </li>
         3811 <li><p>Skill investments:
         3812 <ul>
         3813 <li>For melee skills:
         3814 <ul>
         3815 <li>Strength (increases damage and meeting equipment requirements).</li>
         3816 <li>One-handed weapons have a bit lower weapon damage but are less clunky and
         3817 faster. You can also interrupt enemy attacks.</li>
         3818 <li>One-handed sword trainers:
         3819 <ul>
         3820 <li>Cord, New Camp, 30 ore, near the waterfall at the lake and bar.</li>
         3821 <li>Scatty, Old Camp, 50 ore, usually sitting on bench, near the arena.</li>
         3822 </ul>
         3823 </li>
         3824 <li>Two-handed weapons have the highest damage, but are slower.</li>
         3825 <li>Get at least the first tier of one-handed training. It will change the
         3826 combat animations and make combat less slow and clunky.</li>
         3827 </ul>
         3828 </li>
         3829 <li>For ranged skills:
         3830 <ul>
         3831 <li>Dexterity (increases damage and meeting equipment requirements).</li>
         3832 <li>Cross-bows have high damage and are very good.
         3833 <ul>
         3834 <li>Cross-bow: the path for cross-bow training is easier in the old camp.
         3835 When you become an Old Camp guard then Scorpio can train you. Later in
         3836 the game in chapter 4 after some story progression he will train everyone.</li>
         3837 </ul>
         3838 </li>
         3839 </ul>
         3840 </li>
         3841 <li>For mage characters:
         3842 <ul>
         3843 <li>Investing a little bit into strength, lets say 30 STR is OK.</li>
         3844 <li>Magic skills are powerful but are a bit clunky and slow.</li>
         3845 <li>Joining the Old Camp (fire mage) or New Camp (water mage) for the magician
         3846 path is probably easier.</li>
         3847 </ul>
         3848 </li>
         3849 <li>Harvest animals (not necessary):
         3850 <ul>
         3851 <li>Early investments of a few skill points into getting skins, teeth and
         3852 claws from animals is OK (it is easy enough to get a lot of ore if you loot
         3853 everything though).</li>
         3854 </ul>
         3855 </li>
         3856 <li>Lockpicking (not necessary): training in lockpicking only reduces the
         3857 chance to break locks when you fail the combination. Investing in it is OK but
         3858 not necessary.  A small cheat: the lock pick combination stays the same, you
         3859 can save and reload the game to avoid losing lockpicks.</li>
         3860 <li>Bad skill investments to avoid:
         3861 <ul>
         3862 <li>Sneak and pickpocket are nearly useless.</li>
         3863 </ul>
         3864 </li>
         3865 </ul>
         3866 </p>
         3867 </li>
         3868 </ul>
         3869 <p>Overall recommendation: I'd recommend a hybrid of melee/magic or melee/range.
         3870 Early game for melee: get max strength to 100 and get at least the first tier
         3871 of one-handed training.<br />  
         3872 In the later game focus more on ranged combat or learning the magic circles.</p>
         3873 <h1>Late-game fun tip: when the Old Camp turns against you</h1>
         3874 <p>Before you go to Xardas's old tower (where you get the spell to teleport to the
         3875 circle of Magicians of Fire in the Old Camp) you can go to the Old Camp and
         3876 enter it by using the transform into meatbug spell and crawl under the door or
         3877 using the transform into bloodfly spell.</p>
         3878 <p>Useful NPCs to murder:
         3879 <ul>
         3880 <li>Huno (the blacksmith) in the Old Camp has usually a lot of materials worth much ore.</li>
         3881 <li>Fisk (trader) has lots of swords and items.</li>
         3882 <li>Other NPCs, especially ones you have traded with before. You can get the items back
         3883 for "free" &gt;:)</li>
         3884 <li>Arto (one of the elite guards near Gomez) has a nice one-handed sword.</li>
         3885 </ul>
         3886 </p>
         3887 <p>NOTE: some NPCs at this point are invulnerable (Thorus, Scar, Gomez).</p>
         3888 <p>Fun things:</p>
         3889 <ul>
         3890 <li>Use the rain of fire spell to burn everyone.</li>
         3891 <li>Spawn many golems or army of undead in the middle of the camp.</li>
         3892 <li>Mindcontrolling low-level NPCs and doing stupid things.</li>
         3893 </ul>
         3894 <h1>Late-game: hidden sleeper temple potions</h1>
         3895 <p>There are some hidden permanent increase potions in the Sleeper temple.  It is
         3896 easy to miss this location and even some Gothic experts have maybe missed these
         3897 (I did ;))</p>
         3898 <p>The potions are a dexterity potion (+5), life (+15) and mana (+10).</p>
         3899 <p><a href="downloads/gothic1/video/sleeper_hidden_potions.mp4">Video</a></p>
         3900 <h1>Side-quest Chromanin / The Stranger</h1>
         3901 <p>This describes an interesting side quest in the Gothic 1 game, which is not too
         3902 obvious to find and may be overlooked.</p>
         3903 <p>The first Chromanin book is found by defeating the skeleton mage in the Fog
         3904 Tower. On its bones you can find the Chromanin book. Reading the book starts
         3905 the Chromanin / The Stranger quest.  The books contain some typos, being
         3906 demonicly possessed could be an excuse for that :)</p>
         3907 <p>Note that the Old books only spawn in a specific order after reading each found
         3908 book.  So they have to be done in this specific order.</p>
         3909 <p><a href="downloads/gothic1/chromanin/0_mage_fog_tower.png">Fog tower mage</a><br />  
         3910 <a href="downloads/gothic1/chromanin/0_fog_tower.png">Location</a><br />  
         3911 <a href="downloads/gothic1/chromanin/0_map.png">Map</a><br />  </p>
         3912 <p>Text:</p>
         3913 <pre><code>"He who is willing to
         3914 renounce all depravity
         3915 and wanders on the path
         3916 of righteousness, shall
         3917 know where the source
         3918 of my power lies
         3919 hidden. So that he might
         3920 use it to break the chains
         3921 of this world and prove
         3922 worthy to receive Chromanin."
         3923 
         3924 "The Wise One sees to
         3925 having a general overview before he
         3926 dedicates himself to his
         3927 next mission."
         3928 </code></pre>
         3929 <h2>Chromanin</h2>
         3930 <p>The clue is in the words "general overview" on the second page.
         3931 One of the highest points on the map is the tower where you find and free the orc Ur-Shak
         3932 from being attacked by other orcs.</p>
         3933 <p>The Wise One sees to having a general overview before he dedicates himself to
         3934 his next mission".<br />  
         3935 Location: on top of the tower near where the orc Ur-Shak was.<br />  
         3936 Item: Old Book.</p>
         3937 <p><a href="downloads/gothic1/chromanin/1_book.png">Item</a><br />  
         3938 <a href="downloads/gothic1/chromanin/1_tower.png">Location</a><br />  
         3939 <a href="downloads/gothic1/chromanin/1_map.png">Map</a><br />  </p>
         3940 <h2>Chromanin 2</h2>
         3941 <p>Text:</p>
         3942 <pre><code>"Carried from the tides
         3943 of time, Chromanin's
         3944 visions have opened my
         3945 eyes. No price could be
         3946 high enough to ever
         3947 renounce my faith in
         3948 them, for it touched my
         3949 heart too insensely."
         3950 
         3951 "What is devided will be
         3952 reunited, after being
         3953 massively separated for
         3954 a short time."
         3955 </code></pre>
         3956 <p>Clue: "What is devided (sic) will be reunited, after being massively separated for a short time".
         3957 Location: small island near the (divided) river near the Old Camp.</p>
         3958 <p><a href="downloads/gothic1/chromanin/2_book.png">Item</a><br />  
         3959 <a href="downloads/gothic1/chromanin/2_river.png">Location</a><br />  
         3960 <a href="downloads/gothic1/chromanin/2_map.png">Map</a><br />  </p>
         3961 <h2>Chromanin 3</h2>
         3962 <p>Text:</p>
         3963 <pre><code>"Oh, Ancient Gods. How
         3964 can it be that a man like
         3965 me, simple and unworthy,
         3966 may receive such great a
         3967 legacy. I feel great
         3968 fear to lose all of it
         3969 again by a slight
         3970 faltering in word or
         3971 deed."
         3972 
         3973 "The wise fisherman
         3974 occasionally tries to get
         3975 lucky on the other side
         3976 of the lake."
         3977 </code></pre>
         3978 <p>Clue: a fisherman lake and (partially sunken hut) can be found close the the entrance of the New Camp.
         3979 At the other side is the Old Book.</p>
         3980 <p><a href="downloads/gothic1/chromanin/3_book.png">Item</a><br />  
         3981 <a href="downloads/gothic1/chromanin/3_lake_new_camp.png">Location</a><br />  
         3982 <a href="downloads/gothic1/chromanin/3_map.png">Map</a><br />  </p>
         3983 <h2>Chromanin 4</h2>
         3984 <p>Text:</p>
         3985 <pre><code>"I dare not to be in
         3986 the presence of
         3987 Chromanin one day. Gone
         3988 are the days of wasting
         3989 and wailing. So easy it
         3990 will be to acheive
         3991 absolute perfection. I'm
         3992 not far from it!"
         3993 
         3994 "Long forgotten are the
         3995 deeds of those who once
         3996 were aboard."
         3997 </code></pre>
         3998 <p>Clue: "Long forgotten are the deeds of those who once were aboard."
         3999 A broken ship can be found near the beach at the entrance of the Fog Tower.</p>
         4000 <p><a href="downloads/gothic1/chromanin/4_book.png">Item</a><br />  
         4001 <a href="downloads/gothic1/chromanin/4_aboard.png">Location</a><br />  
         4002 <a href="downloads/gothic1/chromanin/4_map.png">Map</a><br />  </p>
         4003 <h2>Chromanin 5</h2>
         4004 <p>Text:</p>
         4005 <pre><code>"But I shall not walk this
         4006 path alone. This honor is
         4007 mine. I must accept to
         4008 share the power within
         4009 myself with the worthy
         4010 ones who are to come and
         4011 find me. I hope they're
         4012 coming soon..."
         4013 
         4014 "You will find me where it all began."
         4015 </code></pre>
         4016 <p>Clue: "You will find me where it all began."
         4017 Very obvious it is the same location as were the first book was found.</p>
         4018 <p><a href="downloads/gothic1/chromanin/5_book.png">Item</a><br />  
         4019 <a href="downloads/gothic1/chromanin/5_begin.png">Location</a><br />  
         4020 <a href="downloads/gothic1/chromanin/5_map.png">Map</a><br />  </p>
         4021 <h2>Chromanin 6</h2>
         4022 <p>Text:</p>
         4023 <pre><code>"Empty pages"
         4024 </code></pre>
         4025 <p><a href="downloads/gothic1/chromanin/6_book.png">Item</a></p>
         4026 <p>On the corpse is the last chromanin book.
         4027 When reading this last book the book is empty.
         4028 Then there is evil laugh and 2 skeleton mages and skeleton minions will spawn.</p>
         4029 <h2>Chromanin quest log</h2>
         4030 <p>Here are the texts in the quest log:</p>
         4031 <p><a href="downloads/gothic1/chromanin/quest_log_part_1.png">Quest log part 1</a><br />  
         4032 <a href="downloads/gothic1/chromanin/quest_log_part_2.png">Quest log part 2</a><br />  </p>
         4033 <h3>The End</h3>
         4034 <p>When you use the tips described above Gothic should be an easier game and you
         4035 should be able to get at a high(er) level with lots of mana/strength/hp.</p>
         4036 <p>Have fun!</p>
         4037 ]]></content>
         4038 </entry>
         4039 </feed>