<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><title>deadbaed</title><id>deadbaed-dead-4444-baed-dddeadbaeddd</id><updated>2026-05-19T19:35:20+02:00</updated><generator uri="https://github.com/deadbaed/leptos_ssg" version="0.1.0">leptos_ssg</generator><link href="https://philippeloctaux.com/blog/atom.xml" rel="self" type="application/atom+xml"/><link href="https://philippeloctaux.com/blog/" rel="alternate" type="text/html"/><subtitle>broke my bed, now it&apos;s dead</subtitle><entry><title>Self hosted Nix binary cache with Attic</title><id>urn:uuid:e83b932b-4ea4-54fc-ba06-11cdac934061</id><updated>2026-05-19T19:35:20+02:00</updated><author><name>Philippe Loctaux</name><uri>https://philippeloctaux.com</uri></author><link href="https://philippeloctaux.com/blog/self-hosted-nix-binary-cache-attic/" rel="alternate" type="text/html"/><published>2026-05-19T19:35:20+02:00</published><content xml:lang="en" type="html">&lt;p&gt;In nixpkgs, most of derivations are cached on &lt;a href=&quot;https://cache.nixos.org&quot;&gt;cache.nixos.org&lt;/a&gt;. This is great when consuming derivations on a NixOS system or a home manager configuration, but when you start writing your own derivations which will not end up on nixpkgs, you might spend a lot of time waiting on those derivations to build, especially when automating stuff like CI/CD, servers, etc. The goal is to build everything only once, and reuse what was built on any machine.&lt;/p&gt;
&lt;p&gt;Fortunately, you can spin up your own binary cache and every time you build a derivation, push it to the cache. Finally, configure nix to mark your cache as trusted to use those cached derivations.&lt;/p&gt;
&lt;h2&gt;Install the cache&lt;/h2&gt;
&lt;p&gt;You will need a NixOS machine. Additionally, attic can work with a S3 bucket and a PostgreSQL database. If you need to use S3 and want to self host it, I recommend &lt;a href=&quot;https://garagehq.deuxfleurs.fr/&quot;&gt;Garage&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Credentials&lt;/h3&gt;
&lt;p&gt;A JWT secret is required for user authentication, you will need to generate one:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;nix-shell -p openssl --run &quot;openssl genrsa -traditional 4096 | base64 -w0&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Afterwards, put that secret as the value of the environment variable &lt;code&gt;ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64&lt;/code&gt;. You can put it inside a file (so the file will contain &lt;code&gt;ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64=foo&lt;/code&gt; where &lt;code&gt;foo&lt;/code&gt; is the secret you just generated), but make sure only root can see it. Or you can encrypt it with something like &lt;a href=&quot;https://github.com/ryantm/agenix&quot;&gt;agenix&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Configuration&lt;/h3&gt;
&lt;p&gt;Nothing special here.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  services.atticd = {
    enable = true;
    environmentFile = &quot;/etc/atticd.env&quot;; # Your file might be somewhere else
    settings = {
      listen = &quot;127.0.0.1:8080&quot;;
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Reverse proxy&lt;/h3&gt;
&lt;p&gt;If you intend to have your cache open to the internet, put it behind a reverse proxy with a TLS certicate. I personally use nginx, it works great and thanks to nix I can configure it without being an expert in the nginx configuration format.&lt;/p&gt;
&lt;p&gt;One thing to note if you use a reverse proxy: you need to bump the &lt;code&gt;clientMaxBodySize&lt;/code&gt; setting otherwise you will not be able to push derivations (the default is &lt;code&gt;10m&lt;/code&gt; for nginx on NixOS).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;let
  fqdn = &quot;attic.domain.example&quot;;
in
{
  services.atticd.settings.api-endpoint = &quot;https://${fqdn}/&quot;; # Must end with a trailing slash
  services.nginx = {
    virtualHosts.&quot;${fqdn}&quot; = {
      forceSSL = true;
      enableACME = true;
      locations.&quot;/&quot; = {
        proxyPass = &quot;http://127.0.0.1:8080&quot;;
        proxyWebsockets = true;
      };
    };
    clientMaxBodySize = &quot;1024m&quot;;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Configure the cache&lt;/h2&gt;
&lt;h3&gt;Create a user token&lt;/h3&gt;
&lt;p&gt;If you point your web browser to the FQDN your provided earlier, you should see a happy computer prompting you to run &lt;code&gt;attic push&lt;/code&gt;. That means the installation worked, you can close your browser. You are ready to create your first cache.&lt;/p&gt;
&lt;p&gt;It took me a while to understand the authn/authz model, but there are 2 commands to use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;As the administrator, you run &lt;code&gt;atticd-atticadm&lt;/code&gt; on the server running &lt;code&gt;atticd&lt;/code&gt; to generate tokens which you give to users. When generating a token, you befine what are the token can do.&lt;/li&gt;
&lt;li&gt;As a user, you run &lt;code&gt;attic login&lt;/code&gt; with a name for the cache, the FQDN, and the token generated by the administrator.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On the server, let&apos;s create the first token which will give all the possible permissions to the user for one hour:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;atticd-atticadm make-token \
    --sub root \
    --validity &apos;1 hour&apos; \
    --pull &apos;*&apos; \
    --push &apos;*&apos; \
    --delete &apos;*&apos; \
    --create-cache &apos;*&apos; \
    --configure-cache &apos;*&apos; \
    --configure-cache-retention &apos;*&apos; \
    --destroy-cache &apos;*&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, the &lt;code&gt;&apos;*&apos;&lt;/code&gt; mean that the permission is given for all caches. For examaple, to create a token which can pull indefinitely only from cache &lt;code&gt;hello&lt;/code&gt;, run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;atticd-atticadm make-token \
    --sub hello-user \
    --validity &apos;99 years&apos; \
    --pull &apos;hello&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Consume token&lt;/h3&gt;
&lt;p&gt;Our regular computer, let&apos;s connect to the cache:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;nix-shell -p attic-client
attic login mycache https://attic.domain.example eyJh...
cat .config/attic/config.toml # The credentials will show up here
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can now create a cache:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;attic cache create hello
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you get the message &lt;code&gt;✨ Created cache &quot;hello&quot; on &quot;mycache&quot;&lt;/code&gt; that means you are good, otherwise the error would be &lt;code&gt;Error: Unauthorized: Unauthorized.&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To show information about a cache such as the public key, run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;attic cache info hello
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To make the cache publicly available (meaning, no token is required to pull data), run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;attic cache configure --public hello
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Use the cache&lt;/h2&gt;
&lt;p&gt;Make sure you create a token which only allows to pull from the cache, to prevent unwanted pushes.&lt;/p&gt;
&lt;p&gt;Now that you have the public key and the binary cache endopoint, you can tell nix to use your cache, for example on NixOS you can use this config:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{
  nix.settings = {
    substituters = [
      &quot;https://attic.domain.example/hello&quot;
    ];
    trusted-public-keys = [
      &quot;hello:1aL6vcQ7mKF0oEXlHsV330qXTMfafsN2radupW8OSMM=&quot;
    ];
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatively you can also use &lt;code&gt;attic use hello&lt;/code&gt;, which will write into &lt;code&gt;$HOME/.config/nix/nix.conf&lt;/code&gt; for you.&lt;/p&gt;
&lt;h2&gt;Push to the cache&lt;/h2&gt;
&lt;p&gt;If you have a token which can push, you can simply use &lt;code&gt;attic push hello &amp;amp;lt;path&amp;amp;gt;&lt;/code&gt; to push to your &lt;code&gt;hello&lt;/code&gt; cache, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;attic push hello /nix/store/...
attic push hello ./result
attic push hello $(nix-build)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;CI&lt;/h2&gt;
&lt;p&gt;With GitHub actions compatible CI, the &lt;a href=&quot;https://github.com/ryanccn/attic-action&quot;&gt;attic-action&lt;/a&gt; works great. You do not even need to specify what needs to be pushed, everything will be pushed at the end of the workflow run.&lt;/p&gt;
&lt;p&gt;[If the formatting of this post looks odd in your feed reader, &lt;a href=&quot;https://philippeloctaux.com/blog/self-hosted-nix-binary-cache-attic/&quot;&gt;visit the original article&lt;/a&gt;]&lt;/p&gt;
</content></entry><entry><title>Run Nix on Forgejo Actions on NixOS</title><id>urn:uuid:83effbd9-eda3-51a4-8a32-3cff00c7bf32</id><updated>2026-03-26T18:47:13+01:00</updated><author><name>Philippe Loctaux</name><uri>https://philippeloctaux.com</uri></author><link href="https://philippeloctaux.com/blog/run-nix-on-forgejo-actions-on-nixos/" rel="alternate" type="text/html"/><published>2026-03-26T18:47:13+01:00</published><content xml:lang="en" type="html">&lt;p&gt;I will start by assuming that you have a Forgejo instance running, and you know what is Forgejo Actions. If that is not the case, I invite you to read the &lt;a href=&quot;https://forgejo.org/docs/latest/user/actions/overview/&quot;&gt;Forgejo User documentation&lt;/a&gt; and the &lt;a href=&quot;https://forgejo.org/docs/latest/admin/actions/&quot;&gt;Forgejo Admin documentation&lt;/a&gt;. They are a good read!&lt;/p&gt;
&lt;p&gt;Please note that NixOS is not required to have a Forgejo Actions runner building nix code. I deploy my runner with NixOS because it is so damn simple to run and maintain.&lt;/p&gt;
&lt;h2&gt;Setup a runner&lt;/h2&gt;
&lt;h3&gt;Registration token&lt;/h3&gt;
&lt;p&gt;Let&apos;s say that your forgejo instance is available at https://forgejo.example.net.&lt;/p&gt;
&lt;p&gt;With an administrator account, go to https://forgejo.example.net/admin/actions/runners and get a registration token.&lt;/p&gt;
&lt;p&gt;Get the token, and be ready to put it when needed.&lt;/p&gt;
&lt;p&gt;If you put the registration token directly in your configuration file, it might end up in the nix store, which is publicly readable by any user on the machine. To prevent this you can encrypt it, I recommend using &lt;a href=&quot;https://github.com/ryantm/agenix&quot;&gt;agenix&lt;/a&gt; for that. It uses SSH keys for the encryption and it works great.&lt;/p&gt;
&lt;h3&gt;OCI containers and Forgejo labels&lt;/h3&gt;
&lt;p&gt;The runner will need to have a label to know which jobs it can pick up. There are many labels &lt;a href=&quot;https://forgejo.org/docs/latest/admin/actions/#choosing-labels&quot;&gt;from which you can choose from&lt;/a&gt; but for our usecase the &lt;code&gt;docker&lt;/code&gt; label is enough.&lt;/p&gt;
&lt;p&gt;I prefer to use &lt;a href=&quot;https://podman.io&quot;&gt;Podman&lt;/a&gt; over Docker, but using Docker is just fine.&lt;/p&gt;
&lt;p&gt;In the past I used the excellent guide from &lt;a href=&quot;https://icewind.nl/entry/gitea-actions-nix/&quot;&gt;Robin Appelman&lt;/a&gt; which creates a container image with Nix pre-installed. But it required flakes, and every once in a while I had to update the flake&apos;s inputs, rebuild the image, and load it to the container tool.&lt;/p&gt;
&lt;p&gt;I want something that is simpler, and requires less maintenance. Fortunately there are pre-built container images (which already work with existing GitHub workflows) for &lt;a href=&quot;https://github.com/nektos/act&quot;&gt;act&lt;/a&gt; (the Forgejo actions runner is based on act). A popular option is &lt;a href=&quot;https://github.com/catthehacker/docker_images&quot;&gt;https://github.com/catthehacker/docker_images&lt;/a&gt;, and an alternative could be &lt;a href=&quot;https://github.com/tcpipuk/act-runner&quot;&gt;https://github.com/tcpipuk/act-runner&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I will name the images like they are GitHub Actions to keep things similar.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ config, lib, pkgs, ... }:

{
  virtualisation.containers.enable = true;
  virtualisation.oci-containers.backend = &quot;podman&quot;;
  virtualisation = {
    podman = {
      enable = true;
      dockerCompat = true;
    };
  };

  services.gitea-actions-runner = {
    package = pkgs.forgejo-runner;
    instances.forgejo-runner = {
      enable = true;
      name = &quot;my-forgejo-runner&quot;; # TODO: put a cute name for your runner
      token = &quot;put-token-here&quot;; # TODO: use agenix
      url = &quot;https://forgejo.example.net/&quot;; # TODO: put the link to your instance
      labels = [
        &quot;node-24:docker://node:24-trixie&quot;
        &quot;ubuntu-24.04:docker://ghcr.io/catthehacker/ubuntu:runner-24.04&quot;
        &quot;ubuntu-latest:docker://ghcr.io/catthehacker/ubuntu:runner-latest&quot;
      ];
      settings = {
        container.network = &quot;host&quot;;
        log.level = &quot;debug&quot;; # have logs of the jobs appear in system logs
      };
    };
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You are done configuring the runner! Rebuild the NixOS machine and you should see the runner appear in https://forgejo.example.net/admin/actions/runners.&lt;/p&gt;
&lt;h2&gt;Write your workflow file&lt;/h2&gt;
&lt;p&gt;To make sure everything works, create a test git repo on Forgejo with a simple &lt;code&gt;default.nix&lt;/code&gt; or &lt;code&gt;shell.nix&lt;/code&gt;, and create a file in &lt;code&gt;.forgejo/workflows/ci.yml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To install Nix, simply use the &lt;a href=&quot;https://github.com/cachix/install-nix-action&quot;&gt;install-nix-action&lt;/a&gt; from &lt;a href=&quot;https://www.cachix.org&quot;&gt;cachix&lt;/a&gt;. If you use &lt;a href=&quot;https://github.com/andir/npins&quot;&gt;npins&lt;/a&gt;, you can even set the path to nixpkgs directly to save time:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: CI
on:
  push:
jobs:
  my_nix_job:
    runs-on: ubuntu-latest
    steps:
      - uses: https://data.forgejo.org/actions/checkout@v6
      - uses: https://github.com/cachix/install-nix-action@v31
      - name: Get Nixpkgs path
        run: |
          nixpkgs_path=$(nix eval --raw -f npins/default.nix nixpkgs)
          echo &quot;NIX_PATH=nixpkgs=$nixpkgs_path&quot; &amp;gt;&amp;gt; &quot;$FORGEJO_ENV&quot;
      - run: nix-build ./default.nix
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Commit and push the file, and you should have the job being picked on https://forgejo.example.net/user/repo/actions!&lt;/p&gt;
&lt;p&gt;[If the formatting of this post looks odd in your feed reader, &lt;a href=&quot;https://philippeloctaux.com/blog/run-nix-on-forgejo-actions-on-nixos/&quot;&gt;visit the original article&lt;/a&gt;]&lt;/p&gt;
</content></entry><entry><title>2025 Retrospective</title><id>urn:uuid:fd6ed4ca-6807-5a10-b0e3-f9f1a1b41a3b</id><updated>2025-12-31T22:53:07+01:00</updated><author><name>Philippe Loctaux</name><uri>https://philippeloctaux.com</uri></author><link href="https://philippeloctaux.com/blog/retrospective-2025/" rel="alternate" type="text/html"/><published>2025-12-31T22:53:07+01:00</published><content xml:lang="en" type="html">&lt;p&gt;Every year people set their new year&apos;s resolutions for the upcoming year, but I always forget mine. I&apos;d like to do the opposite instead: look back on what I accomplished the year that going to close in a couple of hours.&lt;/p&gt;
&lt;h2&gt;Starting with a failure&lt;/h2&gt;
&lt;p&gt;The year opened with a project started early 2024, which is a cool concept of a mobile multi player game without an internet connection. I had the plan of the technical details, and was spending a lot of time on the infrastructure and auxiliary details, but not on the application itself.&lt;/p&gt;
&lt;p&gt;My involvement in this project ended early march, pretty disappointed in the communication with the other party involved.&lt;/p&gt;
&lt;p&gt;But I learned a lot of &lt;a href=&quot;https://nixos.org&quot;&gt;nix&lt;/a&gt;, and I ported a GitHub action launching self-hosted &lt;a href=&quot;https://forgejo.org&quot;&gt; Forgejo&lt;/a&gt; Actions runners on Hetzner Cloud! It worked pretty well, here&apos;s a &lt;a href=&quot;https://github.com/deadbaed/hcloud-github-runner/tree/forgejo&quot;&gt;link to my fork&lt;/a&gt; if people using Forgejo are interested. It was useful for me to build for a aarch64 target while owning only x86-64 workers; cross-compilation did not work when I tried it.&lt;/p&gt;
&lt;h2&gt;Try something else&lt;/h2&gt;
&lt;p&gt;Took some time off &quot;big projects&quot; to experiment a bit with encryption. I tried &lt;a href=&quot;https://age-encryption.org&quot;&gt;age&lt;/a&gt; and &lt;a href=&quot;https://paseto.io&quot;&gt;PASETO&lt;/a&gt;, it was fun!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://neovim.io&quot;&gt;Neovim&lt;/a&gt; 0.11 got released, and I care a lot (too much?) about my tools, I wanted to simplify my configuration because I was adding more and more plugins. I took the time to re-work my config, and learn more about neovim and its features!&lt;/p&gt;
&lt;p&gt;I also migrated most of my dotfiles to nix with &lt;a href=&quot;https://github.com/nix-community/home-manager&quot;&gt;home-manager&lt;/a&gt; and &lt;a href=&quot;https://github.com/nix-darwin/nix-darwin&quot;&gt;nix-darwin&lt;/a&gt;. I discovered this website &lt;a href=&quot;https://home-manager-options.extranix.com&quot;&gt;listing home-manager options&lt;/a&gt; and it&apos;s pretty neat! Wished nix-darwin has the same.&lt;/p&gt;
&lt;h2&gt;Back to the old school&lt;/h2&gt;
&lt;p&gt;When I was in high school, I got to work on a project (a connected trash can) which involved an ESP32 with sensors. On this project I was working on the software: get data from the sensors, connect to a Wi-Fi network and send data over HTTP, drive a SSD1306 display to show some information. I was bad at it (first real coding experience), but I loved it.&lt;/p&gt;
&lt;p&gt;My first paid internship was to work with a ESP8266, I was tasked to write the software to connect and manage Wi-Fi networks for a IoT product. Loved it as well, I love seeing the end result and knowing what a physical device can do to enhance people&apos;s lives.&lt;/p&gt;
&lt;p&gt;I feel there is something special about embedded devices: you can create something (from the firmware to the PCB, passing by the buttons and the LEDs and the screens!), and a human being can physically interract with it! Compared to a website or an app where you can only click, type and touch -- it&apos;s still wonderful all the things we can make with software and the web, but I feel there is something special when you can actually hold it in your hand.&lt;/p&gt;
&lt;p&gt;I&apos;ve been following &lt;a href=&quot;https://mabez.dev/blog/posts/esp32-rust/&quot;&gt;Scott Mabin&lt;/a&gt;&apos;s work on getting rust programs run on ESP32s, and I am happy to see the progress made!&lt;/p&gt;
&lt;p&gt;There&apos;s a Rust library to make rust apps with a nice TUI (Terminal User Interface) called &lt;a href=&quot;https://ratatui.rs&quot;&gt;ratatui&lt;/a&gt;. You might use it if you use some apps such as &lt;a href=&quot;https://github.com/ClementTsang/bottom&quot;&gt;bottom&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I had an idea of a GPS project, and I discovered &lt;a href=&quot;https://github.com/j-g00da/mousefood&quot;&gt;mousefood&lt;/a&gt;: a backend of ratatui but for embedded devices! This is the perfect tool to display data on the embedded device itself.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./esp32-ssd1306-mousefood.jpeg&quot; alt=&quot;An ESP32 with a SSD1306 display running mousefood&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The GPS project did not go anywhere, but I contributed a bit to mousefood! It&apos;s a really cool project, it&apos;s a library I wish I had in high school when working on the connected trash can.&lt;/p&gt;
&lt;h2&gt;Blog&lt;/h2&gt;
&lt;p&gt;I wrote &lt;a href=&quot;https://philippeloctaux.com/blog/my-own-blog-engine/&quot;&gt;my own blog engine&lt;/a&gt; this summer! I need to improve the tooling around it (add more nix), but I am very happy with the result.&lt;/p&gt;
&lt;h2&gt;Rest of the year&lt;/h2&gt;
&lt;p&gt;I wanted to rewrite a project I did in the past, and fix design decisions I was not happy about. At the moment I only worked on tooling, infrastructure, and project scaffolding, but I spent a bunch of time on the architecture with diagrams and did some writing, let&apos;s see how this will go.&lt;/p&gt;
&lt;p&gt;I created &lt;a href=&quot;https://github.com/deadbaed/nix-supervisord&quot;&gt;nix-supervisord&lt;/a&gt; as an outcome of doing the tooling of that project. Pretty happy of the result! It&apos;s my first nix library, did not think I was going to write one someday!&lt;/p&gt;
&lt;p&gt;I also did maintenance on the &lt;a href=&quot;https://github.com/deadbaed/gen_passphrase&quot;&gt;gen_passphprase&lt;/a&gt; crate I maintain.&lt;/p&gt;
&lt;h2&gt;Lookback&lt;/h2&gt;
&lt;p&gt;I am overall happy with what I did this year, even though I learned some lessons the hard way: when working with someone else or for someone else, make sure to have your back covered in case something goes wrong.&lt;/p&gt;
&lt;p&gt;I enjoy working on my tools to improve them, I just need to be careful of not spending too much time on them. I like nix a lot, I think it&apos;s a wonderful tool to build and to maintain software in the long run!&lt;/p&gt;
&lt;p&gt;Embedded devices are cool, I&apos;d like to spend more time with them, maybe create something useful!&lt;/p&gt;
&lt;p&gt;Happy 2026!&lt;/p&gt;
&lt;p&gt;[If the formatting of this post looks odd in your feed reader, &lt;a href=&quot;https://philippeloctaux.com/blog/retrospective-2025/&quot;&gt;visit the original article&lt;/a&gt;]&lt;/p&gt;
</content></entry><entry><title>Neovim LSP config for TypeScript (vtsls) with Yarn Plug&apos;N&apos;Play</title><id>urn:uuid:5e822684-a223-5f79-bdd8-cf56e5e39d35</id><updated>2025-09-07T01:15:38+02:00</updated><author><name>Philippe Loctaux</name><uri>https://philippeloctaux.com</uri></author><link href="https://philippeloctaux.com/blog/neovim-yarn-plug-and-play-typescript-setup-vtsls/" rel="alternate" type="text/html"/><published>2025-09-07T01:15:38+02:00</published><content xml:lang="en" type="html">&lt;p&gt;I am starting a new project with Yarn. The last time I used it new projects defaulted to use v1, which uses the old way of installing packages in &lt;code&gt;node_modules&lt;/code&gt;, like &lt;code&gt;npm&lt;/code&gt; does.&lt;/p&gt;
&lt;p&gt;When I created my project and upgraded to use the latest version of Yarn (v4 as I am writing this), I was surpised to see that neovim could not find TypeScript types in my code! VSCodium was not able to find types either. I started to research what was going on.&lt;/p&gt;
&lt;h2&gt;Plug And Play&lt;/h2&gt;
&lt;p&gt;Yarn has multiple modes to install packages, the latest and default one is called &lt;a href=&quot;https://yarnpkg.com/features/pnp&quot;&gt;Plug&apos;N&apos;Play&lt;/a&gt; (PNP). Instead of installing packages inside a &lt;code&gt;node_modules&lt;/code&gt; folder, it will instead download them in a cache, and create a special file named &lt;code&gt;.pnp.cjs&lt;/code&gt;, defining what packages are resolved.&lt;/p&gt;
&lt;p&gt;From what I understand, you give this file to the node runtime, and it will know where to load packages from its cache.&lt;/p&gt;
&lt;h3&gt;Ghost dependencies&lt;/h3&gt;
&lt;p&gt;The main issue I had so far is the &lt;a href=&quot;https://yarnpkg.com/features/pnp#ghost-dependencies-protection&quot;&gt;ghost dependencies protection&lt;/a&gt; which requires packages to precisely define all their dependencies, something which can be tricky to do correctly.&lt;/p&gt;
&lt;p&gt;If a package does not declare their dependencies correctly and it breaks at runtime, you have multiple options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Declare in your project which packages need some extra information with a &lt;code&gt;.yarnrc.yml&lt;/code&gt; file, see the &lt;a href=&quot;https://yarnpkg.com/features/pnp#how-can-i-fix-ghost-dependencies&quot;&gt;Yarn documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Fix the packages upstream directly, but you need to be ready to spend some time doing that (which can be hard to do in some cases)&lt;/li&gt;
&lt;li&gt;Disable the PNP mode entirely, by using &lt;a href=&quot;https://yarnpkg.com/features/linkers&quot;&gt;another linker&lt;/a&gt; like &lt;code&gt;pnpm&lt;/code&gt; or going back to the &lt;code&gt;node_modules&lt;/code&gt; way.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;PNP is still recommended to use, because it has an optimized way of managing installed packages across projects, and a good protection against &lt;a href=&quot;https://yarnpkg.com/features/pnp#ghost-dependencies-protection&quot;&gt;unaccounted dependencies at runtime&lt;/a&gt; (at the cost of having all your packages declaring their dependencies correctly).&lt;/p&gt;
&lt;h3&gt;Editor support for TypeScript and others&lt;/h3&gt;
&lt;p&gt;By default, tools like LSPs and text editors will look for packages in the &lt;code&gt;node_modules&lt;/code&gt; folder, because it is the way node always worked so far.&lt;/p&gt;
&lt;p&gt;In PNP mode however, since the &lt;code&gt;.pnp.cjs&lt;/code&gt; file tells where to go instead of &lt;code&gt;node_modules&lt;/code&gt;, many tools have no idea how to load that file out of the box.&lt;/p&gt;
&lt;p&gt;Yarn has a concept of &lt;a href=&quot;https://yarnpkg.com/getting-started/editor-sdks&quot;&gt;editor SDKs&lt;/a&gt; which will help your editor find your packages. I will cover neovim, since it requires a bit of work to get it working.&lt;/p&gt;
&lt;h2&gt;Neovim setup&lt;/h2&gt;
&lt;h3&gt;Zip files&lt;/h3&gt;
&lt;p&gt;Yarn seems to heavily use zip files to store packages, and stores zip files inside other zip files. There is a plugin named &lt;a href=&quot;https://github.com/lbrayner/vim-rzip&quot;&gt;vim-rzip&lt;/a&gt; which brings this capability to neovim. Simply install it with your favorite package manager, with &lt;a href=&quot;https://lazy.folke.io&quot;&gt;lazy.nvim&lt;/a&gt; I installed it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lua&quot;&gt;require(&quot;lazy&quot;).setup({
  spec = {
    { &quot;lbrayner/vim-rzip&quot; }
  }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Editor SDK generation&lt;/h3&gt;
&lt;p&gt;To generate the Yarn editor SDK, a CLI tool is provided: Install it in inside your project with &lt;code&gt;yarn add -D @yarnpkg/sdks&lt;/code&gt;, and run &lt;code&gt;yarn sdks base&lt;/code&gt; to generate the base SDKs. For neovim this is enough, if you need some integrations can be &lt;a href=&quot;https://yarnpkg.com/cli/sdks/default#examples&quot;&gt;additionally generated&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The location of the editor SDK is in &lt;code&gt;.yarn/sdks&lt;/code&gt;. From what I understand this folder should be committed in version control.&lt;/p&gt;
&lt;h3&gt;Integration with the &lt;code&gt;vtsls&lt;/code&gt; LSP&lt;/h3&gt;
&lt;p&gt;I use the &lt;code&gt;vtsls&lt;/code&gt; LSP wrapper over &lt;code&gt;ts_ls&lt;/code&gt;, because it seems to be the only one working with the &lt;a href=&quot;https://github.com/vuejs/language-tools/wiki/Neovim/a6764ad4f57d4a9a5cf88040a792034d861d7ca1#configuration&quot;&gt;VueJS LSP&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;vtsls&lt;/code&gt; allows to provide the location of the TypeScript library and compiler (&lt;a href=&quot;https://github.com/yioneko/vtsls/blob/ae8aea1cf71acd25f10f5122b91d9bf29bdce675/packages/service/configuration.schema.json#L7&quot;&gt;config reference&lt;/a&gt;), required to use Yarn with PNP.&lt;/p&gt;
&lt;p&gt;I found a config snippet doing &lt;a href=&quot;https://github.com/parksb/dotfiles/blob/2ae492bdd4827e065ab448fb4d8315736f418f1e/nvim/lua/plugins/langs/typescript.lua#L18&quot;&gt;exactly what I wanted&lt;/a&gt;. When using neovim 0.11, the lua code will look like this to configure &lt;code&gt;vtsls&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-lua&quot;&gt;vim.lsp.config(&quot;vtsls&quot;, {
  settings = {
    vtsls = {},
    typescript = {},
  },
  before_init = function(_, config)
    local yarnPnpFile = vim.fs.find({ &quot;.pnp.cjs&quot; }, { upward = true })[1]
    if yarnPnpFile then
      config.settings.typescript.tsdk = vim.fs.dirname(yarnPnpFile) .. &quot;/.yarn/sdks/typescript/lib&quot;
      config.settings.vtsls.autoUseWorkspaceTsdk = true
    end
  end,
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thank you very much &lt;a href=&quot;https://parksb.github.io&quot;&gt;Simon Park&lt;/a&gt; for sharing your dotfiles! It saved me a lot of time.&lt;/p&gt;
&lt;p&gt;Now when opening a TypeScript file, the LSP should find your packages and do its work normally.&lt;/p&gt;
&lt;h2&gt;Future&lt;/h2&gt;
&lt;p&gt;Hopefully the &lt;a href=&quot;https://github.com/microsoft/typescript-go&quot;&gt;Go port of TypeScript&lt;/a&gt; will implement the automatic detection of packages with Yarn PNP, so that in the future no additional setup will be required when working with projects using Yarn and its PNP mode.&lt;/p&gt;
&lt;p&gt;[If the formatting of this post looks odd in your feed reader, &lt;a href=&quot;https://philippeloctaux.com/blog/neovim-yarn-plug-and-play-typescript-setup-vtsls/&quot;&gt;visit the original article&lt;/a&gt;]&lt;/p&gt;
</content></entry><entry><title>Migrating my blog to my own engine</title><id>urn:uuid:bfff5443-7f33-5512-bf21-67d50cc752c6</id><updated>2025-08-05T02:48:36+02:00</updated><author><name>Philippe Loctaux</name><uri>https://philippeloctaux.com</uri></author><link href="https://philippeloctaux.com/blog/my-own-blog-engine/" rel="alternate" type="text/html"/><published>2025-08-05T02:48:36+02:00</published><content xml:lang="en" type="html">&lt;h2&gt;Begginings&lt;/h2&gt;
&lt;p&gt;I created my blog in 2016 with a very popular blog engine at the time: &lt;a href=&quot;https://jekyllrb.com&quot;&gt;Jekyll&lt;/a&gt;. Combined with hosting provided by GitHub Pages, it is a quick way to start publishing content. I did not write a lot when I started my blog, so it was not a big deal. That worked fine for a couple of years, until I was tired of managing Ruby dependencies and warnings, and I remember getting a bit of trouble when I was running Windows.&lt;/p&gt;
&lt;p&gt;At some point I switched to &lt;a href=&quot;https://getzola.org&quot;&gt;Zola&lt;/a&gt;, and I liked it a lot: instead of managing many dependencies, there is a single binary to run to create/preview/build websites, and a good feature set out of the box.&lt;/p&gt;
&lt;p&gt;But so far the look of my blog was pretty generic, for a simple reason: I disliked writting CSS. In fact, I prefered searching for a theme that I could use rather than spending time making my own. Fortunately, there is a big pool of &lt;a href=&quot;https://www.getzola.org/themes/&quot;&gt;Zola&lt;/a&gt; and &lt;a href=&quot;https://jekyllrb.com/docs/themes/&quot;&gt;Jekyll&lt;/a&gt; themes ready to be used, so it was prefect for me!&lt;/p&gt;
&lt;p&gt;I started by blog when I did not know how to make web applications, but now I can! And now I can write some CSS, thanks to a wonderful tool named &lt;a href=&quot;https://tailwindcss.com&quot;&gt;TailwindCSS&lt;/a&gt;. Tailwind made me tolereate writting CSS, it helps me a lot to understand what I do, now it is making me enjoy writting CSS!&lt;/p&gt;
&lt;p&gt;A couple of years ago I redesigned &lt;a href=&quot;https://philippeloctaux.com&quot;&gt;my website&lt;/a&gt;, and started to use &lt;a href=&quot;https://leptos.dev&quot;&gt;Leptos&lt;/a&gt; for the templating / components part. It turns out I like Leptos &lt;em&gt;a lot&lt;/em&gt; to make reusable components.&lt;/p&gt;
&lt;p&gt;I wanted to give a fresh look to my blog, but I did not want to write HTML templates by hand or deal with raw CSS, I wanted to do it with Leptos and Tailwind! While browsing the web I found someone &lt;a href=&quot;https://blog.jlewis.sh/post/building-this-blog&quot;&gt;building their blog with Leptos and TailwindCSS&lt;/a&gt; as well!&lt;/p&gt;
&lt;h2&gt;Feature set&lt;/h2&gt;
&lt;p&gt;In my opinion, static sites generators are awesome for blogs -- You write a piece a content, give it to a tool, and it will emit files that are ready to be viewed in a web browser. That is pretty powerful, and makes the website portable, no lock-in to a specific tool! So my new blog had to be statically generated.&lt;/p&gt;
&lt;p&gt;I want to have an Web feed, it is non negociable. Web feeds are awesome, Zola and Jekyll generate an Atom feed by default, so my blog will have an Atom feed.&lt;/p&gt;
&lt;p&gt;It has to look nice, whether on desktop or mobile. Tailwind makes this easy: simply write the CSS for a mobile screen, and make some adjustements when making the screen bigger.&lt;/p&gt;
&lt;p&gt;Something I wanted to have is nice code blocks, I used &lt;a href=&quot;https://highlightjs.org/&quot;&gt;&lt;code&gt;highlight.js&lt;/code&gt;&lt;/a&gt; for this, it&apos;s a pretty nice library! If JavaScript is completely blocked on the reader&apos;s browser this is not a problem, there is a fallback option which shows the code block without any syntax highlighting. Currently highlight.js is being pulled from a CDN, it could be vendored at some point, the JavaScript files would be need to be downloaded during build time.&lt;/p&gt;
&lt;h3&gt;Content&lt;/h3&gt;
&lt;p&gt;All content is written in &lt;a href=&quot;https://commonmark.org&quot;&gt;Markdown&lt;/a&gt;, the excellent crate &lt;a href=&quot;https://crates.io/crates/pulldown-cmark/&quot;&gt;&lt;code&gt;pulldown-cmark&lt;/code&gt;&lt;/a&gt; is used to parse the content.&lt;/p&gt;
&lt;p&gt;A tiny bit of metadata is required in every piece of content:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;title&lt;/code&gt;: The Tagline of the content. Used on the homepage and on the Web feed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;date&lt;/code&gt;: Moment of publication. I started to use the crate &lt;a href=&quot;https://docs.rs/jiff/0.2.15/jiff/fmt/temporal/index.html&quot;&gt;jiff&lt;/a&gt; in all my projects, I like its API. An example moment is &lt;code&gt;2025-07-01T12:56:42+02:00[Europe/Paris]&lt;/code&gt;, I discovered &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9557.html&quot;&gt;RFC 9657&lt;/a&gt; extends &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc3339.html&quot;&gt;RFC 3339&lt;/a&gt;, it allows the IANA Time Zone to be in the date/time string!&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uuid&lt;/code&gt;: The unique identifier of the content, it is only used for the Web feed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Filename&lt;/h4&gt;
&lt;p&gt;To be considered for inclusion, the filename of every piece of content must either be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;YYYY-MM-DD-slug.md&lt;/code&gt; when there is no additional assets&lt;/li&gt;
&lt;li&gt;&lt;code&gt;YYYY-MM-DD-slug/index.md&lt;/code&gt; when assets will be put next to the &lt;code&gt;index.md&lt;/code&gt; file.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;slug&lt;/code&gt; will appear in the URL of the content. It can be anything, but it &lt;strong&gt;must not change after publication&lt;/strong&gt;, otherwise it will be published as a new content.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;YYYY-MM-DD&lt;/code&gt; part is not used on the final website anywhere, it is simply there to make the files sort naturally by name on the filesystem. It also checked against the &lt;code&gt;date&lt;/code&gt; tag in the metadata in case a typo was made.&lt;/p&gt;
&lt;h4&gt;Custom components&lt;/h4&gt;
&lt;p&gt;During markdown parsing, when an HTML block is detected, HTML tags are scanned against a list of custom HTML tags in &lt;code&gt;leptos_ssg&lt;/code&gt; to see if they match (they must not clash with HTML tags from the W3C).&lt;/p&gt;
&lt;p&gt;If there is a match, a Leptos view is rendered on the HTML page, but not on the Web feed (to keep things simple, since all Web feed readers might not display it correctly). The content author should put a paragraph above or below the custom component to explain what is happening, for Web browsers without JavaScript (if the component requires it), or for Web feed readers.&lt;/p&gt;
&lt;p&gt;The first custom component is &lt;code&gt;ImageGrid&lt;/code&gt;, which will render a grid of images from a folder. It can be used like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;amp;lt;ImageGrid src=&quot;path-to-images/&quot; /&amp;amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And it will look like this (for Web feed readers, here is &lt;a href=&quot;sample-images/&quot;&gt;the folder with sample images&lt;/a&gt;):&lt;/p&gt;
&lt;ImageGrid src=&quot;sample-images/&quot; /&gt;
&lt;h3&gt;Web feed&lt;/h3&gt;
&lt;p&gt;An Atom feed is generated, and not an RSS feed, because the tools I used previously generated Atom feeds, and because it is standardized. It seems a bit simpler to understand compared to RSS 1.0 and RSS 2.0, at a first glance.&lt;/p&gt;
&lt;p&gt;Every piece of content requires to have its own UUID, to make sure there will never be any conflicts. I would like to thank those sites:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.alanwsmith.com/en/2a/ne/cw/if/&quot;&gt;https://www.alanwsmith.com/en/2a/ne/cw/if/&lt;/a&gt; For a quick &quot;getting started&quot; guide&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://peterbabic.dev/blog/using-uuid-in-atom-feed/&quot;&gt;https://peterbabic.dev/blog/using-uuid-in-atom-feed/&lt;/a&gt; For explaining why UUIDs are needed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the Web feed, the UUID of every piece of content is a UUIDv5 composed of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The UUIDv4 of the site, which is &lt;code&gt;deadbaed-dead-4444-baed-dddeadbaeddd&lt;/code&gt; (very random huh?)&lt;/li&gt;
&lt;li&gt;The UUIDv4 of the piece of content&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That way, every single piece of content is guaranteed to always have the same UUID since the moment of publication.&lt;/p&gt;
&lt;p&gt;The only downside to this approach is to remember to generate an UUIDv4 when writting new content, but it should be fine.&lt;/p&gt;
&lt;h2&gt;It will never be finished&lt;/h2&gt;
&lt;p&gt;I am aware that a blogging engine will never be finished and it will never have all the features big engines have, and it will have some bugs.&lt;/p&gt;
&lt;p&gt;But I am very happy to make my own tools, and use them in production!&lt;/p&gt;
&lt;p&gt;The name of the engine is &lt;code&gt;leptos_ssg&lt;/code&gt;, the source code is &lt;a href=&quot;https://github.com/deadbaed/leptos_ssg&quot;&gt;available here&lt;/a&gt;, and there is an &lt;a href=&quot;https://deadbaed.github.io/leptos_ssg/&quot;&gt;instance to showcase the engine&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Write more&lt;/h2&gt;
&lt;p&gt;I hope re-building my blog (and &lt;em&gt;accidentally&lt;/em&gt; writting my own blog engine) will encourage me to write more!&lt;/p&gt;
&lt;p&gt;And the writting better be about projects I am working on, and not about the blog engine itself 😁&lt;/p&gt;
&lt;p&gt;[If the formatting of this post looks odd in your feed reader, &lt;a href=&quot;https://philippeloctaux.com/blog/my-own-blog-engine/&quot;&gt;visit the original article&lt;/a&gt;]&lt;/p&gt;
</content></entry><entry><title>Automount a Hetzner Storage Box with sshfs on NixOS</title><id>urn:uuid:3170bde1-ed1d-540c-8b9e-7c59d585d8af</id><updated>2024-08-10T00:00:00+00:00</updated><author><name>Philippe Loctaux</name><uri>https://philippeloctaux.com</uri></author><link href="https://philippeloctaux.com/blog/hetzner-storagebox-nixos-sshfs-fstab/" rel="alternate" type="text/html"/><published>2024-08-10T00:00:00+00:00</published><content xml:lang="en" type="html">&lt;p&gt;I had my eyes on some nice arm64 servers from Hetzner, and I finally pulled the trigger, I got the &lt;code&gt;CAX21&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It is also the opportunity to reduce time of maintaining my infrastructure, I will use NixOS to setup my server.
By having a couple of configuration files, it will be easier to review, edit and update the system.&lt;/p&gt;
&lt;p&gt;But time will tell if it is the good decision, and not sticking to a imperative distribution such as Debian.&lt;/p&gt;
&lt;h2&gt;Not a lot of storage&lt;/h2&gt;
&lt;p&gt;The only downside with these servers is the storage -- I only have 80 gigabytes of storage on mine.
Fortunately, Hetzner has their &lt;strong&gt;Storage Box&lt;/strong&gt; offerings, I picked up a &lt;code&gt;BX11&lt;/code&gt; which has 1 terabyte of storage!&lt;/p&gt;
&lt;p&gt;The plan is to mount the storage box as a regular drive and have applications use it normally.
The main applications will be documents, media, backups -- not speed critical data such as databases or logs.&lt;/p&gt;
&lt;h2&gt;Storage Box ordering and setup&lt;/h2&gt;
&lt;p&gt;Start by ordering your Storage Box, I think mine took less than an hour to be provisioned and delivered to me.&lt;/p&gt;
&lt;p&gt;On the configuration panel, I only ticked &lt;code&gt;SSH support&lt;/code&gt; and the disabled the rest.
If you will use the storage box outside of the Hetzner network, enable &lt;code&gt;External reachability&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Finally, you cannot set the password yourself, you will have to reset it.&lt;/p&gt;
&lt;h2&gt;SSH keys&lt;/h2&gt;
&lt;p&gt;On the server, generate a new ssh key with &lt;code&gt;ssh-keygen&lt;/code&gt; which will be used to connect to the storage box.&lt;/p&gt;
&lt;p&gt;Since I want the storage box to be mounted automatically on startup, so I did not set a passphrase on the key.&lt;/p&gt;
&lt;p&gt;Copy the ssh to the storage box with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;ssh-copy-id -p 23 -s user@storagebox.example.org
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;More documentation on ssh keys with storage box: &lt;a href=&quot;https://docs.hetzner.com/robot/storage-box/backup-space-ssh-keys&quot;&gt;https://docs.hetzner.com/robot/storage-box/backup-space-ssh-keys&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;NixOS configuration&lt;/h2&gt;
&lt;p&gt;The easy part, and the reason why I think I will like to use NixOS on my server:&lt;/p&gt;
&lt;p&gt;You can put it inside your &lt;code&gt;configuration.nix&lt;/code&gt; directly, I placed it inside its own file.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nix&quot;&gt;{ ... }:

{
  fileSystems.&quot;/mnt/storagebox&quot; = {
    device = &quot;user@storagebox.example.org:/some/path&quot;;
    fsType = &quot;fuse.sshfs&quot;;
    options = [
      &quot;identityfile=/place/to/ssh/key/somewhere&quot;
      &quot;idmap=user&quot;
      &quot;x-systemd.automount&quot; # mount the filesystem automatically on first access
      &quot;allow_other&quot; # don&apos;t restrict access to only the user which `mount`s it (because that&apos;s probably systemd who mounts it, not you)
      &quot;user&quot; # allow manual `mount`ing, as ordinary user.
      &quot;_netdev&quot;
    ];
  };
  boot.supportedFilesystems.&quot;fuse.sshfs&quot; = true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thank you so much to &lt;a href=&quot;https://discourse.nixos.org/t/how-to-auto-mount-with-sshfs-as-a-normal-user/48276/3?u=philt3r&quot;&gt;this Discourse post&lt;/a&gt; for the configuration snippet!&lt;/p&gt;
&lt;p&gt;Run&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;nixos-rebuild switch &amp;amp;&amp;amp; cd /mnt/storagebox
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and you are able to read and write files!&lt;/p&gt;
&lt;p&gt;[If the formatting of this post looks odd in your feed reader, &lt;a href=&quot;https://philippeloctaux.com/blog/hetzner-storagebox-nixos-sshfs-fstab/&quot;&gt;visit the original article&lt;/a&gt;]&lt;/p&gt;
</content></entry><entry><title>First setup of Archivebox</title><id>urn:uuid:57bf8254-2c14-5c6a-b22e-72ab6078fe43</id><updated>2024-03-02T00:00:00+00:00</updated><author><name>Philippe Loctaux</name><uri>https://philippeloctaux.com</uri></author><link href="https://philippeloctaux.com/blog/archivebox-setup/" rel="alternate" type="text/html"/><published>2024-03-02T00:00:00+00:00</published><content xml:lang="en" type="html">&lt;p&gt;I discovered &lt;a href=&quot;https://archivebox.io&quot;&gt;Archivebox&lt;/a&gt; and decided to install it on my server using containers.&lt;/p&gt;
&lt;p&gt;I just had to make a couple of adjustements, because all the content on the instance is publically available. I do not want that, I want to restrict access with user accounts.&lt;/p&gt;
&lt;p&gt;To do so, start by finding out the container name, and open a shell:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;docker exec --user=archivebox -it container-name-goes-here bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Go into the directory where Archivebox data is stored, and you will be able to run command to manage the instance.&lt;/p&gt;
&lt;h2&gt;Hide everything from the public&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;archivebox config --set SAVE_ARCHIVE_DOT_ORG=False
archivebox config --set PUBLIC_INDEX=False
archivebox config --set PUBLIC_SNAPSHOTS=False
archivebox config --set PUBLIC_ADD_VIEW=False
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Create first user account&lt;/h2&gt;
&lt;p&gt;Now that you cut outside world access to your instance, create an admin user to access and add new content:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;archivebox manage createsuperuser
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once finished, exit the shell and restart Archivebox.&lt;/p&gt;
&lt;p&gt;[If the formatting of this post looks odd in your feed reader, &lt;a href=&quot;https://philippeloctaux.com/blog/archivebox-setup/&quot;&gt;visit the original article&lt;/a&gt;]&lt;/p&gt;
</content></entry><entry><title>Setup a private Docker registry</title><id>urn:uuid:dd343f58-46d2-511e-8347-eba2e8fb4e72</id><updated>2023-07-15T00:00:00+00:00</updated><author><name>Philippe Loctaux</name><uri>https://philippeloctaux.com</uri></author><link href="https://philippeloctaux.com/blog/docker-private-registry/" rel="alternate" type="text/html"/><published>2023-07-15T00:00:00+00:00</published><content xml:lang="en" type="html">&lt;p&gt;My internal infrastructure is complete. I can now work on my projects, but at some point they need to go out to the world!&lt;/p&gt;
&lt;p&gt;The platform for most of my projects is the web, and the best tool I found so far to deploy them is Docker.&lt;/p&gt;
&lt;p&gt;I want to keep the code on the private infrastructure, but I also want to be in control of where the docker images will be stored.&lt;/p&gt;
&lt;p&gt;The perfect solution is a private Docker registry! But it will not be on the internal infrastructure, it will be publicly available on a regular server.&lt;/p&gt;
&lt;p&gt;That way, projects can be deployed in their final form whenever and wherever, while the source remaining private.&lt;/p&gt;
&lt;h2&gt;Get started locally&lt;/h2&gt;
&lt;p&gt;To start, I will launch a test registry on my machine to make sure everything works.&lt;/p&gt;
&lt;p&gt;I will use these docker images:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://hub.docker.com/_/registry&quot;&gt;registry&lt;/a&gt;: The official registry made by Docker themselves&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://joxit.dev/docker-registry-ui&quot;&gt;docker-registry-ui&lt;/a&gt;: A nice webui to view and manage images on the registry&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&apos;s the docker-compose file I used to get started:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:

  registry-server:
    image: registry:2.8.2
    ports:
      - 5000:5000
    volumes:
      - ./registry-data:/var/lib/registry
      - ./passwords:/auth/htpasswd
    environment:
      REGISTRY_AUTH: &apos;htpasswd&apos;
      REGISTRY_AUTH_HTPASSWD_REALM: &apos;Registry Realm&apos;
      REGISTRY_AUTH_HTPASSWD_PATH: &apos;/auth/htpasswd&apos;
      REGISTRY_HTTP_HEADERS_Access-Control-Origin: &apos;[http://registry.example.com]&apos;
      REGISTRY_HTTP_HEADERS_Access-Control-Allow-Methods: &apos;[HEAD,GET,OPTIONS,DELETE]&apos;
      REGISTRY_HTTP_HEADERS_Access-Control-Credentials: &apos;[true]&apos;
      REGISTRY_HTTP_HEADERS_Access-Control-Allow-Headers: &apos;[Authorization,Accept,Cache-Control]&apos;
      REGISTRY_HTTP_HEADERS_Access-Control-Expose-Headers: &apos;[Docker-Content-Digest]&apos;
      REGISTRY_STORAGE_DELETE_ENABLED: &apos;true&apos;
    container_name: registry-server

  registry-ui:
    image: joxit/docker-registry-ui:2.5.0
    ports:
      - 8001:80
    environment:
      SINGLE_REGISTRY: true
      REGISTRY_TITLE: Docker Registry UI
      DELETE_IMAGES: true
      SHOW_CONTENT_DIGEST: true
      NGINX_PROXY_PASS_URL: http://registry-server:5000
      SHOW_CATALOG_NB_TAGS: true
      CATALOG_MIN_BRANCHES: 1
      CATALOG_MAX_BRANCHES: 1
      TAGLIST_PAGE_SIZE: 100
      REGISTRY_SECURED: false
      CATALOG_ELEMENTS_LIMIT: 1000
    container_name: registry-ui
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But don&apos;t start the services right away.&lt;/p&gt;
&lt;h2&gt;Authentication&lt;/h2&gt;
&lt;p&gt;I don&apos;t want the registry being open to everyone though, let&apos;s add some authentication.&lt;/p&gt;
&lt;p&gt;To keep things simple, I will use HTTP basic auth. If you want, there&apos;s a possibility to have a more &lt;a href=&quot;https://docs.docker.com/registry/spec/auth/&quot;&gt;complex setup&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here&apos;s a quick script to get passwords in a format that Docker will accept:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;#!/bin/sh
#
# new-password.sh

if [ -z &quot;$1&quot; ]
then
echo &quot;usage: $0 username&quot;
exit 1
fi

echo &quot;creating password for user \&quot;$1\&quot;&quot;
htpasswd -nB $1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How to use it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;$ ./new-password.sh phil
creating password for user &quot;phil&quot;
New password: phil
Re-type new password: phil
phil:$2y$05$asxsqfmEQJpg8zuKGyieMOmTirok.Gd/noliF.y48DJXe.97ufGHG
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copy the last line in the &lt;code&gt;passwords&lt;/code&gt; file (see the &lt;code&gt;docker-compose&lt;/code&gt; file).&lt;/p&gt;
&lt;p&gt;Repeat the process for every user you want to give authentication to your registry.&lt;/p&gt;
&lt;p&gt;Keep in mind I only cover &lt;strong&gt;AUTHENTICATION&lt;/strong&gt; (who can access the registry), and not &lt;strong&gt;AUTHORIZATION&lt;/strong&gt; (who can do what on the registry). With this setup, if you have access to the registry, you can do anything on it.&lt;/p&gt;
&lt;h2&gt;Use the registry&lt;/h2&gt;
&lt;p&gt;Start the services with&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;docker compose up
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, you can access the webui by going to&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http://localhost:8001
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;in a web browser and sign-in with your credentials. You should see an empty list. Let&apos;s add some images!&lt;/p&gt;
&lt;h2&gt;Naming images&lt;/h2&gt;
&lt;p&gt;Pick an image you want on the registry.&lt;/p&gt;
&lt;p&gt;If it&apos;s an existing image:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;docker tag name-of-existing-image localhost:5000/existing-image-name
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you build the image directly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;docker build -t localhost:5000/new-image-name
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The name of the image must have the domain of the registry, in our case it&apos;s &lt;code&gt;localhost:5000&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Login to registry&lt;/h2&gt;
&lt;p&gt;To sign in to the registry, use&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;docker login localhost:5000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and enter your credentials.&lt;/p&gt;
&lt;h2&gt;Push / Pull&lt;/h2&gt;
&lt;p&gt;Simply run the usual docker command to push or pull images. Docker will know which registry to use based of the image&apos;s name.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;docker push localhost:5000/new-image-name
docker pull localhost:5000/existing-image-name
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s pretty much it!&lt;/p&gt;
&lt;h2&gt;Deploy to production&lt;/h2&gt;
&lt;p&gt;I use &lt;a href=&quot;https://caprover.com&quot;&gt;Caprover&lt;/a&gt; to deploy my docker images easily, it comes with a reverse proxy and automatic TLS certificates with Let&apos;s encrypt.&lt;/p&gt;
&lt;p&gt;Here&apos;s the one-click-app config I created for the registry:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;captainVersion: 4

services:
  $$cap_appname-registry:
    image: registry:$$cap_registry_version
    volumes:
      - $$cap_appname-data:/var/lib/registry
      - $$cap_appname-auth:/auth/
    environment:
      REGISTRY_AUTH: &apos;htpasswd&apos;
      REGISTRY_AUTH_HTPASSWD_REALM: &apos;Registry Realm&apos;
      REGISTRY_AUTH_HTPASSWD_PATH: &apos;/auth/htpasswd&apos;
      REGISTRY_HTTP_HEADERS_Access-Control-Origin: &apos;[https://$$cap_appname-registry.$$cap_root_domain, https://$$cap_appname-ui.$$cap_root_domain]&apos;
      REGISTRY_HTTP_HEADERS_Access-Control-Allow-Methods: &apos;[HEAD,GET,OPTIONS,DELETE]&apos;
      REGISTRY_HTTP_HEADERS_Access-Control-Credentials: &apos;[true]&apos;
      REGISTRY_HTTP_HEADERS_Access-Control-Allow-Headers: &apos;[Authorization,Accept,Cache-Control]&apos;
      REGISTRY_HTTP_HEADERS_Access-Control-Expose-Headers: &apos;[Docker-Content-Digest]&apos;
      REGISTRY_STORAGE_DELETE_ENABLED: &apos;true&apos;
    caproverExtra:
        containerHttpPort: &apos;5000&apos;

  $$cap_appname-ui:
    image: joxit/docker-registry-ui:$$cap_ui_version
    environment:
      SINGLE_REGISTRY: true
      REGISTRY_TITLE: Docker Registry UI
      DELETE_IMAGES: true
      SHOW_CONTENT_DIGEST: true
      NGINX_PROXY_PASS_URL: http://srv-captain--$$cap_appname-registry:5000
      SHOW_CATALOG_NB_TAGS: true
      CATALOG_MIN_BRANCHES: 1
      CATALOG_MAX_BRANCHES: 1
      TAGLIST_PAGE_SIZE: 100
      REGISTRY_SECURED: false
      CATALOG_ELEMENTS_LIMIT: 1000

caproverOneClickApp:
    variables:
        - id: &apos;$$cap_registry_version&apos;
          label: Registry Version
          defaultValue: &apos;2.8.2&apos;
          description: Check out the Docker page for the valid tags https://hub.docker.com/_/registry/tags
          validRegex: &quot;/.{1,}/&quot;
        - id: &apos;$$cap_ui_version&apos;
          label: UI Version
          defaultValue: &apos;2.5.0&apos;
          description: Check out the Docker page for the valid tags https://hub.docker.com/r/joxit/docker-registry-ui/tags
          validRegex: &quot;/.{1,}/&quot;
    instructions:
        start: |-
            A private docker registry, with a webui to see images
        end: |-
            The registry has been deployed! Look in the &quot;auth&quot; volume to update credentials
    displayName: docker-registry-with-ui
    isOfficial: false
    description: A private docker registry, with a webui to see images
    documentation: https://docs.docker.com/registry/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;[If the formatting of this post looks odd in your feed reader, &lt;a href=&quot;https://philippeloctaux.com/blog/docker-private-registry/&quot;&gt;visit the original article&lt;/a&gt;]&lt;/p&gt;
</content></entry><entry><title>Setup a service on our internal infrastructure on Alpine Linux</title><id>urn:uuid:18497c66-dfb4-5763-a598-e7ce187763dd</id><updated>2023-07-02T00:00:00+00:00</updated><author><name>Philippe Loctaux</name><uri>https://philippeloctaux.com</uri></author><link href="https://philippeloctaux.com/blog/service-internal-infra-alpine/" rel="alternate" type="text/html"/><published>2023-07-02T00:00:00+00:00</published><content xml:lang="en" type="html">&lt;p&gt;Now we have a basic internal infrastructure with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Everything hidden and encrypted through the network (WireGuard)&lt;/li&gt;
&lt;li&gt;Pretty internal domain names instead of raw ip addresses (CoreDNS)&lt;/li&gt;
&lt;li&gt;A basic http server just in case (Caddy)&lt;/li&gt;
&lt;li&gt;Our own TLS certificates that are easy to get (Step CA)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But everything is on the same machine. While it could be okay, I will host the services I want on other machines.&lt;/p&gt;
&lt;p&gt;I will run them on the same Proxmox cluster, but the possibilities are endless (as long you can get WireGuard running).&lt;/p&gt;
&lt;h2&gt;Get started&lt;/h2&gt;
&lt;p&gt;Install Alpine. Setup ssh and repositories.&lt;/p&gt;
&lt;h2&gt;WireGuard&lt;/h2&gt;
&lt;p&gt;We will set up WireGuard, but not a server, a regular peer that will connect to the WireGuard server.&lt;/p&gt;
&lt;p&gt;Create a new peer on the WireGuard server, and get the config file ready.&lt;/p&gt;
&lt;h2&gt;Install&lt;/h2&gt;
&lt;p&gt;Install WireGuard:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;apk add wireguard-tools
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To load the WireGuard module on startup, edit&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/modules
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and simply add&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wireguard
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and reboot.&lt;/p&gt;
&lt;h2&gt;Configure&lt;/h2&gt;
&lt;p&gt;Put the WireGuard config to&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/wireguard/wg0.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Start&lt;/h2&gt;
&lt;p&gt;Copy the &lt;code&gt;init.d&lt;/code&gt; script for WireGuard like we did for the original server.&lt;/p&gt;
&lt;p&gt;And ask it to start on boot.&lt;/p&gt;
&lt;p&gt;Reboot and make sure everything works, you should see WireGuard logs when the machine is starting.&lt;/p&gt;
&lt;p&gt;And the DNS should be working! Try to ping an internal DNS name.&lt;/p&gt;
&lt;p&gt;Sometimes the DNS will go back to the system&apos;s default (probably your DHCP server&apos;s), so force the DNS as seen in the post about CoreDNS.&lt;/p&gt;
&lt;h2&gt;DNS entry&lt;/h2&gt;
&lt;p&gt;In the main server, edit CoreDNS to add a new DNS entry for the newly added peer.&lt;/p&gt;
&lt;p&gt;Save and restart CoreDNS.&lt;/p&gt;
&lt;h2&gt;MOTD&lt;/h2&gt;
&lt;p&gt;Add the dynamic MOTD if you feel like it. I did.&lt;/p&gt;
&lt;h2&gt;Reverse proxy&lt;/h2&gt;
&lt;p&gt;Before installing and starting services, let&apos;s add a reverse proxy for security + some sweet TLS certs.&lt;/p&gt;
&lt;p&gt;I&apos;ll be using caddy. You will need to enable the &lt;code&gt;community&lt;/code&gt; repo first.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;apk add caddy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s get a hello world:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;/etc/caddy/Caddyfile
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;## global
{
        # step-ca ACME server
        acme_ca https://10.131.111.1:444/acme/acme/directory
}

docker.philt3r docker.philt3r:80 {
    respond &quot;Hello, world!&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I start the service on ports &lt;code&gt;80&lt;/code&gt; and &lt;code&gt;443&lt;/code&gt; to get the initial TLS certificate, I will remove access on port &lt;code&gt;80&lt;/code&gt; afterward.&lt;/p&gt;
&lt;p&gt;Don&apos;t start caddy yet.&lt;/p&gt;
&lt;h2&gt;TLS certificates&lt;/h2&gt;
&lt;p&gt;On our new server, we need to trust the root ca. Download the root ca, and ask the system to trust it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;apk add ca-certificates ca-certificates-bundle
wget --no-check-certificate https://10.131.111.1:444/roots.pem -O /usr/local/share/ca-certificates/philt3r.crt
update-ca-certificates 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can start caddy and enable it on boot:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;rc-service caddy start
rc-update add caddy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should get a Hello World on port 443. If you do, you can disable access from port &lt;code&gt;80&lt;/code&gt; in the Caddyfile and restart caddy.&lt;/p&gt;
&lt;h2&gt;Install the service&lt;/h2&gt;
&lt;p&gt;Now we can install the service we want to host, start it, and configure caddy to be a reverse proxy for it.&lt;/p&gt;
&lt;p&gt;Repeat the process for the other services you want to host.&lt;/p&gt;
&lt;p&gt;Protip: serve the services on &lt;code&gt;127.0.0.1&lt;/code&gt; and use caddy to restrict access only from the WireGuard peers (since there is the DNS restriction).&lt;/p&gt;
&lt;p&gt;Sample &lt;code&gt;Caddyfile&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## global
{
        # step-ca ACME server
        acme_ca https://10.131.111.1:444/acme/acme/directory
}

docker.philt3r {
        reverse_proxy 127.0.0.1:3000
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Docker&lt;/h2&gt;
&lt;p&gt;Since I&apos;ll be using Docker to host most services, I&apos;ll install it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;apk add docker docker-compose
rc-update add docker
rc-service docker start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then spin up your docker containers and route them with caddy.&lt;/p&gt;
&lt;p&gt;[If the formatting of this post looks odd in your feed reader, &lt;a href=&quot;https://philippeloctaux.com/blog/service-internal-infra-alpine/&quot;&gt;visit the original article&lt;/a&gt;]&lt;/p&gt;
</content></entry><entry><title>Dynamic MOTD on Alpine Linux</title><id>urn:uuid:f483f723-f67a-525a-bd14-fcae972f68d1</id><updated>2023-06-30T00:00:00+00:00</updated><author><name>Philippe Loctaux</name><uri>https://philippeloctaux.com</uri></author><link href="https://philippeloctaux.com/blog/dynamic-motd-alpine/" rel="alternate" type="text/html"/><published>2023-06-30T00:00:00+00:00</published><content xml:lang="en" type="html">&lt;p&gt;When we sign in to our server, the message of the day (MOTD) is pretty lame. Let&apos;s get something better!&lt;/p&gt;
&lt;p&gt;This is the default MOTD of alpine:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Welcome to Alpine!

The Alpine Wiki contains a large amount of how-to guides and general
information about administrating Alpine systems.
See &amp;amp;lt;http://wiki.alpinelinux.org&amp;amp;gt;.

You can setup the system with the command: setup-alpine

You may change this message by editing /etc/motd.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here&apos;s my new MOTD. I even show the WireGuard ip address:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;

  Name: intra.philt3r
  Kernel: 6.1.35-0-lts
  Distro: Alpine Linux v3.18
  Version 3.18.2

  Uptime: 0 days, 0 hours, 22 minutes
  CPU Load: 0.00, 0.00, 0.00

  Memory: 468M
  Free Memory: 217M

  Disk: 6.6G
  Free Disk: 6.6G

  eth0 Address: 192.168.1.71
  wg0 Address: 10.131.111.1


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start and enable cron at startup (it should be installed by default):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;rc-service crond start
rc-update add crond
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s run a script every 15 minutes to update the &lt;code&gt;/etc/motd&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/periodic/15min/motd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&apos;s the content of my MOTD:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;#!/bin/sh
#. /etc/os-release
PRETTY_NAME=`awk -F= &apos;$1==&quot;PRETTY_NAME&quot; { print $2 ;}&apos; /etc/os-release | tr -d &apos;&quot;&apos;`
VERSION_ID=`awk -F= &apos;$1==&quot;VERSION_ID&quot; { print $2 ;}&apos; /etc/os-release`
UPTIME_DAYS=$(expr `cat /proc/uptime | cut -d &apos;.&apos; -f1` % 31556926 / 86400)
UPTIME_HOURS=$(expr `cat /proc/uptime | cut -d &apos;.&apos; -f1` % 31556926 % 86400 / 3600)
UPTIME_MINUTES=$(expr `cat /proc/uptime | cut -d &apos;.&apos; -f1` % 31556926 % 86400 % 3600 / 60)
cat &amp;gt; /etc/motd &amp;lt;&amp;lt; EOF


  Name: `hostname`
  Kernel: `uname -r`
  Distro: $PRETTY_NAME
  Version $VERSION_ID

  Uptime: $UPTIME_DAYS days, $UPTIME_HOURS hours, $UPTIME_MINUTES minutes
  CPU Load: `cat /proc/loadavg | awk &apos;{print $1 &quot;, &quot; $2 &quot;, &quot; $3}&apos;`

  Memory: `free -m | head -n 2 | tail -n 1 | awk {&apos;print  $2&apos;}`M
  Free Memory: `free -m | head -n 2 | tail -n 1 | awk {&apos;print $4&apos;}`M

  Disk: `df -h / | awk  &apos;{ a = $2 } END { print a }&apos;`
  Free Disk: `df -h / | awk &apos;{ a =  $2 } END { print a }&apos;`

  eth0 Address: `ifconfig eth0 | grep &quot;inet addr&quot; |  awk -F: &apos;{print $2}&apos; | awk &apos;{print $1}&apos;`
  wg0 Address: `ifconfig wg0 | grep &quot;inet addr&quot; |  awk -F: &apos;{print $2}&apos; | awk &apos;{print $1}&apos;`


EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make the script executable, and check if it&apos;s good:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;chmod a+x /etc/periodic/15min/motd
run-parts --test /etc/periodic/15min
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you&apos;re lazy and don&apos;t want to wait 15 minutes, run the script directly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;/etc/periodic/15min/motd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Log out and log back in, you should see the new MOTD!&lt;/p&gt;
&lt;h2&gt;Resources&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://kingtam.win/archives/apline-custom.html&quot;&gt;https://kingtam.win/archives/apline-custom.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I just copy/pasted and changed the MOTD.&lt;/p&gt;
&lt;p&gt;[If the formatting of this post looks odd in your feed reader, &lt;a href=&quot;https://philippeloctaux.com/blog/dynamic-motd-alpine/&quot;&gt;visit the original article&lt;/a&gt;]&lt;/p&gt;
</content></entry><entry><title>Setup Caddy with a CA and ACME server on Alpine Linux</title><id>urn:uuid:fc2b90d0-a852-5e83-9303-53a58b88b35b</id><updated>2023-06-28T00:00:00+00:00</updated><author><name>Philippe Loctaux</name><uri>https://philippeloctaux.com</uri></author><link href="https://philippeloctaux.com/blog/caddy-ca-acme-alpine/" rel="alternate" type="text/html"/><published>2023-06-28T00:00:00+00:00</published><content xml:lang="en" type="html">&lt;p&gt;Now that we have a WireGuard VPN with an awesome internal DNS server, let&apos;s get a web server with HTTPS!&lt;/p&gt;
&lt;h2&gt;Caddy&lt;/h2&gt;
&lt;h2&gt;Install&lt;/h2&gt;
&lt;p&gt;You will need to enable the &lt;code&gt;community&lt;/code&gt; repo first.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;doas apk add caddy
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Configuration&lt;/h2&gt;
&lt;p&gt;Create a folder to serve stuff from, I placed it in&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/srv/www
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create the config in&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;/etc/caddy/Caddyfile
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&apos;s the config, it&apos;s very simple to get started:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;intra.philt3r:80
root * /srv/www
file_server browse
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This config will only launch an HTTP server, the HTTPS will come later.&lt;/p&gt;
&lt;p&gt;It should work only from the WireGuard peers, since they can resolve the DNS name &lt;code&gt;intra.philt3r&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If there is no &lt;code&gt;index.html&lt;/code&gt; in the folder, it will serve static files directly.&lt;/p&gt;
&lt;h2&gt;Script to launch&lt;/h2&gt;
&lt;p&gt;Caddy already has a service!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start: &lt;code&gt;rc-service caddy start&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Stop: &lt;code&gt;rc-service caddy stop&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Reload configuration without downtime: &lt;code&gt;rc-service caddy reload&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Generate keys and certificates&lt;/h2&gt;
&lt;p&gt;We will generate the Root CA, the Intermediate CA.&lt;/p&gt;
&lt;p&gt;Generate these with &lt;code&gt;openssl&lt;/code&gt; installed on a computer, preferabbly offline.&lt;/p&gt;
&lt;p&gt;Make sure the keys are stored in a safe place, I will store mine inside of a KeePassXC keystore.&lt;/p&gt;
&lt;h2&gt;OpenSSL Configuration&lt;/h2&gt;
&lt;p&gt;inside a folder, create a file&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;config.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In &lt;code&gt;[CA_root]&lt;/code&gt;, make sure to put your folder &lt;code&gt;dir&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;# OpenSSL root CA configuration file.

[ ca ]
# `man ca`
default_ca = CA_root

[ CA_root ]
# Directory and file locations.
dir               = /home/phil/ca
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial
RANDFILE          = $dir/private/.rand

# The root key and root certificate.
# Match names with Smallstep naming convention
private_key       = $dir/root_ca_key
certificate       = $dir/root_ca.crt

# For certificate revocation lists.
crlnumber         = $dir/crlnumber
crl               = $dir/crl/ca.crl.pem
crl_extensions    = crl_ext
default_crl_days  = 30

# SHA-1 is deprecated, so use SHA-2 instead.
default_md        = sha256

name_opt          = ca_default
cert_opt          = ca_default
default_days      = 25202
preserve          = no
policy            = policy_strict

[ policy_strict ]
# The root CA should only sign intermediate certificates that match.
# See the POLICY FORMAT section of `man ca`.
countryName             = match
stateOrProvinceName 	= supplied
localityName	    	= supplied
organizationName        = match
commonName              = supplied

[ req ]
# Options for the `req` tool (`man req`).
default_bits        = 4096
distinguished_name  = req_distinguished_name
string_mask         = utf8only

# SHA-1 is deprecated, so use SHA-2 instead.
default_md          = sha256

# Extension to add when the -x509 option is used.
x509_extensions     = v3_ca

[ req_distinguished_name ]
# See &amp;amp;lt;https://en.wikipedia.org/wiki/Certificate_signing_request&amp;gt;&amp;amp;gt;.
countryName          = Country (2 letter code)
stateOrProvinceName  = State or Region
localityName         = City
commonName           = Common Name
0.organizationName   = Organization Name

[ v3_ca ]
# Extensions for a typical CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]
# Extensions for a typical intermediate CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After, run&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;mkdir newcerts
touch index.txt
echo 1420 &amp;gt; serial
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We are now ready to generate keys and certificates.&lt;/p&gt;
&lt;h2&gt;Root key and certificate&lt;/h2&gt;
&lt;p&gt;Generate key:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;openssl genrsa -aes256 -out root_ca_key 4096
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will ask for a passphrase, I generated mine with my KeePassXC.&lt;/p&gt;
&lt;p&gt;Generate root certificate:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;openssl req -config config.conf -key root_ca_key -days 3650 -new -x509 -sha256 -extensions v3_ca -out root_ca.crt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My root CA will last for 3650 days (10 years).&lt;/p&gt;
&lt;p&gt;Here&apos;s the info I provided:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Country (2 letter code) []:FR
State or Region []:Bretagne
City []:Rennes
Common Name []:philt3r CA
Organization Name []:philt3r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I saved the &lt;code&gt;root_ca_key&lt;/code&gt; and &lt;code&gt;root_ca.crt&lt;/code&gt; inside my KeePassXC.&lt;/p&gt;
&lt;h2&gt;Intermediate key and certificate&lt;/h2&gt;
&lt;p&gt;Generate key:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;openssl genrsa -aes256 -out intermediate_ca_key 4096
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will ask for a passphrase, I generated mine with my KeePassXC.&lt;/p&gt;
&lt;p&gt;Generate certificate request:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;openssl req -config config.conf -new -sha256 -key intermediate_ca_key -out intermediate_ca.csr.pem
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&apos;s the info I provided:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Country (2 letter code) []:FR
State or Region []:Bretagne
City []:Rennes
Common Name []:philt3r Intermediate CA 
Organization Name []:philt3r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sign certificate request with Root key:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;openssl ca -config config.conf -keyfile root_ca_key -cert root_ca.crt -extensions v3_intermediate_ca -days 1825 -notext -md sha256 -in intermediate_ca.csr.pem -out intermediate_ca.crt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My Intermediate certificate will last for 1825 days (5 years).&lt;/p&gt;
&lt;p&gt;Save these files, I saved them in my KeePassXC:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;intermediate_ca_key&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;intermediate_ca.csr.pem&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;intermediate_ca.crt&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once everything is saved and backed up, delete everything from your computer securely.&lt;/p&gt;
&lt;h2&gt;CA and ACME server&lt;/h2&gt;
&lt;p&gt;I discovered &lt;a href=&quot;https://smallstep.com/&quot;&gt;Smallstep&lt;/a&gt;, which allows to become your own ACME server.&lt;/p&gt;
&lt;h2&gt;Install&lt;/h2&gt;
&lt;p&gt;They provide packages for Alpine!&lt;/p&gt;
&lt;p&gt;Install the packages with&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;apk add step-cli step-certificates
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Configuration&lt;/h2&gt;
&lt;p&gt;Start by creating the folder where &lt;code&gt;step&lt;/code&gt; will save all the configs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;mkdir /etc/step-ca -p
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s configure &lt;code&gt;step-ca&lt;/code&gt;!&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;STEPPATH=/etc/step-ca step ca init --name=&quot;philt3r&quot; --acme --address=&quot;10.131.111.1:444&quot; --provisioner=&quot;philt3r&quot; --deployment-type standalone
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I ask it to run on the address &lt;code&gt;10.131.111.1&lt;/code&gt; (the WireGuard ip) and on the port &lt;code&gt;444&lt;/code&gt;. The port &lt;code&gt;443&lt;/code&gt; will be used for a https server, so I picked 443 + 1.&lt;/p&gt;
&lt;p&gt;Since I want an ACME server, I asked to get one.&lt;/p&gt;
&lt;p&gt;Step will ask what IP address the clients will use to reach your ca, reply with &lt;code&gt;10.131.111.1&lt;/code&gt;, because only WireGuard peers and the server should be allowed.&lt;/p&gt;
&lt;p&gt;This will prompt a password, put one.&lt;/p&gt;
&lt;p&gt;Step will generate a root and intermediate key, as well as an intermediate certificate. We don&apos;t want that, since we already generated our own.&lt;/p&gt;
&lt;p&gt;Copy these files in &lt;code&gt;/etc/step-ca/certs&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;root_ca.crt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;intermediate_ca.crt&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Copy &lt;code&gt;intermediate_ca_key&lt;/code&gt; in &lt;code&gt;/etc/step-ca/secrets&lt;/code&gt; folder. I use the key directly, but in a safe environment use a Yubikey, but I don&apos;t have one.&lt;/p&gt;
&lt;h2&gt;Start the CA/ACME server&lt;/h2&gt;
&lt;p&gt;Run&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;step-ca /etc/step-ca/config/ca.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to start the server. It will ask your password to decrypt the &lt;code&gt;intermediate_ca_key&lt;/code&gt;. Provide the password.&lt;/p&gt;
&lt;p&gt;The server should start, stop it.&lt;/p&gt;
&lt;p&gt;We will now create a file containing the password of the &lt;code&gt;intermediate_ca_key&lt;/code&gt;, since we want to have the ACME server starting when Alpine will boot.&lt;/p&gt;
&lt;p&gt;Why put the password inside a file? Well, simply because we can&apos;t type the password at boot. Again, in an ideal environment, use a Yubikey.&lt;/p&gt;
&lt;p&gt;Create a file at&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/step-ca/password.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and place the password inside that file.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;step&lt;/code&gt; should run as the user &lt;code&gt;step-ca&lt;/code&gt;, so update the permissions on the config folder:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;chown step-ca:step-ca -Rv /etc/step-ca/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To verify that everything worked, run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;step-ca /etc/step-ca/config/ca.json --password-file=/etc/step-ca/password.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Stop the server again.&lt;/p&gt;
&lt;h2&gt;Script to launch&lt;/h2&gt;
&lt;p&gt;Step already has a service!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start: &lt;code&gt;rc-service step-ca start&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Stop: &lt;code&gt;rc-service step-ca stop&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Use ACME with Caddy&lt;/h1&gt;
&lt;p&gt;Now let&apos;s tell Caddy to get TLS certificates with our ACME server.&lt;/p&gt;
&lt;p&gt;Edit the &lt;code&gt;/etc/caddy/Caddyfile&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# global
{
        # step-ca ACME server
        acme_ca https://10.131.111.1:444/acme/acme/directory
}

intra.philt3r intra.philt3r:80 {
        root * /srv/www
        file_server browse
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure &lt;code&gt;step-ca&lt;/code&gt; is started, and restart Caddy to make sure everything is good:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;rc-service caddy restart
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we need to tell our system to trust the certificates.&lt;/p&gt;
&lt;p&gt;Download the file containing the certificates. It is available at this URL:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://10.131.111.1:444/roots.pem
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On every device you want to trust your certificates, you will need to download the file on the device, then you will need to tell your operating system to trust it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OSX: &lt;a href=&quot;https://tosbourn.com/getting-os-x-to-trust-self-signed-ssl-certificates/&quot;&gt;Trust the certificate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;iOS: Download the certificate from the device and &lt;a href=&quot;https://support.apple.com/en-us/HT204477&quot;&gt;trust it&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Firefox: Install the certificate on your system and &lt;a href=&quot;https://support.mozilla.org/en-US/kb/setting-certificate-authorities-firefox&quot;&gt;tell firefox to trust it&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Linux distros: &lt;a href=&quot;https://ubuntu.com/server/docs/security-trust-store&quot;&gt;Ubuntu&lt;/a&gt;, &lt;a href=&quot;https://docs.fedoraproject.org/en-US/quick-docs/using-shared-system-certificates&quot;&gt;Fedora&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Start on boot&lt;/h2&gt;
&lt;p&gt;Start &lt;code&gt;caddy&lt;/code&gt; and &lt;code&gt;step-ca&lt;/code&gt; on startup with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;rc-update add step-ca
rc-update add caddy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reboot to make sure everything works.&lt;/p&gt;
&lt;h2&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.alpinelinux.org/wiki/Repositories&quot;&gt;https://wiki.alpinelinux.org/wiki/Repositories&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Awesome guide that helped me a lot: &lt;a href=&quot;https://www.apalrd.net/posts/2023/network_acme/&quot;&gt;https://www.apalrd.net/posts/2023/network_acme/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;[If the formatting of this post looks odd in your feed reader, &lt;a href=&quot;https://philippeloctaux.com/blog/caddy-ca-acme-alpine/&quot;&gt;visit the original article&lt;/a&gt;]&lt;/p&gt;
</content></entry><entry><title>Setup CoreDNS on Alpine Linux</title><id>urn:uuid:1b54b7b4-3656-5631-848d-6d87e978d03e</id><updated>2023-06-25T00:00:00+00:00</updated><author><name>Philippe Loctaux</name><uri>https://philippeloctaux.com</uri></author><link href="https://philippeloctaux.com/blog/coredns-alpine/" rel="alternate" type="text/html"/><published>2023-06-25T00:00:00+00:00</published><content xml:lang="en" type="html">&lt;p&gt;Now that we have a WireGuard VPN, let&apos;s add a DNS server, to type letters instead of numbers!&lt;/p&gt;
&lt;h2&gt;Install CoreDNS&lt;/h2&gt;
&lt;p&gt;You will need to enable the &lt;code&gt;community&lt;/code&gt; repo first.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;doas apk add coredns
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Configuration&lt;/h2&gt;
&lt;p&gt;Create the config in&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;/etc/coredns/Corefile
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# snippets
(common) {
    cache 60
    acl {
        allow net 127.0.0.1 10.131.110.0/24 10.131.111.0/24
        block
    }
}

# intranet
philt3r {
    import common
    log . {combined} {
        class denial error success
    }

    hosts {
        10.131.111.1 intra.philt3r
        falltrough
    }
}

# extranet
. {
    import common

    # Free DNS
    forward . 212.27.40.240 212.27.40.241
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My DNS service of choice comes from &lt;a href=&quot;https://free.fr&quot;&gt;free.fr&lt;/a&gt;. Feel free to put your own favorite DNS service!&lt;/p&gt;
&lt;h2&gt;Script to launch on server startup&lt;/h2&gt;
&lt;p&gt;CoreDNS already has a service!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add at startup: &lt;code&gt;rc-update add coredns&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Remove from startup: &lt;code&gt;rc-update del coredns&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Show services at startup: &lt;code&gt;rc-status&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The logs of CoreDNS should be available at&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/var/log/coredns/coredns.log
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Use CoreDNS on the system&lt;/h2&gt;
&lt;p&gt;Now that we have our DNS server, let&apos;s use it on our server!&lt;/p&gt;
&lt;p&gt;If you use DHCP to get the ip address of your server, the DNS will always be used from the DHCP.&lt;/p&gt;
&lt;p&gt;We want to use our own DHCP server.&lt;/p&gt;
&lt;p&gt;Create the file (and the folder associated with it)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/udhcpc/udhcpc.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and put&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RESOLV_CONF=&quot;NO&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, edit the&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/resolv.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and put&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nameserver 127.0.0.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart the server.&lt;/p&gt;
&lt;p&gt;[If the formatting of this post looks odd in your feed reader, &lt;a href=&quot;https://philippeloctaux.com/blog/coredns-alpine/&quot;&gt;visit the original article&lt;/a&gt;]&lt;/p&gt;
</content></entry><entry><title>Setup WireGuard server on Alpine Linux</title><id>urn:uuid:8bffb2df-7fdb-513f-a06f-842cfeee3821</id><updated>2023-06-24T00:00:00+00:00</updated><author><name>Philippe Loctaux</name><uri>https://philippeloctaux.com</uri></author><link href="https://philippeloctaux.com/blog/wireguard-alpine/" rel="alternate" type="text/html"/><published>2023-06-24T00:00:00+00:00</published><content xml:lang="en" type="html">&lt;p&gt;Let&apos;s do this baremetal, no Docker!&lt;/p&gt;
&lt;p&gt;I will do this inside a &lt;a href=&quot;https://www.proxmox.com/en/&quot;&gt;Proxmox&lt;/a&gt; virtual machine.&lt;/p&gt;
&lt;h2&gt;Get started&lt;/h2&gt;
&lt;p&gt;Start by installing &lt;a href=&quot;https://www.alpinelinux.org/&quot;&gt;Alpine Linux&lt;/a&gt;: Run the installer, next, next, next, and boot the os once it&apos;s done.&lt;/p&gt;
&lt;h2&gt;Setup ssh&lt;/h2&gt;
&lt;p&gt;Copy ssh key (run this on your local machine):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;ssh-copy-id -i ~/.ssh/id_rsa.pub user@ip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Login via ssh, and install your favorite editor:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;doas apk add vim
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Edit ssh config to force ssh key use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;doas vim /etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Find and update these statements:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PermitRootLogin no
PubkeyAuthentication yes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart ssh service, logout, and log back in&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;doas rc-service sshd restart
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Setup alpine package manager&lt;/h2&gt;
&lt;p&gt;I use &lt;code&gt;mirrors.ircam.fr&lt;/code&gt; as my mirror&lt;/p&gt;
&lt;p&gt;Open&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;/etc/apk/repositories
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;add the community repo, and run updates:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;doas apk -U upgrade
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;WireGuard basics&lt;/h2&gt;
&lt;p&gt;Install WireGuard:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;doas apk add wireguard-tools
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Kernel module&lt;/h2&gt;
&lt;p&gt;Load the module&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;doas modprobe wireguard
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To launch the module on startup, edit&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/modules
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and simply add&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wireguard
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;at the bottom, and save the file.&lt;/p&gt;
&lt;h2&gt;IP forwarding&lt;/h2&gt;
&lt;p&gt;Edit&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/sysctl.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and add&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;net.ipv4.ip_forward = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;at the bottom of the file, and save&lt;/p&gt;
&lt;p&gt;Launch sysctl on startup with&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;doas rc-update add sysctl
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and reboot.&lt;/p&gt;
&lt;h2&gt;IP Addresses&lt;/h2&gt;
&lt;p&gt;Pick a range if ip addresses to use: &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc1918&quot;&gt;RFC 1918&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&apos;ll pick &lt;code&gt;10.131.111.x&lt;/code&gt; for the WireGuard peers.&lt;/p&gt;
&lt;p&gt;Calculate your CIDR: &lt;a href=&quot;https://www.ipaddressguide.com/cidr&quot;&gt;https://www.ipaddressguide.com/cidr&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here&apos;s my network layout:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CIDR: &lt;code&gt;10.131.110.0/23&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Start: &lt;code&gt;10.131.110.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;End: &lt;code&gt;10.131.111.255&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Network services:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start: &lt;code&gt;10.131.110.0/24&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;End: &lt;code&gt;10.131.110.255/24&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;WireGuard:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start: &lt;code&gt;10.131.111.0/24&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;End: &lt;code&gt;10.131.111.255/24&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Generate keys for WireGuard&lt;/h2&gt;
&lt;p&gt;Do everything as root (doas is the equivalent of sudo):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;doas su
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Move to the wireguard configuration, I&apos;ll store everything there for easy access:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;cd /etc/wireguard/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Generate the private and public key, store them in files (we&apos;ll use them later):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;wg genkey | tee philt3r-privatekey | wg pubkey &amp;gt; philt3r-publickey
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Configure server interface&lt;/h2&gt;
&lt;p&gt;All the server configuration will happen in&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/wireguard/wg0.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Protip for vim users: To add content of a file in current buffer directly: &lt;a href=&quot;https://stackoverflow.com/a/19087947/4809297&quot;&gt;StackOverflow answer&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[Interface]
# Name = wg0
Address = 10.131.111.1/24
ListenPort = 51820
PrivateKey = &amp;amp;lt;server-private-key&amp;amp;gt;
PostUp = iptables -t nat -A POSTROUTING -s 10.131.111.0/24 -o %i -j MASQUERADE;
PostUp = iptables -t nat -A POSTROUTING -s 10.131.110.0/24 -o %i -j MASQUERADE;
PostUp = iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT;
PostUp = iptables -A FORWARD -i %i -j ACCEPT;
PostUp = iptables -A FORWARD -o %i -j ACCEPT;
PostDown = iptables -t nat -D POSTROUTING -s 10.131.111.0/24 -o %i -j MASQUERADE;
PostDown = iptables -t nat -D POSTROUTING -s 10.131.110.0/24 -o %i -j MASQUERADE;
PostDown = iptables -D INPUT -p udp -m udp --dport 51820 -j ACCEPT;
PostDown = iptables -D FORWARD -i %i -j ACCEPT;
PostDown = iptables -D FORWARD -o %i -j ACCEPT;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once it&apos;s good, make sure only root can read and write to the files:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;chmod 600 /etc/wireguard/*
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Add new peer&lt;/h2&gt;
&lt;p&gt;You will need to repeat this for each new peer&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;cd /etc/wireguard/
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Generate keys&lt;/h2&gt;
&lt;p&gt;Starting now, &lt;code&gt;name&lt;/code&gt; is a placeholder for the name of the peer.&lt;/p&gt;
&lt;p&gt;I typically use the format &lt;strong&gt;name-of-person&lt;/strong&gt; followed by &lt;strong&gt;device-name&lt;/strong&gt;. For example, the peer for my phone will be &lt;strong&gt;phil-iphone&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Create folder to store keys for the peer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;mkdir -p peers/name
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Generate preshared key (not required):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;wg genpsk | tee peers/name/preshared.psk
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Generate private and public keys for the peer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;wg genkey | tee peers/name/private.key | wg pubkey &amp;gt; peers/name/public.key
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Update server configuration&lt;/h2&gt;
&lt;p&gt;Edit your &lt;code&gt;wg0.conf&lt;/code&gt;, add at the bottom:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[Peer]
# Name = name
PublicKey = peers/name/public.key
PresharedKey = peers/name/preshared.psk
AllowedIPs = 10.131.111.2/32
AllowedIPs = 10.131.110.0/24
AllowedIPs = 10.131.111.0/24
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Peer configuration&lt;/h2&gt;
&lt;p&gt;Now let&apos;s create the configuration to give to the peer:&lt;/p&gt;
&lt;p&gt;Create the file&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;peers/name/philt3r-name.wg.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And put the following&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[Interface]
PrivateKey = peers/name/private.key
Address = 10.131.111.2/24
#DNS = 10.131.111.1

[Peer]
PublicKey = &amp;amp;lt;server-public-key&amp;amp;gt;
PresharedKey = peers/name/preshared.psk
Endpoint = server-ip:51820
AllowedIPs = 10.131.110.0/24
AllowedIPS = 10.131.111.0/24
PersistentKeepalive = 25
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DNS info is not used yet, it&apos;s normal, I will enable it once my DNS server will be created (not in this blog post though).&lt;/p&gt;
&lt;h2&gt;Distribute config&lt;/h2&gt;
&lt;p&gt;Either give the configuration file we just created, or you can have multiple choices.&lt;/p&gt;
&lt;h3&gt;QR Code&lt;/h3&gt;
&lt;p&gt;Start by installing&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;apk add libqrencode-tools
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And run&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;qrencode -t ansiutf8 &amp;lt; peers/name/philt3r-name.wg.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Base64&lt;/h3&gt;
&lt;p&gt;Note: I&apos;m using &lt;code&gt;base64&lt;/code&gt; on Alpine, which comes from BusyBox, the CLI may be different depending on the operating system you&apos;re using.&lt;/p&gt;
&lt;p&gt;Encode the configuration file to a base64 string:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;cat philt3r-name.wg.conf | base64 -w 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And on the other device, decode the string and save to a file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;base64 -d &amp;gt; philt3r-name.wg.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Put the base64 encoded string, and send a EOF (usually &lt;code&gt;ctrl + d&lt;/code&gt;).&lt;/p&gt;
&lt;h2&gt;Restart WireGuard&lt;/h2&gt;
&lt;p&gt;If you already have WireGuard running, simply run&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rc-service wg restart
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to restart the server with your new peer.&lt;/p&gt;
&lt;h2&gt;Start WireGuard manually&lt;/h2&gt;
&lt;p&gt;Make sure to open the port on your router in &lt;strong&gt;UDP&lt;/strong&gt; mode! I spent a lot of time debugging to realize that my port was in TCP, double check!&lt;/p&gt;
&lt;p&gt;Make sure to be root before, don&apos;t use &lt;code&gt;doas&lt;/code&gt; or &lt;code&gt;sudo&lt;/code&gt;!&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;wg-quick up wg0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the peer, start the tunnel.&lt;/p&gt;
&lt;p&gt;On the server, run&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;wg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to check the status of WireGuard. You should see the peer and some stats it is connected.&lt;/p&gt;
&lt;p&gt;If you do not see info about the peer even if it is not connected, that means you did something wrong in the configuration!&lt;/p&gt;
&lt;p&gt;From your peer, you should be able to ping the WireGuard internal IP:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10.131.111.1
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;iOS: &lt;a href=&quot;https://apps.apple.com/fr/app/ping-network-utility/id576773404&quot;&gt;Ping&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;OSX / Linux: &lt;code&gt;ping&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you can ping the ip, you&apos;re good!&lt;/p&gt;
&lt;p&gt;You may not be able to go on the internet, or even make DNS requests, it&apos;s normal.&lt;/p&gt;
&lt;p&gt;We are just testing if the tunnel works. You can stop the tunnel.&lt;/p&gt;
&lt;h2&gt;Stop WireGuard manually&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;wg-quick down wg0
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Script to launch on server startup&lt;/h2&gt;
&lt;p&gt;To start WireGuard on startup, we will write an OpenRC script. It will be located in&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/init.d/wg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Put the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;#!/sbin/openrc-run
#

description=&quot;WireGuard&quot;

depend() {
    need localmount net sysctl
    after bootmisc
}

start() {
    ebegin &quot;Starting WireGuard&quot;
    wg-quick up wg0
    eend $?
}

stop() {
    ebegin &quot;Stopping WireGuard&quot;
    wg-quick down wg0
    eend $?
}

status() {
    wg show wg0
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Give it executable access&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;chmod +x /etc/init.d/wg
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Manual&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Start: &lt;code&gt;rc-service wg start&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Stop: &lt;code&gt;rc-service wg stop&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Restart: &lt;code&gt;rc-service wg restart&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Status: &lt;code&gt;rc-service wg status&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Startup&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Add at startup: &lt;code&gt;rc-update add wg&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Remove from startup: &lt;code&gt;rc-update del wg&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Show services at startup: &lt;code&gt;rc-status&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Reboot and make sure everything works, you should see WireGuard logs when your server is starting.&lt;/p&gt;
&lt;h2&gt;Resources&lt;/h2&gt;
&lt;p&gt;These resources helped me when setting up my WireGuard server. Thanks!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/pirate/wireguard-docs&quot;&gt;https://github.com/pirate/wireguard-docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.ruanbekker.com/blog/2020/01/11/setup-a-wireguard-vpn-server-on-linux/&quot;&gt;https://blog.ruanbekker.com/blog/2020/01/11/setup-a-wireguard-vpn-server-on-linux/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://try.popho.be/wg.html&quot;&gt;https://try.popho.be/wg.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;[If the formatting of this post looks odd in your feed reader, &lt;a href=&quot;https://philippeloctaux.com/blog/wireguard-alpine/&quot;&gt;visit the original article&lt;/a&gt;]&lt;/p&gt;
</content></entry><entry><title>how to use the docker of the epitech moulinette</title><id>urn:uuid:bf27e538-5e07-5fe0-8651-2098c6582478</id><updated>2020-01-19T00:00:00+00:00</updated><author><name>Philippe Loctaux</name><uri>https://philippeloctaux.com</uri></author><link href="https://philippeloctaux.com/blog/docker-epitech-moulinette/" rel="alternate" type="text/html"/><published>2020-01-19T00:00:00+00:00</published><content xml:lang="en" type="html">&lt;p&gt;this guide will show you how to install docker, download the epitech moulinette container and learn how to use it for your projects.&lt;/p&gt;
&lt;h2&gt;install&lt;/h2&gt;
&lt;p&gt;ubuntu:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install docker.io
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;arch:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo pacman -S docker
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;setup docker before first use&lt;/h2&gt;
&lt;p&gt;to use docker without root privileges run&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo usermod -aG docker $USER
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and &lt;strong&gt;REBOOT&lt;/strong&gt; your computer afterwards for changes to take effect.&lt;/p&gt;
&lt;p&gt;to start docker on every boot&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl enable docker
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;get the epitech container&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;docker pull epitechcontent/epitest-docker
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;will download the epitech moulinette environement. make sure to have fast internet, because the container is about 5 gigabytes.&lt;/p&gt;
&lt;h2&gt;start the container and get a shell&lt;/h2&gt;
&lt;p&gt;go into the directory you want to get a shell in the epitech container.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run -it --rm -v $(pwd):/home/project -w /home/project epitechcontent/epitest-docker /bin/bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;will get you a bash prompt: you are now in the container. run the commands you want, and exit the shell when you are done.&lt;/p&gt;
&lt;p&gt;if you are using docker on windows (inside powershell), run&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run -it --rm -v ${pwd}:/home/project -w /home/project epitechcontent/epitest-docker /bin/bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;[If the formatting of this post looks odd in your feed reader, &lt;a href=&quot;https://philippeloctaux.com/blog/docker-epitech-moulinette/&quot;&gt;visit the original article&lt;/a&gt;]&lt;/p&gt;
</content></entry><entry><title>archlinux how old is your installation</title><id>urn:uuid:326a3188-6d65-59b0-a195-b1f41d51a776</id><updated>2019-04-18T00:00:00+00:00</updated><author><name>Philippe Loctaux</name><uri>https://philippeloctaux.com</uri></author><link href="https://philippeloctaux.com/blog/archlinux-how-old-is-your-installation/" rel="alternate" type="text/html"/><published>2019-04-18T00:00:00+00:00</published><content xml:lang="en" type="html">&lt;p&gt;on archlinux, to see when you installed arch on your computer, run this command&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sed -n &quot;/ installed $1/{s/].*/]/p;q}&quot; /var/log/pacman.log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;it will display the date and the time when you ran &lt;code&gt;pacstrap&lt;/code&gt; on the live cd to install your system.&lt;/p&gt;
&lt;p&gt;on my laptop, i get &lt;strong&gt;[2018-10-21 21:05]&lt;/strong&gt;, which is when i switched from fedora back to arch because my school required fedora.&lt;/p&gt;
&lt;p&gt;[If the formatting of this post looks odd in your feed reader, &lt;a href=&quot;https://philippeloctaux.com/blog/archlinux-how-old-is-your-installation/&quot;&gt;visit the original article&lt;/a&gt;]&lt;/p&gt;
</content></entry><entry><title>openbsd first setup after install</title><id>urn:uuid:4408cce5-36e0-536c-a111-a9f0db408439</id><updated>2019-03-01T00:00:00+00:00</updated><author><name>Philippe Loctaux</name><uri>https://philippeloctaux.com</uri></author><link href="https://philippeloctaux.com/blog/openbsd-setup/" rel="alternate" type="text/html"/><published>2019-03-01T00:00:00+00:00</published><content xml:lang="en" type="html">&lt;p&gt;just installed openbsd on my chromebook, seems to be working fine!&lt;/p&gt;
&lt;p&gt;since it&apos;s my first time using openbsd, here are some stuff that i will start to do on my machines after installing openbsd:&lt;/p&gt;
&lt;h2&gt;enable &lt;code&gt;doas&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;doas&lt;/code&gt; is kinda the equivalent of &lt;code&gt;sudo&lt;/code&gt;. to enable it, run &lt;code&gt;cp /etc/examples/doas.conf /etc&lt;/code&gt; to copy the doas config file.&lt;/p&gt;
&lt;h2&gt;disable root account&lt;/h2&gt;
&lt;p&gt;now that &lt;code&gt;doas&lt;/code&gt; is ready, we dont need to root account anymore. to disable it, run &lt;code&gt;usermod -p&apos;*&apos; root&lt;/code&gt; to set the root password to &lt;code&gt;*&lt;/code&gt;. this will prevent root from log on directly to the machine (with &lt;code&gt;su&lt;/code&gt; as an example), but with &lt;code&gt;doas&lt;/code&gt; we can run &lt;code&gt;doas sh&lt;/code&gt; to get a shell.&lt;/p&gt;
&lt;h2&gt;install missing firmware for your hardware&lt;/h2&gt;
&lt;p&gt;maybe your wifi card isn&apos;t working? or maybe you can&apos;t display any graphical interface? maybe that&apos;s because you don&apos;t have the firmware for it: here&apos;s how to install it:&lt;/p&gt;
&lt;p&gt;run &lt;code&gt;doas fw_update -i&lt;/code&gt; to see the missing firmwares.&lt;/p&gt;
&lt;p&gt;so grab a flash drive, format it in &lt;em&gt;fat&lt;/em&gt; filesystem format, go to &lt;a href=&quot;http://firmware.openbsd.org/firmware/&quot;&gt;firmware.openbsd.org&lt;/a&gt;, download the missing firmwares, along with the &lt;strong&gt;SHA256.sig&lt;/strong&gt; and &lt;strong&gt;index.txt&lt;/strong&gt; files, and put them on the usb key.&lt;/p&gt;
&lt;p&gt;mount the flash drive on openbsd, and run &lt;code&gt;doas fw_update -p *path of flash drive*&lt;/code&gt; to install the firmwares from the flash drive.&lt;/p&gt;
&lt;p&gt;your missing firmware should not be anymore.&lt;/p&gt;
&lt;p&gt;[If the formatting of this post looks odd in your feed reader, &lt;a href=&quot;https://philippeloctaux.com/blog/openbsd-setup/&quot;&gt;visit the original article&lt;/a&gt;]&lt;/p&gt;
</content></entry><entry><title>how to put a custom boot logo on a thinkpad</title><id>urn:uuid:6b1a142c-034c-5947-aac3-07be2cce3b35</id><updated>2019-02-24T00:00:00+00:00</updated><author><name>Philippe Loctaux</name><uri>https://philippeloctaux.com</uri></author><link href="https://philippeloctaux.com/blog/thinkpad-custom-boot-logo/" rel="alternate" type="text/html"/><published>2019-02-24T00:00:00+00:00</published><content xml:lang="en" type="html">&lt;h2&gt;disclaimer&lt;/h2&gt;
&lt;p&gt;i need to warn you that you are on your own. even though it works fine, i&apos;m not responsible of what you do.&lt;/p&gt;
&lt;h2&gt;introduction&lt;/h2&gt;
&lt;p&gt;you have a thinkpad. it&apos;s beautiful, it&apos;s fast, it&apos;s perfect, it doesn&apos;t run windows; there&apos;s just one thing that could be perfect: the boot logo.&lt;/p&gt;
&lt;p&gt;by default on new models, your boot logo look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;default-logo.jpg&quot; alt=&quot;default boot logo on new lenovo models&quot; /&gt;&lt;/p&gt;
&lt;p&gt;to get a custom boot logo, you need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;an internet connection&lt;/li&gt;
&lt;li&gt;a compatible model&lt;/li&gt;
&lt;li&gt;a usb flash drive&lt;/li&gt;
&lt;li&gt;a gif image (you can also use a &lt;em&gt;bmp&lt;/em&gt; or a &lt;em&gt;jpg&lt;/em&gt; image, but i&apos;ve found that with &lt;em&gt;gif&lt;/em&gt; images it works better)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;get the BIOS update&lt;/h2&gt;
&lt;p&gt;to install your custom boot logo, you need to flash a BIOS update.&lt;/p&gt;
&lt;p&gt;to download the BIOS update, go on &lt;a href=&quot;https://pcsupport.lenovo.com/us/en&quot;&gt;lenovo&apos;s support website&lt;/a&gt;, choose &lt;strong&gt;drivers and updates&lt;/strong&gt; and find your model.&lt;/p&gt;
&lt;p&gt;next, go in the section &lt;strong&gt;BIOS/UEFI&lt;/strong&gt; and download the &lt;strong&gt;BIOS Update (Bootable CD)&lt;/strong&gt;. it will download a &lt;em&gt;iso&lt;/em&gt; image.&lt;/p&gt;
&lt;p&gt;if you can&apos;t find the update image, that means that you&apos;re out of luck, and you can&apos;t get a custom boot logo. sorry.&lt;/p&gt;
&lt;h2&gt;convert the iso image&lt;/h2&gt;
&lt;p&gt;now that you have the iso image, you need to convert it to a &lt;em&gt;img&lt;/em&gt; file. to do so, run the following command in a terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;geteltorito -o bios-image.img bios-image-downloaded.iso
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;if you don&apos;t have &lt;strong&gt;geteltorito&lt;/strong&gt;, look online to install it.&lt;/p&gt;
&lt;h2&gt;flash the image on your usb drive&lt;/h2&gt;
&lt;p&gt;now it&apos;s time to flash the image on your usb drive. get the name of your flash drive using &lt;code&gt;lsblk&lt;/code&gt;, plug your usb drive, and run &lt;code&gt;lsblk&lt;/code&gt; again to see your drive.&lt;/p&gt;
&lt;p&gt;go into the folder where the &lt;em&gt;img&lt;/em&gt; file is located, and run in a terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dd if=bios-image.img of=/dev/sdX bs=1M status=progress oflag=sync
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;where &lt;strong&gt;X&lt;/strong&gt; is your drive letter that you know thanks to &lt;code&gt;lsblk&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;get the &lt;em&gt;gif&lt;/em&gt; file to use&lt;/h2&gt;
&lt;p&gt;if you want, i already have this selection of images ready to be used, or you can make your own!&lt;/p&gt;
&lt;p&gt;Here is a &lt;a href=&quot;boot-logo/&quot;&gt;folder with images ready to be used&lt;/a&gt;&lt;/p&gt;
&lt;ImageGrid src=&quot;boot-logo/&quot; /&gt;
&lt;p&gt;you can see the requirements, go in your usb drive, open the &lt;strong&gt;readme.txt&lt;/strong&gt; in the &lt;em&gt;flash&lt;/em&gt; folder.&lt;/p&gt;
&lt;p&gt;put the &lt;em&gt;gif&lt;/em&gt; image in the &lt;em&gt;flash&lt;/em&gt; folder, and name it &lt;strong&gt;LOGO1.JPG&lt;/strong&gt;. copy that image, and name that one &lt;strong&gt;LOGO2.JPG&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;check the &lt;strong&gt;readme.txt&lt;/strong&gt; file to see the filenames, they might differ on different models.&lt;/p&gt;
&lt;h2&gt;flash the BIOS update&lt;/h2&gt;
&lt;p&gt;reboot your computer, and boot on the usb flash drive. if you don&apos;t know how, the internet should help you with that.&lt;/p&gt;
&lt;p&gt;now that the flash utility has booted, choose the second option, and follow the instructions.&lt;/p&gt;
&lt;p&gt;the computer will reboot, flash the update, and when it will reboot, you should get your custom boot logo!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;custom-logo.jpg&quot; alt=&quot;a thinkpad booting with a custom boot logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;if you want to go back to the default logo, simply reflash the bios update, when when asked if you want to use your custom logo, say no, and the default logo will be put back.&lt;/p&gt;
&lt;p&gt;[If the formatting of this post looks odd in your feed reader, &lt;a href=&quot;https://philippeloctaux.com/blog/thinkpad-custom-boot-logo/&quot;&gt;visit the original article&lt;/a&gt;]&lt;/p&gt;
</content></entry><entry><title>my contributions to the linux kernel</title><id>urn:uuid:6281b0d3-af21-5a6c-abd1-ec27dcc2742a</id><updated>2018-04-10T00:00:00+00:00</updated><author><name>Philippe Loctaux</name><uri>https://philippeloctaux.com</uri></author><link href="https://philippeloctaux.com/blog/linux-kernel-contributions/" rel="alternate" type="text/html"/><published>2018-04-10T00:00:00+00:00</published><content xml:lang="en" type="html">&lt;p&gt;you read it right! i am a contributor to the linux kernel!&lt;/p&gt;
&lt;p&gt;... but you are going to be dissapointed: my contributions are not going to change the world, just comments, alignments and documentation and stuff...&lt;/p&gt;
&lt;p&gt;i submited something like 10 patches (as of april 10, 2018) and 3 patches have been accepted and are now into the kernel!&lt;/p&gt;
&lt;p&gt;if you wanna check them out, you can read them on &lt;a href=&quot;https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/log/?qt=author&amp;amp;q=Philippe+Loctaux&quot;&gt;kernel.org&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;yes, i am aware that Linus has a copy of the repo on github, but when &lt;a href=&quot;https://github.com/torvalds/linux/commits?author=x4m3&quot;&gt;i try to see my commits on github&lt;/a&gt;, i can&apos;t get them because there is too many commits and github can&apos;t get me the list.&lt;/p&gt;
&lt;p&gt;so, here are my kernel contributions, listed by date:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/torvalds/linux/commit/81c18a9e378c87ed6559a4b0a0c2831c88947373&quot;&gt;feb 23, 2016&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/torvalds/linux/commit/ce6550818280c1e7caae727d2b9504140b6370f0&quot;&gt;mar 7, 2016&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/torvalds/linux/commit/9d4c0c9f6a747a9bdec03057be4193994839ec87&quot;&gt;dec 28, 2017&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;[If the formatting of this post looks odd in your feed reader, &lt;a href=&quot;https://philippeloctaux.com/blog/linux-kernel-contributions/&quot;&gt;visit the original article&lt;/a&gt;]&lt;/p&gt;
</content></entry><entry><title>restart chrome with a url</title><id>urn:uuid:79a85614-e52a-5484-a462-e6bd50710c9d</id><updated>2018-04-09T00:00:00+00:00</updated><author><name>Philippe Loctaux</name><uri>https://philippeloctaux.com</uri></author><link href="https://philippeloctaux.com/blog/restart-chrome-url/" rel="alternate" type="text/html"/><published>2018-04-09T00:00:00+00:00</published><content xml:lang="en" type="html">&lt;p&gt;did you know that you can restart chrome/chromium with a simple url?&lt;/p&gt;
&lt;p&gt;here is it: &lt;a href=&quot;about://restart&quot;&gt;about://restart&lt;/a&gt; !&lt;/p&gt;
&lt;p&gt;but this trick does not work with the &lt;code&gt;&amp;amp;lt;a&amp;amp;gt;&lt;/code&gt; html tag, which seems logical, copy the link and paste it in a new tab to get it to work&lt;/p&gt;
&lt;p&gt;it&apos;d be annoying as fuck if you could click on links that restart your browser, imagine if an extension could replace every clickable link with this one! 😊&lt;/p&gt;
&lt;p&gt;i think its usage is for chrome developers, the end user doesn&apos;t really care to type a url to restart their browser, they click on the &lt;code&gt;x&lt;/code&gt; and they reopen it (duh).&lt;/p&gt;
&lt;p&gt;[If the formatting of this post looks odd in your feed reader, &lt;a href=&quot;https://philippeloctaux.com/blog/restart-chrome-url/&quot;&gt;visit the original article&lt;/a&gt;]&lt;/p&gt;
</content></entry><entry><title>error on zsh when using vim and autocomplete</title><id>urn:uuid:d9f0393c-8692-5c8c-a51a-8727a166cd63</id><updated>2018-03-13T00:00:00+00:00</updated><author><name>Philippe Loctaux</name><uri>https://philippeloctaux.com</uri></author><link href="https://philippeloctaux.com/blog/zsh-vim-autocomplete-error/" rel="alternate" type="text/html"/><published>2018-03-13T00:00:00+00:00</published><content xml:lang="en" type="html">&lt;p&gt;do you use &lt;strong&gt;zsh&lt;/strong&gt; and &lt;strong&gt;ohmyzsh&lt;/strong&gt; ?&lt;/p&gt;
&lt;p&gt;do you run into an issue when you are about to edit a file with vim, and you use the &lt;strong&gt;Tab&lt;/strong&gt; key to autocomplete the filename, but instead you get something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ vim ~/filena&amp;amp;lt;TAB&amp;amp;gt;
_arguments:448: _vim_files: function definition file not found
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;annoying af, right ?&lt;/p&gt;
&lt;h2&gt;how to fix it&lt;/h2&gt;
&lt;p&gt;here&apos;s how to fix it: delete the zcompdump directory off your personal directory, and reload your zsh config file (or close and open a new shell).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rm -rf ~/.zcompdump*; source ~/.zshrc;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;it took me at least 15 mins to find that .... hopefully i save some time for you .&lt;/p&gt;
&lt;h3&gt;sources&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://unix.stackexchange.com/questions/280622/zsh-fails-at-path-completition-when-command-is-vim#280626&quot;&gt;stackoverflow&lt;/a&gt; and &lt;a href=&quot;https://github.com/robbyrussell/oh-my-zsh/issues/518&quot;&gt;github&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[If the formatting of this post looks odd in your feed reader, &lt;a href=&quot;https://philippeloctaux.com/blog/zsh-vim-autocomplete-error/&quot;&gt;visit the original article&lt;/a&gt;]&lt;/p&gt;
</content></entry><entry><title>how to link your vimrc file on windows</title><id>urn:uuid:3fb8c13c-4ec1-56c8-b0cb-0c3bf9962a3b</id><updated>2018-02-17T00:00:00+00:00</updated><author><name>Philippe Loctaux</name><uri>https://philippeloctaux.com</uri></author><link href="https://philippeloctaux.com/blog/link-your-vimrc-file-on-windows/" rel="alternate" type="text/html"/><published>2018-02-17T00:00:00+00:00</published><content xml:lang="en" type="html">&lt;p&gt;i code on my linux machine, and i use vim as my text editor.&lt;br /&gt;
i&apos;ve been using vim for almost 4 years, and i started to make my config file.&lt;br /&gt;
i store it with git, along all my config files for my linux setup (aka my dot files).&lt;/p&gt;
&lt;p&gt;here&apos;s the thing, i also use windows to code, and i also use vim.&lt;br /&gt;
i also want to use the same config that i store in git.&lt;/p&gt;
&lt;p&gt;to do so i need to do a symbolink link to my vimrc file. i dunno how to do that on windows.&lt;/p&gt;
&lt;p&gt;here&apos;s how to do it:&lt;/p&gt;
&lt;h2&gt;cmd&lt;/h2&gt;
&lt;p&gt;if you use the old school, black boxed &lt;strong&gt;cmd.exe&lt;/strong&gt;, you need to run these commands:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd c:\users\_username_
mklink .vimrc _path-to-vimrc_
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and you should be good to go.&lt;br /&gt;
just make sure it has been linked correctly with &lt;code&gt;vim ~/.vimrc&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;powershell&lt;/h2&gt;
&lt;p&gt;if you use the new, blue boxed &lt;strong&gt;powershell.exe&lt;/strong&gt;, it&apos;s a bit different:&lt;br /&gt;
you can make it with powershell&apos;s language (or whatever they call the stuff they&apos;re using), but some require admin privileges, some require custom functions or something like that...&lt;/p&gt;
&lt;p&gt;or you can keep it simple and use the &lt;strong&gt;cmd&lt;/strong&gt; command to use &lt;strong&gt;cmd.exe&lt;/strong&gt; to run the &lt;strong&gt;mklink&lt;/strong&gt; program to make the symlink:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;cd c:\users\_username_
cmd /c mklink .vimrc _path-to-vimrc_
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;sources&lt;/h2&gt;
&lt;p&gt;i found &lt;a href=&quot;http://saadware.com/windows-vimrc-link/&quot;&gt;this blog post&lt;/a&gt; to find out how to do symlinks on cmd,&lt;br /&gt;
and i used &lt;a href=&quot;https://en.wikipedia.org/wiki/NTFS_symbolic_link#Tools&quot;&gt;this wikipedia article&lt;/a&gt; and &lt;a href=&quot;https://stackoverflow.com/a/5549583&quot;&gt;this stackoverflow thread&lt;/a&gt; to learn how to do it on powershell.&lt;/p&gt;
&lt;p&gt;[If the formatting of this post looks odd in your feed reader, &lt;a href=&quot;https://philippeloctaux.com/blog/link-your-vimrc-file-on-windows/&quot;&gt;visit the original article&lt;/a&gt;]&lt;/p&gt;
</content></entry><entry><title>dd status</title><id>urn:uuid:42e2e1a1-5276-5835-8daa-34ac1548e188</id><updated>2016-07-14T00:00:00+00:00</updated><author><name>Philippe Loctaux</name><uri>https://philippeloctaux.com</uri></author><link href="https://philippeloctaux.com/blog/dd-status/" rel="alternate" type="text/html"/><published>2016-07-14T00:00:00+00:00</published><content xml:lang="en" type="html">&lt;p&gt;Here&apos;s a cool tip if you use dd to copy disk images to a disk, or if you want to clone drives.&lt;/p&gt;
&lt;p&gt;As we all know (or should know), dd is a powerful tool; but it can be dangerous if not manipulated correctly.&lt;/p&gt;
&lt;p&gt;By default, dd doesn&apos;t give any information on what is it doing; which can be very boring, because you don&apos;t know when you&apos;ll be able to test that new Linux distro you just discovered!&lt;/p&gt;
&lt;p&gt;Well, good news! You can get some status from dd! This tip works on Linux and OS X.&lt;/p&gt;
&lt;h2&gt;Linux&lt;/h2&gt;
&lt;p&gt;When you enter your &lt;code&gt;dd&lt;/code&gt; command, add this little snippet at the end:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;status=progress
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When dd will be busy doing its thing, you should get some info about its progress.&lt;/p&gt;
&lt;h2&gt;OS X&lt;/h2&gt;
&lt;p&gt;Run your dd command normally without &lt;code&gt;status=progress&lt;/code&gt;, after that open a new terminal and enter this command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;while pgrep ^dd; do sudo pkill -INFO dd; sleep 30; done;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And come back into the dd command. When the dd task will be done, the second command we ran will exit as well and you can close that other terminal safely.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;30&lt;/code&gt; is the number of seconds that will update the counter every X seconds.&lt;/p&gt;
&lt;p&gt;[If the formatting of this post looks odd in your feed reader, &lt;a href=&quot;https://philippeloctaux.com/blog/dd-status/&quot;&gt;visit the original article&lt;/a&gt;]&lt;/p&gt;
</content></entry></feed>