<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title></title>
    <link rel="self" type="application/atom+xml" href="https://fredrik.lanker.se/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://fredrik.lanker.se/"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-02-20T00:00:00+00:00</updated>
    <id>https://fredrik.lanker.se/atom.xml</id>
    <entry xml:lang="en">
        <title>Custom commands in Firefox with Tridactyl</title>
        <published>2026-02-20T00:00:00+00:00</published>
        <updated>2026-02-20T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Fredrik Lanker
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://fredrik.lanker.se/w/2026/fxcmd/"/>
        <id>https://fredrik.lanker.se/w/2026/fxcmd/</id>
        
        <content type="html" xml:base="https://fredrik.lanker.se/w/2026/fxcmd/">&lt;h2 id=&quot;the-scenario&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-scenario&quot; aria-label=&quot;Anchor link for: the-scenario&quot;&gt;The scenario&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;You&#x27;re on a web page and you want to extract some content from it and use it in
an external command. Or a more specific use case, you use
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;miniflux.app&#x2F;&quot;&gt;Miniflux&lt;&#x2F;a&gt; as your feed reader to follow some podcasts and
you want to play those podcasts in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.musicpd.org&#x2F;&quot;&gt;MPD&lt;&#x2F;a&gt;. You use
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.firefox.com&#x2F;&quot;&gt;Firefox&lt;&#x2F;a&gt; as your browser together with
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tridactyl&#x2F;tridactyl&quot;&gt;Tridactyl&lt;&#x2F;a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tridactyl&#x2F;tridactyl#extra-features-through-native-messaging&quot;&gt;Native
Messaging&lt;&#x2F;a&gt;.
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;musicpd.org&#x2F;doc&#x2F;mpc&#x2F;html&#x2F;&quot;&gt;mpc&lt;&#x2F;a&gt; will be used for controlling MPD.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-solution&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-solution&quot; aria-label=&quot;Anchor link for: the-solution&quot;&gt;The solution&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The audio part for a podcast is rendered as an &lt;code&gt;&amp;lt;audio&amp;gt;&lt;&#x2F;code&gt; element in Miniflux.
An &lt;code&gt;&amp;lt;audio&amp;gt;&lt;&#x2F;code&gt; element looks something like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;audio&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt; ...&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;source&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt; src&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;https:&#x2F;&#x2F;example.com&#x2F;sound.mp3&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt; ...&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;audio&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So to get the URL to the audio file, we select the &lt;code&gt;src&lt;&#x2F;code&gt; attribute of the
&lt;code&gt;&amp;lt;source&amp;gt;&lt;&#x2F;code&gt; element. For our use case, we know that we&#x27;ll only have one of these
elements, so we&#x27;re happy with just getting the first one:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;js&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt; document&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;querySelector&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;source&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;src&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The output is then piped to the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;tridactyl.xyz&#x2F;build&#x2F;static&#x2F;docs&#x2F;modules&#x2F;_src_excmds_.html#shellescape&quot;&gt;&lt;code&gt;shellescape&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;
method using the special variable &lt;code&gt;JS_ARG&lt;&#x2F;code&gt;, which contains the result from the
previous command (shown on two lines for clarity):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;js&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt; document&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;querySelector&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;source&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;src&lt;&#x2F;span&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt; |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;js&lt;&#x2F;span&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt; tri&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;excmds&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;shellescape&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;JS_ARG&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;-p&lt;&#x2F;code&gt; flag is needed for &lt;code&gt;JS_ARG&lt;&#x2F;code&gt; to work.&lt;&#x2F;p&gt;
&lt;p&gt;Lastly, we call our external command with the escaped URL as an argument.
&lt;code&gt;shellescape&lt;&#x2F;code&gt; returns a promise, so we need to handle that. We also need to
prefix everything with
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;tridactyl.xyz&#x2F;build&#x2F;static&#x2F;docs&#x2F;modules&#x2F;_src_excmds_.html#composite&quot;&gt;&lt;code&gt;composite&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;,
to make the pipe work. Combining everything and creating a new
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;tridactyl.xyz&#x2F;build&#x2F;static&#x2F;docs&#x2F;modules&#x2F;_src_excmds_.html#command&quot;&gt;command&lt;&#x2F;a&gt;,
this is the final line that goes into our &lt;code&gt;~&#x2F;.tridactylrc&lt;&#x2F;code&gt; (or wherever you
keep it) file:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;command pod2mpd composite js&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt; document&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;querySelector&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;quot;source&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;src&lt;&#x2F;span&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; js&lt;&#x2F;span&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt; tri&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;excmds&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;shellescape&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;JS_ARG&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;then&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;font-style: italic;&quot;&gt;url&lt;&#x2F;span&gt;&lt;span style=&quot;color: #C678DD;&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt; tri&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;excmds&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;exclaim_quiet&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;&amp;#39;mpc add &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; url&lt;&#x2F;span&gt;&lt;span&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Finally we bind a key sequence to the command:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;bind ,p pod2mpd&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now, pressing &lt;code&gt;,p&lt;&#x2F;code&gt; will find the first source element on the current page and
add its URL to MPD. This could, of course, be altered to instead send the link
to another media player, download the file, or call an external script for more
advanced handling (perhaps selecting multiple elements on the page, extracting
links and selecting among them using &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;junegunn.github.io&#x2F;fzf&#x2F;&quot;&gt;fzf&lt;&#x2F;a&gt;),
and so on. Lots of possibilities!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>How I Make</title>
        <published>2025-04-17T00:00:00+00:00</published>
        <updated>2025-04-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Fredrik Lanker
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://fredrik.lanker.se/w/2025/make/"/>
        <id>https://fredrik.lanker.se/w/2025/make/</id>
        
        <content type="html" xml:base="https://fredrik.lanker.se/w/2025/make/">&lt;p&gt;For the last decade or so, I&#x27;ve had the privilege of working with colleagues
who have put up with (some of) my ideas on how to do things. One of those ideas
has been to use &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;make&#x2F;&quot;&gt;Makefiles&lt;&#x2F;a&gt; for building and doing other tasks. This post
suggests conventions to follow when dealing with web projects, both frontend
and backend, but it also applies to most other projects. The reason for trying
to follow a convention is that regardless of the project you end up with, you
should never wonder how to get it running, run tests, etc. (This is not
targeted at C-like languages and compilers, where using Make might be a more
obvious choice due to historical reasons and the way those languages are compiled.)&lt;&#x2F;p&gt;
&lt;p&gt;But first:&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-not-scripts-in-package-json&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-not-scripts-in-package-json&quot; aria-label=&quot;Anchor link for: why-not-scripts-in-package-json&quot;&gt;Why not scripts in package.json?&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Defining tasks in a format that does not support newlines, comments,
dependencies between tasks, only does what is needed, etc., is a...peculiar
choice. Also, this approach would only work for projects using npm (or similar
tools).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;rules&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#rules&quot; aria-label=&quot;Anchor link for: rules&quot;&gt;Rules&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;This is the rules I try to use. Most examples are using
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.npmjs.com&#x2F;&quot;&gt;npm&lt;&#x2F;a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.typescriptlang.org&#x2F;&quot;&gt;Typescript&lt;&#x2F;a&gt;
and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;parceljs.org&#x2F;&quot;&gt;Parcel&lt;&#x2F;a&gt;, but it will work with any tools.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;build&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#build&quot; aria-label=&quot;Anchor link for: build&quot;&gt;&lt;code&gt;build&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;This will do whatever is needed to create something that can be deployed.
Typical commands for a project using Parcel and Typescript include:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;make&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt;.PHONY&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; build&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;build&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; node_modules&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;	node_modules&#x2F;.bin&#x2F;tsc --noEmit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;	node_modules&#x2F;.bin&#x2F;parcel build&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;node_modules&lt;&#x2F;code&gt; is a prerequisit, this makes it possible to just clone the
project and then run this rule, and everything should just work.&lt;&#x2F;p&gt;
&lt;p&gt;This rule is the default target, i.e., it&#x27;s enough to execute &lt;code&gt;$ make&lt;&#x2F;code&gt; to run
it.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;run&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#run&quot; aria-label=&quot;Anchor link for: run&quot;&gt;&lt;code&gt;run&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;This target will run the project.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;make&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt;.PHONY&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; run&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;run&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; node_modules&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;	node_modules&#x2F;.bin&#x2F;parcel serve&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For a frontend project without any build steps (or tools not having a server function),
serving via python&#x27;s http module is an excellent alternative:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;make&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt;.PHONY&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; run&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;run&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;	python -m http.server&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;test&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#test&quot; aria-label=&quot;Anchor link for: test&quot;&gt;&lt;code&gt;test&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;A target for running all the test cases for the project.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;lint&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#lint&quot; aria-label=&quot;Anchor link for: lint&quot;&gt;&lt;code&gt;lint&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Next up is the &lt;code&gt;lint&lt;&#x2F;code&gt; rule. This could either be a single rule that checks
everything that you care about, or include additional rules for other tools.
For example, in web projects, a good command nowadays covering both lint and
formatting using &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;biomejs.dev&#x2F;&quot;&gt;biome&lt;&#x2F;a&gt; would look like:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;make&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt;.PHONY&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; lint&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;lint&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; node_modules biome.json&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;	node_modules&#x2F;.bin&#x2F;biome check src&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you are using ESLint and Prettier (or something else), you might want to
have them in two separate rules, or run both in a single &lt;code&gt;lint&lt;&#x2F;code&gt; rule. It
depends on whether you have the need to run them independently or not.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;ci&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#ci&quot; aria-label=&quot;Anchor link for: ci&quot;&gt;&lt;code&gt;ci&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;This is perhaps the most important rule (or really, all are important, but bear
with me...). This is the &lt;em&gt;&lt;strong&gt;only&lt;&#x2F;strong&gt;&lt;&#x2F;em&gt; rule that is allowed to run in a CI
workflow for checking the code (and possibly the commit message or other
checks). This rule usually doesn&#x27;t have any commands, instead it has other
rules as prerequisites. Example:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;make&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt;.PHONY&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; ci&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;ci&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; lint build test&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you run &lt;code&gt;$ make ci&lt;&#x2F;code&gt; before pushing to a CI workflow, you can be confident
that the CI job won&#x27;t fail either (as long as you&#x27;re running it on the same
code base). You should never have to open any CI configuration to figure out
what is running.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;node-modules&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#node-modules&quot; aria-label=&quot;Anchor link for: node-modules&quot;&gt;&lt;code&gt;node_modules&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;This is the only rule with a &quot;real&quot; target, i.e., the target is an actual
file&#x2F;directory. The rule  makes sure &lt;code&gt;node_modules&lt;&#x2F;code&gt; exists and is up to date.
Most other rules have this one as a prerequisite.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;make&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;node_modules&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; package.json package-lock.json&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;        npm ci&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;clean&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#clean&quot; aria-label=&quot;Anchor link for: clean&quot;&gt;&lt;code&gt;clean&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;If the build target generates any files, for example a &lt;code&gt;dist&lt;&#x2F;code&gt; or &lt;code&gt;build&lt;&#x2F;code&gt;
folder, have a &lt;code&gt;clean&lt;&#x2F;code&gt; target for cleaning up:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;make&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt;.PHONY&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; clean&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;clean&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;	rm -rf build&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;bonus-rules&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#bonus-rules&quot; aria-label=&quot;Anchor link for: bonus-rules&quot;&gt;Bonus rules&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The above is the standard that would be relevant for all projects. Besides
these, there may be more specific rules. For example, if the project depends on
a database, it would be helpful to define a target for running it:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;make&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt;.PHONY&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; database&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;database&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;	docker ps | grep -q database || docker run --rm -p 127.0.0.1:27017:27017 --name database -d mongo --bind_ip_all&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And then have this as a prerequisite for the &lt;code&gt;run&lt;&#x2F;code&gt; rule.&lt;&#x2F;p&gt;
&lt;p&gt;If the project is packaged as a Docker&#x2F;Podman&#x2F;other image, add targets for
building, tagging and publishing it. Similarly, if it&#x27;s a npm package, add
targets for publishing it. Use these targets in the CI workflow.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusions&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#conclusions&quot; aria-label=&quot;Anchor link for: conclusions&quot;&gt;Conclusions&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The takeaway from this isn&#x27;t so much about which rules you should have, what
they are named, or what they do. Instead, the point is that it&#x27;s worth striving
for consistency between projects, especially in a team (or across teams) using
multiple tools and programming languages. Another key takeaway is that if you
have a CI setup, whatever it runs should also be possible to run locally. By
running &lt;code&gt;make ci&lt;&#x2F;code&gt; and then pushing to the CI tool, you should be able to assume
it will pass.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;full-example&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#full-example&quot; aria-label=&quot;Anchor link for: full-example&quot;&gt;Full example&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Finally, here&#x27;s a full example for a node project using typescript, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;avajs&#x2F;ava&quot;&gt;ava&lt;&#x2F;a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.mongodb.com&#x2F;&quot;&gt;mongodb&lt;&#x2F;a&gt;,
biome and docker:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;make&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;IMAGE&lt;&#x2F;span&gt;&lt;span&gt; :=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt; company&#x2F;project&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;NAME&lt;&#x2F;span&gt;&lt;span&gt; ?=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt; my_project&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;BIOME_CONFIG&lt;&#x2F;span&gt;&lt;span&gt; :=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt; node_modules&#x2F;@company&#x2F;configs&#x2F;biome.json&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt;.PHONY&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; build&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;build&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; node_modules&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;	node_modules&#x2F;.bin&#x2F;tsc&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt;.PHONY&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; run&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;run&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; node_modules database&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;	node --import tsx src&#x2F;index.ts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt;.PHONY&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; database&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;database&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;	docker ps | grep -q database || docker run --rm -p 127.0.0.1:27017:27017 --name database -d mongo --bind_ip_all&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;biome.json&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt; $(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;BIOME_CONFIG&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;	ln -s $&amp;lt; $@&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt;.PHONY&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; lint&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;lint&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; node_modules biome.json&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;	node_modules&#x2F;.bin&#x2F;biome check src&#x2F; test&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt;.PHONY&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; lint-fix&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;lint-fix&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; node_modules biome.json&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;	node_modules&#x2F;.bin&#x2F;biome check --write src&#x2F; test&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;node_modules&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; package.json package-lock.json&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;	npm ci&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt;.PHONY&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; docker&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;docker&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;	docker build --progress=plain --pull -t &lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;$(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;IMAGE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;:latest .&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt;.PHONY&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; docker-run&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;docker-run&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;	docker run -rm --name &lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;$(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;NAME&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt; -it &lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;$(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;IMAGE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #98C379;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;:latest&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt;.PHONY&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; test&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;test&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; node_modules database&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;	NODE_OPTIONS=&amp;#39;--import=tsx&amp;#39; node_modules&#x2F;.bin&#x2F;ava test&#x2F;test*&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt;.PHONY&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; ci&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;ci&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; lint build test&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #56B6C2;&quot;&gt;.PHONY&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt; clean&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;clean&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E5C07B;&quot;&gt;	rm -rf dist&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;</content>
        
    </entry>
    <entry xml:lang="en">
        <title>WireGuard VPN in a network namespace</title>
        <published>2024-08-22T00:00:00+00:00</published>
        <updated>2024-08-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Fredrik Lanker
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://fredrik.lanker.se/w/2024/vpn/"/>
        <id>https://fredrik.lanker.se/w/2024/vpn/</id>
        
        <content type="html" xml:base="https://fredrik.lanker.se/w/2024/vpn/">&lt;h2 id=&quot;the-scenario&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-scenario&quot; aria-label=&quot;Anchor link for: the-scenario&quot;&gt;The scenario&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Say you want to run only a specific program via a VPN, but let the rest of your
