{"@attributes":{"version":"2.0"},"channel":{"title":"Wolfgang's Blog","link":"https:\/\/notthebe.ee\/","description":{},"generator":"Zola","language":"en","lastBuildDate":"Mon, 29 Dec 2025 00:00:00 +0000","item":[{"title":"Saying goodbye to GitHub","pubDate":"Mon, 29 Dec 2025 00:00:00 +0000","author":"Unknown","link":"https:\/\/notthebe.ee\/blog\/moving-away-from-github\/","guid":"https:\/\/notthebe.ee\/blog\/moving-away-from-github\/","description":"<p><strong>TL;DR: As of December 2025, I've moved all my GitHub projects to a self-hosted Forgejo instance at <a href=\"https:\/\/git.notthebe.ee\/\">https:\/\/git.notthebe.ee\/<\/a><\/strong><\/p>\n<h2 id=\"why\">Why?<\/h2>\n<p>There are many reasons for that, and unless you've been living under a rock (if that's the case, I envy you),\nyou've also been following a slow but sure enshittification and ensloppification of the platform.<\/p>\n<p><img src=\"https:\/\/notthebe.ee\/blog\/moving-away-from-github\/images\/copilot.webp\" alt=\"GitHub Copilot presentation\" \/><\/p>\n<p>This includes (but isn't limited to):<\/p>\n<ul>\n<li><a href=\"https:\/\/www.legitsecurity.com\/blog\/camoleak-critical-github-copilot-vulnerability-leaks-private-source-code\">Leaking code from private repositories<\/a><\/li>\n<li><a href=\"https:\/\/www.theregister.com\/2025\/09\/05\/github_copilot_complaints\/\">Forcing Copilot code reviews on its users<\/a><\/li>\n<li><a href=\"https:\/\/www.crn.com\/news\/ai\/2025\/microsoft-moves-github-into-coreai-group-ceo-confirms-upcoming-departure\">Microsoft moving GitHub into CoreAI Group following its CEO's departure<\/a><\/li>\n<li><a href=\"https:\/\/resources.github.com\/actions\/2026-pricing-changes-for-github-actions\/\">Introducing a per-minute fee for self-hosted Actions runners<\/a><\/li>\n<\/ul>\n<p>...and it's (now former) CEO even going on record to proclaim that <a href=\"https:\/\/github.blog\/news-insights\/product-news\/universe-2023-copilot-transforms-github-into-the-ai-powered-developer-platform\/\">\"just as GitHub was founded on Git, today we are re-founded on Copilot\"<\/a> or even <a href=\"https:\/\/www.finalroundai.com\/blog\/github-ceo-thomas-dohmke-warns-developers-embrace-ai-or-quit\">\"Either Embrace AI or Get Out of This Career\"<\/a>.<\/p>\n<p>Personally, I want no part of it.<\/p>\n<p>Now obviously, GitHub has its benefits. It's not just a code hosting platform,\nbut a social network first and foremost. Millions of people use it to discover and contribute\nto other people's projects - which means that it's also easy for your projects to get\ndiscovered and contributed to. And the more eyes there are on an open source project, the better.<\/p>\n<p>GitHub Sponsors also presents a convenient way for open-source developers to fund their\nprojects without relying on a third-party service like Patreon for donations<\/p>\n<p>Vulnerability scanning is another great feature, especially for developers with fewer resources.<\/p>\n<p>Finally, your GitHub profile doubles as your career portfolio, a place where you can easily\npresent all of your open-source work to a potential employer or client.<\/p>\n<p>These aspects of GitHub make it nearly impossible to move away from it for some of its users.<\/p>\n<p>That being said, I am lucky enough not to be affected by these factors. I don't rely on GitHub\nSponsors for donations and don't maintain a big open-source project with many contributors. Most of\nthe projects I host on GitHub are small programs or NixOS modules, as well as IaC for my personal machines.<\/p>\n<p>Besides, it's only when people actually move off the platform that the network effect starts to diminish.\nNothing will change if everyone stays on the GitHub while complaining about enshittifcation and bemoaning the lack of alternatives.\nAnd seeing as I have an audience online, maybe I can convince some of you to also pull the plug.<\/p>\n<h2 id=\"forgejo\">Forgejo<\/h2>\n<p>Instead of moving to another managed code hosting service such as GitLab or Codeberg, I decided to self-host a forge on my own NixOS infrastructure.<\/p>\n<p>I chose <a href=\"https:\/\/forgejo.org\/\">Forgejo<\/a> as a forge for multiple reasons:<\/p>\n<ul>\n<li>It's developed and maintained by a non-profit organization ( Codeberg e.V.) based in Germany<\/li>\n<li>It's based on Go and doesn't require a lot of resources<\/li>\n<li>It supports CI pipelines based on <a href=\"https:\/\/github.com\/nektos\/act\">nektos\/act<\/a>, which uses Github Actions syntax and allows me to continue using my existing CI pipelines with minor tweaks<\/li>\n<li>It has a NixOS module<\/li>\n<\/ul>\n<p>I was able to set it up in a weekend, which included rewriting Github Actions workflows for several of my projects \u2013 even thougth <code>act<\/code> is mostly compatible with the GitHub Actions syntax, I didn't want to keep relying on some reusable workflows that are hosted on GitHub, e.g. <a href=\"https:\/\/github.com\/DeterminateSystems\/update-flake-lock\">https:\/\/github.com\/DeterminateSystems\/update-flake-lock<\/a>. Running a (free) self-hosted Actions runner also allows you to use your own container images \u2013 which means that I can simply pre-build a Docker image with Nix already pre-installed instead of relying on reusable workflows.<\/p>\n<p>In my opinion, act's (and by extension, GitHub's) Actions system does not feel as mature and fleshed out as GitLab CI - especially reusable workflows. That being said, for smaller deployments, Forgejo's modest resource requirements definitely outweigh its shortcomings.<\/p>\n<p>As for the \"career portfolio\" aspect, Forgejo's UI is very similar to GitHub,\nwhich means that I can simply send my self-hosted Forgejo instance to a potential\nemployee\/client instead of my GitHub profile.<\/p>\n<p><img src=\"https:\/\/notthebe.ee\/blog\/moving-away-from-github\/images\/forgejo.png\" alt=\"Forgejo UI screenshot\" \/><\/p>\n<p>Besides, hosting my own Git infrastructure has to count for something in terms of technical skills.<\/p>\n<p>That being said, there's nothing wrong with moving to a hosted\/managed platform - not everyone is able to (or even wants to) self-host.<\/p>\n<p>One of the obvious choices is <a href=\"https:\/\/codeberg.org\/\">Codeberg<\/a>, which is led by the same team that develops Forgejo.\nThe platform itself obviously runs Forgejo under the hood. If you rely on contributions of other people,\nhosting your projects on Codeberg or a similar managed forge might make more sense than self-hosting.<\/p>\n<h2 id=\"what-now\">What now?<\/h2>\n<p>As you're reading this, the contents of most of my GitHub projects will have been replaced with a single README.md file containing a link to this blog post.\nYou can find up-to-date code for my active projects on <a href=\"https:\/\/git.notthebe.ee\/\">https:\/\/git.notthebe.ee\/<\/a><\/p>\n<p>I will not be removing my GitHub account altogether, and will continue using it in order to contribute to open source projects that are hosted exclusively on GitHub.<\/p>\n<p>As for incoming contributions, unfortunately, there is no easy way to create an issue or a pull request in one of my projects.\n<a href=\"https:\/\/forgejo.org\/2023-01-10-answering-forgejo-federation-questions\/\">Federation functionality<\/a>\nis currently being worked on by the Forgejo team, but there's no ETA.\nIf this becomes a problem in the future, I will consider moving to Codeberg or another managed forge platform.<\/p>\n<p>If you're using one of my NixOS modules (e.g. AutoASPM) in your flake.nix, simply replace this:<\/p>\n<pre data-lang=\"nix\" class=\"language-nix z-code\"><code class=\"language-nix\" data-lang=\"nix\"><span class=\"z-source z-nix\"><span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">autoaspm<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">      <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">url<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>github:notthebee\/AutoASPM<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">      <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">inputs<\/span>.<span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">nixpkgs<\/span>.<span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">follows<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>nixpkgs<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\"><span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span>\n<\/span><\/code><\/pre>\n<p>...with this:<\/p>\n<pre data-lang=\"nix\" class=\"language-nix z-code\"><code class=\"language-nix\" data-lang=\"nix\"><span class=\"z-source z-nix\"><span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">autoaspm<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">      <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">url<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>git+https:\/\/git.notthebe.ee\/notthebee\/AutoASPM<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">      <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">inputs<\/span>.<span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">nixpkgs<\/span>.<span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">follows<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>nixpkgs<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\"><span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span>\n<\/span><\/code><\/pre>\n<p>If you've discovered a serious security issue or a bug in one of my projects, please contact me at <a href=\"mailto:wolfgangschannel@mailbox.org\">wolfgangschannel@mailbox.org<\/a>.<\/p>\n"},{"title":"Ephemeral root partition on NixOS using ZFS","pubDate":"Wed, 22 Jan 2025 00:00:00 +0000","author":"Unknown","link":"https:\/\/notthebe.ee\/blog\/nixos-ephemeral-zfs-root\/","guid":"https:\/\/notthebe.ee\/blog\/nixos-ephemeral-zfs-root\/","description":"<p><img src=\"https:\/\/notthebe.ee\/blog\/nixos-ephemeral-zfs-root\/images\/sparkjoy.jpg\" alt=\"Marie Kondo, &quot;This one does not spark joy&quot;\" \/><\/p>\n<h2 id=\"what-is-ephemeral-root\">What is 'ephemeral root'?<\/h2>\n<p>Ephemeral root (also \"opt-in state\", immutable root or\n<a href=\"https:\/\/grahamc.com\/blog\/erase-your-darlings\/\">Erase your darlings<\/a>)\nis a root parition setup in which the filesystem root is erased on\nevery system boot.\nWe can then manually \"opt in\" directories which we want to preserve accross reboots.<\/p>\n<h2 id=\"why-would-i-want-that\">Why would I want that?<\/h2>\n<p>Even if you use IaC for your machines, over time, most systems accumulate\ngarbage state \u2013 be that through the user themselves (e.g. files like\n<code>very_important_script123.sh<\/code> in random places on the drive) or through\nthird-party software.<\/p>\n<p>Some systems also require manual steps\/workarounds even after provisioning them\nusing IaC. These steps often stay undocumented and become forgotten until the\nnext reinstall.<\/p>\n<p>Ephemeral root setup disciplines the operator to eliminate those workarounds and\nforces them to develop better, more robust IaC.<\/p>\n<p>Besides, it feels good to know that your system has no leftover\/unnecessary state\nand only the things that are truly necessary are persisted.<\/p>\n<h2 id=\"how-does-it-work\">How does it work?<\/h2>\n<p>In a regular UNIX-like system, packages and configuration files are installed\ninto directories like \/usr\/share, \/usr\/local, \/bin, \/opt or \/etc.\nRemoving most of these directories effectively breaks your system.<\/p>\n<p>NixOS, on the other hand, keeps most of the installed packages and configuration\nfiles in the Nix store (\/nix). These are then symlinked into the usual\nFHS directories, like \/bin and \/etc, on every boot.<\/p>\n<p>In theory, this makes it possible to boot NixOS with only two\ndirectories present \u2013 \/nix and \/boot.<\/p>\n<p>We can then create separate ZFS datasets for these and other\ndirectories which we want to preserve across reboots\n(e.g. <code>\/var\/log\/<\/code>, <code>\/var\/lib<\/code> and <code>home<\/code>).<\/p>\n<p>As for erasing the rest of the files\/folders on every boot \u2013\nthis is made possible by using ZFS snapshots. Once we create a\nZFS dataset for the root partition (<code>\/<\/code>), we can immediately\nmake an empty snapshot of it, and then write a systemd service\nthat resets our root filesystem to that snapshot.<\/p>\n<h2 id=\"creating-and-formatting-zfs-datasets\">Creating and formatting ZFS datasets<\/h2>\n<p>The relevant filesystems can be created using <a href=\"https:\/\/github.com\/nix-community\/disko\">disko<\/a>.<\/p>\n<p>In particular, disko can automatically run shell commands after creating ZFS\ndatasets using <code>postCreateHook<\/code>:<\/p>\n<pre data-lang=\"nix\" class=\"language-nix z-code\"><code class=\"language-nix\" data-lang=\"nix\"><span class=\"z-source z-nix\"><span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">  <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">disko<\/span>.<span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">devices<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">zpool<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">      <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">rpool<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">        <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">datasets<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">          <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>nixos\/empty<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">            <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">type<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>zfs_fs<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">            <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">options<\/span>.<span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">mountpoint<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>legacy<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">            <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">mountpoint<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>\/<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">            <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">postCreateHook<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>zfs snapshot rpool\/nixos\/empty@start<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">          <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">        <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">      <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">  <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\"><span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span>\n<\/span><\/code><\/pre>\n<p>We can then create other datasets for data which we want to preserve across reboots:<\/p>\n<pre data-lang=\"nix\" class=\"language-nix z-code\"><code class=\"language-nix\" data-lang=\"nix\"><span class=\"z-source z-nix\"><span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">  <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">disko<\/span>.<span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">devices<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">zpool<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">      <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">rpool<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">        <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">datasets<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">          <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">nixos<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">            <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">type<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>zfs_fs<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">            <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">options<\/span>.<span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">mountpoint<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>none<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">          <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">          <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>nixos\/var<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">            <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">type<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>zfs_fs<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">            <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">options<\/span>.<span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">mountpoint<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>none<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">          <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">          <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>nixos\/var\/log<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">            <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">type<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>zfs_fs<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">            <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">options<\/span>.<span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">mountpoint<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>legacy<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">            <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">mountpoint<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>\/var\/log<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">          <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">          <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>nixos\/var\/lib<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">            <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">type<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>zfs_fs<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">            <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">options<\/span>.<span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">mountpoint<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>legacy<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">            <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">mountpoint<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>\/var\/lib<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">          <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">          <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>nixos\/nix<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">            <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">type<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>zfs_fs<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">            <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">options<\/span>.<span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">mountpoint<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>legacy<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">            <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">mountpoint<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>\/nix<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">          <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">          <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>nixos\/config<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">            <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">type<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>zfs_fs<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">            <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">options<\/span>.<span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">mountpoint<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>legacy<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">            <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">mountpoint<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>\/etc\/nixos<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">          <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">        <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">      <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">  <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\"><span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span>\n<\/span><\/code><\/pre>\n<p>The full code for the disko configuration (assuming a single ZFS drive and EFI) can be found\n<a href=\"https:\/\/github.com\/notthebee\/nix-config\/blob\/main\/machines\/nixos\/aria\/disko.nix\">in my nix-config repository on Github<\/a><\/p>\n<p>After booting into the NixOS live ISO, we can then download the disko file using curl, and replace the disk variable with the ID of our boot drive (as seen in <code>\/dev\/disk\/by-id<\/code>):<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">curl<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> https:\/\/github.com\/notthebee\/nix-config\/raw\/refs\/heads\/main\/machines\/nixos\/aria\/disko.nix<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>o<\/span> \/tmp\/disko.nix<\/span>\n<\/span><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sed<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"><span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>i<\/span> <span class=\"z-string z-quoted z-double z-shell\"><span class=\"z-punctuation z-definition z-string z-begin z-shell\">&quot;<\/span>s|builtins.head config.zfs-root.bootDevices|nvme-PC601_NVMe_SK_hynix_256GB_AI97N00681CA38E2W|<span class=\"z-punctuation z-definition z-string z-end z-shell\">&quot;<\/span><\/span> \/tmp\/disko.nix<\/span>\n<\/span><\/code><\/pre>\n<p>Finally, we can format the boot drive and mount the file systems relative to \/mnt:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">nix<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"><span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> --<\/span>experimental-features<\/span> <span class=\"z-string z-quoted z-double z-shell\"><span class=\"z-punctuation z-definition z-string z-begin z-shell\">&quot;<\/span>nix-command flakes<span class=\"z-punctuation z-definition z-string z-end z-shell\">&quot;<\/span><\/span> run github:nix-community\/disko <span class=\"z-punctuation z-separator z-continuation z-line z-shell\">\\\n<\/span><\/span><\/span><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-arguments z-shell\"><span class=\"z-keyword z-operator z-end-of-options z-shell\">    --<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> -m destroy,format,mount \/tmp\/disko.nix<\/span>\n<\/span><\/code><\/pre>\n<h2 id=\"nixos-configuration\">NixOS configuration<\/h2>\n<p>The OS configuration itself is relatively simple.<\/p>\n<p>We declare our ZFS datasets and their respective mountpoints:<\/p>\n<pre data-lang=\"nix\" class=\"language-nix z-code\"><code class=\"language-nix\" data-lang=\"nix\"><span class=\"z-source z-nix\"><span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">  <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">fileSystems<\/span>.<span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>\/<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-variable z-parameter z-name z-nix\">lib<\/span><span class=\"z-keyword z-operator z-nix\">.<\/span><span class=\"z-variable z-parameter z-name z-nix\">mkForce<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">device<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>rpool\/nixos\/empty<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">fsType<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>zfs<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">  <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">\n<\/span><span class=\"z-source z-nix\">  <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">fileSystems<\/span>.<span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>\/nix<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">device<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>rpool\/nixos\/nix<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">fsType<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>zfs<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">neededForBoot<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-constant z-language z-nix\">true<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">  <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">\n<\/span><span class=\"z-source z-nix\">  <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">fileSystems<\/span>.<span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>\/etc\/nixos<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">device<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>rpool\/nixos\/config<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">fsType<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>zfs<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">neededForBoot<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-constant z-language z-nix\">true<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">  <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">\n<\/span><span class=\"z-source z-nix\">  <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">fileSystems<\/span>.<span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>\/boot<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">device<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>bpool\/nixos\/root<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">fsType<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>zfs<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">  <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">\n<\/span><span class=\"z-source z-nix\">  <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">fileSystems<\/span>.<span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>\/var\/log<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">device<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>rpool\/nixos\/var\/log<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">fsType<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>zfs<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">neededForBoot<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-constant z-language z-nix\">true<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">  <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">\n<\/span><span class=\"z-source z-nix\">  <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">fileSystems<\/span>.<span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>\/var\/lib<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">device<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>rpool\/nixos\/var\/lib<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">fsType<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>zfs<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">neededForBoot<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-constant z-language z-nix\">true<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">  <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\"><span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span>\n<\/span><\/code><\/pre>\n<p>We will also need to enable systemd in initrd, and declare a systemd service\nthat takes care of resetting our root filesystem to a blank slate on every boot:<\/p>\n<pre data-lang=\"nix\" class=\"language-nix z-code\"><code class=\"language-nix\" data-lang=\"nix\"><span class=\"z-source z-nix\"><span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">  <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">boot<\/span>.<span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">initrd<\/span>.<span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">systemd<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">enable<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-constant z-language z-nix\">true<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">services<\/span>.<span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">initrd-rollback-root<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-attrset-or-function z-nix\">{<\/span>\n<\/span><span class=\"z-source z-nix\">      <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">after<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-list z-nix\">[<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>zfs-import-rpool.service<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span> <span class=\"z-punctuation z-definition z-list z-nix\">]<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">      <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">wantedBy<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-list z-nix\">[<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>initrd.target<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span> <span class=\"z-punctuation z-definition z-list z-nix\">]<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">      <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">before<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-list z-nix\">[<\/span>\n<\/span><span class=\"z-source z-nix\">        <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>sysroot.mount<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span>\n<\/span><span class=\"z-source z-nix\">      <span class=\"z-punctuation z-definition z-list z-nix\">]<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">      <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">path<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-punctuation z-definition z-list z-nix\">[<\/span> <span class=\"z-variable z-parameter z-name z-nix\">pkgs<\/span><span class=\"z-keyword z-operator z-nix\">.<\/span><span class=\"z-variable z-parameter z-name z-nix\">zfs<\/span> <span class=\"z-punctuation z-definition z-list z-nix\">]<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">      <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">description<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>Rollback root fs<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">      <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">unitConfig<\/span>.<span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">DefaultDependencies<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>no<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">      <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">serviceConfig<\/span>.<span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">Type<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>oneshot<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">      <span class=\"z-entity z-other z-attribute-name z-multipart z-nix\">script<\/span> <span class=\"z-keyword z-operator z-bind z-nix\">=<\/span> <span class=\"z-string z-quoted z-double z-nix\"><span class=\"z-punctuation z-definition z-string z-double z-start z-nix\">&quot;<\/span>zfs rollback -r rpool\/nixos\/empty@start<span class=\"z-punctuation z-definition z-string z-double z-end z-nix\">&quot;<\/span><\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">    <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\">  <span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span><span class=\"z-punctuation z-terminator z-bind z-nix\">;<\/span>\n<\/span><span class=\"z-source z-nix\"><span class=\"z-punctuation z-definition z-attrset z-nix\">}<\/span>\n<\/span><\/code><\/pre>\n<h2 id=\"credits\">Credits<\/h2>\n<p>This post is largely inspired by Graham Christensen's\n<a href=\"https:\/\/grahamc.com\/blog\/erase-your-darlings\/\">Erase your darlings<\/a> post.<\/p>\n<p>However, Graham's post, as well as many other tutorials\/posts which cover the topic,\nuse <code>boot.initrd.postDeviceCommands<\/code> for ZFS rollback on boot,\nwhich <a href=\"https:\/\/github.com\/NixOS\/nixpkgs\/blob\/7046248e4f0acb90b47cc4336203eb810293e147\/nixos\/modules\/system\/boot\/systemd\/initrd.nix#L403\">isn't supported when using systemd in stage 1<\/a>.<\/p>\n<p>Since I need systemd in stage 1 due to MergerFS shenanigans,\nI've decided to write my own post that includes a systemd implementaion\nof the rollback script.<\/p>\n"},{"title":"Updating the BIOS and the BMC firmware on Gigabyte MC12-LE0","pubDate":"Mon, 09 Dec 2024 00:00:00 +0000","author":"Unknown","link":"https:\/\/notthebe.ee\/blog\/updating-bios-and-bmc-firmware-gigabyte-mc12-le0\/","guid":"https:\/\/notthebe.ee\/blog\/updating-bios-and-bmc-firmware-gigabyte-mc12-le0\/","description":"<p><img src=\"https:\/\/notthebe.ee\/blog\/updating-bios-and-bmc-firmware-gigabyte-mc12-le0\/images\/motherboard.jpg\" alt=\"Gigabyte MC12-LE0\" \/><\/p>\n<p>Gigabyte MC12-LE0 is an microATX AMD AM4 motherboard based on the B550 chipset.<\/p>\n<p>It has two PCIe 4.0 slots, 6 SATA slots, 6 (!) PWM fan connectors, and features an\nASPEED KVM, which lets you control the motherboard remotely.\nIt's also fairly inexpensive on the used market.<\/p>\n<p>The catch? Its BIOS update process.<\/p>\n<h2 id=\"prerequisites\">Prerequisites<\/h2>\n<p>You can find the latest BMC firmware <a href=\"https:\/\/www.gigabyte.com\/us\/Enterprise\/Server-Motherboard\/MC12-LE0-rev-1x#Support-Firmware\">here<\/a>,\nand the latest BIOS version <a href=\"https:\/\/www.gigabyte.com\/us\/Enterprise\/Server-Motherboard\/MC12-LE0-rev-1x#Support-Bios\">here<\/a>.<\/p>\n<p>You'll need both regardless of the update method.<\/p>\n<p>In order to update the BIOS, you will also need a working CPU that is already recognized by the motherboard.\nThe BIOS chip is only exposed to the BMC once the CPU is initialized and the system has POSTed, so you\ncan't flash the BIOS with no CPU installed, like on some other AM4 motherboards.<\/p>\n<h2 id=\"the-good\">The Good<\/h2>\n<p>I'd like to preface by saying that on some BMC and BIOS versions,\nupdating the firmware using BMC just works (tm) with no issues\nwhatsoever. So I would encourage you to try and update your\nmotherboard this way first.<\/p>\n<p><img src=\"https:\/\/notthebe.ee\/blog\/updating-bios-and-bmc-firmware-gigabyte-mc12-le0\/images\/bmc_login_screen.png\" alt=\"BMC login screen\" \/><\/p>\n<p>Open the BMC's WebUI, and log in with your credentials. The default login is\n<code>admin<\/code>, and the password is a part of the motherboard's serial number after the slash.<\/p>\n<p>For example, if your motherboard's serial number is <code>4\/I\/DK3I5 456864<\/code>, the password is <code>DK3I5456864<\/code>.\nThe serial number can be found on a sticker in the middle of the motherboard.<\/p>\n<p>After logging in, go to Maintenance &gt; Firmware Update, and click on \"Browse...\".<\/p>\n<p>Here, you'll want to select the RBU file (RBU\/image.rbu) from the <strong>BIOS archive<\/strong> (not the BMC one).<\/p>\n<p>After selecting the file, click on Start Firmware Update. You will be prompted to select the component to update.<\/p>\n<p><img src=\"https:\/\/notthebe.ee\/blog\/updating-bios-and-bmc-firmware-gigabyte-mc12-le0\/images\/firmwareupdate_bmc.png\" alt=\"BMC update screen\" \/><\/p>\n<p>Select \"BMC\". You can additionally choose to preserve either the whole BMC configuration, or single configuration items\n(e.g. networking, authentication, etc.).<\/p>\n<p>Finally, scroll to the bottom of the page and click on \"Proceed to Flash\".<\/p>\n<p><img src=\"https:\/\/notthebe.ee\/blog\/updating-bios-and-bmc-firmware-gigabyte-mc12-le0\/images\/firmwareupdate_failed_bmc.png\" alt=\"Failed BMC update screen\" \/><\/p>\n<p>Well that didn't seem to work.<\/p>\n<p>However, if you're reading this article, chances are, you've already seen this message, and you've seen it coming.<\/p>\n<p>Luckily, this is not the only way to update the BMC\/BIOS firmware.<\/p>\n<h2 id=\"the-bad\">The Bad<\/h2>\n<p>Our backup strategy involves using the UEFI shell to perform the updates.<\/p>\n<p>For that, you'll want to copy the contents of the BMC firwmare archive to a\nflash drive (formatted as FAT32), and plug the drive it into the motherboard.<\/p>\n<p><img src=\"https:\/\/notthebe.ee\/blog\/updating-bios-and-bmc-firmware-gigabyte-mc12-le0\/images\/usb_bmc_firmware_files.png\" alt=\"BMC firmware on the USB drive\" \/><\/p>\n<p>Power on the motherboard, either using the case button, or the BMC's WebUI.<\/p>\n<p>In the WebUI, go to Remote Control &gt; Launch H5 Viewer. If you don't have any bootable device installed,\nyou should already be seeing the UEFI Shell. If not, you can reboot the server, enter the BIOS settings\nand choose the UEFI Shell boot override.<\/p>\n<p><img src=\"https:\/\/notthebe.ee\/blog\/updating-bios-and-bmc-firmware-gigabyte-mc12-le0\/images\/bios_settings.png\" alt=\"BIOS Settings \u2013 UEFI Shell boot override\" \/><\/p>\n<p>Once you are in the UEFI Shell, take a look at the file systems available. In my case, there's FS0 and FS1.<\/p>\n<p>Type <code>FS0:<\/code>, press Enter, and then type <code>ls<\/code>. This will list the contents of the file system. Repeat with all\navailable file system until you see the BMC firmware files.<\/p>\n<p><img src=\"https:\/\/notthebe.ee\/blog\/updating-bios-and-bmc-firmware-gigabyte-mc12-le0\/images\/uefi_shell_bmc.png\" alt=\"UEFI Shell \u2013 BMC update\" \/><\/p>\n<p>Now, type \"bmc\" and press Tab to autocomplete the command until you see <code>bmc_fw_update_uefi.nsh<\/code>.\nPress Enter to start the BMC firmware update process.<\/p>\n<p>Once the update has started, the WebUI will stop responding. Be patient, and wait for the BMC to become available\non the same IP address. Keep in mind that if you chose not to preserve the settings, the motherboard will automatically\ngrab an IP address from your router using DHCP.<\/p>\n<p>And after a few minutes, we're back in business!<\/p>\n<p><img src=\"https:\/\/notthebe.ee\/blog\/updating-bios-and-bmc-firmware-gigabyte-mc12-le0\/images\/updated_bmc.png\" alt=\"Updated BMC firwmare\" \/><\/p>\n<p>It's time for the BIOS update.<\/p>\n<h2 id=\"the-ugly\">The Ugly<\/h2>\n<p>For some reason, the BIOS archive from the Gigabyte's website is missing\nthe UEFI executable needed to flash the BIOS from the UEFI shell (SPI_UPD\/AfuEfix64.efi).<\/p>\n<p><img src=\"https:\/\/notthebe.ee\/blog\/updating-bios-and-bmc-firmware-gigabyte-mc12-le0\/images\/no_afu_tool.png\" alt=\"No AfuEfix64.efi tool in the BIOS archive\" \/><\/p>\n<p>However, there's no need to scour the shady darknet sites for a version of the executable.<\/p>\n<p>Looking at the contents of the BIOS archive, we can see that it includes <em>yet another<\/em>\narchive inside with a \"zi_\" extension.<\/p>\n<p><img src=\"https:\/\/notthebe.ee\/blog\/updating-bios-and-bmc-firmware-gigabyte-mc12-le0\/images\/another_archive.png\" alt=\"Another archive\" \/><\/p>\n<p>Once we rename it to \"zip\" and unpack it, it appears to be an identical copy of the\nmain archive.<\/p>\n<p>However, lo and behold, this copy <em>does<\/em> actually include the necessary executable to update the BIOS!<\/p>\n<p><img src=\"https:\/\/notthebe.ee\/blog\/updating-bios-and-bmc-firmware-gigabyte-mc12-le0\/images\/afu_tool.png\" alt=\"The AfuEfix64 utility\" \/><\/p>\n<p>To avoid confusion, make sure to only copy the contents of the \"inside\" archive to the flash drive,\nbut not before removing the BMC firmware files from it first.<\/p>\n<p>The rest is identical to the BMC update process \u2013 plug the USB drive into the motherboard,\nenter the UEFI shell, select the FS containing the update files (FS1: in my case).<\/p>\n<p><img src=\"https:\/\/notthebe.ee\/blog\/updating-bios-and-bmc-firmware-gigabyte-mc12-le0\/images\/uefi_shell_bios.png\" alt=\"UEFI Shell \u2013 BIOS update\" \/><\/p>\n<p>Once you've found the update files, type <code>f.nsh<\/code>, and press Enter.<\/p>\n<p>You may see the following error:<\/p>\n<pre class=\"z-code\"><code><span class=\"z-text z-plain\">THE ROM file information does not match the system BIOS\n<\/span><\/code><\/pre>\n<p>If that's the case, select the option \"E\" (Update Entire BIOS Region and Exit)\nwhen prompted.<\/p>\n<p>After the update is finished, the motherboard should reboot on its own. However, some users over at the STH forum\nreport that their motherboard got stuck with no output after the process was finished. If that's the case for you,\nwait for ~30 minutes and reboot the board manually.<\/p>\n<p>Great Success!<\/p>\n<p><img src=\"https:\/\/notthebe.ee\/blog\/updating-bios-and-bmc-firmware-gigabyte-mc12-le0\/images\/bios_updated.png\" alt=\"Updated BIOS\" \/><\/p>\n"},{"title":"Easy, quick and free valid SSL certificates for your homelab using DuckDNS and ACME DNS-01","pubDate":"Sat, 13 May 2023 00:00:00 +0000","author":"Unknown","link":"https:\/\/notthebe.ee\/blog\/easy-ssl-in-homelab-dns01\/","guid":"https:\/\/notthebe.ee\/blog\/easy-ssl-in-homelab-dns01\/","description":"<p><img src=\"https:\/\/notthebe.ee\/blog\/easy-ssl-in-homelab-dns01\/images\/hero.png\" alt=\"Jellyfin with a Let&#39;s Encrypt certificate\" \/><\/p>\n<p>If you're running local web applications in your homelab, chances are, you're accessing them with a combination of an IP address and a port (e.g. <code>http:\/\/192.168.1.32:8096<\/code> for Jellyfin).<\/p>\n<p>However, at some point, you might get tired of memorizing IP addresses, so you'll switch to a combination of a reverse proxy and local domain names, such as <code>https:\/\/jellyfin.local<\/code> or <code>https:\/\/homeassistant.local<\/code>.<\/p>\n<p>After doing that, you'll quickly notice that you get a browser warning every time you visit your applications. Annoying.<\/p>\n<p>So what are some possible solutions?<\/p>\n<ol>\n<li>Create a local CA, generate your own certificates and trust them on every device that you browse the Internet on.<\/li>\n<li>Add an exception for every application in your homelab on every device.<\/li>\n<li>Just ignore it.<\/li>\n<\/ol>\n<p>However, these solutions are half-measures at best, and a pain in the ass at worst. There's an infinitely better way to serve your local web applications with pretty domain names and valid SSL certificates, and it involves an SSL certificate verification method called DNS-01<\/p>\n<h2 id=\"how-does-it-work\">How does it work?<\/h2>\n<p>Here's a very simplified explanation of how Let's Encrypt handles certificate validation and generation.<\/p>\n<p>In order to generate a Let's Encrypt certificate, we would usually let <code>certbot<\/code> spin up a temporary web server on the machine that we'll be generating the certificate for. Then, the Let's Encrypt validation servers try to access a secret code stored on that temporary server, and if that works, we get our certificate. This method is called HTTP-01.<\/p>\n<p>One of the big limitations of HTTP-01 is that the machine that the certificates are being generated for <strong>has to be publicly accessible<\/strong> for the validation servers to reach it. Moreover, the domain that we're generating certificates for <strong>has to resolve to the IP address of the machine<\/strong>.<\/p>\n<p>DNS-01 helps us circumvent these limitations by using a special DNS record to verify domain ownership, instead of directly \"talking\" to the machine that we're generating certificates for. And as an added bonus, DNS-01 lets us generate <strong>wildcard certificates<\/strong> for our domains. Which means that if we have multiple applications with subdomains like <code>jellyfin.homelab.xyz<\/code>, <code>homeasssistant.homelab.xyz<\/code>, <code>proxmox.homelab.xyz<\/code>, we won't need to specify them one-by-one in our certificate request. Instead, we can generate a certificate for <code>*.homelab.xyz<\/code>, which will be valid for all subdomains on the same level. Neat!<\/p>\n<h2 id=\"what-will-i-need\">What will I need?<\/h2>\n<h3 id=\"domain-name\">Domain name<\/h3>\n<p>In my opinion, DuckDNS is the best free option for this method. It's very easy to set up and doesn't require registration (you can simply log in with your GitHub account). The only downside is that the resulting domain names will be pretty long, e.g. <code>jellyfin.homelab.duckdns.org<\/code>.<\/p>\n<p>If this is a deal breaker for you, you can buy a shorter domain name on websites like Namecheap, GoDaddy or Google Domains. Just make sure that your domain registar <a href=\"https:\/\/community.letsencrypt.org\/t\/dns-providers-who-easily-integrate-with-lets-encrypt-dns-validation\/86438\">supports Let's Encypt DNS-01 validation<\/a>. Many domains are sold for as little as $0.90, but <strong>pay attention to renewal fees<\/strong>: those can be as high as $40-50.<\/p>\n<p>In this example, we'll be using DuckDNS.<\/p>\n<h3 id=\"reverse-proxy\">Reverse proxy<\/h3>\n<p>We will also need a reverse proxy application. I will be using <strong>Nginx Proxy Manager<\/strong>, because it's easy to set up and supports Let's Encrypt DNS validation.<\/p>\n<p>However, Nginx Proxy Manager is not the only reverse proxy server that supports DNS-01, and there are many other good alternatives, such as Traefik, Caddy or Linuxserver.io's SWAG.<\/p>\n<h3 id=\"os\">OS<\/h3>\n<p>Last but not least, I'll be running Debian 11, but this tutorial should work on any deb-Based operating system (e.g. Ubuntu)<\/p>\n<h2 id=\"installing-docker\">Installing Docker<\/h2>\n<p>Install dependencies:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> apt<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>y<\/span> install apt-transport-https ca-certificates curl gnupg2 software-properties-common<\/span>\n<\/span><\/code><\/pre>\n<p>Then, add the Docker repository key:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">curl<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"><span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>fsSL<\/span> https:\/\/download.docker.com\/linux\/debian\/gpg<\/span> <span class=\"z-keyword z-operator z-logical z-pipe z-shell\">|<\/span> <span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> gpg<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> --<\/span>dearmor<\/span><span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>o<\/span> \/usr\/share\/keyrings\/docker-archive-keyring.gpg<\/span>\n<\/span><\/code><\/pre>\n<p>Afterwards, add the Docker APT repository. Make sure to replace <code>amd64<\/code> with <code>arm64<\/code> if you're running this on an ARM-based CPU.<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-support z-function z-echo z-shell\">echo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> <span class=\"z-string z-quoted z-double z-shell\"><span class=\"z-punctuation z-definition z-string z-begin z-shell\">&quot;<\/span>deb [arch=amd64 signed-by=\/usr\/share\/keyrings\/docker-archive-keyring.gpg] https:\/\/download.docker.com\/linux\/debian <span class=\"z-meta z-group z-expansion z-command z-parens z-shell\"><span class=\"z-punctuation z-definition z-variable z-shell\">$<\/span><span class=\"z-punctuation z-section z-parens z-begin z-shell\">(<\/span><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">lsb_release<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"><span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>cs<\/span><\/span><span class=\"z-punctuation z-section z-parens z-end z-shell\">)<\/span><\/span> stable<span class=\"z-punctuation z-definition z-string z-end z-shell\">&quot;<\/span><\/span><\/span> <span class=\"z-keyword z-operator z-logical z-pipe z-shell\">|<\/span> <span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> tee \/etc\/apt\/sources.list.d\/docker.list<\/span>\n<\/span><\/code><\/pre>\n<p>Finally, install Docker and docker-compose with the following commands:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> apt update<\/span>\n<\/span><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> apt-get install docker-ce docker-ce-cli containerd.io docker-compose<\/span>\n<\/span><\/code><\/pre>\n<p>If you're running as a non-root user, you might need to add your user to the <code>docker<\/code> group using the following command:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> usermod<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>aG<\/span> docker username<\/span>\n<\/span><\/code><\/pre>\n<p>Then, log out and then log back in again for the changes take effect.<\/p>\n<h2 id=\"setting-up-the-reverse-proxy\">Setting up the reverse proxy<\/h2>\n<p>I'll be running Nginx Proxy Manager, as well as Jellyfin, Home Assistant and Nextcloud, using the following <code>docker-compose.yml<\/code> file:<\/p>\n<pre data-lang=\"yaml\" class=\"language-yaml z-code\"><code class=\"language-yaml\" data-lang=\"yaml\"><span class=\"z-source z-yaml\"><span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">version<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span> <span class=\"z-string z-quoted z-single z-yaml\"><span class=\"z-punctuation z-definition z-string z-begin z-yaml\">&#39;<\/span>2.2<span class=\"z-punctuation z-definition z-string z-end z-yaml\">&#39;<\/span><\/span> \n<\/span><span class=\"z-source z-yaml\"><span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">services<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span>\n<\/span><span class=\"z-source z-yaml\">  <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">nginxproxymanager<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span>\n<\/span><span class=\"z-source z-yaml\">    <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">image<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span> <span class=\"z-string z-quoted z-single z-yaml\"><span class=\"z-punctuation z-definition z-string z-begin z-yaml\">&#39;<\/span>jc21\/nginx-proxy-manager:latest<span class=\"z-punctuation z-definition z-string z-end z-yaml\">&#39;<\/span><\/span> \n<\/span><span class=\"z-source z-yaml\">    <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">container_name<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">nginxproxymanager<\/span>\n<\/span><span class=\"z-source z-yaml\">    <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">restart<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">unless-stopped<\/span> \n<\/span><span class=\"z-source z-yaml\">    <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">ports<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span>\n<\/span><span class=\"z-source z-yaml\">      <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-quoted z-single z-yaml\"><span class=\"z-punctuation z-definition z-string z-begin z-yaml\">&#39;<\/span>80:80<span class=\"z-punctuation z-definition z-string z-end z-yaml\">&#39;<\/span><\/span>\n<\/span><span class=\"z-source z-yaml\">      <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-quoted z-single z-yaml\"><span class=\"z-punctuation z-definition z-string z-begin z-yaml\">&#39;<\/span>81:81<span class=\"z-punctuation z-definition z-string z-end z-yaml\">&#39;<\/span><\/span>\n<\/span><span class=\"z-source z-yaml\">      <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-quoted z-single z-yaml\"><span class=\"z-punctuation z-definition z-string z-begin z-yaml\">&#39;<\/span>443:443<span class=\"z-punctuation z-definition z-string z-end z-yaml\">&#39;<\/span><\/span> \n<\/span><span class=\"z-source z-yaml\">    <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">volumes<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span>\n<\/span><span class=\"z-source z-yaml\">      <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">.\/nginx\/data:\/data<\/span>\n<\/span><span class=\"z-source z-yaml\">      <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">.\/nginx\/letsencrypt:\/etc\/letsencrypt<\/span> \n<\/span><span class=\"z-source z-yaml\">\n<\/span><span class=\"z-source z-yaml\">  <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">nextcloud<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span>\n<\/span><span class=\"z-source z-yaml\">    <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">image<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">lscr.io\/linuxserver\/nextcloud:latest<\/span>\n<\/span><span class=\"z-source z-yaml\">    <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">container_name<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">nextcloud<\/span>\n<\/span><span class=\"z-source z-yaml\">    <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">environment<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span>\n<\/span><span class=\"z-source z-yaml\">      <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">PUID=1000<\/span>\n<\/span><span class=\"z-source z-yaml\">      <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">PGID=1000<\/span>\n<\/span><span class=\"z-source z-yaml\">      <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">TZ=Europe\/Berlin<\/span> \n<\/span><span class=\"z-source z-yaml\">    <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">volumes<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span>\n<\/span><span class=\"z-source z-yaml\">      <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">.\/nextcloud\/appdata:\/config<\/span> \n<\/span><span class=\"z-source z-yaml\">      <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">.\/nextcloud\/data:\/data<\/span>\n<\/span><span class=\"z-source z-yaml\">    <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">restart<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">unless-stopped<\/span> \n<\/span><span class=\"z-source z-yaml\">\n<\/span><span class=\"z-source z-yaml\">  <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">homeassistant<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span>\n<\/span><span class=\"z-source z-yaml\">    <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">image<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">lscr.io\/linuxserver\/homeassistant:latest<\/span>\n<\/span><span class=\"z-source z-yaml\">    <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">container_name<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">homeassistant<\/span> \n<\/span><span class=\"z-source z-yaml\">    <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">environment<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span>\n<\/span><span class=\"z-source z-yaml\">      <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">PUID=1000<\/span>\n<\/span><span class=\"z-source z-yaml\">      <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">PGID=1000<\/span>\n<\/span><span class=\"z-source z-yaml\">      <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">TZ=Europe\/Berlin<\/span> \n<\/span><span class=\"z-source z-yaml\">    <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">volumes<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span>\n<\/span><span class=\"z-source z-yaml\">      <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">.\/hass\/config:\/config<\/span> \n<\/span><span class=\"z-source z-yaml\">    <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">restart<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">unless-stopped<\/span>\n<\/span><span class=\"z-source z-yaml\">\n<\/span><span class=\"z-source z-yaml\">  <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">jellyfin<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span>\n<\/span><span class=\"z-source z-yaml\">    <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">image<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">lscr.io\/linuxserver\/jellyfin:latest<\/span>\n<\/span><span class=\"z-source z-yaml\">    <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">container_name<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">jellyfin<\/span> \n<\/span><span class=\"z-source z-yaml\">    <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">environment<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span>\n<\/span><span class=\"z-source z-yaml\">      <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">PUID=1000<\/span>\n<\/span><span class=\"z-source z-yaml\">      <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">PGID=1000<\/span>\n<\/span><span class=\"z-source z-yaml\">      <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">TZ=Europe\/Berlin<\/span> \n<\/span><span class=\"z-source z-yaml\">    <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">volumes<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span>\n<\/span><span class=\"z-source z-yaml\">      <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">.\/jellyfin\/config:\/config<\/span>\n<\/span><span class=\"z-source z-yaml\">      <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">.\/jellyfin\/tvshows:\/data\/tvshows<\/span>\n<\/span><span class=\"z-source z-yaml\">      <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">.\/jellyfin\/movies:\/data\/movies<\/span> \n<\/span><span class=\"z-source z-yaml\">    <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">restart<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">unless-stopped<\/span>\n<\/span><\/code><\/pre>\n<p>You can also find an example docker-compose snippet on <a href=\"https:\/\/github.com\/NginxProxyManager\/nginx-proxy-manager\">Nginx Proxy Manager's GitHub page<\/a><\/p>\n<p>After creating the <code>docker-compose.yml<\/code> file, we can now launch our Compose stack by running <code>docker-compose up -d<\/code>.<\/p>\n<p>Wait until all the containers have been started, then navigate to <code>http:\/\/server-ip:81<\/code> in your browser<\/p>\n<p><img src=\"https:\/\/notthebe.ee\/blog\/easy-ssl-in-homelab-dns01\/images\/nginxproxymanager.png\" alt=\"Nginx Proxy Manager login screen\" \/><\/p>\n<p>The default credentials are <code>admin@example.com<\/code> and <code>changeme<\/code>. You will be asked to change them after the first log in.<\/p>\n<p>Before generating the certificate, we need to set up the domain name.<\/p>\n<p>Go to <a href=\"https:\/\/duckdns.org\">DuckDNS<\/a> and log in with your account.<\/p>\n<p>Create a new domain and point it to the <em>local IP address<\/em> of your reverse proxy (e.g. 192.168.0.105):<\/p>\n<p><img src=\"https:\/\/notthebe.ee\/blog\/easy-ssl-in-homelab-dns01\/images\/duckdns_create.png\" alt=\"DuckDNS domain creation\" \/><\/p>\n<p>If you're using a different DNS provider, you will also need to create a separate CNAME record pointing to the domain root, in case you want to generate a wildcard certificate for the subdomains:<\/p>\n<p><img src=\"https:\/\/notthebe.ee\/blog\/easy-ssl-in-homelab-dns01\/images\/cname.png\" alt=\"An example CNAME record for the subdomains using Cloudflare\" \/><\/p>\n<p>In the Nginx Proxy Manager's WebUI, go to SSL Certificates, and click \"Add SSL Certificate\".<\/p>\n<p>Here, input your main domain name (e.g. <code>homelab.duckdns.org<\/code>), as well as the wildcard record for all the subdomains (e.g. <code>*.homelab.duckdns.org<\/code>).<\/p>\n<p>Click on \"Use a DNS Challenge\" and select \"DuckDNS\" from the list of providers.<\/p>\n<p>Go back to DuckDNS and copy your <strong>API token<\/strong>. Paste the token into the \"Credentials File Contents\" textbox.<\/p>\n<p>Additionally, you might want to set the \"Propagation Seconds\" parameter to 120 seconds. The default value is 30 sec., however, sometimes this is not enough for the DNS changes to propagate.<\/p>\n<p><img src=\"https:\/\/notthebe.ee\/blog\/easy-ssl-in-homelab-dns01\/images\/cert_settings.png\" alt=\"Certificate settings\" \/><\/p>\n<p>Finally, agree to Let's Encrypt Terms of Service and click on \"Save\".<\/p>\n<p>If you did everything correctly, you should now see your generated certificate:<\/p>\n<p><img src=\"https:\/\/notthebe.ee\/blog\/easy-ssl-in-homelab-dns01\/images\/generated_cert.png\" alt=\"Generated Let&#39;s Encrypt SSL certificate\" \/><\/p>\n<h2 id=\"adding-a-proxy-entry\">Adding a proxy entry<\/h2>\n<p>Let's add our first proxy entry for the Nginx Proxy Manager itself. This will let us access it by going to <code>htts:\/\/notthebee.duckdns.org<\/code>.<\/p>\n<p>In the top menu bar, click on Hosts &gt; Proxy Hosts, and then click on \"Add a Proxy Host\". I will use the following parameters:<\/p>\n<ul>\n<li><strong>Domain Names<\/strong>: notthebee.duckdns.org<\/li>\n<li><strong>Scheme<\/strong>: http<\/li>\n<li><strong>Forward Hostname \/ IP<\/strong>: nginxproxymanager (more on that below)<\/li>\n<li><strong>Forward Port<\/strong> :81<\/li>\n<\/ul>\n<p><img src=\"https:\/\/notthebe.ee\/blog\/easy-ssl-in-homelab-dns01\/images\/domain_settings.png\" alt=\"Proxy entry settings\" \/><\/p>\n<p>If all of your applications are on the same Docker network (e.g. if you're using a single docker-compose.yml file), you can use their respective container names (<code>container_name<\/code> parameter) here instead of the IP (in this case, <code>nginxproxymanager<\/code>).<\/p>\n<p>When it comes to other parameters, feel free to enable them if you know that the application that you're proxying supports\/requires them.<\/p>\n<p>Afterwards, go to the SSL tab and choose the certificate that we've generated in the previous step. Enable Force SSL and HTTP\/2 support.<\/p>\n<p>And that's it! Navigate to the domain in the browser and you should see a lock in the address bar, and no SSL warning.<\/p>\n<p><img src=\"https:\/\/notthebe.ee\/blog\/easy-ssl-in-homelab-dns01\/images\/nginxproxymanager_with_ssl.png\" alt=\"Nginx Proxy Manager Log in Screen\" \/><\/p>\n<p>Repeat for every application that you'd like to access with your new domain :)<\/p>\n<h2 id=\"nginx-proxy-manager\">Nginx Proxy Manager<\/h2>\n<p>Nginx Proxy Manager has a lot of other cool features that I didn't mention in this post, such as:<\/p>\n<ul>\n<li>A full-fledged user management and ACL system, complete with an Audit Log and permissions<\/li>\n<li>Support for custom Nginx configuration snippets, HTTP streams<\/li>\n<li>Custom redirects and 404 pages<\/li>\n<li>And much more<\/li>\n<\/ul>\n<p>Check it out and show some love: <a href=\"https:\/\/nginxproxymanager.com\">https:\/\/nginxproxymanager.com<\/a><\/p>\n"},{"title":"Set up your own VPN at home with Raspberry Pi","pubDate":"Tue, 23 Feb 2021 00:00:00 +0000","author":"Unknown","link":"https:\/\/notthebe.ee\/blog\/set-up-your-own-vpn-on-raspberry-pi\/","guid":"https:\/\/notthebe.ee\/blog\/set-up-your-own-vpn-on-raspberry-pi\/","description":"<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/set-up-your-own-vpn-on-raspberry-pi\/title.jpg\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/set-up-your-own-vpn-on-raspberry-pi\/title.jpg\" alt=\"Raspberry Pi under a Pilea plant\">\n    <\/a>\n<\/div>\n<h2 id=\"requirements\">Requirements<\/h2>\n<ul>\n<li>Globaly accessible IP address<\/li>\n<li>A router that supports <strong>Port forwarding<\/strong><\/li>\n<li>Raspberry Pi 3 or newer<\/li>\n<li>microUSB (RPi 3) or USB-C (RPi 4) power supply<\/li>\n<li>microSD card (8GB or bigger, at least Class 4)<\/li>\n<li>Ethernet cable<\/li>\n<li>microSD USB card reader<\/li>\n<li>(optional) microHDMI (RPi 4) or HDMI (RPi 3) to HDMI cable<\/li>\n<\/ul>\n<h2 id=\"downloading-the-software\">Downloading the software<\/h2>\n<p>This tutorial is based on <strong>Raspberry Pi OS<\/strong>, but you can use any Debian-based operating system that you prefer.<\/p>\n<p>Raspberry Pi OS comes in <strong>Desktop<\/strong> and <strong>Lite<\/strong> flavors. You can download the former if you want to set Raspberry Pi up with a monitor, a keyboard and a mouse; or the latter, if you want to set it up \"headlessly\".<\/p>\n<p>We will also need <strong>balenEtcher<\/strong> to write the OS image to the microSD card. Feel free to use <strong>dd<\/strong> or <strong>Rufus<\/strong>, if you're more comfortable with them.<\/p>\n<p><strong>Download Raspberry Pi OS<\/strong>: <a href=\"https:\/\/www.raspberrypi.org\/software\/operating-systems\/\">https:\/\/www.raspberrypi.org\/software\/operating-systems\/<\/a><\/p>\n<p><strong>Download balenaEtcher:<\/strong> <a href=\"https:\/\/www.balena.io\/etcher\/\">https:\/\/www.balena.io\/etcher\/<\/a><\/p>\n<h2 id=\"writing-the-os-image-to-the-card\">Writing the OS image to the card<\/h2>\n<p>After you have both Etcher and Raspberry Pi OS on your computer, you can now insert the microSD card into your computer. Then, launch Etcher, choose the Raspberry Pi OS image that you downloaded, select your microSD card and click \"Flash\".<\/p>\n<p>After the flashing is done, you'll see a new volume in \"This PC\" menu called \"boot\". Go to that volume, create a new text file and call it \"<strong>ssh<\/strong>\" \u2013 be careful, it's not \"<strong>ssh.txt<\/strong>\", it's \"<strong>ssh<\/strong>\", without any extension. To do that, you need to have the \"Hide extensions for known file types\" option disabled in the File Exporer Options.<\/p>\n<h2 id=\"booting-up\">Booting up<\/h2>\n<p>With that done, you can now eject the microSD card from your computer. Now put the SD card into the Raspberry Pi, plug your Ethernet cable into the router and into the board, then finally plug the USB-C cable into it. In case you want to set your Raspberry Pi up with a monitor, you also need to plug in a monitor, a keyboard and a mouse into it at this point.<\/p>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/set-up-your-own-vpn-on-raspberry-pi\/plugged_in.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/set-up-your-own-vpn-on-raspberry-pi\/plugged_in.png\" alt=\"Raspberry Pi 4\">\n    <\/a>\n<\/div>\n<h2 id=\"for-monitor-users\">For monitor users<\/h2>\n<p>The initial setup wizard on Raspberry Pi OS will guide you through the initial setup process, including changing the password and installing the updates. Afterwards, the installer will prompt you to reboot the board \u2013 do it and skip to the \"Dynamic DNS\" section.<\/p>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/set-up-your-own-vpn-on-raspberry-pi\/raspberrypios.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/set-up-your-own-vpn-on-raspberry-pi\/raspberrypios.png\" alt=\"Raspberry Pi OS Desktop\">\n    <\/a>\n<\/div>\n<h2 id=\"logging-into-the-system\">Logging into the system<\/h2>\n<p>Now you need to wait for about a couple of minutes while your little computer is booting up, and then let's open the browser again and go to the router's admin panel.<\/p>\n<p>Go to the page that lists all of the devices connected to the network and copy the IP address of the Raspberry Pi (it will most likely have the hostname <code>raspberry<\/code>).<\/p>\n<p>Next, open the Terminal on your host machine. You can use <strong>PowerShell<\/strong> on Windows.<\/p>\n<p>Type the following command:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">ssh<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> pi@192.168.1.45 <span class=\"z-comment z-line z-number-sign z-shell\"><span class=\"z-punctuation z-definition z-comment z-begin z-shell\">#<\/span><\/span><span class=\"z-comment z-line z-number-sign z-shell\"> Replace with Raspberry Pi&#39;s IP address<\/span><span class=\"z-comment z-line z-number-sign z-shell\">\n<\/span><\/span><\/span><\/code><\/pre>\n<p>You can use right mouse button to paste text in Windows PowerShell.<\/p>\n<p>Answer \"yes\" to the next question, and type <code>raspberry<\/code> when asked for a password. <strong>The password will not be shown on the screen<\/strong>, not even the asterisks, and that applies to all of the password fields in the Linux command line interface.<\/p>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/set-up-your-own-vpn-on-raspberry-pi\/loggedin.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/set-up-your-own-vpn-on-raspberry-pi\/loggedin.png\" alt=\"Raspberry Pi SSH greeting\">\n    <\/a>\n<\/div>\n<h2 id=\"changing-the-password\">Changing the password<\/h2>\n<p>First thing we need to do is change the default password to something more secure. We won't be setting up public-key authentication in this tutorial, since the SSH port isn't going to be exposed to the Internet. If you'd like to know more, check out <a href=\"https:\/\/www.digitalocean.com\/community\/tutorials\/how-to-configure-ssh-key-based-authentication-on-a-linux-server\">this tutorial from Digital Ocean<\/a>.<\/p>\n<p>In order to change the password you need to type <code>passwd<\/code> , type your current password (<code>raspberry<\/code>), and then type your new password twice.<\/p>\n<h2 id=\"installing-updates\">Installing updates<\/h2>\n<p>Next thing we're gonna do is updating our operating system to all the latest versions of software. For that, type:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> apt update<\/span> <span class=\"z-keyword z-operator z-logical z-and z-shell\">&amp;&amp;<\/span> <span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> apt upgrade<\/span>\n<\/span><\/code><\/pre>\n<p>After the installation is complete and you see the green command prompt again, type <code>sudo reboot<\/code> to reboot the board.<\/p>\n<h2 id=\"setting-up-dynamic-dns\">Setting up dynamic DNS<\/h2>\n<p>Now that all of the preparations are finished, we need to get ourselves a dynamic DNS hostname. Unless you're using a business broadband connection, your external IP address most likely changes dynamically every time your ISP feels like it. For that reason, we'll need to set up a <strong>dynamic DNS service.<\/strong> For this tutorial I'll be using a free plan from <a href=\"http:\/\/freedns.afraid.org\">freedns.afraid.org<\/a> . That being said, you can use any service you want, I'm not endorsing any particular one.<\/p>\n<p>After registering on the website and activating your account via email, click on the \"Add a subdomain\". Here, the things we need to change are:<\/p>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/set-up-your-own-vpn-on-raspberry-pi\/freedns.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/set-up-your-own-vpn-on-raspberry-pi\/freedns.png\" alt=\"FreeDNS settings\">\n    <\/a>\n<\/div>\n<ul>\n<li>Subdomain \u2013 put whatever you want here<\/li>\n<li>Domain \u2013 same<\/li>\n<li>Destination \u2013 by default it's going to have your current IP address in there, but you need to change it to 0.0.0.0 \u2013 that way we'll be able to test if our dynamic IP assignment software actually works.<\/li>\n<li>After that, type in the captcha and click on \"Save!\".<\/li>\n<\/ul>\n<h2 id=\"installing-and-setting-up-ddclient\">Installing and setting up ddclient<\/h2>\n<p>Now we need to log back in to our Raspberry Pi, by typing this command in the PowerShell or Terminal:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">ssh<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> pi@192.168.1.45 <span class=\"z-comment z-line z-number-sign z-shell\"><span class=\"z-punctuation z-definition z-comment z-begin z-shell\">#<\/span><\/span><span class=\"z-comment z-line z-number-sign z-shell\"> Replace with Raspberry Pi&#39;s IP address<\/span><span class=\"z-comment z-line z-number-sign z-shell\">\n<\/span><\/span><\/span><\/code><\/pre>\n<p><strong>Pro-tip:<\/strong> You can also press \u2191 and that will give you the last command you entered. Next, enter the new password that you created earlier.<\/p>\n<p>Now we need to install a piece of software called <code>ddclient<\/code>:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> apt install ddclient <\/span>\n<\/span><\/code><\/pre>\n<p>and press Enter. It's going to ask you for some things so just pretend you don't know anything by typing \"Enter\" until it gives up.<\/p>\n<p>Now we need to tell ddclient which address it needs to update:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> nano \/etc\/ddclient.conf<\/span>\n<\/span><\/code><\/pre>\n<p>Let's just delete all of the lines in the file and replace them with this:<\/p>\n<pre class=\"z-code\"><code><span class=\"z-text z-plain\">daemon=5m\n<\/span><span class=\"z-text z-plain\">timeout=10\n<\/span><span class=\"z-text z-plain\">syslog=no # log update msgs to syslog\n<\/span><span class=\"z-text z-plain\">#mail=root # mail all msgs to root\n<\/span><span class=\"z-text z-plain\">#mail-failure=root # mail failed update msgs to root\n<\/span><span class=\"z-text z-plain\">pid=\/var\/run\/ddclient.pid # record PID in file.\n<\/span><span class=\"z-text z-plain\">ssl=yes # use ssl-support. Works with\n<\/span><span class=\"z-text z-plain\"># ssl-library\n<\/span><span class=\"z-text z-plain\">\n<\/span><span class=\"z-text z-plain\">use=if, if=eth0\n<\/span><span class=\"z-text z-plain\">server=freedns.afraid.org\n<\/span><span class=\"z-text z-plain\">protocol=freedns\n<\/span><span class=\"z-text z-plain\">login=YOUR FREEDNS LOGIN\n<\/span><span class=\"z-text z-plain\">password=YOUR FREEDNS PASSWORD\n<\/span><span class=\"z-text z-plain\">your.freedns.domain\n<\/span><\/code><\/pre>\n<p>After that's done press <strong>Control+O<\/strong> to save the file and <strong>Control+X<\/strong> to exit.<\/p>\n<p>Another file that we need to edit is <code>\/etc\/default\/ddclient<\/code>. Here we need to change everything to <code>false<\/code> except for the <code>run_daemon<\/code> option. This one we need to change to <code>true<\/code>. Once that's done, <strong>Control+O<\/strong>, <strong>Control+X<\/strong>.<\/p>\n<p>Now that all of the configuration is done, let's restart the ddclient service:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> systemctl restart ddclient<\/span>\n<\/span><\/code><\/pre>\n<p>Let's check the service status now:<\/p>\n<pre class=\"z-code\"><code><span class=\"z-text z-plain\">sudo systemctl status ddclient\n<\/span><span class=\"z-text z-plain\">Feb 16 15:05:25 raspberrypi ddclient[1806]: WARNING: Connection: close\n<\/span><span class=\"z-text z-plain\">Feb 16 15:05:25 raspberrypi ddclient[1806]: WARNING: Vary: Accept-Encoding\n<\/span><span class=\"z-text z-plain\">Feb 16 15:05:25 raspberrypi ddclient[1806]: WARNING: Cache-Control: no-store, no-cache, must-revalidate\n<\/span><span class=\"z-text z-plain\">Feb 16 15:05:25 raspberrypi ddclient[1806]: WARNING: Cache-Control: post-check=0, pre-check=0\n<\/span><span class=\"z-text z-plain\">Feb 16 15:05:25 raspberrypi ddclient[1806]: WARNING: Pragma: no-cache\n<\/span><span class=\"z-text z-plain\">Feb 16 15:05:25 raspberrypi ddclient[1806]: WARNING: Expires: Mon, 26 Jul 1997 05:00:00 GMT\n<\/span><span class=\"z-text z-plain\">Feb 16 15:05:25 raspberrypi ddclient[1806]: WARNING: X-Cache: MISS\n<\/span><span class=\"z-text z-plain\">Feb 16 15:05:25 raspberrypi ddclient[1806]: WARNING:  \n<\/span><span class=\"z-text z-plain\">Feb 16 15:05:25 raspberrypi ddclient[1806]: WARNING: Updated 1 host(s) your.freedns.domain to 13.37.420.69\n<\/span><span class=\"z-text z-plain\">Feb 16 15:05:25 raspberrypi ddclient[1806]: **FAILED: updating your.freedns.domain: Invalid reply.**\n<\/span><\/code><\/pre>\n<p>As you can see it actually says <code>FAILED<\/code>, but if you go back to our browser and refresh the page with the subdomain, you should see that 0.0.0.0 changed to your real IP address.<\/p>\n<p>Finally, let's make sure ddclient starts automatically every time we power our Raspberry Pi on:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> systemctl enable ddclient<\/span>\n<\/span><\/code><\/pre>\n<h2 id=\"setting-up-port-forwarding\">Setting up port forwarding<\/h2>\n<p>Set up a new port forwarding entry for Raspberry Pi in your router's administration panel. The settings are as follows:<\/p>\n<ul>\n<li><strong>Device<\/strong>: Raspberry Pi's hostname or IP<\/li>\n<li><strong>Protocol<\/strong>: UDP<\/li>\n<li><strong>Port range<\/strong>: 51820-51820<\/li>\n<li><strong>Outgoing port<\/strong>: 51820<\/li>\n<li><strong>Permit Internet access<\/strong>: yes<\/li>\n<\/ul>\n<p>Here's what the settings look like on a FritzBox router:<\/p>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/set-up-your-own-vpn-on-raspberry-pi\/portfwd.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/set-up-your-own-vpn-on-raspberry-pi\/portfwd.png\" alt=\"Port forwarding settings\">\n    <\/a>\n<\/div>\n<h2 id=\"installing-wireguard-vpn\">Installing Wireguard VPN<\/h2>\n<p>Copy and the following command into the terminal (while logged into RPi):<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">wget<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> https:\/\/git.io\/wireguard<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>O<\/span> wireguard-install.sh<\/span> <span class=\"z-keyword z-operator z-logical z-and z-shell\">&amp;&amp;<\/span> <span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> bash wireguard-install.sh<\/span>\n<\/span><\/code><\/pre>\n<p>The script is going to ask you for the hostname that you want to use for the VPN \u2013 type your dynamic DNS domain that we created earlier.<\/p>\n<p>For client name, just put any name you want, and for DNS, this is kind of personal preference \u2013 I personally use <strong>1.1.1.1<\/strong><\/p>\n<p>Follow the instuctions on the screen further and wait until the installation is finished.<\/p>\n<p><strong>Important:<\/strong> You will need to add a new user for <strong>every device<\/strong> that you use with the VPN. To add a new user, simply re-run the script and follow the instructions.<\/p>\n<h2 id=\"connecting-to-the-vpn-from-a-phone\">Connecting to the VPN from a phone<\/h2>\n<p>Install the Wireguard app from Google Play or App Store<\/p>\n<p><strong>Wireguard (Google Play):<\/strong> <a href=\"https:\/\/play.google.com\/store\/apps\/details?id=com.wireguard.android\">https:\/\/play.google.com\/store\/apps\/details?id=com.wireguard.android<\/a><\/p>\n<p><strong>Wireguard (App Store):<\/strong> <a href=\"https:\/\/apps.apple.com\/us\/app\/wireguard\/id1441195209\">https:\/\/apps.apple.com\/us\/app\/wireguard\/id1441195209<\/a><\/p>\n<p>Scan the QR code shown in the terminal using the function <strong>Create from QR code<\/strong> in the app.<\/p>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/set-up-your-own-vpn-on-raspberry-pi\/qr_code.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/set-up-your-own-vpn-on-raspberry-pi\/qr_code.png\" alt=\"FreeDNS Settings\">\n    <\/a>\n<\/div>\n<p>Disconnect from the WiFi and test the VPN connection using cellular network. Your can check your external IP address <a href=\"http:\/\/whoer.net\">here<\/a>.<\/p>\n<h2 id=\"connecting-to-the-vpn-from-a-pc-windows\">Connecting to the VPN from a PC (Windows)<\/h2>\n<p>Connecting to our VPN from a computer requires a few additional steps.<\/p>\n<p>First, we need to move the configuration files to our home directory. For that, log in to the Raspberry Pi from the terminal, and type:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> su<\/span>\n<\/span><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">cp<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> \/root\/<span class=\"z-keyword z-operator z-regexp z-quantifier z-shell\">*<\/span>.conf \/home\/pi<\/span>\n<\/span><\/code><\/pre>\n<p>Create a folder for the Wireguard configuration files in Windows Explorer. Go to that folder, right-click on the empty space and click \"Open a PowerShell window here\". Press \u2191 to get the latest command and replace <code>ssh<\/code> with <code>sftp<\/code>. Press Enter to execute.<\/p>\n<p>After you've entered your password, you can now copy the configuration file to your machine. For that, type <code>get *.conf<\/code> and press Enter. After the file is copied, type <code>exit<\/code> to quit the sftp interface.<\/p>\n<p>Now you can download the Wireguard desktop app and import the config file into it.<\/p>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/set-up-your-own-vpn-on-raspberry-pi\/wireguard_desktop.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/set-up-your-own-vpn-on-raspberry-pi\/wireguard_desktop.png\" alt=\"Wireguard Desktop\">\n    <\/a>\n<\/div>\n<p><strong>Wireguard client apps:<\/strong> <a href=\"https:\/\/www.wireguard.com\/install\/\">https:\/\/www.wireguard.com\/install\/<\/a><\/p>\n"},{"title":"Split VPN tunelling for selected applications (Windows, Linux, macOS)","pubDate":"Wed, 11 Nov 2020 00:00:00 +0000","author":"Unknown","link":"https:\/\/notthebe.ee\/blog\/split-vpn-tunnelling-for-selected-applications\/","guid":"https:\/\/notthebe.ee\/blog\/split-vpn-tunnelling-for-selected-applications\/","description":"<p><div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/split-vpn-tunnelling-for-selected-applications\/screenshot.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/split-vpn-tunnelling-for-selected-applications\/screenshot.png\" alt=\"docker-compose.yml\">\n    <\/a>\n<\/div>\n\nSometimes you need to use VPN for a few selected applications, but not for your whole Internet traffic. However, most operating systems do not include that functionality.<\/p>\n<p>In this tutorial we will create a local proxy for our VPN using Docker and <a href=\"https:\/\/hub.docker.com\/r\/binhex\/arch-delugevpn\">binhex\/arch-delugevpn<\/a><\/p>\n<h2 id=\"windows\">Windows<\/h2>\n<h3 id=\"installing-wsl2\">Installing WSL2<\/h3>\n<p><strong>IMPORTANT:<\/strong> At the moment of writing, Microsoft's version of the Linux kernel does not include the Wireguard module by default. You can try your luck by compiling the kernel and the module manually, but I didn't manage to make it work on my machine. Hence, I recommend using OpenVPN with Windows.<\/p>\n<p>Open the start menu and type \u201cTurn\". Click on the first result, <strong>Turn Windows features on or off<\/strong>. Here we need to enable two things \u2014 <strong>Virtual Machine Platform<\/strong> and <strong>Windows Subsystem for Linux<\/strong>.<\/p>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/split-vpn-tunnelling-for-selected-applications\/features.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/split-vpn-tunnelling-for-selected-applications\/features.png\" alt=\"Turn Windows features on or off\">\n    <\/a>\n<\/div>\n<p>You can also do the same by typing the following commands in Powershell:<\/p>\n<pre data-lang=\"powershell\" class=\"language-powershell z-code\"><code class=\"language-powershell\" data-lang=\"powershell\"><span class=\"z-source z-powershell\"><span class=\"z-support z-function z-powershell\">dism.exe<\/span> <span class=\"z-keyword z-operator z-assignment z-powershell\">\/<\/span>online <span class=\"z-keyword z-operator z-assignment z-powershell\">\/<\/span><span class=\"z-support z-function z-powershell\">enable-feature<\/span> <span class=\"z-keyword z-operator z-assignment z-powershell\">\/<\/span>featurename:Microsoft<span class=\"z-keyword z-operator z-assignment z-powershell\">-<\/span>Windows<span class=\"z-keyword z-operator z-assignment z-powershell\">-<\/span>Subsystem<span class=\"z-keyword z-operator z-assignment z-powershell\">-<\/span>Linux <span class=\"z-keyword z-operator z-assignment z-powershell\">\/<\/span>all <span class=\"z-keyword z-operator z-assignment z-powershell\">\/<\/span>norestart\n<\/span><span class=\"z-source z-powershell\"><span class=\"z-support z-function z-powershell\">dism.exe<\/span> <span class=\"z-keyword z-operator z-assignment z-powershell\">\/<\/span>online <span class=\"z-keyword z-operator z-assignment z-powershell\">\/<\/span><span class=\"z-support z-function z-powershell\">enable-feature<\/span> <span class=\"z-keyword z-operator z-assignment z-powershell\">\/<\/span>featurename:VirtualMachinePlatform <span class=\"z-keyword z-operator z-assignment z-powershell\">\/<\/span>all <span class=\"z-keyword z-operator z-assignment z-powershell\">\/<\/span>norestart\n<\/span><\/code><\/pre>\n<p>After that, we need to restart Windows to complete the installation \u2013 press the <strong>Restart now<\/strong> button.<\/p>\n<p>After Windows starts up, go to the Start Menu again and type \"power\". Open PowerShell and type <code>wsl --set-default-version 2<\/code>. After that, download the <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows\/wsl\/wsl2-kernel\">Linux kernel update for WSL2<\/a> and install it.<\/p>\n<h3 id=\"installing-docker-desktop\">Installing Docker Desktop<\/h3>\n<p>Download Docker Desktop for Windows from the <a href=\"https:\/\/www.docker.com\/products\/docker-desktop\">official Docker website<\/a>. After the installation is complete, press <strong>Close and log out<\/strong>.<\/p>\n<h3 id=\"installing-ubuntu-20-04\">Installing Ubuntu 20.04<\/h3>\n<p>After you log back in, you'll see the Docker Desktop screen. But before configuring docker, we need to install a WSL distro from which we're going to use in order to manage our container.<\/p>\n<p>Open Microsoft Store and search for your favorite distribution. Personally, I recommend <a href=\"https:\/\/www.microsoft.com\/en-us\/p\/ubuntu-2004-lts\/9n6svws3rx71\">Ubuntu 20.04 LTS<\/a><\/p>\n<p>Once that's installed, click on \"Launch\". Type in your desired username and password in the terminal, and after that you should get the bash prompt.<\/p>\n<p>Now go back to Docker Desktop, open the Settings and untick the \"Send usage statistics\". Then, go to Resources &gt; WSL INTEGRATION and enable \"Ubuntu 20.04\". Close the Linux terminal window and open it again from the Start menu.<\/p>\n<p><img src=\"https:\/\/notthebe.ee\/blog\/split-vpn-tunnelling-for-selected-applications\/img\/vpn2\/docker.png\" alt=\"Docker Settings\" \/><\/p>\n<h3 id=\"creating-folders-and-writing-a-compose-file\">Creating folders and writing a compose file<\/h3>\n<p>In the Linux terminal, type:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">mkdir<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"><span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>p<\/span> \/mnt\/c\/Users\/WINDOWS_USER_NAME\/docker\/arch-delugevpn\/data<\/span>\n<\/span><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">mkdir<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"><span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>p<\/span> \/mnt\/c\/Users\/WINDOWS_USER_NAME\/docker\/arch-delugevpn\/config\/openvpn<\/span>\n<\/span><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">mkdir<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"><span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>p<\/span> \/mnt\/c\/Users\/WINDOWS_USER_NAME\/docker\/arch-delugevpn\/config\/compose<\/span>\n<\/span><\/code><\/pre>\n<p>This will create a folder named <strong>docker<\/strong> in your Windows user's directory and all the necessary subfolders.<\/p>\n<p>Now create a file named <code>docker-compose.yml<\/code> in the <strong>compose<\/strong> folder with the following contents:<\/p>\n<pre data-lang=\"yaml\" class=\"language-yaml z-code\"><code class=\"language-yaml\" data-lang=\"yaml\"><span class=\"z-source z-yaml\"><span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">version<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span> <span class=\"z-string z-quoted z-single z-yaml\"><span class=\"z-punctuation z-definition z-string z-begin z-yaml\">&#39;<\/span>3<span class=\"z-punctuation z-definition z-string z-end z-yaml\">&#39;<\/span><\/span>\n<\/span><span class=\"z-source z-yaml\"><span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">services<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span>\n<\/span><span class=\"z-source z-yaml\">        <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">delugevpn<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span>\n<\/span><span class=\"z-source z-yaml\">                <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">container_name<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">delugevpn<\/span>\n<\/span><span class=\"z-source z-yaml\">                <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">image<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">binhex\/arch-delugevpn:latest<\/span>\n<\/span><span class=\"z-source z-yaml\">                <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">restart<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">unless-stopped<\/span>\n<\/span><span class=\"z-source z-yaml\">                <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">cap_add<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span>\n<\/span><span class=\"z-source z-yaml\">                        <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">net_admin<\/span> <span class=\"z-comment z-line z-number-sign z-yaml\"><span class=\"z-punctuation z-definition z-comment z-line z-number-sign z-yaml\">#<\/span> Necessary for OpenVPN\n<\/span><\/span><span class=\"z-source z-yaml\">                <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">ports<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span>\n<\/span><span class=\"z-source z-yaml\">                        <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">0.0.0.0:8112:8112<\/span>\n<\/span><span class=\"z-source z-yaml\">                        <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">58846:58846<\/span>\n<\/span><span class=\"z-source z-yaml\">                        <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">0.0.0.0:8118:8118<\/span>\n<\/span><span class=\"z-source z-yaml\">                <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">environment<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span>\n<\/span><span class=\"z-source z-yaml\">                        <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">VPN_ENABLED=yes<\/span>\n<\/span><span class=\"z-source z-yaml\">                        <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">VPN_PROV=custom<\/span>\n<\/span><span class=\"z-source z-yaml\">                        <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">VPN_CLIENT=openvpn<\/span>\n<\/span><span class=\"z-source z-yaml\">                        <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">ENABLE_PRIVOXY=yes<\/span>\n<\/span><span class=\"z-source z-yaml\">                        <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">LAN_NETWORK=192.168.178.0\/24<\/span> <span class=\"z-comment z-line z-number-sign z-yaml\"><span class=\"z-punctuation z-definition z-comment z-line z-number-sign z-yaml\">#<\/span> Replace with your network&#39;s IP\n<\/span><\/span><span class=\"z-source z-yaml\">                        <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">NAME_SERVERS=1.1.1.1,1.0.0.1<\/span>\n<\/span><span class=\"z-source z-yaml\">                        <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">DELUGE_DAEMON_LOG_LEVEL=info<\/span>\n<\/span><span class=\"z-source z-yaml\">                        <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">DELUGE_WEB_LOG_LEVEL=info<\/span>\n<\/span><span class=\"z-source z-yaml\">                        <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">DEBUG=false<\/span>\n<\/span><span class=\"z-source z-yaml\">                        <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">UMASK=000<\/span>\n<\/span><span class=\"z-source z-yaml\">                        <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">PUID=1000<\/span>\n<\/span><span class=\"z-source z-yaml\">                        <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">PGID=1000<\/span>\n<\/span><span class=\"z-source z-yaml\">                        <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">TZ=Europe\/Amsterdam<\/span> <span class=\"z-comment z-line z-number-sign z-yaml\"><span class=\"z-punctuation z-definition z-comment z-line z-number-sign z-yaml\">#<\/span> Replace with your timezone \u2013 check https:\/\/en.wikipedia.org\/wiki\/List_of_tz_database_time_zones for reference\n<\/span><\/span><span class=\"z-source z-yaml\">                <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">volumes<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span>\n<\/span><span class=\"z-source z-yaml\">                        <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">\/mnt\/c\/Users\/WINDOWS_USER_NAME\/docker\/arch-delugevpn\/data:\/data<\/span> <span class=\"z-comment z-line z-number-sign z-yaml\"><span class=\"z-punctuation z-definition z-comment z-line z-number-sign z-yaml\">#<\/span> Replace WINDOWS_USER_NAME with your actual username\n<\/span><\/span><span class=\"z-source z-yaml\">                        <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">\/mnt\/c\/Users\/WINDOWS_USER_NAME\/docker\/arch-delugevpn\/config:\/config<\/span>\n<\/span><span class=\"z-source z-yaml\">                        <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-unquoted z-plain z-out z-yaml\">\/etc\/localtime:\/etc\/localtime:ro<\/span>\n<\/span><\/code><\/pre>\n<p>Some additional information for the parameters:<\/p>\n<ul>\n<li><code>VPN_PROV<\/code> \u2014 set it to <code>custom<\/code> if you're using a self-hosted VPN. There are also some other options, such as <code>pia<\/code> or <code>airvpn<\/code>, check out the <a href=\"https:\/\/hub.docker.com\/r\/binhex\/arch-delugevpn\">container documentation<\/a> if you're interested<\/li>\n<li><code>VPN_CLIENT<\/code> \u2014 You can set it either to <code>openvpn<\/code> or <code>wireguard<\/code> \u2014 once again, I couldn't make Wireguard work on Windows, so I will be using OpenVPN<\/li>\n<li><code>LAN_NETWORK<\/code> \u2014 If you don't know the IP of your LAN network, open a PowerShell window and type <code>ipconfig<\/code>. Copy the first three numbers from the <code>IPv4 Address<\/code> field and paste them to the <code>LAN_NETWORK<\/code> field in the compose file. Append a 0 as the fourth number and add <code>\/24<\/code> at the end.<\/li>\n<li>In the <code>volumes<\/code> section you need to expose the config and downloads folders, as well as the <strong>localtime<\/strong> file to the container. The latter will remain the same in any case, no matter which folders you created, but for the first two \u2014 put the path to the folder on your local machine on the left and the path in the container on the right. In this case, the container path for <strong>config<\/strong> folder is <code>\/config<\/code> and the downloads are located in <code>\/data\/incomplete<\/code> by default, but you can put them anywhere and change the download path in the torrent client later.<\/li>\n<\/ul>\n<h3 id=\"running-the-container\">Running the container<\/h3>\n<p>Once you're done, save the file and quit. Now we're ready to run our container \u2014 type <code>docker-compose up -d<\/code>. But once that's done \u2014 wait for about 10 seconds, and if you did everything correctly you should be able to see <strong>Privoxy process listening on port 8118<\/strong> when you type <code>docker logs delugevpn<\/code>.<\/p>\n<h3 id=\"working-with-deluge\">Working with Deluge<\/h3>\n<p>Open a browser and go to localhost:8112. You will get a password prompt and the default password here is \"deluge\". You can change it latter in the settings.<\/p>\n<h3 id=\"privoxy-proxy\">Privoxy proxy<\/h3>\n<p>You can now access the proxy by pointing your applications to <code>localhost:8112<\/code>. Here's an example of what that looks like in Firefox:<\/p>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/split-vpn-tunnelling-for-selected-applications\/firefox.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/split-vpn-tunnelling-for-selected-applications\/firefox.png\" alt=\"Firefox Network Settings\">\n    <\/a>\n<\/div>\n<p>Privoxy also features ad\/tracker filtering and other functinonality. To access the settings, open <a href=\"http:\/\/config.privoxy.org\">http:\/\/config.privoxy.org<\/a> in your browser.\nYou can also use <a href=\"https:\/\/addons.mozilla.org\/en-US\/firefox\/addon\/foxyproxy-standard\/\">FoxyProxy<\/a> in Firefox for advanced whitelisting\/blacklisting functionality with regular expressions.<\/p>\n<h2 id=\"linux\">Linux<\/h2>\n<p>On Linux this whole process takes less than 5 minutes. Open a terminal and type the following commands:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> pacman<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>S<\/span> docker docker-compose <span class=\"z-comment z-line z-number-sign z-shell\"><span class=\"z-punctuation z-definition z-comment z-begin z-shell\">#<\/span><\/span><span class=\"z-comment z-line z-number-sign z-shell\"> Will obviously differ depending on the distribution<\/span><span class=\"z-comment z-line z-number-sign z-shell\">\n<\/span><\/span><\/span><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> usermod<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>aG<\/span> docker username<\/span>\n<\/span><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> systemctl enable<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> --<\/span>now<\/span> docker<\/span>\n<\/span><span class=\"z-source z-shell z-bash\">\n<\/span><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">mkdir<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"><span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>p<\/span> \/home\/LINUX_USERNAME\/docker\/arch-delugevpn\/data<\/span>\n<\/span><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">mkdir<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"><span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>p<\/span> \/home\/LINUX_USERNAME\/docker\/arch-delugevpn\/compose<\/span>\n<\/span><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">mkdir<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"><span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>p<\/span> \/home\/LINUX_USERNAME\/docker\/arch-delugevpn\/config\/wireguard <span class=\"z-comment z-line z-number-sign z-shell\"><span class=\"z-punctuation z-definition z-comment z-begin z-shell\">#<\/span><\/span><span class=\"z-comment z-line z-number-sign z-shell\"> If you&#39;re using Wireguard<\/span><span class=\"z-comment z-line z-number-sign z-shell\">\n<\/span><\/span><\/span><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">mkdir<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"><span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>p<\/span> \/home\/LINUX_USERNAME\/docker\/arch-delugevpn\/config\/openvpn <span class=\"z-comment z-line z-number-sign z-shell\"><span class=\"z-punctuation z-definition z-comment z-begin z-shell\">#<\/span><\/span><span class=\"z-comment z-line z-number-sign z-shell\"> If you&#39;re using OpenVPN<\/span><span class=\"z-comment z-line z-number-sign z-shell\">\n<\/span><\/span><\/span><\/code><\/pre>\n<p>The <code>docker-compose.yml<\/code> will look a little bit different for Wireguard. In particular, we need to add the following lines:<\/p>\n<pre data-lang=\"yaml\" class=\"language-yaml z-code\"><code class=\"language-yaml\" data-lang=\"yaml\"><span class=\"z-source z-yaml\">                <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">privileged<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span> <span class=\"z-constant z-language z-boolean z-yaml\">true<\/span>\n<\/span><span class=\"z-source z-yaml\">                <span class=\"z-string z-unquoted z-plain z-out z-yaml\"><span class=\"z-entity z-name z-tag z-yaml\">sysctls<\/span><\/span><span class=\"z-punctuation z-separator z-key-value z-mapping z-yaml\">:<\/span>\n<\/span><span class=\"z-source z-yaml\">                        <span class=\"z-punctuation z-definition z-block z-sequence z-item z-yaml\">-<\/span> <span class=\"z-string z-quoted z-double z-yaml\"><span class=\"z-punctuation z-definition z-string z-begin z-yaml\">&quot;<\/span>net.ipv4.conf.all.src_valid_mark=1<span class=\"z-punctuation z-definition z-string z-end z-yaml\">&quot;<\/span><\/span>\n<\/span><\/code><\/pre>\n<p>Now reboot. After booting up you can launch the container by running <code>docker-compose up -d<\/code><\/p>\n<h2 id=\"macos\">macOS<\/h2>\n<p>On macOS the process is similar to Windows with a few exceptions:<\/p>\n<ul>\n<li>Wireguard actually works<\/li>\n<li>Docker Desktop is available in <a href=\"https:\/\/brew.sh\/\">Homebrew<\/a> repositories. To install it, type <code>brew cask install docker<\/code><\/li>\n<li>The performance might be slightly inferior to Windows, since macOS doesn't feature WSL2<\/li>\n<\/ul>\n"},{"title":"Windows 10 AME \u2013 Make Windows Suck Less!","pubDate":"Mon, 07 Sep 2020 00:00:00 +0000","author":"Unknown","link":"https:\/\/notthebe.ee\/blog\/windows10-ame\/","guid":"https:\/\/notthebe.ee\/blog\/windows10-ame\/","description":"<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/windows10-ame\/screenshot.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/windows10-ame\/screenshot.png\" alt=\"Windows 10 AME\">\n    <\/a>\n<\/div>\n<p><em>Please refer to the official AME documentation for an up-to-date version<\/em><\/p>\n<p><a href=\"https:\/\/ameliorated.info\/documentation.html\">https:\/\/ameliorated.info\/documentation.html<\/a><\/p>\n<h2 id=\"downloading-and-verifying-windows-10-iso\">Downloading and verifying Windows 10 ISO<\/h2>\n<p>Download the Windows 10 ISO using <a href=\"https:\/\/github.com\/pbatard\/Fido\">Fido<\/a> (Windows-only) or <a href=\"https:\/\/tb.rg-adguard.net\/public.php\">TechBench<\/a>. Choose a version that is officially supported by AME Project for the most stable experience, or alternatively, try your luck with the latest version. Make sure to choose the correct UI language, <strong>you can't change it after the installation<\/strong>.<\/p>\n<p>Calcualte the SHA1 sum of the ISO using <a href=\"https:\/\/www.adaic.org\/resources\/add_content\/standards\/articles\/SHA-1.html\">sha1sum<\/a>.<\/p>\n<p><strong>Updated 08.09.2020:<\/strong> You can also use this PowerShell commmand to calculate the SHA1 sum on Windows without any third party tools (Thank you, @GezeikVan)<\/p>\n<pre class=\"z-code\"><code><span class=\"z-text z-plain\">Get-FileHash -Algorithm SHA1 Windows_2004.iso\n<\/span><\/code><\/pre>\n<p>Use <a href=\"https:\/\/sha1.rg-adguard.net\/\">AdGuard SHA1 catalog<\/a> to verify the ISO checksum against the MSDN image database.<\/p>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/windows10-ame\/success.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/windows10-ame\/success.png\" alt=\"ISO Verification\">\n    <\/a>\n<\/div>\n<h2 id=\"downloading-updates\">Downloading updates<\/h2>\n<p>Use <a href=\"https:\/\/support.microsoft.com\/en-us\/help\/4555932\/windows-10-update-history\">Windows 10 Update History<\/a> page to determinte the KBs of the newest Cumulative update and the SSU for your Windows version. Look up the KBs in the <a href=\"https:\/\/www.catalog.update.microsoft.com\/Home.aspx\">Microsoft Update Catalog<\/a> and download the update packages for your architecture.<\/p>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/windows10-ame\/updates.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/windows10-ame\/updates.png\" alt=\"Windows 10 Update History\">\n    <\/a>\n<\/div>\n<h2 id=\"creating-a-bootable-media\">Creating a bootable media<\/h2>\n<p>Use <a href=\"https:\/\/rufus.ie\">Rufus<\/a> to create a bootable USB drive from the Windows 10 ISO. On Linux you can use <strong>WoeUSB<\/strong> instead. Copy the update packages and Rufus executable to the flash drive. Download a Debian-based Linux LiveUSB image, for example <a href=\"https:\/\/xubuntu.org\">Xubuntu<\/a> and copy it to the USB drive as well. You might also want to add drivers for your PC.<\/p>\n<p>You will also need the newest version of the Amelioration script from the <a href=\"https:\/\/ameliorated.info\/documentation.html\">AME website<\/a><\/p>\n<h2 id=\"installing-windows\">Installing Windows<\/h2>\n<p><strong>Disconnect from the Ethernet\/Wi-Fi<\/strong> and don't connect back until we run the Linux AME script.<\/p>\n<p>Install Windows as usual and make sure to answer \"No\" to every question about telemetry\/data collection\/geolocation\/etc.<\/p>\n<p>After the installation\/setup process is complete, do the following things:<\/p>\n<ul>\n<li>Unpin all tiles from the Start menu<\/li>\n<li>Remove the search bar from the taskbar<\/li>\n<li>Remove task view icon from the taskbar<\/li>\n<\/ul>\n<p>These settings might not be available after the \"amelioration\" process is finished.<\/p>\n<h2 id=\"installing-updates\">Installing updates<\/h2>\n<p>Open the PowerShell as Administrator and type the following commands:<\/p>\n<pre data-lang=\"cmd\" class=\"language-cmd z-code\"><code class=\"language-cmd\" data-lang=\"cmd\"><span class=\"z-source z-dosbatch\"><span class=\"z-keyword z-command z-dosbatch\">mkdir<\/span> C:\/SSU\n<\/span><span class=\"z-source z-dosbatch\"><span class=\"z-keyword z-command z-dosbatch\">mkdir<\/span> C:\/Cumulative\n<\/span><\/code><\/pre>\n<p>Go to the flash drive, open the folder with the update packages, Shift+Right click on the empty space in Explorer and click \"Open PowerShell window here\".<\/p>\n<p>Unpack the updates. You can tell the SSU apart from the Cumulative update by comparing the package sizes: the SSU file is much smaller than the one for the Cumulative update.<\/p>\n<pre data-lang=\"cmd\" class=\"language-cmd z-code\"><code class=\"language-cmd\" data-lang=\"cmd\"><span class=\"z-source z-dosbatch\"><span class=\"z-keyword z-command z-dosbatch\">expand<\/span> -F:* name_of_the_ssu_package.msu C:\\SSU\n<\/span><span class=\"z-source z-dosbatch\"><span class=\"z-keyword z-command z-dosbatch\">expand<\/span> -F:* name_of_the_cumulative_package.msu C:\\Cumulative\n<\/span><\/code><\/pre>\n<p>Install the SSU update first. Copy the filename of the CAB package from Explorer, since you won't be able to utilize auto-completion for the DISM command:<\/p>\n<pre data-lang=\"cmd\" class=\"language-cmd z-code\"><code class=\"language-cmd\" data-lang=\"cmd\"><span class=\"z-source z-dosbatch\">dism \/online \/add-package \/packagepath=C:\\SSU\\name_of_the_ssu.cab\n<\/span><\/code><\/pre>\n<p>Reboot after applying the SSU and install the Cumulative update:<\/p>\n<pre data-lang=\"cmd\" class=\"language-cmd z-code\"><code class=\"language-cmd\" data-lang=\"cmd\"><span class=\"z-source z-dosbatch\">dism \/online \/add-package \/packagepath=C:\\Cumulative\\name_of_the_cumulative.cab\n<\/span><\/code><\/pre>\n<p><strong>Reboot twice<\/strong> after the Cumulative update has been installed.<\/p>\n<p>Finally, launch the cleanup task to get rid of the update cache:<\/p>\n<pre data-lang=\"cmd\" class=\"language-cmd z-code\"><code class=\"language-cmd\" data-lang=\"cmd\"><span class=\"z-source z-dosbatch\">dism \/online \/Cleanup-Image \/StartComponentCleanup\n<\/span><\/code><\/pre>\n<h2 id=\"running-the-amelioration-script\">Running the amelioration script<\/h2>\n<p>Open the flash drive folder and run the AME script as Administrator. Choose <code>1. Run Pre-Amelioration<\/code> in the menu and wait for the process to finish<\/p>\n<p>Afterwards, choose <code>3. User Permissions<\/code>. Reset the Administrator password and change your user privileges from \"Administrator\" to \"Standard User\".<\/p>\n<p>Log out when asked to, use the Ctrl+Alt+Del menu to log out manually if necessary.<\/p>\n<p>After logging back in, run PowerShell as Administrator and reset your password:<\/p>\n<pre data-lang=\"cmd\" class=\"language-cmd z-code\"><code class=\"language-cmd\" data-lang=\"cmd\"><span class=\"z-source z-dosbatch\"><span class=\"z-keyword z-command z-dosbatch\">net user<\/span> username *\n<\/span><\/code><\/pre>\n<p>Enter the password twice. The symbols won't appear in the command line as you type.<\/p>\n<h2 id=\"creating-a-linux-bootable-media\">Creating a Linux bootable media<\/h2>\n<p>Copy the flash drive folder to your desktop and use <a href=\"https:\/\/rufus.ie\">Rufus<\/a> to wipe the Windows bootable flash drive and create a Linux USB drive. Reboot into BIOS, re-enable the Internet connection and then boot into Linux<\/p>\n<h2 id=\"running-the-linux-amelioration-script\">Running the Linux amelioration script<\/h2>\n<p>Mount the Windows drive. Then, navigate to <a href=\"https:\/\/ameliorated.info\/documentation.html\">ameliorated.info<\/a> and copy the download link for the Linux AME script.<\/p>\n<p>Go to the Windows drive, open the terminal in the root folder. Type <code>sudo su<\/code> and then type <code>wget &lt;paste the download link&gt;<\/code>. This will give you root privileges and download the AME script to the root of the Windows drive.<\/p>\n<p>As of writing this, running the script produces an error due to incompatible newline symbols:<\/p>\n<pre class=\"z-code\"><code><span class=\"z-text z-plain\">bash: &#39;\\r&#39;: command not found\n<\/span><\/code><\/pre>\n<p>You can either use a sed script to convert the newline symbols to UNIX ones:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sed<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"><span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>i<\/span> <span class=\"z-string z-quoted z-single z-shell\"><span class=\"z-punctuation z-definition z-string z-begin z-shell\">&#39;<\/span>s\/\\r$\/\/<span class=\"z-punctuation z-definition z-string z-end z-shell\">&#39;<\/span><\/span> filename<\/span>\n<\/span><\/code><\/pre>\n<p>Alternatively, use <code>dos2unix<\/code>:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">apt-get<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> update<\/span> <span class=\"z-keyword z-operator z-logical z-and z-shell\">&amp;&amp;<\/span> <span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">apt-get<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> install dos2unix<\/span>\n<\/span><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">dos2unix<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> filename<\/span>\n<\/span><\/code><\/pre>\n<p>Now you can run the script by typing <code>bash filename<\/code>.<\/p>\n<p>After the script is finished with no errors, you can now reboot into Windows with the Internet connection enabled.<\/p>\n<h2 id=\"post-amelioration\">Post-Amelioration<\/h2>\n<p>Since most of the default Windows applications, including Internet Explorer, Windows Media Player, Photos, etc. are eliminated, we need to install something to replace them.<\/p>\n<p>Edit the AME script that we ran for pre-amelioration and go to the following line:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">choco<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> install<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>y<\/span><span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> --<\/span>force<\/span><span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> --<\/span>allow-empty-checksums<\/span> firefox thunderbird vlc youtube-dl 7zip open-shell jpegview vcredist-all directx onlyoffice<\/span>\n<\/span><\/code><\/pre>\n<p>Add the <code>-installArgs ADDLOCAL=StartMenu<\/code> after the <code>open-shell<\/code> text in order to opt out of \"Classic Explorer\" part of the OpenShell (doesn't work on the current version of Windows as of writing this).<\/p>\n<p>By default, the following applications are installed:<\/p>\n<ul>\n<li>Firefox<\/li>\n<li>Thunderbird<\/li>\n<li>VLC<\/li>\n<li>JPEGView<\/li>\n<li>OpenShell (a Windows 7-style Start Menu replacement)<\/li>\n<li>OnlyOffice<\/li>\n<li>7Zip<\/li>\n<li>youtube-dl<\/li>\n<li>Various Windows runtimes (.NET, C++) and DirectX<\/li>\n<\/ul>\n<p>Feel free to replace or remove the applications in this list depending on your personal preferences.<\/p>\n<p>After making the adjustments, run the script as Administrator and choose <code>2. Run Post-Amelioration<\/code><\/p>\n<h2 id=\"hardening-windows-settings\">Hardening Windows settings<\/h2>\n<p>Download <a href=\"https:\/\/securitywithoutborders.org\/tools\/hardentools.html\">Hardentools<\/a> by Security Without Borders. Run the script as Administrator, reboot after the process is finished.<\/p>\n<h2 id=\"changing-input-language\">Changing input language<\/h2>\n<p>You can remove\/add a new input language using PowerShell.<\/p>\n<pre data-lang=\"powershell\" class=\"language-powershell z-code\"><code class=\"language-powershell\" data-lang=\"powershell\"><span class=\"z-source z-powershell\"><span class=\"z-variable z-other z-readwrite z-powershell\"><span class=\"z-punctuation z-definition z-variable z-powershell\">$<\/span>List<\/span> <span class=\"z-keyword z-operator z-assignment z-powershell\">=<\/span> <span class=\"z-support z-function z-powershell\">Get-WinUserLanguageList<\/span>\n<\/span><span class=\"z-source z-powershell\"><span class=\"z-variable z-other z-readwrite z-powershell\"><span class=\"z-punctuation z-definition z-variable z-powershell\">$<\/span>List<span class=\"z-variable z-other z-member z-powershell\">.add<\/span><\/span><span class=\"z-punctuation z-section z-group z-begin z-powershell\">(<\/span><span class=\"z-interpolated z-simple z-source z-powershell\"><span class=\"z-keyword z-operator z-redirection z-powershell\">&lt;<\/span>language<span class=\"z-keyword z-operator z-assignment z-powershell\">-<\/span>code<span class=\"z-keyword z-operator z-redirection z-powershell\">&gt;<\/span><\/span><span class=\"z-punctuation z-section z-group z-end z-powershell\">)<\/span>\n<\/span><span class=\"z-source z-powershell\"><span class=\"z-variable z-other z-readwrite z-powershell\"><span class=\"z-punctuation z-definition z-variable z-powershell\">$<\/span>List<span class=\"z-variable z-other z-member z-powershell\">.remove<\/span><\/span><span class=\"z-punctuation z-section z-group z-begin z-powershell\">(<\/span><span class=\"z-interpolated z-simple z-source z-powershell\"><span class=\"z-keyword z-operator z-redirection z-powershell\">&lt;<\/span>language<span class=\"z-keyword z-operator z-assignment z-powershell\">-<\/span>code<span class=\"z-keyword z-operator z-redirection z-powershell\">&gt;<\/span><\/span><span class=\"z-punctuation z-section z-group z-end z-powershell\">)<\/span>\n<\/span><span class=\"z-source z-powershell\"><span class=\"z-support z-function z-powershell\">Set-WinUserLanguageList<\/span> <span class=\"z-variable z-other z-readwrite z-powershell\"><span class=\"z-punctuation z-definition z-variable z-powershell\">$<\/span>List<\/span>\n<\/span><\/code><\/pre>\n<p>Replace <code>&lt;language_code&gt;<\/code> with your language code (e.g. \"ru\" for Russian or \"de-DE\" for German)<\/p>\n<h2 id=\"installing-drivers\">Installing drivers<\/h2>\n<p>The drivers can be installed by downloading the driver package (sometimes called \"SCCM\") for your machine, unpacking it in a folder and executing this command in an elevated shell:<\/p>\n<pre data-lang=\"cmd\" class=\"language-cmd z-code\"><code class=\"language-cmd\" data-lang=\"cmd\"><span class=\"z-source z-dosbatch\">pnputil.exe \/add-driver C:\\MyDrivers\\*.inf \/subdirs \/install \/reboot\n<\/span><\/code><\/pre>\n"},{"title":"Removing the Wi-Fi whitelist on Haswell Thinkpads (T440p, W540, T540, etc.)","pubDate":"Wed, 17 Jun 2020 00:00:00 +0000","author":"Unknown","link":"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/","guid":"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/","description":"<p><a href=\"https:\/\/www.youtube.com\/watch?v=ce7kqUEccUM\">Video version<\/a><\/p>\n<p>\"Wi-Fi whitelist\" functionality has been introduced by Lenovo in 2012 to keep users from replacing Wifi cards on their Thinkpad laptops. If you try to install a card that wasn't approved by Lenovo, your laptop will <strong>simply refuse to boot<\/strong><\/p>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/unauthorized.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/unauthorized.png\" alt=\"Unauthorized Wi-Fi card\">\n    <\/a>\n<\/div>\n<h2 id=\"disclaimer\">Disclaimer<\/h2>\n<ol>\n<li>This process is quite involved and requires at least a basic knowledge of Google and a steady hand. I provide zero warranty in case something goes wrong. This tutorial is fairly safe if you follow it carefully, but I'm not responsible for your laptop refusing to boot, taking fire, or shrinking in size.<\/li>\n<li>I'll be working on a <strong>Thinkpad T440p with BIOS version 2.52<\/strong>. I do not know whether it's going to work on other BIOS versions or any other Haswell Thinkpads since I only have T440p, but it's been reported to work on W540 and T540. The only way to find out is to try, and if something goes wrong you can always re-flash the original BIOS file.<\/li>\n<li>This process <strong>does not<\/strong> remove the BIOS or Supervisor password<\/li>\n<\/ol>\n<h2 id=\"gathering-supplies\">Gathering supplies<\/h2>\n<p>Apart from the laptop itself, you will also need:<\/p>\n<ul>\n<li>a second laptop or desktop PC<\/li>\n<li>a CH341a external programmer with an 8-pin clip, they're usually sold together on websites like <a href=\"https:\/\/www.ebay.com\/itm\/EEPROM-BIOS-USB-Programmer-CH341A-SOIC8-Clip-1-8V-Adapter-SOIC8-Adapter\/264715247642?hash=item3da242201a:g:JlYAAOSwpSlekDy~\">eBay<\/a>, <a href=\"https:\/\/www.amazon.com\/Organizer-Socket-Adpter-Programmer-CH341A\/dp\/B07R5LPTYM\/ref=sr_1_1?dchild=1&amp;keywords=ch341a&amp;qid=1592406898&amp;sr=8-1\">Amazon<\/a> or <a href=\"https:\/\/www.aliexpress.com\/item\/32793476447.html?spm=a2g0o.productlist.0.0.2b689f58oUwIIz&amp;algo_pvid=7552baef-b508-474c-be4a-4d317c72faac&amp;algo_expid=7552baef-b508-474c-be4a-4d317c72faac-0&amp;btsid=0b0a187915924069316147306e79d4&amp;ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_\">Aliexpress<\/a> for about $10.<\/li>\n<\/ul>\n<h2 id=\"assembling-the-programmer\">Assembling the programmer<\/h2>\n<p>Before using the programmer you also need to assemble it. Align the red wire on the clip with number \"1\" on the adapter, raise the lever on the programmer and plug the clip in the programmer as shown. Put down the lever.<\/p>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/ch341a_top.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/ch341a_top.png\" alt=\"CH341a \u2013 Top view\">\n    <\/a>\n<\/div>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/ch341a_side.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/ch341a_side.png\" alt=\"CH341a \u2013 Side view\">\n    <\/a>\n<\/div>\n<h2 id=\"installing-software\">Installing software<\/h2>\n<p>First, we need to install three things on our second computer: <a href=\"https:\/\/flashrom.org\/Flashrom\">flashrom<\/a>, <a href=\"https:\/\/github.com\/LongSoft\/UEFITool\/releases\">UEFIPatch<\/a> and <a href=\"https:\/\/github.com\/thrimbor\/thinkpad-uefi-sign\">thinkpad-uefi-sign<\/a>. You'll also need to download the <a href=\"https:\/\/pastebin.com\/TZYnnmY8\">text file with patches<\/a>.<\/p>\n<p>On Linux you can simply install flashrom from your distro's repositories. On Ubuntu the command is <code>sudo apt install flashrom<\/code><\/p>\n<p>If you're on Mac you'll have to install the <a href=\"https:\/\/brew.sh\/\">Homebrew<\/a> package manager. After that you can install flashrom by simply typing <code>brew install flashrom<\/code><\/p>\n<p>On Windows, using flashrom is somewhat complicated since you'll need a special driver that can't be installed on 64-bit versions of Windows without some tinkering. You can find unofficial versions of flashrom with the USB drivers already included on the Internet, but use them at your own risk. The best option would be to follow this tutorial from an Ubuntu LiveCD.<\/p>\n<p>Installing UEFIPatch is easy: simply download the right version for your operating system from the Github and unpack it. Remove both text files from the folder to avoid any confusion.<\/p>\n<p>After that, clone the thinkpad-uefi-sign Github repository or simply download the ZIP and unpack it into the same folder that you used for UEFIPatch.<\/p>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/folder.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/folder.png\" alt=\"UEFIPatch folder\">\n    <\/a>\n<\/div>\n<h2 id=\"preparing-the-patches\">Preparing the patches<\/h2>\n<p>Download the text file with patches from the video description, move it to the UEFIPatch folder and open it with your favorite text editor. Here we're going to put a hash symbol in front of every patch that we don't want to be applied. The two bottom patches that unlock advanced memory settings are already commented out, and since I'll be doing it on a T440p, I'll also comment out the patches for T440 and L540. The file also contains a patch that unlocks the hidden advanced settings in BIOS, but if you don't want that for some reason, put a hash symbol in front of the line. Save the file and quit.<\/p>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/patches.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/patches.png\" alt=\"Text file with patches\">\n    <\/a>\n<\/div>\n<h2 id=\"dumping-the-bios\">Dumping the BIOS<\/h2>\n<p>Turn your laptop upside down, remove the battery and take off the base cover. Take a good look at the motherboard and eventually you'll see a BIOS chip. If you're doing it on a different laptop, your BIOS chip might be at a different location and you might need to take the laptop apart completely to access it.<\/p>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/chip_location_t440p.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/chip_location_t440p.png\" alt=\"Chip location on T440p\">\n    <\/a>\n<\/div>\n<p>Next, align the dimple on the top of the chip and the red wire on your programmer clip and put a clip on the chip. Don't plug the USB programmer into your second laptop yet and double check that your battery is removed. Be careful, anchor the clip on one side and then close it in on the other side. Look at it from all sides and make sure the clip sits tightly on the chip.<\/p>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/dimple.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/dimple.png\" alt=\"Dimple\">\n    <\/a>\n<\/div>\n<p><div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/clip_on_the_chip_side.jpg\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/clip_on_the_chip_side.jpg\" alt=\"Clip on the chip \u2013 side view\">\n    <\/a>\n<\/div>\n\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/clip_on_the_chip_isometric.jpg\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/clip_on_the_chip_isometric.jpg\" alt=\"Clip on the chip \u2013 isometric view\">\n    <\/a>\n<\/div>\n<\/p>\n<p>Next up we're going to plug the programmer into our second laptop and open a terminal. Go to UEFIPatch folder and type this command:<\/p>\n<pre class=\"z-code\"><code><span class=\"z-text z-plain\">sudo flashrom -p ch341a_spi -r bios1.img\n<\/span><\/code><\/pre>\n<p>If you get a \"No chip found\" error, unplug the USB programmer from the laptop and try to re-seat the clip on the chip. Sometimes the connection isn't strong enough, and usually fiddling with the clip helps.<\/p>\n<p>After flashrom has finished reading your chip, repeat the same command again, but this time replace the <code>bios1<\/code> with <code>bios2<\/code>:<\/p>\n<pre class=\"z-code\"><code><span class=\"z-text z-plain\">sudo flashrom -p ch341a_spi -r bios2.img\n<\/span><\/code><\/pre>\n<p>Next, we're going to compare the two BIOS dumps to make sure they're identical:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">diff<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> bios1.img bios2.img<\/span>\n<\/span><\/code><\/pre>\n<p>If this returns nothing, we're good to go. But if it says \"Files are different\", you need to unplug the USB programmer, re-seat the clip on the chip and try again.<\/p>\n<p>After that's done, you can delete the second file since both  are identical anyway. DO NOT delete the first file, keep it somewhere safe: this is going to be your backup in case something goes wrong.<\/p>\n<p>Now, unplug the USB programmer so that you don't accidentally yank the clip. You can also leave the clip on the chip, since we'll have to put in back in a minute anyway.<\/p>\n<h2 id=\"patching-the-bios\">Patching the BIOS<\/h2>\n<p>Now we're going to patch the BIOS file and remove the Wi-Fi whitelist. Make sure you have UEFIPatch and the BIOS file in the same folder and type<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">.\/UEFIPatch<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> bios1.img xx40_patches_v5.txt<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>o<\/span> bios_patched.img<\/span>\n<\/span><\/code><\/pre>\n<p>At this point we're pretty much done, but if you try and flash this BIOS file back to the chip, you'll get a laptop that beeps 10 times every time you turn it on. This means that the BIOS is unsigned and somebody tampered with it [I wonder who that was]<\/p>\n<p>To avoid that, we need to sign the BIOS file. But first, let's install python3 and the <strong>pycryptodome<\/strong> package. On Mac you can do it by typing<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">brew<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> install python3<\/span>\n<\/span><\/code><\/pre>\n<p>And on Linux you'll have to install Python3 using your distro's package manager.<\/p>\n<p>After that, type<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">pip3<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> install pycryptodome<\/span>\n<\/span><\/code><\/pre>\n<p>Next, go to the folder where you unpacked UEFIPatch and thinkpad-uefi-sign and type:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">thinkpad-uefi-sign\/sign.py<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> bios_patched.img<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>o<\/span> bios_patched.img<\/span>\n<\/span><\/code><\/pre>\n<p>And there we go, \"image signed\". We can also verify the image by typing:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">thinkpad-uefi-sign\/verify.py<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> bios_patched.img<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>o<\/span> bios_patched.img<\/span>\n<\/span><\/code><\/pre>\n<p>As you can see, it says \"Signatures correct\"<\/p>\n<p>Now it's time to flash the modded BIOS back to the laptop.<\/p>\n<h2 id=\"flash-the-bios\">Flash the BIOS<\/h2>\n<p>Re-connect the USB programmer to your second laptop and in case you removed the clip from the BIOS chip, put it on again.<\/p>\n<p>The command we need to type now is<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> flashrom<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>p<\/span> ch341a_spi<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>w<\/span> bios_patched.img<\/span>\n<\/span><\/code><\/pre>\n<p>Wait for it to finish, unplug the USB programmer and remove the clip.<\/p>\n<p><strong>IMPORTANT<\/strong>: Unplug the USB programmer FIRST, and THEN remove the clip. You might damage your laptop otherwise.<\/p>\n<p>Put the lid back on the laptop, slide in the battery and turn the laptop on.<\/p>\n<p>Verify that the laptop boots to the OS and connects to Wi-Fi networks.<\/p>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/wifi.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/wifi.png\" alt=\"Wifi networks\">\n    <\/a>\n<\/div>\n<p>Congratulations! Your laptop is now free... as in connecting to all the Wi-Fi networks, I guess.<\/p>\n<p>If you take a look at the BIOS menu, you'll also see a bunch of new settings such as power limits, ACPI states and so on. Have fun!<\/p>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/bios.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/bios.png\" alt=\"Advanced BIOS settings\">\n    <\/a>\n<\/div>\n<h2 id=\"special-thanks\">Special thanks<\/h2>\n<ul>\n<li>Thrimbor <a href=\"https:\/\/github.com\/thrimbor\">https:\/\/github.com\/thrimbor<\/a><\/li>\n<li>tonymacx86 <a href=\"https:\/\/www.tonymacx86.com\/\">https:\/\/www.tonymacx86.com\/<\/a><\/li>\n<li>XX and paranoidbashthot for developing the BootGuard bypass method and patches for xx40 onwards <a href=\"https:\/\/github.com\/digmorepaka\/thinkpad-firmware-patches\/\">https:\/\/github.com\/digmorepaka\/thinkpad-firmware-patches\/<\/a><\/li>\n<\/ul>\n"},{"title":"Setting up an OpenVPN server from scratch on Ubuntu 20.04","pubDate":"Mon, 08 Jun 2020 00:00:00 +0000","author":"Unknown","link":"https:\/\/notthebe.ee\/blog\/creating-your-own-vpn\/","guid":"https:\/\/notthebe.ee\/blog\/creating-your-own-vpn\/","description":"<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/creating-your-own-vpn\/openvpn-logo.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/creating-your-own-vpn\/openvpn-logo.png\" alt=\"OpenVPN Logo\">\n    <\/a>\n<\/div>\n<h2 id=\"choosing-a-provider\">Choosing a provider<\/h2>\n<p>In order to set up a VPN we need to find where we're going to host it.<\/p>\n<p>There are a lot of VPS providers that offer servers for as little as $2 per month, but there are a few things that you need to consider when choosing a VPS provider:<\/p>\n<ol>\n<li><strong>Virtualisation technology<\/strong> \u2014 make sure your provider uses KVM or Xen instead of OpenVZ. OpenVZ is a container based virtualisation technology used on the cheapest bottom of the barrel VPS plans. OpenVZ machines run an old Linux kernel which isn't supported by Wireguard, Docker and other modern software and the way OpenVZ is built lets your provider snoop on your activities and files very easily. Avoid it<\/li>\n<li><strong>An IPv4 address<\/strong> \u2014 this only applies to the ultra budget VPS options, but some of the providers only give you an IPv6 address. This is not as common nowadays, but because of the shortage of IPv4 addresses we might start seeing it more and more often<\/li>\n<li><strong>Location<\/strong> \u2013 pretty self-explanatory. If you want to watch American content \u2013 choose an American data center. If you want to torrent Linux ISOs, don't choose Germany or Austria because they have very strict anti-piracy laws. If you want to use your VPN for gaming, keep in mind that the further the server, the bigger the latency. If you're really serious about privacy, choose a VPS outside of the 14 eyes.<\/li>\n<\/ol>\n<p>In this tutorial I will be using <a href=\"https:\/\/linode.com\/\">Linode<\/a><\/p>\n<h2 id=\"setting-up-an-account\">Setting up an account<\/h2>\n<p>After you sign up on the website and confirm your email, you're going to need to enter some details, including your name, address and credit card information. That's going to be pretty much the same for all VPS providers though sometimes they accept Bitcoin or other cryptocurrencies.<\/p>\n<h2 id=\"creating-a-vps\">Creating a VPS<\/h2>\n<p>Next thing you need to do is add a server or as Linode calls it, a \"linode\". There's a lot of distros to choose from, if you want you can even go with Gentoo or Arch, but for this tutorial I'll go with the latest version of Ubuntu.<\/p>\n<p>You'll also want to choose the location, I'm going to choose UK since it's the closest to me physically.<\/p>\n<p>We're going to take the cheapest \"Nanode\" plan. And even if later on you decide to set up a mail server, a Nextcloud instance or a personal blog, this configuration will still be more than sufficient.<\/p>\n<p>Linode Label is not that important, and neither are tags. I'll call mine wolfgangsvpn.<\/p>\n<p>After that you'll need to choose a root password and upload an SSH key, which we're not going to do now, i'll explain why later. Lastly tick a box that says \"Private IP\" and click the create button on the right... and there we go, our server is now created.<\/p>\n<h2 id=\"generating-ssh-keys\">Generating SSH keys<\/h2>\n<p>Now you should see the control panel of your server, and while the server is starting, let's generate the SSH keys for it. Using a cleartext password to log in to your server is never a good idea since the password is not encrypted in transit and can be exposed on a hostile network. By creating an SSH key we're going to make it so that you can only log in to the server if you have the key file and the password, and at the same time the password is encrypted.<\/p>\n<p>If you're using Linux you probably already know how to open the terminal, if you're on Mac you can find the Terminal app in your Applications folder, and on Windows 10 you'll need to open the PowerShell with administrator's privileges and install SSH using this command:<\/p>\n<pre data-lang=\"powershell\" class=\"language-powershell z-code\"><code class=\"language-powershell\" data-lang=\"powershell\"><span class=\"z-source z-powershell\">PS C:\\<span class=\"z-keyword z-operator z-redirection z-powershell\">&gt;<\/span> <span class=\"z-support z-function z-powershell\">Add-WindowsCapability<\/span> <span class=\"z-keyword z-operator z-assignment z-powershell\">-<\/span>Online <span class=\"z-keyword z-operator z-assignment z-powershell\">-<\/span>Name OpenSSH.Client<span class=\"z-keyword z-operator z-assignment z-powershell\">*<\/span>\n<\/span><\/code><\/pre>\n<p>This is the command that will generate our ssh keys. The RSA algorithm with 4096 key size is what I'd personally recommend, since it's sufficiently secure and widely supported.<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">ssh-keygen<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"><span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>t<\/span> rsa<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>b<\/span> 4096<\/span>\n<\/span><\/code><\/pre>\n<p>Press Enter when asked for the key location to save it to the default one and then enter your password of choice.<\/p>\n<h2 id=\"logging-in-to-the-server\">Logging in to the server<\/h2>\n<p>By now our server has started up and we're ready to log in. Copy the IP address from the server control panel, go back to the terminal and type in<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">ssh<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> root@ip-address<\/span>\n<\/span><\/code><\/pre>\n<p>Type yes, enter the root password that you specified in the first step and that's it, we're in.<\/p>\n<h2 id=\"updating-the-os\">Updating the OS<\/h2>\n<p>First and foremost, let's update our operating system and software:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">apt-get<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> update<\/span> <span class=\"z-keyword z-operator z-logical z-and z-shell\">&amp;&amp;<\/span> <span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">apt-get<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> upgrade<\/span>\n<\/span><\/code><\/pre>\n<p>I'll also install my favourite text editor, feel free to use whatever you want though, for example nano.<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">apt<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> install neovim<\/span>\n<\/span><\/code><\/pre>\n<h2 id=\"creating-a-user\">Creating a user<\/h2>\n<p>As much as it's convenient to not have to enter a password every time, we need to create a user account that isn't root. Exposing root login on an SSH server is probably not a good idea even if you have multi factor authentication. Call me paranoid, but I think having to enter root password sometimes is the price I'm willing to pay for some sense of security. Type<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">useradd<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"><span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>G<\/span> sudo<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>m<\/span> wolfgang<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>s<\/span> \/bin\/bash<\/span>\n<\/span><\/code><\/pre>\n<p>That's going to create a user, set bash as default shell for him and permit sudo usage.<\/p>\n<p>Afterwards we'll need to create a password for our user, using<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">passwd<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> wolfgang<\/span>\n<\/span><\/code><\/pre>\n<p>Enter your password twice and we're good to go.<\/p>\n<h2 id=\"copying-ssh-key-from-host-to-the-server\">Copying SSH key from host to the server<\/h2>\n<p>Now that we've created our user it's a good time to copy the public SSH key to the server. Open a second terminal window for your local terminal and enter:<\/p>\n<h3 id=\"linux-or-mac\">Linux or Mac<\/h3>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">ssh-copy-id<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> wolfgang@ip_address<\/span>\n<\/span><\/code><\/pre>\n<h3 id=\"windows\">Windows<\/h3>\n<pre class=\"z-code\"><code><span class=\"z-text z-plain\">type $env:USERPROFILE\\.ssh\\id_rsa.pub | ssh ip-address &quot;cat &gt;&gt; .ssh\/authorized_keys&quot;\n<\/span><\/code><\/pre>\n<p>You'll be prompted to enter your password and once you do, go back to the terminal window with your server. Don't close the other window yet.<\/p>\n<h2 id=\"restricting-ssh-to-key-authentication\">Restricting SSH to key authentication<\/h2>\n<p>Now that we've copied the SSH keys to the server we have to restrict authentication to the public key only. Let's edit the sshd configuration file<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">nvim<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> \/etc\/ssh\/sshd_config<\/span>\n<\/span><\/code><\/pre>\n<p>First of all, let's change the default port. This won't do much for security, but it will help with those obnoxious SSH scanners that try to log in with default credentials. Not much, but the security logs will definitely get easier to read. You can use any port that's not taken by other services, but I prefer to use 69. Nice<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-comment z-line z-number-sign z-shell\"><span class=\"z-punctuation z-definition z-comment z-begin z-shell\">#<\/span><\/span><span class=\"z-comment z-line z-number-sign z-shell\"> Port 22<\/span><span class=\"z-comment z-line z-number-sign z-shell\">\n<\/span><\/span><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">Port<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> 69<\/span>\n<\/span><\/code><\/pre>\n<p>Next, we need to disable password authentication so that you're only able to log in using a public key.<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">PasswordAuthentication<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> no<\/span>\n<\/span><\/code><\/pre>\n<p>Last but not least, let's also disable root login<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">PermitRootLogin<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> no<\/span>\n<\/span><\/code><\/pre>\n<p>Now save the file and restart the sshd service using<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">systemctl<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> restart sshd<\/span>\n<\/span><\/code><\/pre>\n<p>Now without closing this window let's go back to our local machine and try to log in with our key:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">ssh<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"><span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>i<\/span> <span class=\"z-meta z-group z-expansion z-tilde\"><span class=\"z-variable z-language z-tilde z-shell\">~<\/span><\/span>\/.ssh\/id_rsa wolfgang@ip_address<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>p<\/span> 69<\/span>\n<\/span><\/code><\/pre>\n<p>If you see the prompt to enter your key password, that means we're good to go. It's also a good idea to verify that we can't log in with our password anymore:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">ssh<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> wolfgang@ip_address<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>p<\/span> 69<\/span>\n<\/span><\/code><\/pre>\n<p>This should give us \"Permission denied\".<\/p>\n<h2 id=\"creating-a-server-alias\">Creating a server alias<\/h2>\n<p>But you might have noticed that this command is kind of long and annoying to type, so let's fix that. Create a file in the \".ssh\" folder in your home directory called \"config\" and edit it using your favourite text editor:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">nvim<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> <span class=\"z-meta z-group z-expansion z-tilde\"><span class=\"z-variable z-language z-tilde z-shell\">~<\/span><\/span>\/.ssh\/config<\/span>\n<\/span><\/code><\/pre>\n<p>Here we're going to create an alias for our VPS<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">Host<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> wolfgangsvpn <span class=\"z-comment z-line z-number-sign z-shell\"><span class=\"z-punctuation z-definition z-comment z-begin z-shell\">#<\/span><\/span><span class=\"z-comment z-line z-number-sign z-shell\"> choose a name for your server   <\/span><span class=\"z-comment z-line z-number-sign z-shell\">\n<\/span><\/span><\/span><span class=\"z-source z-shell z-bash\">\t<span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">User<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> wolgang <span class=\"z-comment z-line z-number-sign z-shell\"><span class=\"z-punctuation z-definition z-comment z-begin z-shell\">#<\/span><\/span><span class=\"z-comment z-line z-number-sign z-shell\"> the username of the user that we created<\/span><span class=\"z-comment z-line z-number-sign z-shell\">\n<\/span><\/span><\/span><span class=\"z-source z-shell z-bash\">  \t<span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">Port<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> 69<\/span>\n<\/span><span class=\"z-source z-shell z-bash\">  \t<span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">IdentityFile<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> <span class=\"z-meta z-group z-expansion z-tilde\"><span class=\"z-variable z-language z-tilde z-shell\">~<\/span><\/span>\/.ssh\/id_rsa <span class=\"z-comment z-line z-number-sign z-shell\"><span class=\"z-punctuation z-definition z-comment z-begin z-shell\">#<\/span><\/span><span class=\"z-comment z-line z-number-sign z-shell\"> that&#39;s the location of our key file<\/span><span class=\"z-comment z-line z-number-sign z-shell\">\n<\/span><\/span><\/span><span class=\"z-source z-shell z-bash\">\t<span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">HostName<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> ip_address <span class=\"z-comment z-line z-number-sign z-shell\"><span class=\"z-punctuation z-definition z-comment z-begin z-shell\">#<\/span><\/span><span class=\"z-comment z-line z-number-sign z-shell\"> that&#39;s the IP address of our server<\/span><span class=\"z-comment z-line z-number-sign z-shell\">\n<\/span><\/span><\/span><\/code><\/pre>\n<p>Save and close, and now we can login to our server by simply typing <code>ssh wolfgangsvpn<\/code><\/p>\n<p>If you also don't want to see this wall of text every time you login, type in <code>touch .hushlogin<\/code><\/p>\n<h2 id=\"setting-up-openvpn\">Setting up OpenVPN<\/h2>\n<p>I know that Wireguard has been the hot new VPN protocol that everyone's been focused on lately, but in this video I'm going to use OpenVPN instead. Why? Because it has wider support when it comes to client applications and some of the applications that I'll be talking about in the 2nd part of this tutorial utilize OpenVPN. If you're interested in setting up a Wireguard server, there are a lot of tutorials on the Internet about it.<\/p>\n<p>Normally setting up an OpenVPN server takes some time since you need to install the packages, generate the keys, set up IPTables, write the config files for the server and the client. Thankfully, we won't do any of this in the tutorial and instead we'll use the OpenVPN road warrior script from a github user called Nyr - https:\/\/github.com\/Nyr\/openvpn-install. This script will do all the hard work for us and all we have to do is answer a few simple questions and download the config file at the end. Needless to say you shouldn't just go around executing random scripts you downloaded from the internet, so if you know some bash, read the script first and make sure there's nothing fishy in there. If you don't know any bash, maybe send it to a friend who does. When you done with the perusal of the script, click raw and copy the link from your browser.<\/p>\n<p>Log in to your server and install <code>wget<\/code> if you haven't already. Sometimes it comes with your OS image already, but sometimes it doesn't.<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> apt install wget<\/span>\n<\/span><\/code><\/pre>\n<p>Next, type <code>wget<\/code>, press Space and paste the link that you copied earlier. Press Enter<\/p>\n<p>Now let's launch the script<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> bash openvpn-install.sh<\/span>\n<\/span><\/code><\/pre>\n<p>The script will ask you some questions, and in most cases you'll want to pick the default answer. For the port, you can either choose the default port, 1194, but I prefer to choose 443, since 1194 is known as \"the OpenVPN port\" and in some cases it can be blocked on your network. 443 is the same port that is used for HTTPS, but whereas HTTPS uses TCP, OpenVPN (in this configuration) uses UDP, so they won't conflict with each other.<\/p>\n<p>You're also going to be asked which DNS you want to use. Feel free to choose whatever you like, but I normally choose 1.1.1.1<\/p>\n<p>As for the client name, choose whatever you like.<\/p>\n<p>Now that the configuration is done, press any key and the installation process is going to start. It's fully automated and at the end you'll going to get a configuration file that we'll download to our local machine later on. The problem is that the script places the file in the root directory by default, and in order to download it later, we need to move it to our user's home directory and give ourselves the correct priviliges:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> mv \/root\/thinkpad.ovpn <span class=\"z-meta z-group z-expansion z-tilde\"><span class=\"z-variable z-language z-tilde z-shell\">~<\/span><\/span><\/span>\n<\/span><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> chown wolfgang thinkpad.ovpn<\/span>\n<\/span><\/code><\/pre>\n<p>With this out of the way there's only one thing left to be done on the server's side, and that's to disable the logs. Let's edit the config file:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> nvim \/etc\/openvpn\/server\/server.conf<\/span>\n<\/span><\/code><\/pre>\n<p>And change <code>verb 3<\/code> to <code>verb 0<\/code><\/p>\n<p>Now restart the OpenVPN service:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">systemctl<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> restart openvpn-server@server.service<\/span>\n<\/span><\/code><\/pre>\n<p>And there we go! A VPN that <strong>actually<\/strong> doesn't keep logs. Amazing.<\/p>\n<p>I also just noticed that the hostname of the server is \"localhost\", which is not cool for many reasons. So let's change it to something else, I'm going to call it \"wolfgangsvpn\"<\/p>\n<h2 id=\"downloading-the-config-file\">Downloading the config file<\/h2>\n<p>Now all we need to do is to download the configuration file to our local machine so that we can actually use the VPN. Open a terminal on your local machine and type in <code>sftp servername<\/code> Next, download the file using the command <code>get configname.ovpn<\/code>. And finally type <code>exit<\/code><\/p>\n<p>Now if you want to use this VPN for all your traffic, which I don't recommend, you can download <a href=\"https:\/\/tunnelblick.net\/\">Tunnelblick<\/a> on Mac, <a href=\"https:\/\/openvpn.net\/client-connect-vpn-for-windows\/\">OpenVPN Connect<\/a> on Windows or load it into the NetworkManager on Linux.<\/p>\n<h2 id=\"nice-to-have\">Nice to have<\/h2>\n<p>At this point we have a barebones VPN server up and running. You can stop here and use it like you would normally use a VPN (in which case thanks for reading and i'm glad I could help), but if you want to know how to make it even more secure and add some features that are nice to have, like unattended upgrades, keep reading.<\/p>\n<h2 id=\"optional-installing-mosh\">Optional: Installing mosh<\/h2>\n<p>Now, ssh is nice but it does get annoying sometimes, especially when you change your network and your connection drops immediately. Instead, I prefer to use mosh. There's no complicated config file shenenigans, you just install mosh on both your local and your remote machine, and after that you can simply use the <code>mosh<\/code> command as a drop-in replacement for <code>ssh<\/code><\/p>\n<h2 id=\"optional-setting-up-multi-factor-authentication\">Optional: Setting up multi-factor authentication<\/h2>\n<p>Now, public key authentication is probably secure enough for most, but if you want to be extra fancy, you can also add MFA or multi-factor authentication. The way it works is you install an app on your phone (there are a lot of open source apps on Android like AndOTP) and every time you log in you get a one time password in the app which you need to enter in order to log in. This provides an additional layer of security for your server which can be useful for some of us who are especially paranoid.<\/p>\n<p>The first thing you have to do is to install <code>google-authenticator-libpam<\/code> Yes, the protocol is made by Google, but it's completely open-source and you don't have to use the Google Authenticator app on your phone, there are many open source options as I've already mentioned<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> apt install libpam-google-authenticator<\/span>\n<\/span><\/code><\/pre>\n<p>After that, launch the initialization script by typing <code>google-authenticator<\/code> . There, basically answer yes to all questions except for the one about multiple users and the one about 30 second tokens.<\/p>\n<p>Once you've done that, you might have noticed a big QR code in your command line as well as the recovery codes. Make sure to write those codes down somewhere save, they'll be useful in case you lose the access to the app on your phone. After that what you need to do is launch the authenticator app on your phone, I'll use OTP Auth, add a new account and chooe \"Scan a QR code\". After you scan the code, the account will be added to the app. And we're done with the phone part for now.<\/p>\n<p>Let's go back to the server terminal and edit the authentication settings file for sshd:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> nvim \/etc\/pam.d\/sshd<\/span>\n<\/span><\/code><\/pre>\n<p>Here we'll comment out the line that says <code>@include common-auth<\/code>. Normally the two factor authentication will ask you for your user password and the one time password, but since we're already using a public key with the password, having to enter your password twice is slightly annoying. That way you'll only have to enter the public key password and the one time password.<\/p>\n<p>Next we need to add this line to the end of the file:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">auth<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> required pam_google_authenticator.so<\/span>\n<\/span><\/code><\/pre>\n<p>Let's save the file and quit. Now we need to edit the SSHD configuration file to make SSH aware of the new authentication method:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> nvim \/etc\/ssh\/sshd_config<\/span>\n<\/span><\/code><\/pre>\n<p>Here we need to change the following lines:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">ChallengeResponseAuthentication<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> yes<\/span>\n<\/span><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">UsePAM<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> yes<\/span>\n<\/span><\/code><\/pre>\n<p>And add a new line after UsePAM that says:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">AuthenticationMethods<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> publickey,password publickey,keyboard-interactive<\/span>\n<\/span><\/code><\/pre>\n<p>That's it, let's save the file and exit. And now let's restart the SSH service for the changes to take effect:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> systemctl restart sshd<\/span>\n<\/span><\/code><\/pre>\n<p>As I mentioned in the beginning, it's always a good idea to try and log in in a separate terminal window without closing the server session. Otherwise if you messed up you'll be locked out of the SSH and obviously you don't want that.<\/p>\n<p>If you try to log in now you'll see that apart from the usual public key password you're also going to be asked for the one time password from your app. Once again, if you're using Gnome, you won't be prompted for the public key until you log out and log back in again, only the one time password from your phone app. Let's enter the password and voila! Now our server is secured by two-factor authentication.<\/p>\n<h2 id=\"optional-unattended-upgrades\">Optional: Unattended upgrades<\/h2>\n<p>One last thing that I want to show you today is unattended software upgrades. What this means is we're going to have a script that runs <code>apt update<\/code> and <code>apt upgrade<\/code> regularly, thus liberating us from the burden of having to log in to the server and do this manually. The server will also be rebooted for kernel updates, but since the reboot takes less than a minute, and since kernel updates are not very frequent, your VPN won't actually have much downtime because of the upgrades. You can also disable the automatic reboots if you prefer to do it yourself.<\/p>\n<p>So the first thing we need to do is to install the unattended-upgrades package:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> apt install unattended-upgrades apt-listchanges bsd-mailx<\/span>\n<\/span><\/code><\/pre>\n<p>Next, enable the stable security updates:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> dpkg-reconfigure<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>plow<\/span> unattended-upgrades<\/span>\n<\/span><\/code><\/pre>\n<p>After that's done, let's edit the config file<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> nvim \/etc\/apt\/apt.conf.d\/50unattended-upgrades<\/span>\n<\/span><\/code><\/pre>\n<p>Here we need to set our email address which is going to be used for update notifications:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">Unattended-Upgrade::Mail<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> <span class=\"z-string z-quoted z-double z-shell\"><span class=\"z-punctuation z-definition z-string z-begin z-shell\">&quot;<\/span>mail@example.com<span class=\"z-punctuation z-definition z-string z-end z-shell\">&quot;<\/span><\/span><\/span><span class=\"z-keyword z-operator z-logical z-continue z-shell\">;<\/span>\n<\/span><\/code><\/pre>\n<p>And then also enable automatic reboots, set up cleanup tasks for removing unused kernels and set the automatic reboot time at 5AM.<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">Unattended-Upgrade::Automatic-Reboot<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> <span class=\"z-string z-quoted z-double z-shell\"><span class=\"z-punctuation z-definition z-string z-begin z-shell\">&quot;<\/span>true<span class=\"z-punctuation z-definition z-string z-end z-shell\">&quot;<\/span><\/span><\/span><span class=\"z-keyword z-operator z-logical z-continue z-shell\">;<\/span>  <span class=\"z-comment z-line z-number-sign z-shell\"><span class=\"z-punctuation z-definition z-comment z-begin z-shell\">#<\/span><\/span><span class=\"z-comment z-line z-number-sign z-shell\"> this is kind of obvious<\/span><span class=\"z-comment z-line z-number-sign z-shell\">\n<\/span><\/span><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">Unattended-Upgrade::Remove-Unused-Kernel-Packages<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> <span class=\"z-string z-quoted z-double z-shell\"><span class=\"z-punctuation z-definition z-string z-begin z-shell\">&quot;<\/span>true<span class=\"z-punctuation z-definition z-string z-end z-shell\">&quot;<\/span><\/span><\/span><span class=\"z-keyword z-operator z-logical z-continue z-shell\">;<\/span>\n<\/span><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">Unattended-Upgrade::Remove-Unused-Dependencies<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> <span class=\"z-string z-quoted z-double z-shell\"><span class=\"z-punctuation z-definition z-string z-begin z-shell\">&quot;<\/span>true<span class=\"z-punctuation z-definition z-string z-end z-shell\">&quot;<\/span><\/span><\/span><span class=\"z-keyword z-operator z-logical z-continue z-shell\">;<\/span>\n<\/span><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">Unattended-Upgrade::Automatic-Reboot<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> <span class=\"z-string z-quoted z-double z-shell\"><span class=\"z-punctuation z-definition z-string z-begin z-shell\">&quot;<\/span>true<span class=\"z-punctuation z-definition z-string z-end z-shell\">&quot;<\/span><\/span><\/span><span class=\"z-keyword z-operator z-logical z-continue z-shell\">;<\/span>\n<\/span><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">Unattended-Upgrade::Automatic-Reboot-Time<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> <span class=\"z-string z-quoted z-double z-shell\"><span class=\"z-punctuation z-definition z-string z-begin z-shell\">&quot;<\/span>05:00<span class=\"z-punctuation z-definition z-string z-end z-shell\">&quot;<\/span><\/span><\/span><span class=\"z-keyword z-operator z-logical z-continue z-shell\">;<\/span> <span class=\"z-comment z-line z-number-sign z-shell\"><span class=\"z-punctuation z-definition z-comment z-begin z-shell\">#<\/span><\/span><span class=\"z-comment z-line z-number-sign z-shell\"> here we&#39;ll specify when we want our system to reboot<\/span><span class=\"z-comment z-line z-number-sign z-shell\">\n<\/span><\/span><\/code><\/pre>\n<p>That's it! Let's see if it works<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">sudo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> unattended-upgrades<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> --<\/span>dry-run<\/span><\/span>\n<\/span><\/code><\/pre>\n<p>So now your system and all the packages will be updated automatically and you'll get an email every time an upgrade has been performed.<\/p>\n"},{"title":"Putting an Airport card into Thinkpad T440p","pubDate":"Tue, 11 Jun 2019 00:00:00 +0000","author":"Unknown","link":"https:\/\/notthebe.ee\/blog\/installing-an-airport-card-into-t440p\/","guid":"https:\/\/notthebe.ee\/blog\/installing-an-airport-card-into-t440p\/","description":"<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/installing-an-airport-card-into-t440p\/Card.jpg\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/installing-an-airport-card-into-t440p\/Card.jpg\" alt=\"Airport Card in a T440p\">\n    <\/a>\n<\/div>\n<p>As you probably know, there are two cards that are officially physically compatible with T440p and work on macOS (almost) out of the box: <strong>DW1830<\/strong> and <strong>DW1560<\/strong>. Both of these cards are pretty expensive (90-120\u20ac and 50-70\u20ac respectively) and difficult to source (sellers are often out of stock due to the high demand).<\/p>\n<p>So I started looking for alternative solutions and eventually decided to buy an <a href=\"https:\/\/www.ebay.de\/itm\/BCM94360CS2-BCM943224PCIEBT2-12-6-Pin-WIFI-wireless-card-module-to-NGFF-Gut-CC\/233302599936\">NGFF to Airport adapter<\/a> and a <a href=\"https:\/\/www.ebay.de\/itm\/MacBook-Air-2010-2011-2012-BCM943224PCIEBT2-AirPort-WLan-BlueTooth-Board\/173902975009?ssPageName=STRK%3AMEBIDX%3AIT&amp;_trksid=p2057872.m2749.l2649\">BCM943224PCIEBT2<\/a> Airport card from a Macbook Air (18\u20ac in total), and try my luck.<\/p>\n<h3 id=\"some-considerations\">Some considerations<\/h3>\n<ul>\n<li>Please bear in mind that this is a higly experimental method. The results might be different for you, and this is <strong>by no means<\/strong> a 100% final working method. There's no guarantee, so do it at your own risk<\/li>\n<li>Make sure you know what you're doing, since the guide involves taking the laptop apart and this process is not for the faint of heart.<\/li>\n<\/ul>\n<h3 id=\"preparations\">Preparations<\/h3>\n<p>Remove <a href=\"https:\/\/notthebe.ee\/blog\/removing-the-wifi-whitelist\/\">Wi-Fi whitelist<\/a> in your BIOS if you haven't done that yet.<\/p>\n<p>Modify the NGFF adapter, since it won't fit into the T440p. Use diagonal pliers (cutters) to cut off a part of the adapter (be careful not to touch <strong>contacts<\/strong> on the left side \u2013 slightly left to a \"pb\" label on the picture):<br \/>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/installing-an-airport-card-into-t440p\/adapter.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/installing-an-airport-card-into-t440p\/adapter.png\" alt=\"Cutting the adapter\">\n    <\/a>\n<\/div>\n<\/p>\n<h3 id=\"disassembly\">Disassembly<\/h3>\n<p>Dissassemble the laptop \u2013 you will need to remove:<\/p>\n<ul>\n<li>External battery<\/li>\n<li>Big door<\/li>\n<li>CMOS battery<\/li>\n<li>Webcam cable<\/li>\n<li>Fan cable<\/li>\n<li>DVD drive<\/li>\n<li>Keyboard and keyboard bezel<\/li>\n<li>Base cover assembly<\/li>\n<\/ul>\n<p>Refer to <a href=\"https:\/\/thinkpads.com\/support\/hmm\/hmm_pdf\/t440p_hmm_en_sp40a25467_01.pdf\">Lenovo Maintenance Manual<\/a> for instructions. There are a few plastics snaps around the docking station connector that are pretty tight, apply a little bit of force when you take off the base cover assembly and it should come right off.<\/p>\n<p>After you removed the base cover assembly, disconnect the <em>Ethernet controller<\/em> here:<br \/>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/installing-an-airport-card-into-t440p\/Ethernet.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/installing-an-airport-card-into-t440p\/Ethernet.png\" alt=\"Ethernet controller location\">\n    <\/a>\n<\/div>\n<\/p>\n<p>Screw the adapter to the motherboard and insert the Airport card into the adapter. Then, lift the Airport card from the adapter and try to \"sandwich\" the rear part of the Ethernet controller between the adapter and the card, as shown here (sorry for the madskillz):<br \/>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/installing-an-airport-card-into-t440p\/scheme.jpg\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/installing-an-airport-card-into-t440p\/scheme.jpg\" alt=\"Scheme\">\n    <\/a>\n<\/div>\n<\/p>\n<p>Finally, connect the Ethernet card to the motherboard, screw it in and assemble the laptop. Don't put the big door yet: there is a plastic hook on it that connects with the space between the Ethernet connector and the base cover assembly, which is now occupied by the Airport card. Use cutters to remove it.<br \/>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/installing-an-airport-card-into-t440p\/hook.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/installing-an-airport-card-into-t440p\/hook.png\" alt=\"Big door hook\">\n    <\/a>\n<\/div>\n<\/p>\n<p>Turn the laptop on. It will display a message about CMOS battery and wrong checksum  \u2013 go to the BIOS settings and <strong>restore default settings<\/strong>. My laptop refused to boot until I did that, but YMMV.<\/p>\n<p><strong>Voil\u00e1!<\/strong> The card works out of the box with no additional configuration required!<br \/>\n<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/installing-an-airport-card-into-t440p\/screen.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/installing-an-airport-card-into-t440p\/screen.png\" alt=\"System Report screenshot\">\n    <\/a>\n<\/div>\n<\/p>\n"},{"title":"How to add your custom bootsplash to SeaBIOS","pubDate":"Thu, 27 Sep 2018 00:00:00 +0000","author":"Unknown","link":"https:\/\/notthebe.ee\/blog\/custom-seabios-bootsplash\/","guid":"https:\/\/notthebe.ee\/blog\/custom-seabios-bootsplash\/","description":"<div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/custom-seabios-bootsplash\/photo.jpg\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/custom-seabios-bootsplash\/photo.jpg\" alt=\"Bootsplash\">\n    <\/a>\n<\/div>\n<p><strong>WARNING!<\/strong> Proceed at your own risk! Be sure to have a Raspberry Pi and a Pomona clip handy in case you mess up.<\/p>\n<h2 id=\"saving-the-image\">Saving the image<\/h2>\n<p>Open the desired image and export it as JPG, in the saving options change \"Subsampling\" to 4:2:0 and uncheck \"Progressive\":<\/p>\n<p><div class=\"post__image\">\n    <a href=\"https:\/\/notthebe.ee\/blog\/custom-seabios-bootsplash\/screenshot.png\" class=\"image-link\" target=\"_blank\">\n        <img src=\"https:\/\/notthebe.ee\/blog\/custom-seabios-bootsplash\/screenshot.png\" alt=\"GIMP Settings\">\n    <\/a>\n<\/div>\n\n<sub><a href=\"https:\/\/puri.sm\/posts\/librem-13-coreboot-report-february-25th-2017\/\">Source<\/a><\/sub><\/p>\n<p>The output image must be 1024px wide and 768px tall. Don't forget that the X230's screen is 16:9, so the it will be additionally shrinked.<\/p>\n<h2 id=\"making-a-dump-of-up-your-current-rom\">Making a dump of up your current ROM<\/h2>\n<p>Dump your current ROM using <strong>flashrom<\/strong> (don't forget to boot with <code>iomem=relaxed<\/code> kernel option):<\/p>\n<pre class=\"z-code\"><code><span class=\"z-text z-plain\"># flashrom -p internal -r file.rom\n<\/span><\/code><\/pre>\n<p>Additionally, if you're on X220, you might have to force flashrom (as mentioned <a href=\"https:\/\/www.coreboot.org\/Board:lenovo\/x220\">here<\/a>):<\/p>\n<pre class=\"z-code\"><code><span class=\"z-text z-plain\"># flashrom -p internal:laptop=force_I_want_a_brick -r file.rom\n<\/span><\/code><\/pre>\n<h2 id=\"adding-the-bootsplash\">Adding the bootsplash<\/h2>\n<p>Then, add the bootsplash image using <code>cbfstool<\/code> from <a href=\"https:\/\/github.com\/coreboot\/coreboot\">coreboot-utils<\/a>:<\/p>\n<pre class=\"z-code\"><code><span class=\"z-text z-plain\"># cbfstool file.rom add -f &lt;name of your bootsplash file&gt; -n bootsplash.jpg -t raw\n<\/span><\/code><\/pre>\n<p>For X230 users:<\/p>\n<pre class=\"z-code\"><code><span class=\"z-text z-plain\">$ cat &gt;layout.txt &lt;&lt;EOL\n<\/span><span class=\"z-text z-plain\">0x00000000:0x007fffff ifdmegbe\n<\/span><span class=\"z-text z-plain\">0x00800000:0x00bfffff bios\n<\/span><span class=\"z-text z-plain\">EOL\n<\/span><\/code><\/pre>\n<h2 id=\"flashing-the-rom\">Flashing the ROM<\/h2>\n<pre class=\"z-code\"><code><span class=\"z-text z-plain\">X230:\n<\/span><span class=\"z-text z-plain\"># flashrom -p internal --layout layout.txt --image bios -w file.rom\n<\/span><span class=\"z-text z-plain\">\n<\/span><span class=\"z-text z-plain\">X220:\n<\/span><span class=\"z-text z-plain\"># flashrom -p internal:laptop=force_I_want_a_brick --image bios -w file.rom\n<\/span><\/code><\/pre>\n<p>Have fun with your new bootsplash!<\/p>\n"},{"title":"Reverting Coreboot installation on Thinkpad X220 without an original SPI dump","pubDate":"Tue, 11 Sep 2018 00:00:00 +0000","author":"Unknown","link":"https:\/\/notthebe.ee\/blog\/revert-coreboot\/","guid":"https:\/\/notthebe.ee\/blog\/revert-coreboot\/","description":"<p>Congratulations! You forgot to back up your original BIOS dump, and now you're stuck with coreboot forever...right? Not so fast.<\/p>\n<p>Even without the SPI dump, you can still go back to the original BIOS, which is really useful if you experience some problems or decide to install macOS.<\/p>\n<h2 id=\"requirements\">Requirements<\/h2>\n<ul>\n<li><code>$01CB000.FL1<\/code> file from this repository<\/li>\n<li>X220 running Linux with <code>flashrom<\/code> package installed, as well as kernel option <code>iomem=relaxed<\/code> set in bootloader<\/li>\n<\/ul>\n<h2 id=\"warning\">WARNING<\/h2>\n<p>Only do this if you know what you're doing, be sure to have a Raspberry Pi and a Pomona clip handy to re-flash your SPI chip in case you mess up. I am not responsible for any damage your computer may suffer during this process.<\/p>\n<h2 id=\"the-process\">The process<\/h2>\n<p>Download <a href=\"https:\/\/notthebe.ee\/blog\/revert-coreboot\/$01CB000.FL1\">this file<\/a>, and truncate it to exactly 8MByte:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">dd<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> if=<span class=\"z-constant z-character z-escape z-shell\">\\$<\/span>01CB000.FL1 of=x220_spi.bin bs=<span class=\"z-meta z-group z-expansion z-arithmetic z-shell\"><span class=\"z-punctuation z-definition z-variable z-shell\">$<\/span><span class=\"z-punctuation z-section z-parens z-begin z-shell\">((<\/span><span class=\"z-constant z-numeric z-integer z-hexadecimal z-shell\"><span class=\"z-punctuation z-definition z-numeric z-base z-shell\">0x<\/span>800000<\/span><span class=\"z-punctuation z-section z-parens z-end z-shell\">))<\/span><\/span> count=1<\/span>\n<\/span><\/code><\/pre>\n<p>Backup the old content <strong>and put it on an external storage<\/strong>:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">flashrom<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"><span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>p<\/span> internal:laptop=force_I_want_a_brick<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>r<\/span> file.rom<\/span>\n<\/span><\/code><\/pre>\n<p>You may need to tell the tool which flash IC it is with the \"-c\" option:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">flashrom<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"><span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>p<\/span> internal:laptop=force_I_want_a_brick<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>c<\/span> MX25L6405<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>r<\/span> file.rom<\/span>\n<\/span><\/code><\/pre>\n<p>As we want to write only the BIOS part in the SPI flash starting at offset 0x500000, we need a layout file:<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-support z-function z-echo z-shell\">echo<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"> <span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\">-<\/span>e<\/span> <span class=\"z-string z-quoted z-double z-shell\"><span class=\"z-punctuation z-definition z-string z-begin z-shell\">&quot;<\/span>000000:4fffff dummy\\n500000:7fffff bios<span class=\"z-punctuation z-definition z-string z-end z-shell\">&quot;<\/span><\/span> <span class=\"z-keyword z-operator z-assignment z-redirection z-shell\">&gt;<\/span> x220.layout<\/span>\n<\/span><\/code><\/pre>\n<p>Update the BIOS portion in the SPI flash<\/p>\n<pre data-lang=\"bash\" class=\"language-bash z-code\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"z-source z-shell z-bash\"><span class=\"z-meta z-function-call z-shell\"><span class=\"z-variable z-function z-shell\">flashrom<\/span><\/span><span class=\"z-meta z-function-call z-arguments z-shell\"><span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>p<\/span> internal:laptop=force_I_want_a_brick<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>c<\/span> MX25L6405<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> --<\/span>layout<\/span> x220.layout<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>i<\/span> bios<span class=\"z-variable z-parameter z-option z-shell\"><span class=\"z-punctuation z-definition z-parameter z-shell\"> -<\/span>w<\/span> x220_spi.bin<\/span>\n<\/span><\/code><\/pre>\n<p>Done!<\/p>\n<p>This guide is mostly based on <a href=\"http:\/\/thinkwiki.de\/UEFI_BIOS_T420_BIOS_Structure\">this wiki page<\/a><\/p>\n"}]}}