<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>self-hosted.tools</title><link>https://self-hosted.tools/</link><description>Recent content on self-hosted.tools</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Tue, 25 Feb 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://self-hosted.tools/rss.xml" rel="self" type="application/rss+xml"/><item><title>How to use ZITADEL for Authentication with Open WebUI</title><link>https://self-hosted.tools/p/openwebui-with-zitadel-oidc/</link><pubDate>Tue, 25 Feb 2025 00:00:00 +0000</pubDate><guid>https://self-hosted.tools/p/openwebui-with-zitadel-oidc/</guid><description>&lt;img src="https://self-hosted.tools/p/openwebui-with-zitadel-oidc/cover.jpg" alt="Featured image of post How to use ZITADEL for Authentication with Open WebUI" />&lt;p>If you&amp;rsquo;re considering implementing Single Sign-On (SSO) for &lt;a class="link" href="https://github.com/open-webui/open-webui" target="_blank" rel="noopener"
>Open WebUI&lt;/a> I&amp;rsquo;ve got you covered. Integrating it with &lt;a class="link" href="https://zitadel.com/" target="_blank" rel="noopener"
>ZITADEL&lt;/a> is a great solution.&lt;br>
This guide will take you step-by-step through setting up SSO and managing roles, helping to boost both security and user experience. &lt;br>
Let&amp;rsquo;s get started on making your (and your users) experience as seamless as possible!&lt;/p>
&lt;p>This guide will not cover on how to setup and configure ZITADEL or Open WebUI, I will focus on the oidc part.&lt;/p>
&lt;h2 id="steps-in-zitadel">Steps in ZITADEL
&lt;/h2>&lt;p>Create a new Project:
&lt;img src="https://self-hosted.tools/p/openwebui-with-zitadel-oidc/image.png"
width="1077"
height="560"
loading="lazy"
alt="Create Project"
class="gallery-image"
data-flex-grow="192"
data-flex-basis="461px"
>&lt;/p>
&lt;p>I would advise setting those three settings (you dont need to set the last two, but it gives you more control over what users can try to log in):
&lt;img src="https://self-hosted.tools/p/openwebui-with-zitadel-oidc/image-1.png"
width="915"
height="759"
loading="lazy"
alt="Settings"
class="gallery-image"
data-flex-grow="120"
data-flex-basis="289px"
>
Dont forget to click Save!&lt;/p>
&lt;p>Create a new Application and select &amp;ldquo;WEB&amp;rdquo; as type:
&lt;img src="https://self-hosted.tools/p/openwebui-with-zitadel-oidc/image-2.png"
width="1683"
height="1174"
loading="lazy"
alt="New Application"
class="gallery-image"
data-flex-grow="143"
data-flex-basis="344px"
>&lt;/p>
&lt;p>Select Code as the authentication method:
&lt;img src="https://self-hosted.tools/p/openwebui-with-zitadel-oidc/image-3.png"
width="1683"
height="1079"
loading="lazy"
alt="Select Authentication Method"
class="gallery-image"
data-flex-grow="155"
data-flex-basis="374px"
>&lt;/p>
&lt;p>Add &lt;code>https://ai.example.com/oauth/oidc/callback&lt;/code> as the callback URL (change ai.example.com to your deployment URL):
&lt;img src="https://self-hosted.tools/p/openwebui-with-zitadel-oidc/image-4.png"
width="1683"
height="1079"
loading="lazy"
alt="Add Callback URL"
class="gallery-image"
data-flex-grow="155"
data-flex-basis="374px"
>&lt;/p>
&lt;p>Control overview and click &amp;ldquo;Create&amp;rdquo;:
&lt;img src="https://self-hosted.tools/p/openwebui-with-zitadel-oidc/image-5.png"
width="1683"
height="1079"
loading="lazy"
alt="Control Overview"
class="gallery-image"
data-flex-grow="155"
data-flex-basis="374px"
>&lt;/p>
&lt;p>Copy your ClientId and ClientSecret and store them somewhere temporary:
&lt;img src="https://self-hosted.tools/p/openwebui-with-zitadel-oidc/image-6.png"
width="1683"
height="1079"
loading="lazy"
alt="Copy Credentials"
class="gallery-image"
data-flex-grow="155"
data-flex-basis="374px"
>&lt;/p>
&lt;p>Switch to Token Settings, check both options, and click Save:
&lt;img src="https://self-hosted.tools/p/openwebui-with-zitadel-oidc/image-7.png"
width="1683"
height="1121"
loading="lazy"
alt="Token Settings"
class="gallery-image"
data-flex-grow="150"
data-flex-basis="360px"
>&lt;/p>
&lt;p>Switch back to your Project and click on &amp;ldquo;Roles&amp;rdquo;.&lt;br>
Create an admin and user role:
&lt;img src="https://self-hosted.tools/p/openwebui-with-zitadel-oidc/image-8.png"
width="1683"
height="715"
loading="lazy"
alt="Create Roles"
class="gallery-image"
data-flex-grow="235"
data-flex-basis="564px"
>&lt;/p>
&lt;p>Open WebUI expects the roles claim to be flat and won&amp;rsquo;t work with the standard ZITADEL Roles Claim.
We will fix that with a custom action that adds the roles of a user flat to the token.
Switch to the &amp;ldquo;Actions&amp;rdquo; Tab and create a new action:
&lt;img src="https://self-hosted.tools/p/openwebui-with-zitadel-oidc/image-9.png"
width="1683"
height="676"
loading="lazy"
alt="Create New Action"
class="gallery-image"
data-flex-grow="248"
data-flex-basis="597px"
>&lt;/p>
&lt;p>Name: &lt;code>flatRolesClaim&lt;/code>
(The name of the action and the name of the function in the javascript have to be the same)&lt;/p>
&lt;p>Add the following content (adjusted from &lt;a class="link" href="https://github.com/zitadel/actions/blob/main/examples/custom_roles.js" target="_blank" rel="noopener"
>here&lt;/a>):&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">/**
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * Adds an additional claim in the token with roles in flat format.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> *
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * The role claims of the token look like the following:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> *
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * // added by the code below
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * &amp;#34;flatRolesClaim&amp;#34;: [&amp;#34;test&amp;#34;, &amp;#34;role2&amp;#34;, ...],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * // added automatically
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * &amp;#34;urn:zitadel:iam:org:project:roles&amp;#34;: {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * &amp;#34;test&amp;#34;: {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * &amp;#34;201982826478953724&amp;#34;: &amp;#34;zitadel.localhost&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> *
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * Flow: Complement token, Triggers: Pre Userinfo creation, Pre access token creation
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> *
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * @param ctx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * @param api
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> */
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">function flatRolesClaim(ctx, api) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (ctx.v1.user.grants == undefined || ctx.v1.user.grants.count == 0) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> let grants = [];
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ctx.v1.user.grants.grants.forEach(claim =&amp;gt; {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> claim.roles.forEach(role =&amp;gt; {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> grants.push(role);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> })
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> api.v1.claims.setClaim(&amp;#39;flatRolesClaim&amp;#39;, grants);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Add the following flow:&lt;br>
&lt;img src="https://self-hosted.tools/p/openwebui-with-zitadel-oidc/image-10.png"
width="796"
height="825"
loading="lazy"
alt="New Flow"
class="gallery-image"
data-flex-grow="96"
data-flex-basis="231px"
>&lt;/p>
&lt;p>Last thing to do in Zitadel is to give a user access. Go to your project and give a user the needed authorizations:
&lt;img src="https://self-hosted.tools/p/openwebui-with-zitadel-oidc/image-11.png"
width="1192"
height="825"
loading="lazy"
alt="User Access"
class="gallery-image"
data-flex-grow="144"
data-flex-basis="346px"
>&lt;/p>
&lt;h2 id="open-webui">Open WebUI
&lt;/h2>&lt;p>Add the following to your &lt;a class="link" href="https://docs.openwebui.com/features/sso/" target="_blank" rel="noopener"
>Open WebUI env variables&lt;/a>:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">OAUTH_CLIENT_ID: &amp;#34;308724015575405835&amp;#34; # copied before
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">OAUTH_CLIENT_SECRET: &amp;#34;your-secret&amp;#34; # copied before
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ENABLE_OAUTH_SIGNUP: &amp;#34;true&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># if your user has the same email in ZITADEL and existing open webui install, the users will be merged
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">OAUTH_MERGE_ACCOUNTS_BY_EMAIL: &amp;#34;true&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># change to your zitadel url
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">OPENID_PROVIDER_URL: &amp;#34;https://sso.example.com/.well-known/openid-configuration&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">OAUTH_PROVIDER_NAME: &amp;#34;ZITADEL&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">OAUTH_SCOPES: &amp;#34;openid email profiles&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ENABLE_OAUTH_ROLE_MANAGEMENT: &amp;#34;true&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">OAUTH_ROLES_CLAIM: &amp;#34;flatRolesClaim&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">OAUTH_ALLOWED_ROLES: &amp;#34;openwebui_user&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">OAUTH_ADMIN_ROLES: &amp;#34;admin&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>You can still have signups disabled for normal users; ZITADEL users can still log in (even if they have never logged in before).&lt;br>
If you have your default user role set to &amp;ldquo;pending&amp;rdquo;, ZITADEL users with the role &amp;ldquo;openwebui_user&amp;rdquo; or &amp;ldquo;admin&amp;rdquo; assigned will have their respective role inside Open WebUI and won&amp;rsquo;t be set to &amp;ldquo;pending&amp;rdquo;. If you did not set &amp;ldquo;Check authorization on Authentication&amp;rdquo; and a ZITADEL user without one of your roles logs in, the account will be in a pending state.&lt;/p>
&lt;p>I hope this tutorial helped you set up SSO in Open WebUI.&lt;br>
If you do not already have central authentication in your homelab, just try it - it&amp;rsquo;s worth it 😉&lt;/p>
&lt;p>&lt;font size ="2">&lt;em>Cover Photo by &lt;a class="link" href="https://unsplash.com/@mattartz" target="_blank" rel="noopener"
>Matt Artz&lt;/a> on &lt;a class="link" href="https://unsplash.com/" target="_blank" rel="noopener"
>Unsplash&lt;/a>&lt;/em>&lt;/font>&lt;/p></description></item><item><title>Recovering VMs from a Broken QubesOS Install</title><link>https://self-hosted.tools/p/recover-vms-from-broken-qubesos/</link><pubDate>Sun, 20 Oct 2024 00:00:00 +0000</pubDate><guid>https://self-hosted.tools/p/recover-vms-from-broken-qubesos/</guid><description>&lt;img src="https://self-hosted.tools/p/recover-vms-from-broken-qubesos/cover.jpg" alt="Featured image of post Recovering VMs from a Broken QubesOS Install" />&lt;p>Is your QubesOS installation no longer booting, and you need to recover a qube (VM)? You’re in the right place. &lt;br>
Recently, my Qubes test install encountered issues after the machine crashed during a dom0 update. While it still booted, the system was unresponsive and most Qubes command line tools didn&amp;rsquo;t function properly. There was limited information online, so I had to dig through old forum threads and unrelated blog posts. &lt;br>
Here’s the solution I came up with (spoiler: it worked perfectly).&lt;/p>
&lt;h2 id="install-qubes-on-a-new-disk">Install Qubes on a New Disk
&lt;/h2>&lt;p>The first step is to install Qubes on a new disk. Do &lt;strong>NOT&lt;/strong> install it on the same disk as your failed Qubes installation!&lt;br>
You can download qubes &lt;a class="link" href="https://www.qubes-os.org/downloads/" target="_blank" rel="noopener"
>here&lt;/a>, and don&amp;rsquo;t forget to &lt;a class="link" href="https://www.qubes-os.org/security/verifying-signatures/" target="_blank" rel="noopener"
>verify&lt;/a> the downloaded file. &lt;br>
The fingerprint of the master signing key is: &lt;code>427F 11FD 0FAA 4B08 0123 F01C DDFA 1A3E 3687 9494&lt;/code>.&lt;/p>
&lt;h2 id="mounting-the-broken-install">Mounting the Broken Install
&lt;/h2>&lt;p>First, create a new disposable qube on your fresh Qubes installation and try to attach the failed disk via the GUI.
If that doesn&amp;rsquo;t work, you can identify the disk with lsblk and manually attach it to your new disposable VM using the following command.&lt;br>
&lt;code>sudo qvm-block attach dispXXX sys-usb:sdX&lt;/code>&lt;/p>
&lt;p>You can then either mount it using the Thunar file manager (GUI) or via the command line:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">mkdir /mnt/old_install # create a mount point for the old disk
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo cryptsetup luksOpen /dev/&amp;lt;disk&amp;gt; old_install # decrypt disk
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>I tried attaching it directly to dom0 but had no success. While that would have saved a step, I encountered strange errors that I couldn&amp;rsquo;t resolve. However, using a fresh VM made it easy.&lt;/p>
&lt;h2 id="recover-the-old-vm-disks">Recover the old VM Disks
&lt;/h2>&lt;p>When recovering an AppVM, you only need to copy the &amp;ldquo;&lt;strong>private&lt;/strong>&amp;rdquo; volume. The &lt;strong>root&lt;/strong> volume is provided by the template. &lt;br>
Ensure that the same version of Fedora/Debian is installed as template in your new installation, matching the one from your old setup.&lt;/p>
&lt;p>To transfer the disk images, use the following commands to dump the volumes of the mounted disk into a new image files.&lt;/p>
&lt;p>To recover the private volume, use the following command: &lt;br>
&lt;code>sudo dd if=/dev/qubes_dom0/vm-xxx-private of=xxx_restore_private.img bs=4M&lt;/code> &lt;br>
If it&amp;rsquo;s not an AppVM, you will also need to save the root volume: &lt;br>
&lt;code>sudo dd if=/dev/qubes_dom0/vm-xxx-root of=xxx_restore_root.img bs=4M&lt;/code>&lt;/p>
&lt;p>You can recover the templates the same way if you had installed extra programs there, but I recommend simply installing them again in a fresh template.&lt;/p>
&lt;h3 id="transfer-to-dom0">Transfer to dom0
&lt;/h3>&lt;p>If your files exceed the available space in dom0 , you&amp;rsquo;ll need to resize it:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">sudo lvresize -L +30G /dev/qubes_dom0/root
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo resize2fs /dev/qubes_dom0/root
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">df -h
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>You must transfer the saved images to dom0 to make them functional again. You can do this using the following command (executed in dom0 ): &lt;br>
&lt;code>qvm-run --pass-io &amp;lt;src_domain&amp;gt; 'cat /path/to/file_in_src_domain' &amp;gt; /path/to/file_name_in_dom0&lt;/code> &lt;br>
for example: &lt;br>
&lt;code>qvm-run --pass-io disp4321 'cat /home/user/restore/xxx_restore_private.img' &amp;gt; ORIG_VM-private.img&lt;/code>&lt;/p>
&lt;h3 id="recreate-and-recover-vms">Recreate and Recover VMs
&lt;/h3>&lt;p>Create a qube in the GUI with the same disk size as the original qube. &lt;br>
Next, overwrite the new &amp;ldquo;private&amp;rdquo; image with the old one, and the &amp;ldquo;root&amp;rdquo; image as well (if it&amp;rsquo;s not an AppVM).&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">sudo dd if=/home/user/QubesIncoming/disposable-vm-name/ORIG_VM-private.img of=/dev/qubes_dom0/vm-RESTORE_VM-private bs=4M
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo dd if=/home/user/QubesIncoming/disposable-vm-name/ORIG_VM-root.img of=/dev/qubes_dom0/vm-RESTORE_VM-root bs=4M
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This process may take some time, but once it&amp;rsquo;s complete, you&amp;rsquo;ll be able to start the qube and use it as usual. &lt;br>
You may need to scan for programs and services to add them to the start menu. This can be done through the GUI in the qube&amp;rsquo;s settings.&lt;/p>
&lt;h2 id="cleanup">Cleanup
&lt;/h2>&lt;p>I recommend making a backup of all restored qubes and performing a fresh install, where you can restore the recovered qubes from that backup.&lt;br>
Otherwise, your dom0 disk size might remain incorrect, and you could end up with unnecessary data lingering in dom0.&lt;/p>
&lt;p>I hope this guide helped you successfully recover your Qubes installation.&lt;/p></description></item><item><title>Create Custom Noreply Email for Github</title><link>https://self-hosted.tools/p/git-custom-noreply-email/</link><pubDate>Wed, 01 May 2024 00:00:00 +0000</pubDate><guid>https://self-hosted.tools/p/git-custom-noreply-email/</guid><description>&lt;img src="https://self-hosted.tools/p/git-custom-noreply-email/cover.jpg" alt="Featured image of post Create Custom Noreply Email for Github" />&lt;p>Today, I will show you how and why you should set up a custom noreply address for git commits on GitHub and other git hosting providers with Cloudflare mail routing. Git displays the author&amp;rsquo;s email address on every commit.&lt;br>
As a result, many of those emails are harvested by spammers.&lt;/p>
&lt;h2 id="platform-provided-noreply-address">Platform provided Noreply Address:
&lt;/h2>&lt;p>GitHub and other git hosting providers give you the ability to use a noreply email address from them for commits. For example, GitHub provides something like this: &amp;ldquo;&lt;a class="link" href="mailto:id&amp;#43;username@users.noreply.github.com" >id+username@users.noreply.github.com&lt;/a>&amp;rdquo;. All emails sent to that address will bounce, as they should for a noreply address. &lt;br>
Learn more about private emails from &lt;a class="link" href="https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-email-preferences/setting-your-commit-email-address#about-commit-email-addresses" target="_blank" rel="noopener"
>Github&lt;/a> and &lt;a class="link" href="https://docs.gitlab.com/ee/user/profile/index.html#use-an-automatically-generated-private-commit-email" target="_blank" rel="noopener"
>Gitlab&lt;/a>&lt;/p>
&lt;h2 id="why-use-a-custom-noreply-address">Why use a Custom Noreply Address?
&lt;/h2>&lt;ul>
&lt;li>&lt;strong>Portability:&lt;/strong> With a custom noreply address, you&amp;rsquo;re not tied to any specific platform, making it easier to transition between git hosting providers or collaborate with teams using different platforms.&lt;/li>
&lt;li>&lt;strong>Verification Challenges:&lt;/strong> You can&amp;rsquo;t use your GitHub noreply address on other platforms because you can&amp;rsquo;t verify it. Almost all providers require you to verify an author email address to display the commit on your profile.&lt;/li>
&lt;li>&lt;strong>Commit signing:&lt;/strong> If you sign your commits with GPG, do you want an email as a UID over which you don&amp;rsquo;t have control?
&lt;br> It is also easier to verify your signature because you can set up &lt;a class="link" href="https://wiki.gnupg.org/WKD" target="_blank" rel="noopener"
>WKD&lt;/a>, allowing anyone to import your key with &lt;code>gpg --locate-key EMAIL&lt;/code>.&lt;/li>
&lt;li>&lt;strong>Consistency:&lt;/strong> A custom noreply address allows you to maintain consistency, especially if you use multiple git hosting providers.&lt;/li>
&lt;/ul>
&lt;h2 id="how-to-set-it-up">How to set it up?
&lt;/h2>&lt;p>If you own a domain and use Cloudflare as your DNS provider, it&amp;rsquo;s a matter of a few clicks and it is &lt;strong>free&lt;/strong>, so let&amp;rsquo;s dive in:&lt;br>
First, go to your Cloudflare dashboard and select the domain you want to use. Then click on &amp;ldquo;Email&amp;rdquo; -&amp;gt; &amp;ldquo;Email Routing&amp;rdquo; -&amp;gt; &amp;ldquo;Get Started.&amp;rdquo; &lt;br>
You can find the documentation about Cloudflare email routing &lt;a class="link" href="https://developers.cloudflare.com/email-routing/" target="_blank" rel="noopener"
>here&lt;/a>.&lt;br>
If you don&amp;rsquo;t plan on using your apex domain (your domain name without a subdomain) for email, you can set up &lt;a class="link" href="mailto:noreply@example.com" >noreply@example.com&lt;/a> (or anything you want) on the next screen:&lt;/p>
&lt;img src="get-started.png" alt="Get started with Email Routing" style="width: 80%; height: auto;">
&lt;p>If you plan to use your apex domain with another mail provider, you can click on &amp;ldquo;Skip getting started&amp;rdquo; and proceed to &amp;ldquo;Settings&amp;rdquo; on the next screen.&lt;br>
Select &amp;ldquo;Add Subdomain&amp;rdquo; and input the subdomain you want to use.&lt;/p>
&lt;img src="add-subdomain.png" alt="Add a subdomain" style="width: 80%; height: auto;">
&lt;p>You can use any subdomain you prefer. Click on &amp;ldquo;Add records and enable,&amp;rdquo; and you are nearly good to go. &lt;br>
For my personal domain, I chose &amp;ldquo;git.example.com&amp;rdquo; because I prefer &lt;a class="link" href="mailto:noreply@git.example.com" >noreply@git.example.com&lt;/a> and because I have a few use cases for the noreply subdomain already in mind.
Using the git subdomain could potentially avoid confusion if I decide to use the noreply subdomain in the future.&lt;/p>
&lt;p>Click on &amp;ldquo;Destination Address&amp;rdquo; and add a real email that will be used to receive the confirmation email.&lt;/p>
&lt;p>After that, go to &amp;ldquo;Routing Rules&amp;rdquo; and create a custom email address, set the action to &amp;ldquo;Send to an email&amp;rdquo; and select your verified mail that you added in the previous step.&lt;/p>
&lt;img src="custom-address.png" alt="Add custom email" style="width: 80%; height: auto;">
&lt;p>Now you are ready to &lt;a class="link" href="https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-email-preferences/adding-an-email-address-to-your-github-account" target="_blank" rel="noopener"
>add the new email&lt;/a> on your git hosting provider like Github as an additional mail address.&lt;/p>
&lt;p>If you are using Github, keep &amp;ldquo;Keep my email addresses private&amp;rdquo; checked, but uncheck &amp;ldquo;Block command line pushes that expose my email&amp;rdquo;:&lt;/p>
&lt;img src="github-mail-settings.png" alt="Github Mail Settings" style="width: 80%; height: auto;">
&lt;p>Now set your new email in your local git config:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">git config --global user.email &amp;#34;git@noreply.yourdomain.com&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Your locally created commits will now have &amp;ldquo;&lt;a class="link" href="mailto:git@noreply.yourdomain.com" >git@noreply.yourdomain.com&lt;/a>&amp;rdquo; as the author name. However, commits, merges, and other changes made through the GitHub web interface will have your &amp;ldquo;&lt;a class="link" href="mailto:id&amp;#43;username@users.noreply.github.com" >id+username@users.noreply.github.com&lt;/a>&amp;rdquo; email as the commit author, otherwise you would expose your primary email address.&lt;/p>
&lt;p>After you have verified the mail on your git provider, you can change the action for the custom address in Cloudflare from &amp;ldquo;send to an email&amp;rdquo; to &amp;ldquo;Drop&amp;rdquo;. &lt;br>
If you need to verify the email on a new git provider, simply route the mails to your normal inbox, verify them, and then set the action again to &amp;ldquo;Drop&amp;rdquo;&lt;/p>
&lt;h2 id="send-a-custom-reject-message-with-cloudflare-workers">Send a custom reject message with Cloudflare Workers
&lt;/h2>&lt;p>Optionally, you can use a worker to send a custom reject message when someone sends an email to your git noreply address.&lt;br>
Cloudflare offers a generous free tier for up to &lt;a class="link" href="https://developers.cloudflare.com/workers/platform/pricing/#workers" target="_blank" rel="noopener"
>100,000 worker requests per day&lt;/a>.&lt;/p>
&lt;blockquote>
&lt;p>If you exceed 100000 worker requests per day and &lt;strong>don&amp;rsquo;t&lt;/strong> use a paid plan, the requests to the worker will fail, but it appears you won&amp;rsquo;t be charged, see &lt;a class="link" href="https://developers.cloudflare.com/workers/observability/errors/" target="_blank" rel="noopener"
>here&lt;/a> and &lt;a class="link" href="https://community.cloudflare.com/t/action-required-daily-request-limit-exceeded-for-cloudflare-workers/331556" target="_blank" rel="noopener"
>here&lt;/a> (Please do your own research before activation!). &lt;br>
If you use a &lt;a class="link" href="https://workers.cloudflare.com/#plans" target="_blank" rel="noopener"
>paid plan&lt;/a>, you will be charged according to your plan.&lt;/p>
&lt;/blockquote>
&lt;p>To set this up:&lt;/p>
&lt;ol>
&lt;li>Click on &amp;ldquo;Email Routing&amp;rdquo; -&amp;gt; &amp;ldquo;Email Workers&amp;rdquo; -&amp;gt; &amp;ldquo;Create.&amp;rdquo;&lt;/li>
&lt;li>You can change the name or leave the autogenerated one.&lt;/li>
&lt;li>Select &amp;ldquo;Create my own&amp;rdquo; and then click &amp;ldquo;Create.&amp;rdquo;&lt;/li>
&lt;/ol>
&lt;p>Insert the following code into the next page and click &amp;ldquo;Save and deploy&amp;rdquo;. &lt;br>
Change &amp;ldquo;My rejection notice&amp;rdquo; to something you would like to send when someone sends an email to your git noreply address.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl"> &lt;span class="k">export&lt;/span> &lt;span class="n">default&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">async&lt;/span> &lt;span class="n">email&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">env&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ctx&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">message&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">setReject&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;My rejection notice&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>I set mine to:&lt;/p>
&lt;blockquote>
&lt;p>Your email has been rejected because the address you sent it to does not accept emails. This is likely because you obtained the address through automated scripts or internet scraping. Please refrain from contacting me using this address.&lt;/p>
&lt;/blockquote>
&lt;p>To activate it go back to &amp;ldquo;Routing rules&amp;rdquo; and edit the one you created before.&lt;br>
Change it from Action &amp;ldquo;Drop&amp;rdquo; to &amp;ldquo;Send to a worker&amp;rdquo; and select the worker you just created.&lt;/p>
&lt;p>Cloudlfare also automatically created a URL where the worker is accessible, go back to the Tab &amp;ldquo;Email Workers&amp;rdquo;, click on the three dots and select &amp;ldquo;Manage Worker&amp;rdquo;. &lt;br>
Navigate to Tab &amp;ldquo;Settings&amp;rdquo; and select &amp;ldquo;Triggers&amp;rdquo;. Under Routes is a workers.dev URL where your worker gets triggered but shows only an error because nothing is configured for a webrequest.&lt;br>
Click on the three dots and select &amp;ldquo;Disable Route&amp;rdquo;.&lt;br>
The Email Trigger will still be active.&lt;/p>
&lt;p>Now you can test it; you will get an email from the mailer daemon of your provider from which you sent it.&lt;br>
Your custom notice will be in the diagnostic code:&lt;/p>
&lt;img src="rejection-notice.png" alt="Rejection Notice" style="width: 80%; height: auto;">
&lt;p>&lt;font size ="2">&lt;em>Photo by &lt;a class="link" href="https://unsplash.com/@synkevych" target="_blank" rel="noopener"
>Roman Synkevych&lt;/a> on &lt;a class="link" href="https://unsplash.com/photos/black-and-white-penguin-toy-wX2L8L-fGeA" target="_blank" rel="noopener"
>Unsplash&lt;/a>&lt;/em>&lt;/font>&lt;/p></description></item><item><title>Get Interface IP from OPNsense API</title><link>https://self-hosted.tools/p/opnsense-api-get-ip/</link><pubDate>Mon, 18 Mar 2024 00:00:00 +0000</pubDate><guid>https://self-hosted.tools/p/opnsense-api-get-ip/</guid><description>&lt;img src="https://self-hosted.tools/p/opnsense-api-get-ip/cover.jpg" alt="Featured image of post Get Interface IP from OPNsense API" />&lt;p>Hey everyone! I&amp;rsquo;m really excited about today&amp;rsquo;s topic: getting the IP address of an interface from the OPNsense API. To be honest, I&amp;rsquo;ve been on a bit of a hunt for reliable and current information on this, but it&amp;rsquo;s been a bit elusive.&lt;br />
So, I&amp;rsquo;ve decided to put together this guide in the hopes that it&amp;rsquo;ll be useful not just for me, but for others who might be in the same boat.&lt;/p>
&lt;h2 id="create-user-and-group">Create user and group
&lt;/h2>&lt;p>First, you should create a new user and group to grant it only the authorizations it needs.&lt;br />
Access your OPNsense firewall and navigate to System -&amp;gt; Access -&amp;gt; Groups.&lt;/p>
&lt;p>Add a new group and name it something like &amp;ldquo;ApiUsers&amp;rdquo;. Do not add a member yet, as we are going to create a new one. Save the group and click on &amp;ldquo;Edit&amp;rdquo;. Then, open &amp;ldquo;Assigned Privileges&amp;rdquo; and select the following:
&lt;figure>&lt;img src="https://self-hosted.tools/p/opnsense-api-get-ip/system-privileges.png"
alt="Assigned Privileges">&lt;figcaption>
&lt;p>Assigned Privileges&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;/p>
&lt;p>Create a new User, named &amp;ldquo;api_user&amp;rdquo;. Set a strong password or select &amp;ldquo;Generate a scrambled password to prevent local database logins for this user&amp;rdquo;. Set the Login shell to /usr/sbin/nologin. &lt;br />
Under Group Memberships, select your new ApiUsers Group and assign it to the user.
Save the user and click on &amp;ldquo;Edit&amp;rdquo;. Now you will see the option &amp;ldquo;API keys&amp;rdquo; for the user - generate a new one.&lt;br />
You will receive a txt file with the following content:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">key=xxx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">secret=xxx
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="call-the-api">Call the API
&lt;/h2>&lt;p>First, you need to find the interface you want the IP for, which is most likely your WAN interface.&lt;br />
Navigate to Interfaces -&amp;gt; Overview to see all your interfaces. Search for your desired interface and note the &amp;ldquo;Device&amp;rdquo;.&lt;br />
You will need that for the API call.&lt;/p>
&lt;p>Here&amp;rsquo;s a simple bash script to call the API and retrieve the IP of the interface.
It utilizes &lt;a class="link" href="https://github.com/jqlang/jq" target="_blank" rel="noopener"
>jq&lt;/a>, a lightweight and flexible command-line JSON processor.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Enter your Opnsense IP or URL&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">OPNSENSE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;http://192.168.1.1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Replace key and secret with the values from the downloaded API txt file&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">KEY&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;your-key-here&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">SECRET&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;your-secret-here&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Specify the value from &amp;#34;Device&amp;#34; for your Interface, see GUI or API output&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">DEVICE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;device-name-from-interface&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Make GET request with curl&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">interface_info&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>curl -k -u &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$KEY&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>:&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$SECRET&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="nv">$OPNSENSE&lt;/span>/api/interfaces/overview/getInterface/&lt;span class="nv">$DEVICE&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Extract Interface IP with jq&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">ip&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>&lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$interface_info&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="p">|&lt;/span> jq -r &lt;span class="s1">&amp;#39;.message.ipv4.value[0].ipaddr | split(&amp;#34;/&amp;#34;)[0]&amp;#39;&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$ip&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>You can save this as a file and make it executable with &lt;code>chmod +x filename.sh&lt;/code>.&lt;br />&lt;/p>
&lt;p>You could also use the script above as a one-liner:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">curl -k -u &amp;#34;&amp;lt;your-key&amp;gt;&amp;#34;:&amp;#34;&amp;lt;your-secret&amp;gt;&amp;#34; &amp;lt;OPNsense-url&amp;gt;/api/interfaces/overview/getInterface/&amp;lt;device&amp;gt; | jq -r &amp;#39;.message.ipv4.value[0].ipaddr | split(&amp;#34;/&amp;#34;)[0]&amp;#39;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>There are plenty of use cases. For example, you could avoid calling an external service to get your public IP and update a DNS record if it changed.&lt;br />
However, if you&amp;rsquo;ve ended up here, you likely already have a specific use case in mind 😉&lt;/p>
&lt;p>&lt;font size ="2">&lt;em>Photo by &lt;a class="link" href="https://unsplash.com/@6heinz3r" target="_blank" rel="noopener"
>Gabriel Heinzer&lt;/a> on &lt;a class="link" href="https://unsplash.com/" target="_blank" rel="noopener"
>Unsplash&lt;/a>&lt;/em>&lt;/font>&lt;/p></description></item><item><title>Why I Switched from Ghost to Hugo</title><link>https://self-hosted.tools/p/why-i-switched-to-hugo/</link><pubDate>Wed, 27 Sep 2023 00:00:00 +0000</pubDate><guid>https://self-hosted.tools/p/why-i-switched-to-hugo/</guid><description>&lt;img src="https://self-hosted.tools/p/why-i-switched-to-hugo/cover.png" alt="Featured image of post Why I Switched from Ghost to Hugo" />&lt;p>I recently made the switch from a Self-Hosted &lt;a class="link" href="https://ghost.org/" target="_blank" rel="noopener"
>Ghost&lt;/a> instance to &lt;a class="link" href="https://gohugo.io/" target="_blank" rel="noopener"
>Hugo&lt;/a>, an open-source static site generator.&lt;/br>In this article, I&amp;rsquo;ll explain my motivation for this change and the outcomes.&lt;/p>
&lt;p>I started this blog last year on Ghost. When I began blogging, my top contenders for the blogging platform were the usual suspects: &lt;a class="link" href="https://wordpress.com/" target="_blank" rel="noopener"
>WordPress&lt;/a>, &lt;a class="link" href="https://ghost.org/" target="_blank" rel="noopener"
>Ghost&lt;/a>, or &lt;a class="link" href="https://gohugo.io/" target="_blank" rel="noopener"
>Hugo&lt;/a>. &lt;/br>WordPress was quickly ruled out due to its high maintenance and frequent security vulnerabilities. I dismissed Hugo without a closer look because it appeared overly complicated and intimidating. Thus, the logical choice was to set up the blog with Ghost. It boasted excellent features such as Members, a user-friendly web editor, Newsletter integration, and many other appealing features and quirks.&lt;/p>
&lt;p>Initially, it worked like a charm. However, all those enticing features became its downfall for my specific needs. &lt;/br>Initially, I believed I required a members-only space and a newsletter, among other things. Over time, I realized that I didn&amp;rsquo;t need all these features. With these features came a database and a backend, but all I was doing was publishing content on my blog, which didn&amp;rsquo;t necessitate a database. &lt;/br>What I needed was a robust, reliable, low-maintenance site that was fast, secure, and cost-effective to operate. So, what fulfills these requirements?&lt;/p>
&lt;h2 id="static-sites">Static Sites
&lt;/h2>&lt;p>Pure HTML, CSS, and JS.&lt;/br>
No backend.&lt;/br>
No database.&lt;/br>
No server-side language.&lt;/br>&lt;/p>
&lt;p>Manually managing static pages is a significant pain. The solution lies in static site generators like Gatsby, Jekyll, Next.js, and Hugo.&lt;/p>
&lt;p>With these tools, you write your content primarily in Markdown, and they compile your code and content into a static site. There is no dynamic content. The static site is served by hosting solutions, often for free. &lt;/br>If you make changes, you push the updates, and the site is rebuilt from scratch in milliseconds.&lt;/p>
&lt;h2 id="hugo">Hugo
&lt;/h2>&lt;p>Hugo initially appeared intimidating, but after spending some time with it, I found it to be a joy to work with. &lt;/br>The local website reloads nearly instantly when you make changes, and I can write content in Markdown inside VS Code - that&amp;rsquo;s just plain fun. &lt;/br>After the initial setup, customization, and template creation, it requires very little maintenance and is easy to work with.&lt;/p>
&lt;p>The site is blazingly fast, and the scores on &lt;a class="link" href="https://pagespeed.web.dev/" target="_blank" rel="noopener"
>web.dev&lt;/a> are impressive, to say the least.&lt;/p>
&lt;p>See for yourself:&lt;/br>
&lt;img src="https://self-hosted.tools/p/why-i-switched-to-hugo/pagespeed.png"
width="935"
height="926"
loading="lazy"
alt="Pagespeed Screenshot"
class="gallery-image"
data-flex-grow="100"
data-flex-basis="242px"
>&lt;/p>
&lt;p>The page currently achieves a perfect score in every category (on mobile).&lt;/p>
&lt;p>I am still learning a lot about Hugo; there are many cool features, and the documentation is good. &lt;/br>You will definitely hear more about Hugo in the future on this blog.&lt;/p>
&lt;h2 id="conclusion">Conclusion
&lt;/h2>&lt;p>If you are thinking about starting a blog, please don&amp;rsquo;t dismiss Hugo as an option just because it may seem too complicated (like I did).&lt;/br>
It is a very powerful tool for bringing your blog into existence. Ghost and Hugo target different use cases; Ghost was overkill for me. &lt;/br>
Hugo fits my needs perfectly, at least for now.&lt;/p>
&lt;p>&lt;font size ="2">&lt;em>Hugo Logo from &lt;a class="link" href="https://gohugo.io/" target="_blank" rel="noopener"
> Steve Francia&lt;/a>&lt;/em>&lt;/font>&lt;/p></description></item><item><title>Privacy: A Luxurious Relic of the Past or a Vital Necessity Today?</title><link>https://self-hosted.tools/p/why-privacy-matters/</link><pubDate>Sun, 09 Jul 2023 00:00:00 +0000</pubDate><guid>https://self-hosted.tools/p/why-privacy-matters/</guid><description>&lt;img src="https://self-hosted.tools/p/why-privacy-matters/cover.jpg" alt="Featured image of post Privacy: A Luxurious Relic of the Past or a Vital Necessity Today?" />&lt;p>In a world that thrives on connectivity and digital advancements, the concept of privacy often finds itself in the crosshairs of a profound dilemma. Is privacy a vanishing relic of the past or an essential need in this day and age as our lives get more and more entwined with technology?&lt;/p>
&lt;p>Diving into the depths of this question, the article explores why privacy matters and the implications of overlooking its significance. From safeguarding our personal lives to preserving individual autonomy, the case for privacy in the modern era demands our attention.&lt;br>
So let&amp;rsquo;s explore the subject of privacy, its value in a world that is becoming more and more digital, the misunderstandings that surround it, and its importance in free societies.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Privacy is not merely about hiding; it&amp;rsquo;s about protecting.&lt;/strong>&lt;/p>
&lt;/blockquote>
&lt;p>Let&amp;rsquo;s kick things off by breaking down what privacy actually entails. It&amp;rsquo;s a deceptively simple concept, but upon closer examination, it reveals itself as a profound cornerstone of our lives.&lt;br>
It&amp;rsquo;s the right to keep aspects of our lives hidden from public view, and it directly correlates with our sense of freedom.&lt;/p>
&lt;p>Let&amp;rsquo;s briefly stray off course and define privacy as opposed to anonymity.&lt;br>
While privacy allows individuals to control the flow of personal information and thereby express themselves selectively, anonymity goes a step further by concealing identity altogether. Privacy is a broader concept that encompasses the selective sharing of information, while anonymity involves complete disassociation from personal identity.&lt;br>
While these concepts are closely related and often overlap, the main focus of this post will be on privacy.&lt;/p>
&lt;h2 id="privacy-a-cornerstone-of-free-societies">Privacy: A Cornerstone of Free Societies
&lt;/h2>&lt;p>Freedom and privacy are two concepts that go hand in hand. Just as freedom is the power to act, think, or speak without hindrance, privacy provides the space for such actions. It&amp;rsquo;s a symbiotic relationship where one fuels the other.&lt;/p>
&lt;p>People can make decisions freely and independently when they have control over their personal information and preferences. Giving individuals a secure space to freely express their ideas and opinions is strongly related to freedom of expression.&lt;/p>
&lt;p>Every person has a unique set of beliefs, preferences, and experiences that shape their identity. This individuality is nurtured in a private setting, away from public scrutiny or pressure to conform, which results in a variety of viewpoints.&lt;br>
Without the right to privacy, society would be at risk of homogeneity - everyone thinking the same way due to constant monitoring and fear of reprisal for &amp;ldquo;different&amp;rdquo; thoughts.&lt;br>
In essence, privacy is an enabler of personal growth.&lt;/p>
&lt;p>So yes, privacy is far from being an underestimated relic; it&amp;rsquo;s an essential pillar supporting free societies today - strengthening individual identities while fostering diversity in perspectives.&lt;br>
As we move on to debunking common misconceptions about privacy, let&amp;rsquo;s ensure these invaluable roles remain at the center of attention.&lt;/p>
&lt;h2 id="debunking-the-nothing-to-hide-argument">Debunking the &amp;lsquo;Nothing to Hide&amp;rsquo; Argument
&lt;/h2>&lt;blockquote>
&lt;p>&amp;ldquo;I&amp;rsquo;ve got nothing to hide, so why should I care about privacy?&amp;rdquo;&lt;/p>
&lt;/blockquote>
&lt;p>This is one of the most commonly heard arguments when discussing privacy rights. But it&amp;rsquo;s a flawed one and here&amp;rsquo;s why.&lt;/p>
&lt;h3 id="privacy-isnt-only-about-hiding">Privacy isn&amp;rsquo;t Only About Hiding
&lt;/h3>&lt;p>The idea that privacy becomes significant solely when you have something to hide is an overly simplistic and inadequate representation.&lt;br>
&lt;strong>It&amp;rsquo;s not about hiding; it&amp;rsquo;s about protecting.&lt;/strong>&lt;br>&lt;/p>
&lt;p>Privacy preserves your freedom to be yourself without judgment or interference. It gives you control over your personal information, preventing others from using it in ways you don’t consent to.&lt;/p>
&lt;blockquote>
&lt;p>&amp;ldquo;Arguing that you don&amp;rsquo;t care about the right to privacy because you have nothing to hide is no different than saying you don&amp;rsquo;t care about free speech because you have nothing to say.&amp;quot;&lt;br>
— &lt;cite>Edward Snowden&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>&lt;/cite>&lt;/p>
&lt;/blockquote>
&lt;p>In general, individuals hold sensitive aspects of their lives that they strive to keep confidential and shielded from public view.&lt;br>
A straightforward way to illustrate this, is to ask people if they would let all their emails, texts, online searches, financial details, health records, and so forth, be made public. The number of individuals who&amp;rsquo;d be comfortable with such a proposition is zero.&lt;/p>
&lt;h3 id="benefits-and-value-of-upholding-privacy">Benefits and Value of Upholding Privacy
&lt;/h3>&lt;p>Measuring the value of privacy can be a difficult task since it depends on personal perspectives and cultural contexts, making it a subjective matter. Nevertheless, privacy holds significant value for everyone. Some highlights include:&lt;/p>
&lt;ul>
&lt;li>Encouraging individualism and fostering variety in viewpoints&lt;/li>
&lt;li>Establishing trust in personal relationships and professional settings to ensure confidentiality of sensitive information&lt;/li>
&lt;li>Allowing free expression without fear of being monitored or judged&lt;/li>
&lt;li>Protecting citizens against potential abuses by those in power&lt;/li>
&lt;li>Maintaining social harmony by respecting boundaries and promoting healthy relationships built on trust, respect, and mutual understanding&lt;/li>
&lt;li>Enabling open and honest political discourse. It allows individuals to voice their opinions and engage in debates&lt;/li>
&lt;li>Encourages innovation and creativity by providing individuals with a safe space to explore new ideas&lt;/li>
&lt;li>Protects individuals from potential harm, including identity theft, fraud, stalking, harassment, and other malicious activities&lt;/li>
&lt;/ul>
&lt;h3 id="potential-harms-of-disregarding-privacy">Potential Harms of Disregarding Privacy
&lt;/h3>&lt;p>Disregarding privacy can lead to a multitude of potential harms, essentially negating all the positive aspects mentioned earlier.&lt;/p>
&lt;p>When private data falls into the wrong hands, it can result in identity theft, discrimination, or harassment. Even when held by seemingly benign entities (like corporations), the collected data can be used for manipulative advertising, creating echo chambers, and perpetuating biased algorithms.&lt;/p>
&lt;p>As we delve further into the digital age, where personal data can spread faster and wider than ever before, defending the basic right to privacy becomes increasingly crucial.&lt;/p>
&lt;p>While most law-abiding citizens might currently believe that they have &amp;ldquo;nothing to hide&amp;rdquo;, it is worth considering the drastic change in the situation if they were to live in a totalitarian state such as China or North Korea with widespread police surveillance. There exists a possibility that their residing state could move in a similar direction, making such a scenario not impossible. Widespread police surveillance is the very definition of a police state.&lt;br>
It is essential to recognize the potential consequences and safeguard privacy, even if it may not seem immediately relevant.&lt;/p>
&lt;p>&amp;ldquo;Privacy is only for those with something to hide&amp;rdquo; - this belief belongs in the trash bin of debunked misconceptions.&lt;/p>
&lt;h2 id="societal-implications-of-digitalization">Societal Implications of Digitalization
&lt;/h2>&lt;p>The digital advancements and software innovations in the 21st century, have had a profound impact on society.&lt;br>
Technological advancements have redefined the world in unimaginable ways. On one hand, they&amp;rsquo;ve made life more convenient - consider how easy it is to book a hotel or order something with just a few taps by a consumer! But on the flip side, they&amp;rsquo;re shaping new societal norms that are going to be harmful if left unchecked.&lt;/p>
&lt;p>With this increasing digitization, the private lives are becoming public like never before. Personal data is the currency that fuels most digital platforms. Every click, swipe or like can be tracked, stored, and used in ways the user may not even realize. This constant surveillance culture raises serious privacy concerns.&lt;/p>
&lt;p>Amidst this digital revolution, privacy holds an even greater value. It&amp;rsquo;s not just about hiding personal information anymore - it&amp;rsquo;s about preserving human dignity and individuality. It&amp;rsquo;s about maintaining control over personal data in a world where data is power.&lt;/p>
&lt;p>As our society continues to embrace the digital revolution, understanding the societal implication becomes crucial. There’s no denying their benefits but simultaneously it must be ensured that the right to privacy isn&amp;rsquo;t compromised.&lt;/p>
&lt;h2 id="why-you-should-protect-your-privacy-today">Why you should protect your privacy today
&lt;/h2>&lt;p>Today privacy protection is not just a concern, it&amp;rsquo;s a necessity. With every click, like, and share, there are left behind digital footprints that can be easily taken advantage of. Here&amp;rsquo;s why safeguarding privacy rights in the digital age is crucial:&lt;/p>
&lt;p>&lt;strong>Risk of Identity Theft&lt;/strong>&lt;br>
The more information available about you online, the higher the risk of identity theft. Cybercriminals can use this information to impersonate you, apply for credit in your name, or commit other online crimes.&lt;/p>
&lt;p>&lt;strong>Manipulation Through Ads&lt;/strong>&lt;br>
Ever noticed how ads seem to know exactly what you&amp;rsquo;ve been thinking about buying? That’s not coincidence but a result of advertisers using your personal data to tailor ads specifically to your preferences.&lt;/p>
&lt;p>&lt;strong>Mental Health&lt;/strong>&lt;br>
A constant invasion to privacy has a grave impact on your mental health.&lt;/p>
&lt;blockquote>
&lt;p>“This leads to hypervigilance, doubts, constant fears, and paranoia in some cases.”&lt;br>
— &lt;cite>Namrata Khetan&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>&lt;/cite>&lt;/p>
&lt;/blockquote>
&lt;p>&lt;strong>Harassment and Personal Safety&lt;/strong>&lt;br>
Unwanted attention and access to your personal information can lead to harassment and pose serious threats to your personal safety. From cyberstalking to identity theft, such invasions can cause psychological distress and even lead to physical harm.&lt;/p>
&lt;p>&lt;strong>Data Aggregation and Filter Bubbles&lt;/strong>&lt;br>
When personal data is collected en mass, it becomes a powerful tool for manipulating perception. Information about our likes, dislikes, habits, and opinions are aggregated to create comprehensive profiles. These profiles are then used to tailor our digital experiences, pushing us towards specific products, ideas, or viewpoints - a phenomenon known as filter bubbles.&lt;/p>
&lt;p>Let&amp;rsquo;s remember that privacy isn&amp;rsquo;t merely a matter of personal preference - it&amp;rsquo;s a fundamental right that safeguards our freedom.&lt;/p>
&lt;h2 id="gdpr-eu">GDPR (EU)
&lt;/h2>&lt;p>By living in the EU, every citizen is protected by the General Data Protection Regulation (GDPR). It aims to safeguard individuals&amp;rsquo; privacy and give them control over their personal data. The following rights are fundamental principles established by GDPR:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Right to Information:&lt;/strong> You have the right to know what data is being collected about you.&lt;/li>
&lt;li>&lt;strong>Right to Access:&lt;/strong> You can request a copy of your personal data.&lt;/li>
&lt;li>&lt;strong>Right to Rectification:&lt;/strong> If your data is inaccurate or incomplete, you can have it corrected.&lt;/li>
&lt;li>&lt;strong>Right to Erasure:&lt;/strong> Also known as &amp;rsquo;the right to be forgotten&amp;rsquo;, you can request for your data to be deleted.&lt;/li>
&lt;li>&lt;strong>Right to Restrict Processing:&lt;/strong> You can request that your data is not used for processing.&lt;/li>
&lt;/ul>
&lt;p>While GDPR has brought significant improvements in data protection and privacy, it is essential not to be careless and assume that every company strictly adheres to the law.&lt;br>
In order to protect basic privacy rights, it&amp;rsquo;s a must to be constantly on guard and take proactive measures. Attention should be paid, if organizations ask for excessive or pointless amounts of personal information, don&amp;rsquo;t give clear privacy policies, or share sensitive information without the owners permission.&lt;/p>
&lt;p>Keep in mind that the effectiveness of the GDPR depends, on both corporate compliance and public awareness. Society has to work together to improve privacy protection and guarantee that the goals of the GDPR are upheld by remaining informed, challenge data practices, and hold companies responsible.&lt;/p>
&lt;h2 id="what-can-be-done">What can be done?
&lt;/h2>&lt;p>The ideal approach for protecting data is to refrain from sharing it with third parties. But given the desire to engage in social media and other platforms, the following basic actions can help to strike a balance between privacy and convenience.&lt;/p>
&lt;p>While these methods may not provide protection against nation-state surveillance or motivated threat actors, they serve as a good starting point to improve your general digital privacy.&lt;/p>
&lt;h3 id="threat-modeling">Threat Modeling
&lt;/h3>&lt;p>A threat model is a compilation of the most likely risks to the personal privacy and security. Since it is impossible to protect against every potential attacker, it is important to focus on the most probable threats. By concentrating on the threats that are most relevant, attack surface can be narrowed down and the appropriate tools for the task at hand can be selected.&lt;br>
More can be found under the following link:&lt;br>
&lt;a class="link" href="https://www.privacyguides.org/en/basics/threat-modeling/" target="_blank" rel="noopener"
>Threat Modeling: The First Step on Your Privacy Journey - Privacy Guides&lt;/a>&lt;/p>
&lt;h3 id="be-wary-of-social-media">Be Wary of Social Media
&lt;/h3>&lt;p>Social media is a double-edged sword. It connects individuals within the digitalized world, but also exposes their lives to it. The following guidelines must always be sticked to:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Sharing of personal data should be limited:&lt;/strong> Don&amp;rsquo;t put out all your personal information and photos for everyone to see.&lt;/li>
&lt;li>&lt;strong>Privacy settings:&lt;/strong> Adjust privacy settings to be more restrictive.&lt;/li>
&lt;li>&lt;strong>Random username:&lt;/strong> Use a different random username for every platform.&lt;/li>
&lt;li>&lt;strong>Turn of location:&lt;/strong> Never forget to turn off location sharing.&lt;/li>
&lt;li>&lt;strong>Avoid third-party links:&lt;/strong> Do not click on links shared on social media. They will track you in most cases.&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>⚠️ &lt;strong>Remember:&lt;/strong> What goes on the internet, stays on the internet.&lt;/p>
&lt;/blockquote>
&lt;h3 id="use-privacy-friendly-alternatives">Use privacy friendly alternatives
&lt;/h3>&lt;p>Privacy Guides has compiled an extensive and great list of recommendations for privacy friendly alternatives:&lt;br>
&lt;a class="link" href="https://www.privacyguides.org/en/tools/" target="_blank" rel="noopener"
>Best privacy alternatives by Privacy Guides&lt;/a>&lt;/p>
&lt;p>PrivacyTests has the most complete browser privacy checklist:&lt;br>
&lt;a class="link" href="https://privacytests.org/" target="_blank" rel="noopener"
>Which browsers are best for privacy?&lt;/a>&lt;/p>
&lt;h3 id="passwords-and-two-factor-authentication">Passwords and Two-Factor Authentication
&lt;/h3>&lt;p>Strong, unique passwords and 2FA protect accounts from unauthorized access.&lt;br>
The most recommended password manager by the privacy and security community is &lt;a class="link" href="https://bitwarden.com/" target="_blank" rel="noopener"
>Bitwarden&lt;/a>, for 2FA on Android it is &lt;a class="link" href="https://getaegis.app/" target="_blank" rel="noopener"
>Aegis&lt;/a> and for iOS &lt;a class="link" href="https://raivo-otp.com/" target="_blank" rel="noopener"
>Raivo&lt;/a> OTP is recommended.&lt;/p>
&lt;h3 id="keep-your-devices-updated">Keep Your Devices Updated
&lt;/h3>&lt;p>Devices should be always running the latest software releases. &lt;br>
They often contain security enhancements that protect against new threats.&lt;/p>
&lt;h3 id="cloud-storage">Cloud Storage
&lt;/h3>&lt;p>Cloud Storage options such as &lt;a class="link" href="https://proton.me/drive/" target="_blank" rel="noopener"
>Proton Drive&lt;/a> or self-hosted &lt;a class="link" href="https://nextcloud.com/" target="_blank" rel="noopener"
>Nextcloud&lt;/a> should be utilized.&lt;br>
Alternatively, all uploaded files should be encrypted to ensure that the data can&amp;rsquo;t be processed. &lt;a class="link" href="https://cryptomator.org/" target="_blank" rel="noopener"
>Cryptomator&lt;/a> is the best tool for this specific use-case.&lt;/p>
&lt;h3 id="full-disk-encryption">Full-Disk Encryption
&lt;/h3>&lt;p>All disks in devices that hold personal data should be encrypted.&lt;br>
Activate Bitlocker in Windows, FileVault on macOS, LUKS on Linux.&lt;br>
Modern iOS and Android devices have full-disk encryption enabled by default.&lt;/p>
&lt;h3 id="account-creation-and-deletion">Account Creation and Deletion
&lt;/h3>&lt;p>Before signing up and giving out sensitive data, the risks and benefits should be evaluated. If a user decides to sign up, email aliases can be used (&lt;a class="link" href="https://simplelogin.io/" target="_blank" rel="noopener"
>Simplelogin&lt;/a> is a well-suited tool for this task), temporary phone numbers (&lt;a class="link" href="https://www.textverified.com/" target="_blank" rel="noopener"
>Textverified&lt;/a> or other tools can be helpful), and a random username should be used to protect personal privacy.&lt;/p>
&lt;p>Accounts that are not used anymore should be deleted.&lt;br>
&lt;a class="link" href="https://justdeleteme.xyz/" target="_blank" rel="noopener"
>JustDeleteMe&lt;/a> is a great page to guide on how to delete unused accounts.&lt;/p>
&lt;h3 id="other-methods">Other Methods
&lt;/h3>&lt;p>There are numerous other methods to enhance privacy, albeit requiring more effort. These include switching to a different operating system, utilizing Tor for anonymous browsing, self-hosting services, using Bitcoin with mixers for financial privacy, exploring alternative chat apps, and more. While these options may reduce convenience, if they fit the threat model they are worth it.&lt;/p>
&lt;h2 id="conclusion-the-value-of-privacy-today">Conclusion: The Value of Privacy Today
&lt;/h2>&lt;p>In a world brimming with technological advancements, privacy stands as an irreplaceable cornerstone of modern society. Concealing information isn&amp;rsquo;t the only thing to consider.&lt;/p>
&lt;blockquote>
&lt;p>&lt;em>Privacy is about guarding what means most to us.&lt;/em>&lt;/p>
&lt;/blockquote>
&lt;p>The boundary of privacy gives individuals the freedom to be themselves, without the fear of judgment or manipulation.&lt;/p>
&lt;p>It is crucial to reiterate the undeniable correlation between privacy and freedom. Freedom finds true expression when privacy is revered, enabling individuals to develop distinct identities and fostering diverse viewpoints in society.&lt;/p>
&lt;p>In the current digital era, where personal data can easily become public, it can&amp;rsquo;t be stressed enough to remain vigilant and prioritize the preservation of privacy.&lt;/p>
&lt;blockquote>
&lt;p>⚠️ &lt;strong>Your information is your asset, don’t let it fall into the wrong hands.&lt;/strong>&lt;/p>
&lt;/blockquote>
&lt;p>Mindful steps must be taken towards protecting sensible personal information online. Strong, unique passwords are a must and individuals have to be cautious while sharing personal information on social media platforms or any online platform for that matter.&lt;/p>
&lt;p>Privacy can not be dismissed as a relic of the past but it must be uphold as a vital necessity today.&lt;/p>
&lt;hr>
&lt;h2 id="additional-reads-and-resources">Additional reads and resources
&lt;/h2>&lt;p>Here are some great additional reads and resources:&lt;/p>
&lt;ul>
&lt;li>&lt;a class="link" href="https://ssd.eff.org/" target="_blank" rel="noopener"
>Surveillance Self-Defense&lt;/a> by the &lt;a class="link" href="https://www.eff.org/" target="_blank" rel="noopener"
>Electronic Frontier Foundation&lt;/a>&lt;br>&lt;/li>
&lt;li>&lt;a class="link" href="https://www.schneier.com/blog/archives/2006/05/the_value_of_pr.html" target="_blank" rel="noopener"
>The Value of Privacy - Schneier on Security&lt;/a>&lt;br>&lt;/li>
&lt;li>&lt;a class="link" href="https://www.reddit.com/r/privacy/" target="_blank" rel="noopener"
>Privacy Subreddit&lt;/a>&lt;br>&lt;/li>
&lt;li>&lt;a class="link" href="https://www.privacyguides.org/en/" target="_blank" rel="noopener"
>Privacy Guides: Your Independent Privacy and Security Resource&lt;/a>&lt;br>&lt;/li>
&lt;li>&lt;a class="link" href="https://sethforprivacy.com" target="_blank" rel="noopener"
>sethforprivacy.com Blog&lt;/a>&lt;br>&lt;/li>
&lt;li>&lt;a class="link" href="https://dergigi.com/2022/06/19/freedom-and-privacy/?ref=self-hosted.tools" target="_blank" rel="noopener"
>Freedom and Privacy - Two Sides of the Same Coin&lt;/a> by &lt;a class="link" href="https://dergigi.com" target="_blank" rel="noopener"
>Gigi&lt;/a>&lt;br>&lt;/li>
&lt;/ul>
&lt;p>&lt;font size ="2">&lt;em>Photo by &lt;a class="link" href="https://unsplash.com/@dizzydizz" target="_blank" rel="noopener"
>Muhammad Zaqy Al Fattah&lt;/a> on &lt;a class="link" href="https://unsplash.com/" target="_blank" rel="noopener"
>Unsplash&lt;/a>&lt;/em>&lt;/font>&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>&lt;a class="link" href="https://www.reddit.com/r/IAmA/comments/36ru89/comment/crglgh2/" target="_blank" rel="noopener"
>&amp;ldquo;Just days left to kill mass surveillance under Section 215 of the Patriot Act. We are Edward Snowden and the ACLU&amp;rsquo;s Jameel Jaffer. AUA.&amp;rdquo;&lt;/a> - reddit. Retrieved 2023-07-09.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>&lt;a class="link" href="https://theswaddle.com/what-is-a-constant-lack-of-digital-privacy-doing-to-our-mental-health/" target="_blank" rel="noopener"
>&amp;ldquo;What Is a Constant Lack of Digital Privacy Doing to Our Mental Health?&amp;rdquo;&lt;/a> - The Swaddle. Retrieved 2023-07-09.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Your Microsoft 365 Calendar on Your Phone - A Privacy-Friendly Approach</title><link>https://self-hosted.tools/p/ms365-privacy-calendar/</link><pubDate>Thu, 15 Jun 2023 00:00:00 +0000</pubDate><guid>https://self-hosted.tools/p/ms365-privacy-calendar/</guid><description>&lt;img src="https://self-hosted.tools/p/ms365-privacy-calendar/cover.jpg" alt="Featured image of post Your Microsoft 365 Calendar on Your Phone - A Privacy-Friendly Approach" />&lt;p>In today&amp;rsquo;s fast-paced world, it&amp;rsquo;s not uncommon for employers or schools to use the Microsoft 365 calendar solution as the primary method for scheduling appointments and managing events. Consequently, many individuals find themselves in a situation where having their schedule readily available on their phone becomes a necessity. However, the concern of installing apps like Outlook or other mail clients, which often request extensive permissions, raises valid privacy considerations.&lt;/p>
&lt;h2 id="how-to-sync-the-calendar-privately">How to sync the calendar privately
&lt;/h2>&lt;p>So, let&amp;rsquo;s dive into the easiest way to sync your Microsoft 365 calendar onto your phone while maintaining control over your privacy.&lt;/p>
&lt;p>By following the steps outlined below, you can conveniently have your schedule at your fingertips, with the only trade-off being that the calendar will be read-only. For users like myself, who primarily use it to prevent scheduling conflicts between private and work appointments, this limitation is not an issue.&lt;/p>
&lt;p>We will publish the calendar and sync it periodically with the open source app ICSx⁵ (&lt;a class="link" href="https://icsx5.bitfire.at/" target="_blank" rel="noopener"
>website&lt;/a>, &lt;a class="link" href="https://github.com/bitfireAT/icsx5" target="_blank" rel="noopener"
>source code&lt;/a>). The published ICS link will have a unique hash, ensuring that only individuals to whom you have shared the link will have access (in most cases only you).&lt;/p>
&lt;h2 id="steps-in-microsoft-365">Steps in Microsoft 365
&lt;/h2>&lt;ol>
&lt;li>Access &lt;a class="link" href="https://outlook.office.com/" target="_blank" rel="noopener"
>outlook.office.com&lt;/a> with the Account, from which you want to import the calendar.&lt;/li>
&lt;li>Click on the gear Icon in the upper right corner and select &amp;ldquo;Show all Outlook settings&amp;rdquo;&lt;/li>
&lt;li>Switch to Calendar on the left and select Shared calendars.&lt;/li>
&lt;li>Within the &amp;ldquo;Publish a calendar&amp;rdquo; section, select the specific calendar you wish to publish and determine the level of detail visible to others (in this case your phone) and publish it.&lt;/li>
&lt;li>Copy the ICS link.&lt;/li>
&lt;/ol>
&lt;h2 id="steps-on-your-phone">Steps on your Phone
&lt;/h2>&lt;ol>
&lt;li>Download ICSx⁵ from &lt;a class="link" href="https://f-droid.org/packages/at.bitfire.icsdroid/" target="_blank" rel="noopener"
>F-Droid&lt;/a>, &lt;a class="link" href="https://play.google.com/store/apps/details?id=at.bitfire.icsdroid" target="_blank" rel="noopener"
>Google Play&lt;/a>, or directly from &lt;a class="link" href="https://github.com/bitfireAT/icsx5/releases" target="_blank" rel="noopener"
>Github&lt;/a> and install it.&lt;/li>
&lt;li>Click on the + to add a calendar and insert the link you copied before, give your calendar a name.&lt;/li>
&lt;li>To set your preferred sync interval, simply click on the three dots located in the upper right corner.&lt;/li>
&lt;/ol>
&lt;p>Your work or school calendar is now visible within your calendar app on your phone and will be updated according to your set sync interval. Only changed resources will be updated in the calendar.&lt;/p>
&lt;p>ICSx⁵ doesn’t collect, process or transmit any statistical data like app or server usage. There are no ads and there’s no tracking, you can have a look at their &lt;a class="link" href="https://icsx5.bitfire.at/privacy/" target="_blank" rel="noopener"
>privacy policy here&lt;/a>.&lt;/p>
&lt;p>If you find ICSx⁵ useful, please consider supporting it by purchasing it from the Play Store or making a &lt;a class="link" href="https://icsx5.bitfire.at/donate/" target="_blank" rel="noopener"
>donation to the developer&lt;/a>.&lt;/p>
&lt;p>&lt;font size ="2">&lt;em>Photo by &lt;a class="link" href="https://unsplash.com/@sci_fi_superfly" target="_blank" rel="noopener"
>Omar Al-Ghosson&lt;/a> on &lt;a class="link" href="https://unsplash.com/" target="_blank" rel="noopener"
>Unsplash&lt;/a>&lt;/em>&lt;/font>&lt;/p></description></item><item><title>Embed Markdown From Github in Ghost</title><link>https://self-hosted.tools/p/embed-markdown-from-github-in-ghost/</link><pubDate>Tue, 13 Jun 2023 00:00:00 +0000</pubDate><guid>https://self-hosted.tools/p/embed-markdown-from-github-in-ghost/</guid><description>&lt;img src="https://self-hosted.tools/p/embed-markdown-from-github-in-ghost/cover.jpg" alt="Featured image of post Embed Markdown From Github in Ghost" />&lt;p>If you&amp;rsquo;ve ever wondered how to embed a Markdown file hosted on GitHub directly into a Ghost article, you might have encountered a lack of readily available resources online. While preparing for an upcoming article, I encountered this challenge of integrating Github Markdown files smoothly. I was searching for a solution that would avoid manual copy-pasting or complicated workarounds.&lt;/p>
&lt;p>So, let&amp;rsquo;s dive in and explore this method that will incorporate GitHub-hosted Markdown files into your Ghost articles.&lt;/p>
&lt;p>Create a raw HTML card and insert the following code:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;!DOCTYPE html&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">html&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">head&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">title&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>Embedded Markdown from GitHub&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">title&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;https://cdn.jsdelivr.net/npm/marked/marked.min.js&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">head&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">body&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;markdown-content&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">markdownContent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">document&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getElementById&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;markdown-content&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">repoUrl&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;https://api.github.com/repos/{owner}/{repo}/contents/{path}&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">owner&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;your-github-username&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">repo&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;your-repo-name&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;path-to-your-markdown-file.md&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">apiUrl&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">repoUrl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;{owner}&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">owner&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;{repo}&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">repo&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;{path}&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">path&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">apiUrl&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">then&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">function&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">response&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">json&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">then&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">function&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">markdown&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">window&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">atob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">content&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">html&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">marked&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">markdown&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">markdownContent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">innerHTML&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">html&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">body&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">html&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>You need to customize the variables &amp;ldquo;owner,&amp;rdquo; &amp;ldquo;repo,&amp;rdquo; and &amp;ldquo;path&amp;rdquo; according to your specific requirements.&lt;/p>
&lt;h2 id="how-it-works">How it works
&lt;/h2>&lt;p>The code provided will query the GitHub API on the client-side. It runs in the browser and uses JavaScript&amp;rsquo;s &lt;code>fetch&lt;/code> function to make an HTTP request to the GitHub API endpoint.&lt;/p>
&lt;p>When the code is executed in a web browser, the client (user&amp;rsquo;s device) sends the request directly to the GitHub API. The API responds with the file content, which is then processed and rendered in the browser.&lt;/p>
&lt;p>It uses &lt;a class="link" href="https://github.com/markedjs/marked" target="_blank" rel="noopener"
>marked&lt;/a>, a markdown parser and compiler, served over &lt;a class="link" href="https://cdn.jsdelivr.net/npm/marked/marked.min.js" target="_blank" rel="noopener"
>jsdeliver.net&lt;/a>.&lt;/p>
&lt;h2 id="limitations">Limitations
&lt;/h2>&lt;p>It&amp;rsquo;s important to note that making API requests from the client-side has some limitations. The GitHub API has rate limits for unauthenticated requests, and if you&amp;rsquo;re making a large number of requests or fetching a lot of data, you may hit these limits. If you only retrieve one markdown file from the GitHub API, there won&amp;rsquo;t be any issues. However, if you encounter rate limits, you might need to switch to a server-side method for fetching the content instead of relying on the client-side approach.&lt;/p>
&lt;p>&lt;font size ="2">&lt;em>Photo by &lt;a class="link" href="https://unsplash.com/@richygreat" target="_blank" rel="noopener"
>Richy Great&lt;/a> on &lt;a class="link" href="https://unsplash.com/" target="_blank" rel="noopener"
>Unsplash&lt;/a>&lt;/em>&lt;/font>&lt;/p></description></item><item><title>Encrypted Proxmox Server on Hetzner</title><link>https://self-hosted.tools/p/encrypted-proxmox-hetzner/</link><pubDate>Mon, 12 Sep 2022 00:00:00 +0000</pubDate><guid>https://self-hosted.tools/p/encrypted-proxmox-hetzner/</guid><description>&lt;img src="https://self-hosted.tools/p/encrypted-proxmox-hetzner/cover.jpg" alt="Featured image of post Encrypted Proxmox Server on Hetzner" />&lt;h2 id="goal">Goal:
&lt;/h2>&lt;p>The goal is to setup a encrypted hypervisor on a dedicated Hetzner Server without the need of a &lt;a class="link" href="https://docs.hetzner.com/robot/dedicated-server/maintainance/kvm-console/" target="_blank" rel="noopener"
>KVM&lt;/a>.&lt;/br>
It needs to be remotely unlockable, but safe and convenient.&lt;/br>
You will have one IP for the proxmox host, and one IP for Opnsense/Pfsense and behind that your VMs.&lt;br />&lt;/p>
&lt;h3 id="why-encrypt-it">Why encrypt it?
&lt;/h3>&lt;p>For my use case its not a must to have it encrypted, because everything that is sensitive I host at home and only external exposed stuff is hosted on this server (for now).&lt;/br>
But it is always nice if the data is encrypted (at rest), because what if a hard drive fails? Hetzner will replace it and I have no control about what happens with the old drive.&lt;/br>
I think Hetzner is responsible enough to destroy the disks, but you can never be sure.&lt;/p>
&lt;h3 id="why-at-hetzner">Why at Hetzner?
&lt;/h3>&lt;p>I decided to get a Server from Hetzner and not host it at home, because the Hardware would cost me (used) ~800€ and the electricity to run this thing arround 35€ per month.&lt;/br>
At Hetzner I pay arround 47€ per month, but that includes the server, power, a second static IP and defective Hardware is changed for free.&lt;/br>
For that price I got a server with 2x 2 TB Enterprise HDD, 2x 480 GB Datacenter SSD, 64 GB ECC Ram, a Xeon E3-1275v5 and two IPs (one bought separately).&lt;/br>
I used them before for a storage box to save my NAS Backups that I make with &lt;a class="link" href="https://github.com/restic/restic" target="_blank" rel="noopener"
>restic&lt;/a>, never had any problems.&lt;/br>
I think I will set it up at home, when the electricity is on a sane level again. At the moment I pay around 0,49€ per kw/h.&lt;/p>
&lt;h2 id="installation-of-debian">Installation of Debian
&lt;/h2>&lt;p>I will install Debian first for the mdadm setup and then proxmox on top of if.&lt;/br>
After you buy your Server, you will get an email with instructions on how to connect to your server.&lt;br /> I would advise you to generate a key for ssh, rather than use a password.&lt;br />You can find a good tutorial &lt;a class="link" href="https://www.linode.com/docs/guides/use-public-key-authentication-with-ssh/" target="_blank" rel="noopener"
>here&lt;/a>.&lt;br />&lt;/p>
&lt;p>Fist connect to your server (in rescue system):&lt;/br>
&lt;code>ssh root@[your-IP]&lt;/code>&lt;/br>
Check if the host key is the same, as you got in your email.&lt;br />&lt;/p>
&lt;p>Upon connection you will see some infos to your server:Here is the basic Info on the disks of mine:&lt;br />Disk /dev/sda: 480 GB&lt;br />Disk /dev/sdc: 480 GB&lt;br />Disk /dev/sdb: 2000 GB&lt;br />Disk /dev/sdd: 2000 GB&lt;/p>
&lt;p>I will work with four disks, if you only have two, just ignore the hdds.&lt;br />The 480 GB ones are SSDs and the 2 TB ones are HDDs. The SSDs will be used for proxmox and the VMs, the HDDs for storage.&lt;br />For example the &lt;a class="link" href="https://www.proxmox.com/en/proxmox-backup-server" target="_blank" rel="noopener"
>Proxmox Backup Server&lt;/a> will store there my Homelab Backups and the Backups from the VMs on this server, a &lt;a class="link" href="https://github.com/restic/rest-server" target="_blank" rel="noopener"
>rest server&lt;/a> will store my &lt;a class="link" href="https://github.com/restic/restic" target="_blank" rel="noopener"
>restic&lt;/a> backups and there will be some music for streaming.&lt;/p>
&lt;p>We will use a script to setup dropbear and busybox for remote unlock, so we have to create some files with inputs for the script:&lt;br />&lt;code>touch /tmp/authorized_keys&lt;/code>&lt;br />Copy your SSH key to this created file. It will be used for the remote unlock and for accessing your server. You can later change that. Use the key that you want for remote unlock and not for access (if you want to use two keys). More on that later.&lt;/p>
&lt;p>You can use one of those commands (on your PC) to copy your key to the file:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="n">gpg&lt;/span> &lt;span class="o">--&lt;/span>&lt;span class="k">export&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">ssh&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">key&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">fingerprint&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">ssh&lt;/span> &lt;span class="n">root&lt;/span>&lt;span class="err">@&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">your&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="ne">IP&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">adress&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="s2">&amp;#34;cat &amp;gt;&amp;gt; /tmp/authorized_keys&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">ssh-copy-id -i ~/.ssh/mykey root@[your-IP]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Lets create another file on the server:&lt;br />&lt;code>nano /tmp/setup.conf&lt;/code>&lt;br />Insert this in you setup.conf file (change CRYPTPASSWORD, Hostname, drive names and if you want the size of the partitions). I have four disks but used only two for the setup with the rescue system, because the other two will use zfs and that must be configured later.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">CRYPTPASSWORD [your-encryption-password]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DRIVE1 /dev/sda
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DRIVE2 /dev/sdc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SWRAID 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SWRAIDLEVEL 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BOOTLOADER grub
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">HOSTNAME [your-hostname]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PART /boot ext4 1G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PART lvm pve all crypt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LV pve root / ext4 100G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LV pve swap swap swap 10G
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">IMAGE /root/images/Debian-1101-bullseye-amd64-base.tar.gz
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SSHKEYS_URL /tmp/authorized_keys
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Now lets add the post-install script:&lt;br />&lt;code>nano /tmp/post-install.sh&lt;/code>&lt;br /> and insert:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">add_rfc3442_hook&lt;span class="o">()&lt;/span> &lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> cat &lt;span class="s">&amp;lt;&amp;lt; EOF &amp;gt; /etc/initramfs-tools/hooks/add-rfc3442-dhclient-hook
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">#!/bin/sh
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">PREREQ=&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">prereqs()
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">{
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> echo &amp;#34;\$PREREQ&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">}
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">case \$1 in
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">prereqs)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> prereqs
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> exit 0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> ;;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">esac
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">if [ ! -x /sbin/dhclient ]; then
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> exit 0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">fi
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">. /usr/share/initramfs-tools/scripts/functions
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">. /usr/share/initramfs-tools/hook-functions
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">mkdir -p \$DESTDIR/etc/dhcp/dhclient-exit-hooks.d/
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">cp -a /etc/dhcp/dhclient-exit-hooks.d/rfc3442-classless-routes \$DESTDIR/etc/dhcp/dhclient-exit-hooks.d/
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">EOF&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> chmod +x /etc/initramfs-tools/hooks/add-rfc3442-dhclient-hook
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Install hook&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">add_rfc3442_hook
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Copy SSH keys for dropbear&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mkdir -p /etc/dropbear-initramfs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cp -a /root/.ssh/authorized_keys /etc/dropbear-initramfs/authorized_keys
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Update system&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">apt-get update &amp;gt;/dev/null
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">apt-get -y install cryptsetup-initramfs dropbear-initramfs
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>make it executable:&lt;br />&lt;code>chmod +x /tmp/post-install.sh&lt;/code>&lt;/p>
&lt;p>Finally start the Debian installation with those parameters:&lt;br />&lt;code>installimage -a -c /tmp/setup.conf -x /tmp/post-install.sh&lt;/code>&lt;/p>
&lt;p>Should look like this:&lt;/p>
&lt;figure>&lt;img src="https://self-hosted.tools/p/encrypted-proxmox-hetzner/image-1.png"
alt="install screen">&lt;figcaption>
&lt;p>install screen&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;p>After some minutes the post-install scrip has finished and you can see more logs with&lt;br />&lt;code>cat debug.txt&lt;/code>.&lt;br /> Some Errors are normal.&lt;br />You can copy the new host keys to a text file for the verification of the ssh connection later.&lt;/p>
&lt;p>Make a reboot to leave the rescue system (&lt;code>reboot&lt;/code>)&lt;/p>
&lt;p>Ping your server to see when its online again, this will take a couple of minutes:&lt;br />&lt;code>ping [your-IP]&lt;/code>&lt;br />When you get a response, you can ssh into it.&lt;br />&lt;code>ssh root@[your-IP]&lt;/code>&lt;br />You will get the following error because the installation changed your hostkey:&lt;/p>
&lt;figure>&lt;img src="https://self-hosted.tools/p/encrypted-proxmox-hetzner/image-3.png">
&lt;/figure>
&lt;p>On your Linux PC (change the path to your username, if you use windows or mac, it should also tell you the path in the error):&lt;br />&lt;code>nano /home/user/.ssh/known_hosts&lt;/code>&lt;br />Comment the old entry from your server (add a # before the entry)Now ssh into your server again&lt;br />&lt;code>ssh root@[your-ip]&lt;/code>&lt;br />Accept the new key, you should have seen the new keys before when the post-install script did its thing.&lt;/p>
&lt;p>You are now in the BusyBox for remote unlocking, enter&lt;br />&lt;code>cryptroot-unlock&lt;/code>&lt;br /> and insert your password (that you choose in the setup.conf file).&lt;br />If the password is correct the boot will continue and you will automatically be disconnected from the temporary SSH session.&lt;/p>
&lt;figure>&lt;img src="https://self-hosted.tools/p/encrypted-proxmox-hetzner/image-7.png"
alt="unlock and disconnection from BusyBox">&lt;figcaption>
&lt;p>unlock and disconnection from BusyBox&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;p>Wait 30 sec and use ssh again to access your server.&lt;br />You will get the same error as before, because you connect now to the debian install and not to the BusyBox. Edit the known_hosts file again.&lt;br />Now comment the key from BusyBox, save it, connect again and accept the new key.&lt;br />Now edit the file again and uncomment (remove the #) from the BusyBox key so that both keys are active. You wont get the error on remote unlock or on a normal ssh connection again.&lt;/p>
&lt;p>When you run &lt;code>lsblk&lt;/code> it should look something like this:&lt;/p>
&lt;figure>&lt;img src="https://self-hosted.tools/p/encrypted-proxmox-hetzner/image-9.png"
alt="lsblk output">&lt;figcaption>
&lt;p>lsblk output&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;h2 id="install-proxmox-on-top-of-debian">Install Proxmox on top of Debian
&lt;/h2>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="n">echo&lt;/span> &lt;span class="s2">&amp;#34;deb [arch=amd64] http://download.proxmox.com/debian/pve bullseye pve-no-subscription&amp;#34;&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="o">/&lt;/span>&lt;span class="n">etc&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">apt&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">sources&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">list&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">d&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">pve&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">install&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">repo&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">list&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">wget https://enterprise.proxmox.com/debian/proxmox-release-bullseye.gpg -O /etc/apt/trusted.gpg.d/proxmox-release-bullseye.gpg
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Now we have to check if the key was not tampered with, run:&lt;br />&lt;code>sha512sum /etc/apt/trusted.gpg.d/proxmox-release-bullseye.gpg&lt;/code>&lt;br />You should get this output:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">7fb03ec8a1675723d2853b84aa4fdb49a46a3bb72b9951361488bfd19b29aab0a789a4f8c7406e71a69aabbc727c936d3549731c4659ffa1a08f44db8fdcebfa /etc/apt/trusted.gpg.d/proxmox-release-bullseye.gpg
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Update and upgrade your packages:&lt;br />&lt;code>apt update &amp;amp;&amp;amp; apt full-upgrade&lt;/code>&lt;br />Install the needed packages:&lt;br />&lt;code>apt install proxmox-ve postfix open-iscsi&lt;/code>&lt;/p>
&lt;p>Postfix will ask you, how you want to configure it:&lt;/p>
&lt;figure>&lt;img src="https://self-hosted.tools/p/encrypted-proxmox-hetzner/image-10.png">
&lt;/figure>
&lt;p>I choose Local only, hostname like it was, but I am not sure thats the best option.&lt;br />It can send me emails through a account so should be good.&lt;br />More on that config later.&lt;/p>
&lt;p>reboot your server&lt;br />&lt;code>systemctl reboot&lt;/code>&lt;br />ping your server, to check when its online again and ssh then into your server&lt;br />&lt;code>ping [your-ip]``ssh root@[your-ip]&lt;/code>&lt;br />SSH should connect without warning, if you have deleted the # from the BusyBox entry &lt;br />&lt;code>cryptroot-unlock&lt;/code>&lt;br /> Enter your password, if correct, you will be disconnected.&lt;/p>
&lt;p>Wait ~ 30 sec and connect to your server: &lt;code>ssh root@[your-ip]&lt;/code>&lt;/p>
&lt;p>remove no longer needed packages from the Debian install:&lt;br />&lt;code>apt remove os-prober linux-image-amd64 'linux-image-5.10*'&lt;/code>&lt;br />Change / set your root password: &lt;code>passwd&lt;/code>&lt;/p>
&lt;p>If you want you can now connect to the proxmox web interface for the first time:https://[your-IP]:8006Use root as username, use the password you just set and select pam authentication.&lt;br />Back to the command line:change the Proxmox repo if you don&amp;rsquo;t have a subscription key:&lt;br />&lt;code>nano /etc/apt/sources.list&lt;/code> &lt;br />Put a # before the subscritpion repo and add&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># PVE pve-no-subscription repository provided by proxmox.com,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># NOT recommended for production use&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">deb&lt;/span> &lt;span class="n">http&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="o">//&lt;/span>&lt;span class="n">download&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">proxmox&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">com&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">debian&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">pve&lt;/span> &lt;span class="n">bullseye&lt;/span> &lt;span class="n">pve&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">no&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">subscription&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Now put a # before the subscription repo and save &amp;amp; exit the file.&lt;/p>
&lt;h3 id="add-lvm-thin-pool">Add lvm thin pool
&lt;/h3>&lt;p>Now we will crate a new lvm thin pool for the vm disks&lt;br />&lt;code>lvcreate -l +99%FREE vm pve``lvconvert --type thin-pool pve/vm&lt;/code>&lt;/p>
&lt;p>Now reboot your server and then access the server with ssh like before.&lt;/p>
&lt;p>Lets add the new storage pool to the proxmox gui:&lt;br />&lt;code>nano /etc/pve/storage.cfg&lt;/code> &lt;br />and add:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">vm-disk #name that should be shown in gui
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">thinpool vm
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">vgname pve
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">content rootdir,images
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;figure>&lt;img src="https://self-hosted.tools/p/encrypted-proxmox-hetzner/image-11.png"
alt="how it looks in the web gui of proxmox">&lt;figcaption>
&lt;p>how it looks in the web gui of proxmox&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;h3 id="install-fail2ban">Install fail2ban
&lt;/h3>&lt;p>&lt;a class="link" href="https://www.fail2ban.org/wiki/index.php/Main_Page" target="_blank" rel="noopener"
>fail2ban&lt;/a> is a very simple, but effective tool to block IPs with malicious behavior.&lt;br />&lt;code>apt install fail2ban&lt;/code>&lt;br />&lt;code>cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local&lt;/code>&lt;br />&lt;code>nano /etc/fail2ban/jail.local&lt;/code>&lt;/p>
&lt;p>In the [sshd] tab, enable must be set to true and if you changed the SSH port, it must be specified.&lt;br />This is part of my config:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">enabled = true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">maxretry = 3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bantime = 45m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">findtime = 45m
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>add to this file:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="n">proxmox&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">enabled&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">https&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">http&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">8006&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">filter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">proxmox&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">logpath&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">/&lt;/span>&lt;span class="k">var&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="nb">log&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">daemon&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">log&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">maxretry&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1 hour&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">bantime&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">3600&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Now we need to create a custom filter file:&lt;br />&lt;code>nano /etc/fail2ban/filter.d/proxmox.conf&lt;/code>&lt;br />and add:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">[Definition]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">failregex = pvedaemon\[.*authentication failure; rhost=&amp;lt;HOST&amp;gt; user=.* msg=.*
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ignoreregex =
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Restart fail2ban to load the new config:&lt;br />&lt;code>systemctl restart fail2ban&lt;/code>&lt;/p>
&lt;p>Check the status of fail2ban:&lt;br />&lt;code>service fail2ban status&lt;/code>&lt;br />&lt;code>fail2ban-client status sshd&lt;/code>&lt;br />Now go to the web interface and make a wrong login attempt to check if it picks it up.&lt;br />You should see the wrong login with that command:&lt;br />&lt;code>fail2ban-regex /var/log/daemon.log /etc/fail2ban/filter.d/proxmox.conf&lt;/code>&lt;/p>
&lt;h3 id="zfs">ZFS
&lt;/h3>&lt;p>You only need this, if you want to use your extra drives with zfs. If you dont have extra drives, skip the entire zfs section.&lt;br />The root drives are not possible to use with zfs and encryption because you cant unlock zfs on root remotely.&lt;/p>
&lt;figure>&lt;img src="https://self-hosted.tools/p/encrypted-proxmox-hetzner/image-13.png"
alt="error: zfsutils-linux not installed (500)">&lt;figcaption>
&lt;p>error: zfsutils-linux not installed (500)&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;p>So we have to install the needed packages to use this feature.&lt;br />&lt;code>apt install pve-headers&lt;/code>&lt;br />&lt;code>apt install zfsutils-linux zfs-dkms zfs-zed&lt;/code>&lt;/p>
&lt;p>If you get the error that it does not build correctly for your kernel, you can try the following.&lt;br />The information provided here, can change quickly, so check before you try it.&lt;br />With Backports it builds correctly for the 5.15.39-1-pve kernel.&lt;br />In the stable repos is zfs-dkms 2.0.3-9, in backports 2.1.5-1.2.0.3-9 is only with the following kernels compatible: 3.10 – 5.10.&lt;/p>
&lt;p>If you run into this problem, you have to enable Backports to fix it. If you did not get an error, skip this:&lt;br />&lt;code>nano /etc/apt/sources.list&lt;/code> &lt;br />and add: &lt;br />&lt;code>deb [http://deb.debian.org/debian](http://deb.debian.org/debian) bullseye-backports main contrib non-free&lt;/code>&lt;br />then you can install the newer version of zfs-dkms:&lt;br />&lt;code>apt install -t bullseye-backports zfs-dkms&lt;/code>&lt;/p>
&lt;p>Then reboot and connect with ssh again.&lt;/p>
&lt;h4 id="create-a-zfs-pool">Create a ZFS Pool
&lt;/h4>&lt;p>Click on your host on the web interface, and under disks select ZFS. On the top you have to select &amp;ldquo;Create: ZFS&amp;rdquo;.&lt;/p>
&lt;figure>&lt;img src="https://self-hosted.tools/p/encrypted-proxmox-hetzner/image-14.png">
&lt;/figure>
&lt;p>I selected my two additional drives, selected mirror for the Raid level and gave it the name &amp;ldquo;HDD_Data&amp;rdquo;. Everything else I left at the defaults.&lt;/p>
&lt;p>If you want to encrypt it you have to create a encrypted pool inside the one you created on the web interface:&lt;br />&lt;code>zfs create -o encryption=on -o keyformat=passphrase HDD_Data/encrypted_data&lt;/code>&lt;br />It will ask you for a password, use a strong one.&lt;br />Add the ZFS pool:&lt;br />&lt;code>pvesm add zfspool encrypted_zfs -pool HDD_Data/encrypted_data&lt;/code>&lt;br />And finally you can mount it:&lt;br />&lt;code>zfs mount -l HDD_Data/encrypted_data&lt;/code>&lt;br />You have to insert your password to mount it.&lt;/p>
&lt;p>My final disk config looks like this:&lt;/p>
&lt;figure>&lt;img src="https://self-hosted.tools/p/encrypted-proxmox-hetzner/image-17.png"
alt="ZFS">&lt;figcaption>
&lt;p>ZFS&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;figure>&lt;img src="https://self-hosted.tools/p/encrypted-proxmox-hetzner/image-16.png"
alt="LVM-Thin">&lt;figcaption>
&lt;p>LVM-Thin&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;h3 id="improvement-settings">Improvement settings
&lt;/h3>&lt;p>You have to set vm swappiness really low, because mdadm is not officially supported and if the host swaps a vm that can lead to problems.&lt;br />&lt;code>sysctl -w vm.swappiness=1&lt;/code>&lt;br />or edit &lt;br />&lt;code>nano /etc/sysctl.conf&lt;/code> &lt;br />and add: &lt;br />&lt;code>vm.swappiness = 1&lt;/code>&lt;br />With 1 it swaps only before crashing, so it should never swap, unless you allocate to much resources :)&lt;/p>
&lt;h3 id="add-a-new-pam-user">Add a new pam user
&lt;/h3>&lt;p>This user will be used to access the proxmox server with ssh so that we can disable root login over ssh.&lt;br />&lt;code>adduser [username]&lt;/code>&lt;br />&lt;code>usermod -aG sudo [username]&lt;/code>&lt;br />&lt;code>sudo -u [username] mkdir /home/[username]/.ssh/&lt;/code>&lt;br />&lt;code>sudo -u [username] touch /home/[username]/.ssh/authorized_keys&lt;/code>&lt;/p>
&lt;p>Now add the ssh key to the authorized keys file from the new user (run this on your PC).&lt;br />You can add here a different key then the one for unlocking your server.&lt;br />&lt;code>gpg --export-ssh-key [fingerprint]| ssh [username]@[your-IP]&amp;quot;cat &amp;gt;&amp;gt; ~/.ssh/authorized_keys&amp;quot;&lt;/code>&lt;br />you can also use &lt;br />&lt;code>ssh-copy-id&lt;/code>&lt;br /> or &lt;br />&lt;code>scp&lt;/code>&lt;br /> (I use the one above, because my key is on a nitrokey).&lt;/p>
&lt;p>Check if (on the server) the key is present:&lt;br />&lt;code>cat /home/[username]/.ssh/authorized_keys&lt;/code>&lt;br />Check if you can log in with the new user:&lt;br />&lt;code>ssh [username]@[your-IP]&lt;/code>&lt;/p>
&lt;p>Now we have to add the user on the web interface to be able to use it for login there:&lt;br />Go to Datacenter -&amp;gt; Groups and add a new one. I called mine &amp;ldquo;Admin&amp;rdquo;&lt;/p>
&lt;figure>&lt;img src="https://self-hosted.tools/p/encrypted-proxmox-hetzner/image-21.png"
alt="Create Admin Group">&lt;figcaption>
&lt;p>Create Admin Group&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;p>Then go to Users and click on add, insert the name of the user you created with the command line before.&lt;br />Select &amp;ldquo;Linux PAM standard authentication&amp;rdquo; and select the Admin group.&lt;br />If you want, you can add an email, name, comment&amp;hellip;&lt;/p>
&lt;p>If you are already here in the Datacenter view, set up 2FA now.&lt;br />Go to &amp;ldquo;Two Factor&amp;rdquo; and add TOTP and Recovery Keys for root and your new user.&lt;br />For WebAuthn you need a &lt;a class="link" href="https://en.wikipedia.org/wiki/Fully_qualified_domain_name" target="_blank" rel="noopener"
>FQDM&lt;/a> and a valid certificate.&lt;/p>
&lt;h3 id="harden-ssh">Harden SSH
&lt;/h3>&lt;p>Now that we have a second user, time to setup ssh securely:&lt;br />&lt;code>sudo nano /etc/ssh/sshd_config&lt;/code>&lt;br />You can add, or change the following:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">PermitRootLogin no
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ClientAliveInterval 300
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ClientAliveCountMax 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">AllowUsers [username]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LoginGraceTime 1m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">MaxAuthTries 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PasswordAuthentication no
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Save the file and check for errors:&lt;br />&lt;code>sudo sshd -t&lt;/code>&lt;br />if there are no errors detected, restart the ssh service:&lt;br />&lt;code>sudo systemctl restart sshd&lt;/code>&lt;br />I didn&amp;rsquo;t change the ssh port, because I limit access with the hetzner firewall to that port from my ip. A changed ssh port is only good to reduce logs, it is not a security measure.&lt;br />If you limit the access to your IP, you will not get any annoying logs from bots.&lt;br />Same goes for the web interface. I will share my Hetzner firewall rules later in the tutorial.&lt;/p>
&lt;h3 id="disable-ipv6-if-not-used">Disable IPV6 if not used
&lt;/h3>&lt;p>For now I will disable ipv6 because I don&amp;rsquo;t know enough about it to set it up (securely), so I disabled it.&lt;br />This is optional, please let me know if you got it working with ipv6 and opnsense.&lt;br />&lt;code>sudo touch /etc/sysctl.d/disable-ipv6.conf&lt;/code>&lt;br />&lt;code>nano /etc/sysctl.d/disable-ipv6.conf&lt;/code>&lt;br />insert those two lines:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">net.ipv6.conf.all.disable_ipv6 = 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">net.ipv6.conf.default.disable_ipv6 = 1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Comment (add a # before) everything ipv6 related in &lt;br />&lt;code>nano /etc/sysctl.d/99-hetzner.conf&lt;/code>&lt;/p>
&lt;p>Now reload all variables with &lt;br />&lt;code>sysctl -system``ip -6 addr&lt;/code>&lt;br />and &lt;br />&lt;code>ip -6 route&lt;/code> &lt;br />should show nothing.&lt;/p>
&lt;h3 id="pbs-restore-client">PBS restore client
&lt;/h3>&lt;p>If you plan to use the proxmox backup server and want to restore from it you have to install:&lt;br />&lt;code>sudo apt install proxmox-backup-restore-image&lt;/code>&lt;/p>
&lt;h3 id="networking-for-opnsense--pfsense">Networking for opnsense / pfsense
&lt;/h3>&lt;p>I bought a second IP for this, it should be possible with only one but then you have a big problem if something doesn&amp;rsquo;t work on your firewall vm.&lt;br />t is not that expensive so I would get one, its a lot more convenient.&lt;br />After you have bought your second IP, you need to get a virtual mac from your hetzner robot web interface.&lt;/p>
&lt;p>We need the mac from the physical interface (change enp0s31f6 to your interface name),you can see the mac in the output of &lt;br />&lt;code>ip link show&lt;/code>&lt;br /> or &lt;br />&lt;code>cat /sys/class/net/enp0s31f6/address&lt;/code>&lt;/p>
&lt;p>Enable ip forwarding, &lt;br />&lt;code>sudo nano /etc/sysctl.conf&lt;/code>&lt;br /> and uncomment (delete the #) before &lt;code>net.ipv4.ip_forward=1&lt;/code>&lt;/p>
&lt;p>With &lt;code>sudo sysctl -p&lt;/code> you should see:&lt;br />&lt;code>net.ipv4.ip_forward = 1&lt;/code>&lt;br />&lt;code>vm.swappiness = 1&lt;/code>&lt;/p>
&lt;hr>
&lt;p>Make a copy of your old interface config:&lt;br />&lt;code>cd /etc/network``sudo cp interfaces interfaces.old&lt;/code>&lt;/p>
&lt;p>Edit the file with &lt;code>sudo nano interfaces&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">auto lo
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">iface lo inet loopback
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">iface enp0s31f6 inet manual
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">auto vmbr0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">iface vmbr0 inet static
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> address [Main-IP/XX]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> gateway [Gateway-IP]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> bridge-ports enp0s31f6
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> bridge-stp off
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> bridge-fd 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> hwaddress [mac from hardware you got above with format xx:xx:...]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> pointopoint [Gateway-IP]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#Main_public_IP
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">auto vmbr1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">iface vmbr1 inet static
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> address 10.0.0.1/24
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> bridge-ports none
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> bridge-stp off
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> bridge-fd 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#mgmt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">auto vmbr2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">iface vmbr2 inet static
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> address 10.0.10.1/24
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> bridge-ports none
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> bridge-stp off
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> bridge-fd 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#internal
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">auto vmbr3
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">iface vmbr3 inet static
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> address 10.0.20.1/24
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> bridge-ports none
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> bridge-stp off
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> bridge-fd 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#external
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>I created 3 bridged networks for the VM, change it how you need it.&lt;br />Before i made a reboot I made a cronjob to revert the config if the network config doesn&amp;rsquo;t work. Otherwise you have to use the rescue system or ask for a &lt;a class="link" href="https://docs.hetzner.com/robot/dedicated-server/maintainance/kvm-console/" target="_blank" rel="noopener"
>KVM Console&lt;/a> to revert the config.&lt;/p>
&lt;p>&lt;code>sudo crontab -e&lt;/code> (select 1), go to &lt;a class="link" href="https://crontab.guru/" target="_blank" rel="noopener"
>crontab.guru&lt;/a> and select a time where you want to revert the config to the old working one (30 min from now or so)&lt;br />&lt;code>0 22 * * * cp /etc/network/interfaces.old /etc/network/interfaces&lt;/code>&lt;br />If the server doesn&amp;rsquo;t comes online after some minutes, just reboot it after the selected time from the hetzner robot web interface.&lt;/p>
&lt;p>Reboot the server&lt;code>sudo reboot&lt;/code>&lt;br />Use &lt;code>ssh root@[your-IP]&lt;/code> to unlock it and &lt;code>ssh [username]@[your-IP]&lt;/code> to access it.&lt;br />You can find the complete start sequence on the the next section of this post.&lt;/p>
&lt;p>Now you can add / change local networks (on the host, behind opnsense) from the web interface or the config file.&lt;/p>
&lt;figure>&lt;img src="https://self-hosted.tools/p/encrypted-proxmox-hetzner/image-23.png"
alt="my networks">&lt;figcaption>
&lt;p>my networks&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;p>After you have done that, I would make management vm from where you access later the opnsense gui in the browser. I made a dedicated management bridge to which the opnsense web interface binds to.&lt;br />So if the opnsense FW stops working, i can access the mgmt VM from the Proxmox gui and access from the mgmt VM the Opnsense webinterface.&lt;/p>
&lt;h2 id="reboot--start-sequence">Reboot / start sequence
&lt;/h2>&lt;p>ssh root@[your-IP]&lt;br />cryptroot-unlock&lt;br />SSD-decryption Password&lt;br />Session ends, wait 15 sec&lt;/p>
&lt;p>ssh [username]@[your-IP]&lt;br />sudo zfs mount -l HDD_Data/encrypted_dataHDD &lt;br />decryption password&lt;/p>
&lt;h2 id="opnsense--pfsense">Opnsense / pfsense
&lt;/h2>&lt;p>I will not document the whole install, just some key points you should look out for on the installation. Basically it is a normal install, like you can find it in the &lt;a class="link" href="https://docs.opnsense.org/manual/virtuals.html" target="_blank" rel="noopener"
>opnsense docs&lt;/a> (or &lt;a class="link" href="https://docs.netgate.com/pfsense/en/latest/recipes/virtualize-proxmox-ve.html" target="_blank" rel="noopener"
>pfsense docs&lt;/a>).&lt;br />Maybe I will write a detailed tutorial for that, I will link it here, if I do.&lt;/p>
&lt;p>Access the Proxmox web interface and create a new VM.&lt;br />Under the Network Tab select the mgmt lan and disable the Firewall, otherwise create the VM as usual.&lt;br />Now click on the new VM and go to the Hardware Tab and add a Network Device for the Wan interface. Here you must enter your virtual mac you got for your second IP from the Robot web interface.&lt;/p>
&lt;figure>&lt;img src="https://self-hosted.tools/p/encrypted-proxmox-hetzner/image-24.png"
alt="Add Network Device to opnsense VM">&lt;figcaption>
&lt;p>Add Network Device to opnsense VM&lt;/p>
&lt;/figcaption>
&lt;/figure>
&lt;p>Now you can start the VM and configure it like described &lt;a class="link" href="https://docs.opnsense.org/manual/install.html" target="_blank" rel="noopener"
>here&lt;/a> and &lt;a class="link" href="https://docs.opnsense.org/manual/virtuals.html" target="_blank" rel="noopener"
>here&lt;/a>.Be sure to map the wan and lan interface correctly (assign them manually), otherwise you will use the wrong mac on the wan interface and you will get an abuse mail from Hetzner.&lt;/p>
&lt;p>I created a Site 2 Site VPN between my Homelab and my Hetzner server, so I can access the VMs with ssh like they are on my local network without opening a port in my opnsense firewall.&lt;br />If the opnsense vm goes down, I can access it with my mgmt vm that I access with spice.&lt;/p>
&lt;h2 id="hetzner-firewall">Hetzner Firewall
&lt;/h2>&lt;p>I use the &lt;a class="link" href="https://docs.hetzner.com/robot/dedicated-server/firewall/" target="_blank" rel="noopener"
>Hetzner Firewall&lt;/a> to protect the Proxmox Host IP. It&amp;rsquo;s nice because you don&amp;rsquo;t get any logs from bots, can change it easily on the robot web interface, are DDOS protected and so on.&lt;br />
I limit access from my home IP to the port 22 (ssh), 8006 (proxmox web interface), icmp (ping) and port 3128 (spice). Everything else is blocked.&lt;br />
To my second IP, I let all traffic through, that traffic handles the opnsense firewall vm.&lt;/p>
&lt;h2 id="final-thoughts">Final thoughts
&lt;/h2>&lt;p>Thanks for sticking around until here, that was a long one.&lt;br />I feel really confident with that setup and it works great (for the last two months). I hope this helped you to setup your own server. &lt;br />If you encountered an error, have feedback, or you have a question, please send me an email to &lt;a class="link" href="mailto:mail@self-hosted.tools" >mail@self-hosted.tools&lt;/a>.&lt;br />Thanks for reading 😊&lt;/p>
&lt;h2 id="resources-that-helped-me">Resources that helped me
&lt;/h2>&lt;p>&lt;a class="link" href="https://community.hetzner.com/tutorials/install-and-configure-proxmox_ve" target="_blank" rel="noopener"
>https://community.hetzner.com/tutorials/install-and-configure-proxmox_ve&lt;/a>&lt;br />
&lt;a class="link" href="https://community.hetzner.com/tutorials/install-ubuntu-2004-with-full-disk-encryption" target="_blank" rel="noopener"
>https://community.hetzner.com/tutorials/install-ubuntu-2004-with-full-disk-encryption&lt;/a>&lt;br />
&lt;a class="link" href="https://github.com/TheReal1604/disk-encryption-hetzner" target="_blank" rel="noopener"
>https://github.com/TheReal1604/disk-encryption-hetzner&lt;/a>&lt;br />
&lt;a class="link" href="https://www.cyberciti.biz/security/how-to-unlock-luks-using-dropbear-ssh-keys-remotely-in-linux/" target="_blank" rel="noopener"
>https://www.cyberciti.biz/security/how-to-unlock-luks-using-dropbear-ssh-keys-remotely-in-linux/&lt;/a>&lt;br />
&lt;a class="link" href="https://pve.proxmox.com/pve-docs/pve-admin-guide.html" target="_blank" rel="noopener"
>https://pve.proxmox.com/pve-docs/pve-admin-guide.html&lt;/a>&lt;br />
Many Reddit and &lt;a class="link" href="https://forum.proxmox.com/" target="_blank" rel="noopener"
>Proxmox forum&lt;/a> posts i cant find anymore.&lt;/p>
&lt;p>&lt;font size ="2">&lt;em>Photo by &lt;a class="link" href="https://unsplash.com/@kelvin1987" target="_blank" rel="noopener"
>Kelvin Ang&lt;/a> on &lt;a class="link" href="https://unsplash.com/" target="_blank" rel="noopener"
>Unsplash&lt;/a>&lt;/em>&lt;/font>&lt;/p></description></item></channel></rss>