system use the ordinary connection. For our example, we will use &lt;code&gt;curl&lt;&#x2F;code&gt; as the
program that should use the VPN tunnel, and as VPN provider, we will use
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;mullvad.net&#x2F;&quot;&gt;Mullvad&lt;&#x2F;a&gt; and their
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.wireguard.com&#x2F;&quot;&gt;WireGuard&lt;&#x2F;a&gt; endpoints, but the setup should work
for any VPN provider using WireGuard.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;before-we-start&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#before-we-start&quot; aria-label=&quot;Anchor link for: before-we-start&quot;&gt;Before we start&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;To verify if we are connected via Mullvad or not, we can make calls to
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;am.i.mullvad.net&#x2F;connected&quot;&gt;https:&#x2F;&#x2F;am.i.mullvad.net&#x2F;connected&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s see how it looks before we start:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ curl https:&#x2F;&#x2F;am.i.mullvad.net&#x2F;connected&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;You are not connected to Mullvad. Your IP address is 111.222.111.111&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Great, just as expected we are not using the VPN tunnel.&lt;&#x2F;p&gt;
&lt;p&gt;For setting everything up, we will use the &lt;code&gt;wg&lt;&#x2F;code&gt; tool, part of the &lt;code&gt;wireguard-tools&lt;&#x2F;code&gt; package. See
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.wireguard.com&#x2F;install&#x2F;&quot;&gt;https:&#x2F;&#x2F;www.wireguard.com&#x2F;install&#x2F;&lt;&#x2F;a&gt; for installation instructions. We will also
need a config file, this can be generated on the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;mullvad.net&#x2F;en&#x2F;account&#x2F;wireguard-config&quot;&gt;Mullvad page&lt;&#x2F;a&gt; and will look
something like:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;Interface&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;PrivateKey&lt;&#x2F;span&gt;&lt;span&gt; = &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;&quot;&gt;private key&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;Address&lt;&#x2F;span&gt;&lt;span&gt; = &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;&quot;&gt;IPv4&amp;gt;&#x2F;32,&amp;lt;IPv6&amp;gt;&#x2F;128&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #61AFEF;&quot;&gt;Peer&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;PublicKey&lt;&#x2F;span&gt;&lt;span&gt; = &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;&quot;&gt;public key&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;AllowedIPs&lt;&#x2F;span&gt;&lt;span&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D19A66;&quot;&gt; 0.0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;&quot;&gt;.0.0&#x2F;0,::0&#x2F;0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E06C75;&quot;&gt;Endpoint&lt;&#x2F;span&gt;&lt;span&gt; = &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FFFFFF;&quot;&gt;IP of the server&amp;gt;:51820&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Where &lt;code&gt;&amp;lt;ip of the server&amp;gt;&lt;&#x2F;code&gt; is the IP address of the server you want to use,
which can also be found in the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;mullvad.net&#x2F;en&#x2F;servers&quot;&gt;list of servers on Mullvad&#x27;s
site&lt;&#x2F;a&gt;. You will probably have some additional
fields in your config.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-setup&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-setup&quot; aria-label=&quot;Anchor link for: the-setup&quot;&gt;The setup&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;We&#x27;ll start by creating a new network namespace, aptly named &lt;code&gt;vpn&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# ip netns add vpn&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then we roughly follow the steps from
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.wireguard.com&#x2F;quickstart&#x2F;#command-line-interface&quot;&gt;https:&#x2F;&#x2F;www.wireguard.com&#x2F;quickstart&#x2F;#command-line-interface&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;p&gt;Add a new wireguard interface:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# ip link add wg0 type wireguard&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Move the device to our &lt;code&gt;vpn&lt;&#x2F;code&gt; namespace:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# ip link set wg0 netns vpn&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;From now on we will use &lt;code&gt;ip -n vpn&lt;&#x2F;code&gt; to run the command in the &lt;code&gt;vpn&lt;&#x2F;code&gt; namespace.
&lt;code&gt;-n&lt;&#x2F;code&gt; is short for &lt;code&gt;netns exec ip&lt;&#x2F;code&gt;, i.e., the command will be run in the provided namespace.&lt;&#x2F;p&gt;
&lt;p&gt;Set IP address for the interface. The IP is found in the &lt;code&gt;Interface.Address&lt;&#x2F;code&gt;
field in the config file, we&#x27;ll use the v4 address.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# ip -n vpn addr add &amp;lt;IP&amp;gt;&#x2F;32 dev wg0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Use &lt;code&gt;wg&lt;&#x2F;code&gt; to set keys and endpoints specified in the config file:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# ip netns exec vpn wg setconf wg0 &amp;lt;config file&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Activate the interface by setting it to UP.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# ip -n vpn link set wg0 up&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Make our interface the default route:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# ip -n vpn route add default dev wg0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;usage&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#usage&quot; aria-label=&quot;Anchor link for: usage&quot;&gt;Usage&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;We now have a network namespace named &lt;code&gt;vpn&lt;&#x2F;code&gt; that will route traffic via your VPN
provider using WireGuard. To use it, prefix commands with &lt;code&gt;ip netns vpn exec &amp;lt;command&amp;gt;&lt;&#x2F;code&gt;. To verify that everything works, we run &lt;code&gt;curl&lt;&#x2F;code&gt; in the same way
as before we started, but this time we run it in our network namespace:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# ip netns exec vpn curl https:&#x2F;&#x2F;am.i.mullvad.net&#x2F;connected&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;You are connected to Mullvad (server gr-ath-wg-101). Your IP address is 132.133.134.134&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And just to make sure, we&#x27;ll also check that the result of running it outside
of the &lt;code&gt;vpn&lt;&#x2F;code&gt; namespace hasn&#x27;t changed:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ curl https:&#x2F;&#x2F;am.i.mullvad.net&#x2F;connected&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;You are not connected to Mullvad. Your IP address is 111.222.111.111&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Success!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Building Zola for Android</title>
        <published>2024-07-29T00:00:00+00:00</published>
        <updated>2024-07-29T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Fredrik Lanker
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://fredrik.lanker.se/w/2024/zola/"/>
        <id>https://fredrik.lanker.se/w/2024/zola/</id>
        
        <content type="html" xml:base="https://fredrik.lanker.se/w/2024/zola/">&lt;p&gt;This page is built using &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;getzola.org&quot;&gt;Zola&lt;&#x2F;a&gt;, a static site generator.
At the time of this writing there are no official builds or packages that can
be used in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;termux.dev&#x2F;&quot;&gt;Termux&lt;&#x2F;a&gt; on Android. Rust and Cargo exists as
packages in Termux, so one option would be to build it directly on the mobile,
but for some reason Termux kept closing&#x2F;crashing (probably oom killer or
similar), so here&#x27;s how to cross compile it on a beefier machine. The steps are
for Arch Linux.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;steps&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#steps&quot; aria-label=&quot;Anchor link for: steps&quot;&gt;Steps&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Get the code for Zola, e.g.:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ git clone --depth 1 https:&#x2F;&#x2F;github.com&#x2F;getzola&#x2F;zola.git&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Install &lt;code&gt;android-sdk&lt;&#x2F;code&gt; from &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;aur.archlinux.org&#x2F;packages&#x2F;android-ndk&quot;&gt;AUR&lt;&#x2F;a&gt; using your favorite method.&lt;&#x2F;p&gt;
&lt;p&gt;Install &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bbqsrc&#x2F;cargo-ndk&quot;&gt;cargo ndk&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# pacman -S cargo-ndk&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Install the correct target platform for Rust, e.g.,&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ rustup target add aarch64-linux-android&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Build! The path to &lt;code&gt;android-ndk&lt;&#x2F;code&gt; need to be specified for
&lt;code&gt;cargo ndk&lt;&#x2F;code&gt; to find it.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #ABB2BF; background-color: #282C34;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ ANDROID_NDK_HOME=&#x2F;opt&#x2F;android-ndk&#x2F; cargo ndk -t aarch64-linux-android build --release&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now you should have a binary in &lt;code&gt;target&#x2F;&amp;lt;platform&amp;gt;&#x2F;release&#x2F;zola&lt;&#x2F;code&gt; that should
work in Termux. Good luck!&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
