<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.4">Jekyll</generator><link href="https://kenhv.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://kenhv.com/" rel="alternate" type="text/html" /><updated>2024-10-12T01:19:43+05:30</updated><id>https://kenhv.com/feed.xml</id><title type="html">Ken Harris</title><subtitle>Tech enthusiast. Occasional doer of stuff. @KenHV</subtitle><author><name>Ken Harris</name></author><entry><title type="html">Sideloading on Any Apple Product</title><link href="https://kenhv.com/blog/sideloading-on-any-apple-product" rel="alternate" type="text/html" title="Sideloading on Any Apple Product" /><published>2024-09-20T00:00:00+05:30</published><updated>2024-09-20T17:01:24+05:30</updated><id>https://kenhv.com/blog/sideloading-on-any-apple-product</id><content type="html" xml:base="https://kenhv.com/blog/sideloading-on-any-apple-product"><![CDATA[<p>Did you know that it’s possible to sideload apps onto your iPhone, iPad, Apple TV, Apple Watch, and even Vision Pro? It’s not the EU method, and it doesn’t involve jailbreaking. This is an official method supported by Apple. The setup is kinda complicated (it’s Apple after all) and has a few caveats, but it is doable.</p>

<p>You can get back Apollo for Reddit, a ReVanced equivalent for YouTube, Minecraft Java Edition, and much more. Even Fortnite.</p>

<p>Apple allows developers to sideload apps for development and testing purposes. If you pay for a developer account, you get to sideload as much as you want. If not, you’re limited to 3 sideloaded apps per Apple ID (it doesn’t stack up with multiple accounts on the same device). Developer accounts cost $100/year, but there is a much cheaper way around this.</p>

<p>The official Apple way of doing this is with Xcode. For Apple Watch and Vision Pro, this is also the only way. But for iPhones, iPads, and Apple TVs, there are tools and apps that make this process a whole lot easier.</p>

<p>I won’t be providing step-by-step instructions; that will make this post several times bigger. I’ll list out all your options and link to all the relevant documentation and instructions.</p>

<h2 id="the-free-route">The Free Route</h2>

<p>If you’re not going to sideload more than three apps, you can use this method. You have two options here: you can either trade one app slot for convenience, or you can sideload three different apps but deal with the additional complexity.</p>

<p>When sideloading with this method, you have to deal with a concept called “refreshing” your apps. Apps sideloaded with free developer accounts need to be resigned every 7 days. If you’re willing to sacrifice one app slot, you can refresh your sideloaded apps on-device.</p>

<p>The first option is <a href="https://sidestore.io/">SideStore</a>, a fork of <a href="https://altstore.io/">AltStore</a>. It can be used on iOS and iPadOS. The difference between SideStore and AltStore is that SideStore only requires computer access for the initial setup. It handles installing, updating, and refreshing, all on-device. They have excellent, detailed documentation explaining every step.</p>

<p>The second option is <a href="https://sideloadly.io/">Sideloadly</a>. Sideloadly is more powerful, but you need a computer to install, update and refresh apps. It supports sideloading to iOS, iPadOS, tvOS and Apple Silicon Macs. You get to keep all three free slots with this option.</p>

<h2 id="the-paid-route">The Paid Route</h2>

<p>If you don’t want to spend $100/year for a developer account, you can purchase signing certificates from third party services. These services buy developer accounts from Apple and set everything up. Each developer account has 100 slots. Each slot gets a developer certificate that can be used to sign apps. When you purchase from them, you’re basically purchasing one individual slot. I use <a href="https://apptesters.org/">AppTesters</a> and I can vouch for them. You can also check out <a href="https://kravasign.com/">KravaSign</a> (previously MapleSign) and <a href="https://www.signulous.com/">Signulous</a>.</p>

<p>While it’s pretty rare, Apple <em>has</em> revoked some of these services’ developer accounts in the past. All the above mentioned services have a track record of issuing new certificates for free in these rare instances. Just check if the service offers “revoke protection” before buying.</p>

<p>Once you have your certificate (either directly from Apple or from a third party service), you can then use <a href="https://github.com/khcrysalis/Feather">Feather</a> or <a href="https://esign.yyyue.xyz/">ESign</a> to sideload apps. Feather is a new and open source app. ESign is a closed source Chinese app, but it’s stable and feature-complete. I know the choice is obvious, but Feather is extremely new, so you might have to use ESign if Feather doesn’t play well with something.</p>

<p>To install Feather/ESign, you can use Sideloadly with your developer certificate. There are online IPA signers as well, like <a href="https://sign.apptesters.org/">AppTesters’ Signer</a> and <a href="https://sign.kravasign.com/">KravaSigner</a>. These let you sign and install IPAs from your device’s browser. Of course, you can install ESign using Feather and vice versa.</p>

<p>AppTesters has video instructions on installing and setting up ESign, which you can find <a href="https://apptesters.org/instructions/">here</a>. You can follow the same process for installing Feather. Note that if you want notifications support on Feather, you have to disable “PPQCheck Protections” in Feather’s settings. It’s not as bad as the app says; no other signing app has this protection. Notifications work out of the box on ESign.</p>

<p>To install apps on iOS 18, you need to use the online install option on Feather and the WiFi option on ESign.</p>

<h2 id="apps-and-tweaks">Apps and Tweaks</h2>

<p>The equivalent of Android’s APKs on iOS are IPAs. To get a usable decrypted IPA, you’ll need a jailbroken device. Fortunately for us, there are places where we can get decrypted IPAs. AppTesters host a Telegram bot that you can access <a href="https://t.me/AppleTesters/58881">here</a>. You can also use <a href="https://decrypt.day/">decrypt.day</a> and <a href="https://armconverter.com/decryptedappstore/us">armconverter</a>. Once you have a decrypted IPA, you can sideload it as it is or inject tweaks into it. Tweaks are like ReVanced patches.</p>

<p>Here are some apps/tweaks to get you started:</p>

<ul>
  <li><a href="https://balackburn.github.io/Apollo/">Apollo for Reddit</a></li>
  <li><a href="https://github.com/dayanch96/YTLite">YouTube Plus</a></li>
  <li><a href="https://github.com/BandarHL/BHTwitter">BHTwitter</a> and <a href="https://github.com/ghl3m0n/FuckElon/">FuckElon</a></li>
  <li><a href="https://github.com/whoeevee/EeveeSpotify">EeveeSpotify</a></li>
  <li><a href="https://github.com/SoCuul/SCInsta">SCInsta</a> (free) and <a href="https://regram.fouadraheb.com/">Regram</a> (paid)</li>
  <li><a href="https://github.com/pyoncord/BunnyTweak">Bunny</a> (Discord)</li>
  <li><a href="https://pojavlauncherteam.github.io/INSTALL.html#ios">PojavLauncher</a> (check the “Sideloading (Jailed)” section)</li>
  <li><a href="https://aidoku.app/">Aidoku</a></li>
  <li><a href="https://www.kingbri.dev/ferrite">Ferrite</a></li>
</ul>

<p>Everything listed above is open source (except Regram).</p>

<h2 id="related-communities">Related Communities</h2>

<p>I strongly recommend joining these communities to get updates and news. You can discover a lot of cool apps to sideload too.</p>

<ul>
  <li><a href="https://t.me/AppleTesters">AppTesters Telegram group</a></li>
  <li><a href="https://discord.gg/kravasign">KravaSign Discord server</a></li>
  <li><a href="https://reddit.com/r/sideloaded">r/sideloaded</a></li>
</ul>]]></content><author><name>Ken Harris</name></author><summary type="html"><![CDATA[A guide to sideloading apps on any and all Apple products.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kenhv.com/assets/images/og/posts/sideloading-on-any-apple-product.png" /><media:content medium="image" url="https://kenhv.com/assets/images/og/posts/sideloading-on-any-apple-product.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Securing A Linux Server</title><link href="https://kenhv.com/blog/securing-a-linux-server" rel="alternate" type="text/html" title="Securing A Linux Server" /><published>2024-08-28T00:00:00+05:30</published><updated>2024-09-20T17:01:24+05:30</updated><id>https://kenhv.com/blog/securing-a-linux-server</id><content type="html" xml:base="https://kenhv.com/blog/securing-a-linux-server"><![CDATA[<p>This post goes over the following: adding a non-root user, setting up system users, securing SSH, setting up a firewall (UFW), blocking known bad IPs with a script, hardening Nginx reverse-proxy configs, implementing Nginx Proxy Manager’s “block common exploits” functionality, setting up Fail2Ban, implementing LinuxServer’s SWAG’s Fail2Ban jails, and implementing CIS benchmarks. Additional instructions for Cloudflare proxy are provided as well.</p>

<h2 id="non-root-user">Non-Root User</h2>

<p>If you’re using a VPS, the default user will be <code class="language-plaintext highlighter-rouge">root</code>. The principle of least privilege is a security concept where each entity only has access to what it needs. To abide by this concept, we need to set up a non-root user.</p>

<p>To add a new non-root user, run the following command as <code class="language-plaintext highlighter-rouge">root</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adduser &lt;user&gt;
</code></pre></div></div>

<p>You can leave all the information empty. I recommend using a randomly generated passphrase; it’s easier to remember and type.</p>

<p>To give the new user rights to use <code class="language-plaintext highlighter-rouge">sudo</code>, run the following command as <code class="language-plaintext highlighter-rouge">root</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>usermod <span class="nt">--append</span> <span class="nt">--groups</span> <span class="nb">sudo</span> &lt;user&gt;
</code></pre></div></div>

<p>You can now log out and log back in as the new user.</p>

<p>For added security, you can set <code class="language-plaintext highlighter-rouge">root</code>’s shell to <code class="language-plaintext highlighter-rouge">nologin</code> and lock the account. Run the following command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>usermod root <span class="nt">--shell</span> /sbin/nologin
<span class="nb">sudo </span>passwd <span class="nt">--lock</span> root
</code></pre></div></div>

<p>If you need a root shell, you can use <code class="language-plaintext highlighter-rouge">sudo -s</code>. <code class="language-plaintext highlighter-rouge">su</code> or <code class="language-plaintext highlighter-rouge">sudo -i</code> won’t work anymore.</p>

<h2 id="system-user">System User</h2>

<p>System users are special user accounts created for running applications. This way, the application will only have access to what it needs. For instance, web servers run under <code class="language-plaintext highlighter-rouge">www-data</code> and Docker runs under <code class="language-plaintext highlighter-rouge">docker</code>.</p>

<p>To create a system user, run the following command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>adduser <span class="nt">--system</span> <span class="nt">--home</span> &lt;path&gt; <span class="nt">--shell</span> /sbin/nologin <span class="nt">--group</span> &lt;user&gt;
</code></pre></div></div>

<p>If you don’t want the user to have a home directory, you can remove that argument. If you need shell access, you can run <code class="language-plaintext highlighter-rouge">sudo -u &lt;user&gt; bash</code>.</p>

<p>Let’s say I want to run an example application called <code class="language-plaintext highlighter-rouge">app</code> on my server. Here’s how I would do it:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>adduser <span class="nt">--system</span> <span class="nt">--home</span> /opt/app <span class="nt">--shell</span> /sbin/nologin <span class="nt">--group</span> app
</code></pre></div></div>

<p>Then I’ll get a shell as <code class="language-plaintext highlighter-rouge">app</code> using <code class="language-plaintext highlighter-rouge">sudo -u app bash</code> and run the following:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd ~
git clone app
exit
</code></pre></div></div>

<p>I’ve downloaded the app. Now I’ll set up a systemd service for the app by creating <code class="language-plaintext highlighter-rouge">/etc/systemd/system/app.service</code>:</p>

<div class="language-systemd highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">[Unit]</span>
<span class="nt">Description</span><span class="p">=</span>App

<span class="k">[Service]</span>
<span class="nt">User</span><span class="p">=</span>app
<span class="nt">Group</span><span class="p">=</span>app
<span class="nt">Type</span><span class="p">=</span>simple
<span class="nt">Restart</span><span class="p">=</span>always
<span class="nt">ExecStart</span><span class="p">=</span>/opt/app/run.sh --db=/opt/app/db.sqlite3

<span class="k">[Install]</span>
<span class="nt">WantedBy</span><span class="p">=</span>multi-user.target
</code></pre></div></div>

<p>Note the <code class="language-plaintext highlighter-rouge">User</code> and <code class="language-plaintext highlighter-rouge">Group</code> entries under <code class="language-plaintext highlighter-rouge">[Service]</code>. This will run the service as our system user <code class="language-plaintext highlighter-rouge">app</code>. Now, I can run <code class="language-plaintext highlighter-rouge">sudo systemctl enable --now app.service</code> to enable and start the application. Neat! You can apply this example to any application you want to run.</p>

<h2 id="ssh">SSH</h2>

<p>Follow the <a href="https://ssh-audit.com/hardening_guides.html">SSH Hardening Guide</a>. It ensures that only strong algorithms are used for encryption. I do this on all my machines, both clients and servers. You can skip the “connection rate throttling” section, we’ll be setting up Fail2Ban to handle that.</p>

<p>Generate an Ed25519 key (passphrase is optional but recommended):</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-keygen <span class="nt">-t</span> ed25519
</code></pre></div></div>

<p>Copy the key to your server:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-copy-id <span class="nt">-i</span> &lt;path-to-key&gt; &lt;user&gt;@&lt;ip&gt;
</code></pre></div></div>

<p>Paste the following at the end of <code class="language-plaintext highlighter-rouge">/etc/ssh/sshd_config</code> on your server:</p>

<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Protocol</span> <span class="m">2</span>
<span class="n">MaxAuthTries</span> <span class="m">3</span>
<span class="n">PermitRootLogin</span> <span class="n">no</span>
<span class="n">PasswordAuthentication</span> <span class="n">no</span>
<span class="n">PubkeyAuthentication</span> <span class="n">yes</span>
<span class="n">AuthenticationMethods</span> <span class="n">publickey</span>
<span class="n">KbdInteractiveAuthentication</span> <span class="n">no</span>
<span class="n">X11Forwarding</span> <span class="n">no</span>
</code></pre></div></div>

<p>The above snippet does a few things. It disables the old protocol 1 and enforces protocol 2 for increased security. It prevents login attempts to <code class="language-plaintext highlighter-rouge">root</code>. Everything other than key-based authentication is disabled. X11 forwarding is also disabled; you’ll know it if you need it.</p>

<p>You can change the SSH port to a random number in the same file. Finally, run the following command to restart the SSH daemon:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>systemctl restart ssh
</code></pre></div></div>

<h2 id="firewall">Firewall</h2>

<p>Make sure the required packages are installed:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt <span class="nb">install </span>iptables ipset ufw cron curl wget rsyslog <span class="nt">-y</span>
</code></pre></div></div>

<p>Allow the SSH port and enable UFW:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>ufw allow &lt;port&gt;/tcp comment <span class="s2">"OpenSSH"</span>
<span class="nb">sudo </span>ufw <span class="nb">enable</span>
</code></pre></div></div>

<p>If you’re using Docker, we need to make some changes to the UFW config to ensure UFW works as intended. Explanation and instructions are given in <a href="https://github.com/chaifeng/ufw-docker#solving-ufw-and-docker-issues">this GitHub repo</a> (thanks to <a href="https://www.reddit.com/r/selfhosted/comments/1f3y16m/comment/lkinkub/?context=3">u/s0ftcorn</a>).</p>

<p>I also like to disable UFW logging. <strong>Do not</strong> do this unless you know what you’re doing:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>ufw logging off
<span class="nb">echo</span> <span class="s2">"&amp; stop"</span> | <span class="nb">sudo tee</span> <span class="nt">--append</span> /etc/rsyslog.d/20-ufw.conf
<span class="nb">sudo </span>systemctl restart rsyslog
</code></pre></div></div>

<p>Next up, we’ll be blocking known bad IPs. <a href="https://www.crowdsec.net/">CrowdSec</a> is complicated to set up, wastes resources, requires an account, and in my opinion, overkill. Instead, we’ll just stick to a simple bash script and a cronjob.</p>

<p><a href="https://github.com/stamparm/ipsum">IPsum</a> is a regularly updated list of malicious IPs, this is what we’re going to use. The script we’ll be using is from <a href="https://gist.github.com/arter97/2b71e193700ab002c75d1e5a0e7da6dc">arter97</a>.</p>

<p>Download the script and run it once:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>wget https://gist.githubusercontent.com/arter97/2b71e193700ab002c75d1e5a0e7da6dc/raw/firewall.sh <span class="nt">-O</span> /opt/firewall.sh
<span class="nb">sudo chmod </span>755 /opt/firewall.sh
<span class="nb">sudo</span> /opt/firewall.sh
</code></pre></div></div>

<p>Check the output of <code class="language-plaintext highlighter-rouge">sudo dmesg</code> to verify that everything is working. Add a cronjob by running <code class="language-plaintext highlighter-rouge">sudo crontab -e</code> and paste the following:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@reboot /opt/firewall.sh
0 5 <span class="k">*</span> <span class="k">*</span> <span class="k">*</span> /opt/firewall.sh
</code></pre></div></div>

<h2 id="nginx">Nginx</h2>

<p>Nginx is my preferred reverse-proxy. There are a few things you can configure to improve security.</p>

<p>Add the following lines to your <code class="language-plaintext highlighter-rouge">server</code> blocks:</p>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">add_header</span> <span class="s">X-Content-Type-Options</span> <span class="s">"nosniff"</span> <span class="s">always</span><span class="p">;</span>
<span class="k">add_header</span> <span class="s">X-XSS-Protection</span> <span class="s">"1</span><span class="p">;</span> <span class="k">mode=block"</span> <span class="s">always</span><span class="p">;</span>
<span class="k">add_header</span> <span class="s">X-Frame-Options</span> <span class="s">"SAMEORIGIN"</span> <span class="s">always</span><span class="p">;</span>
</code></pre></div></div>

<p>The above snippet sets some headers to prevent certain attacks. The first header prevents MIME sniffing attacks, the second header prevents cross-site scripting attacks, and the third header prevents your site from being embedded in another domain, preventing clickjacking attacks.</p>

<p>Nginx Proxy Manager (not to be confused with Nginx) has a feature called “block common exploits”, which blocks SQL injection attacks, file injection attacks, and more. To implement it in Nginx, download the config file:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>wget https://raw.githubusercontent.com/NginxProxyManager/nginx-proxy-manager/develop/docker/rootfs/etc/nginx/conf.d/include/block-exploits.conf <span class="nt">-O</span> /etc/nginx/block-exploits.conf
</code></pre></div></div>

<p>Then add the following line to your <code class="language-plaintext highlighter-rouge">server</code> blocks:</p>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">include</span> <span class="n">block-exploits.conf</span>
</code></pre></div></div>

<p>To avoid getting indexed by search engines, add the following lines to your <code class="language-plaintext highlighter-rouge">server</code> blocks:</p>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">add_header</span> <span class="s">X-Robots-Tag</span> <span class="s">"noindex,</span> <span class="s">nofollow,</span> <span class="s">nosnippet,</span> <span class="s">noarchive"</span> <span class="s">always</span><span class="p">;</span>
<span class="k">location</span> <span class="n">/robots.txt</span> <span class="p">{</span> <span class="kn">return</span> <span class="mi">200</span> <span class="s">"User-agent:</span> <span class="s">*</span><span class="err">\</span><span class="s">nDisallow:</span> <span class="n">/\n"</span><span class="p">;</span> <span class="p">}</span>
</code></pre></div></div>

<p>To prevent referrer info being sent to external sites, add the following line to your <code class="language-plaintext highlighter-rouge">server</code> block:</p>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">add_header</span> <span class="s">Referrer-Policy</span> <span class="s">"same-origin"</span> <span class="s">always</span><span class="p">;</span>
</code></pre></div></div>

<p>I suggest configuring CSP (Content Security Policy) and HSTS (HTTP Strict Transport Security) as well. CSP declares which external resources are allowed to be loaded, you can find the documentation <a href="https://content-security-policy.com/">here</a>. HSTS instructs the browser to only allow HTTPS connections; MDN has good documentation <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security">here</a>.</p>

<p>Keep in mind that inheritance works differently in Nginx for array directives such as <code class="language-plaintext highlighter-rouge">add_header</code> and <code class="language-plaintext highlighter-rouge">proxy_set_header</code>. If you have any array directives in the block above, you <strong>need to re-add</strong> them in the current block.</p>

<p>Incorrect config:</p>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Incorrect</span>
<span class="k">http</span> <span class="p">{</span>
  <span class="kn">add_header</span> <span class="s">X-Header-1</span> <span class="s">""</span><span class="p">;</span>

  <span class="kn">server</span> <span class="p">{</span>
    <span class="kn">add_header</span> <span class="s">X-Header-2</span> <span class="s">""</span><span class="p">;</span>

    <span class="kn">location</span> <span class="n">/</span> <span class="p">{</span>
      <span class="kn">proxy_pass</span> <span class="s">http://localhost:8080/</span><span class="p">;</span>
      <span class="kn">add_header</span> <span class="s">X-Header-3</span> <span class="s">""</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Correct config:</p>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Correct</span>
<span class="k">http</span> <span class="p">{</span>
  <span class="kn">add_header</span> <span class="s">X-Header-1</span> <span class="s">""</span><span class="p">;</span>

  <span class="kn">server</span> <span class="p">{</span>
    <span class="kn">add_header</span> <span class="s">X-Header-1</span> <span class="s">""</span><span class="p">;</span>
    <span class="kn">add_header</span> <span class="s">X-Header-2</span> <span class="s">""</span><span class="p">;</span>

    <span class="kn">location</span> <span class="n">/</span> <span class="p">{</span>
      <span class="kn">proxy_pass</span> <span class="s">http://localhost:8080/</span><span class="p">;</span>
      <span class="kn">add_header</span> <span class="s">X-Header-1</span> <span class="s">""</span><span class="p">;</span>
      <span class="kn">add_header</span> <span class="s">X-Header-2</span> <span class="s">""</span><span class="p">;</span>
      <span class="kn">add_header</span> <span class="s">X-Header-3</span> <span class="s">""</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="fail2ban">Fail2Ban</h2>

<p>Install Fail2Ban and dependencies:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt <span class="nb">install </span>fail2ban rsyslog <span class="nt">-y</span>
</code></pre></div></div>

<p><strong>Do not</strong> copy <code class="language-plaintext highlighter-rouge">/etc/fail2ban/jail.conf</code> to <code class="language-plaintext highlighter-rouge">/etc/fail2ban/jail.local</code>. Most guides I’ve seen suggest doing this, but this <a href="https://github.com/fail2ban/fail2ban/wiki/Proper-fail2ban-configuration">isn’t the right way</a>. Create <code class="language-plaintext highlighter-rouge">/etc/fail2ban/jail.local</code> with the following contents:</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[DEFAULT]</span>
<span class="py">bantime</span> <span class="p">=</span> <span class="mi">1</span><span class="err">d</span>
<span class="py">findtime</span> <span class="p">=</span> <span class="mi">15</span><span class="err">m</span>
<span class="py">maxretry</span> <span class="p">=</span> <span class="mi">3</span>
<span class="py">backend</span> <span class="p">=</span> <span class="err">auto</span>

<span class="nn">[sshd]</span>
<span class="py">port</span> <span class="p">=</span> <span class="err">&lt;port&gt;</span>
</code></pre></div></div>

<p>SSH is the only jail enabled by default, so we just need to give it the correct port.</p>

<p>Fail2Ban ships with some pre-configured jails for Nginx, which you can enable by adding the following to <code class="language-plaintext highlighter-rouge">/etc/fail2ban/jail.local</code>:</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[nginx-http-auth]</span>
<span class="py">enabled</span> <span class="p">=</span> <span class="kc">true</span>
<span class="py">mode</span>    <span class="p">=</span> <span class="err">aggressive</span>

<span class="nn">[nginx-bad-request]</span>
<span class="py">enabled</span> <span class="p">=</span> <span class="kc">true</span>

<span class="nn">[nginx-botsearch]</span>
<span class="py">enabled</span> <span class="p">=</span> <span class="kc">true</span>
</code></pre></div></div>

<p>The HTTP auth jail filters incorrect login attempts to Nginx’s basic auth. Bad request jail filters, well, bad requests (400 error). Botsearch jail filters requests for certain URLs if they don’t exist (such as /wp-login, /admin).</p>

<p>To make sure that the IPs are the real IPs of the end user, add the following line to your Nginx <code class="language-plaintext highlighter-rouge">location</code> blocks:</p>

<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">include</span> <span class="n">proxy_params</span>;
</code></pre></div></div>

<p>LinuxServer’s SWAG has some additional Fail2Ban configs for Nginx. If you’d like to add those, run the following commands:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>wget https://raw.githubusercontent.com/linuxserver/docker-swag/master/root/defaults/fail2ban/filter.d/nginx-badbots.conf <span class="nt">-O</span> /etc/fail2ban/filter.d/nginx-badbots.local
<span class="nb">sudo </span>wget https://raw.githubusercontent.com/linuxserver/docker-swag/master/root/defaults/fail2ban/filter.d/nginx-deny.conf <span class="nt">-O</span> /etc/fail2ban/filter.d/nginx-deny.local
<span class="nb">sudo </span>wget https://raw.githubusercontent.com/linuxserver/docker-swag/master/root/defaults/fail2ban/filter.d/nginx-unauthorized.conf <span class="nt">-O</span> /etc/fail2ban/filter.d/nginx-unauthorized.local
</code></pre></div></div>

<p>Then add the following lines to <code class="language-plaintext highlighter-rouge">/etc/fail2ban/jail.local</code>:</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[nginx-badbots]</span>
<span class="py">enabled</span>  <span class="p">=</span> <span class="kc">true</span>
<span class="py">port</span>     <span class="p">=</span> <span class="err">http</span><span class="p">,</span><span class="err">https</span>
<span class="py">filter</span>   <span class="p">=</span> <span class="err">nginx-badbots</span>
<span class="py">logpath</span>  <span class="p">=</span> <span class="err">%(nginx_access_log)s</span>

<span class="nn">[nginx-deny]</span>
<span class="py">enabled</span>  <span class="p">=</span> <span class="kc">true</span>
<span class="py">port</span>     <span class="p">=</span> <span class="err">http</span><span class="p">,</span><span class="err">https</span>
<span class="py">filter</span>   <span class="p">=</span> <span class="err">nginx-deny</span>
<span class="py">logpath</span>  <span class="p">=</span> <span class="err">%(nginx_error_log)s</span>

<span class="nn">[nginx-unauthorized]</span>
<span class="py">enabled</span>  <span class="p">=</span> <span class="kc">true</span>
<span class="py">port</span>     <span class="p">=</span> <span class="err">http</span><span class="p">,</span><span class="err">https</span>
<span class="py">filter</span>   <span class="p">=</span> <span class="err">nginx-unauthorized</span>
<span class="py">logpath</span>  <span class="p">=</span> <span class="err">%(nginx_access_log)s</span>
</code></pre></div></div>

<p>Badbots jail filters known bad bots by their user-agents. Deny jail filters requests that you’ve blocked in your Nginx config. Unauthorized jail filters, well, unauthorized requests (401 error).</p>

<h2 id="cloudflare">Cloudflare</h2>

<p>If you’re using Cloudflare proxy, we need to do a bit more so that Fail2Ban bans the end user’s IP and not Cloudflare IPs. Follow my blog post on <a href="https://kenhv.com/blog/fail2ban-with-nginx-and-cloudflare-ipv6">setting up Fail2Ban With Nginx and Cloudflare Free</a>. For all Nginx jails, you should be using the same <code class="language-plaintext highlighter-rouge">action</code> as the ones in that post.</p>

<h2 id="cis-benchmarks">CIS Benchmarks</h2>

<p><a href="https://www.cisecurity.org/cis-benchmarks">CIS benchmarks</a> are configuration recommendations and best practices to harden and protect your servers. They provide benchmarks for operating systems, cloud providers, and several applications like Docker and Nginx. You can download their benchmarks <a href="https://downloads.cisecurity.org/">here</a>. Applying their level 1 profiles is good enough for most.</p>

<p>Ubuntu Pro users can use the <a href="https://ubuntu.com/security/certifications/docs/usg">Ubuntu Security Guide</a> tool to audit and apply CIS and DISA-STIG (security guidelines from the U.S. Department of Defense) profiles automatically. For Debian, OVHCloud maintains scripts that you can use. You can find the scripts and instructions in <a href="https://github.com/ovh/debian-cis">this GitHub repo</a>.</p>

<p>Ensure you don’t blindly apply everything. Go through each and every configuration.</p>

<p>That pretty much covers it. Just make sure you’re using strong and random passwords/passphrases for everything.</p>

<p>If you have any comments or suggestions, feel free to <a href="mailto:ken@kenhv.com">mail me</a>!</p>

<h2 id="changelog">Changelog</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">10 Sep 24</code>: Added system account section, additional Nginx headers, and <code class="language-plaintext highlighter-rouge">rsyslog</code> dependency to firewall and Fail2Ban sections</li>
  <li><code class="language-plaintext highlighter-rouge">09 Sep 24</code>: Added CIS benchmarks section</li>
  <li><code class="language-plaintext highlighter-rouge">06 Sep 24</code>: Added <code class="language-plaintext highlighter-rouge">passwd --lock</code> and expanded snippet to disable UFW syslog spam</li>
  <li><code class="language-plaintext highlighter-rouge">30 Aug 24</code>: Added basic explanations</li>
  <li><code class="language-plaintext highlighter-rouge">29 Aug 24</code>: Added info about UFW and Docker</li>
  <li><code class="language-plaintext highlighter-rouge">29 Aug 24</code>: Added nologin setup for root</li>
</ul>]]></content><author><name>Ken Harris</name></author><summary type="html"><![CDATA[A guide to securing and hardening a Linux server install.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kenhv.com/assets/images/og/posts/securing-a-linux-server.png" /><media:content medium="image" url="https://kenhv.com/assets/images/og/posts/securing-a-linux-server.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Fail2Ban With Nginx and Cloudflare Free (With IPv6 Support)</title><link href="https://kenhv.com/blog/fail2ban-with-nginx-and-cloudflare-ipv6" rel="alternate" type="text/html" title="Fail2Ban With Nginx and Cloudflare Free (With IPv6 Support)" /><published>2024-08-04T00:00:00+05:30</published><updated>2024-09-20T17:01:24+05:30</updated><id>https://kenhv.com/blog/fail2ban-with-nginx-and-cloudflare-ipv6</id><content type="html" xml:base="https://kenhv.com/blog/fail2ban-with-nginx-and-cloudflare-ipv6"><![CDATA[<p>This post will teach you how to set up Fail2Ban actions for services reverse-proxied by Nginx and proxied by Cloudflare. I’ll be using Vaultwarden as an example. I have Nginx and Fail2Ban installed natively, and Vaultwarden in a Docker container. You can adjust it to work with Nginx and/or Fail2Ban running in Docker containers.</p>

<h2 id="unmask-visitor-ip-in-nginx">Unmask Visitor IP in Nginx</h2>

<p>Nginx needs to know the visitor’s real IP for the ban to work. Without this step, Nginx will simply see Cloudflare IPs and let it through. Even if you’re not going to use Fail2Ban, you should set this up.</p>

<p>Make sure <code class="language-plaintext highlighter-rouge">jq</code> and <code class="language-plaintext highlighter-rouge">crontab</code> are installed in your system. We’ll be using a cronjob to fetch Cloudflare IPs and Nginx’s <a href="https://nginx.org/en/docs/http/ngx_http_realip_module.html">ngx_http_realip_module</a> to unmask the IPs. The script we’re using is from <a href="https://github.com/jaapmarcus/nginx-cloudflare-real-ip/tree/use-api-instead">this GitHub repo</a>.</p>

<p>Place the script somewhere on your system. I have it in <code class="language-plaintext highlighter-rouge">/opt/scripts/cloudflare.sh</code>.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>

<span class="nv">cf_ips</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span>curl <span class="nt">-fsLm2</span> <span class="nt">--retry</span> 1 https://api.cloudflare.com/client/v4/ips<span class="si">)</span><span class="s2">"</span>
<span class="nv">CLOUDFLARE_FILE_PATH</span><span class="o">=</span><span class="k">${</span><span class="nv">1</span><span class="k">:-</span><span class="p">/etc/nginx/cloudflare</span><span class="k">}</span>

<span class="nb">echo</span> <span class="s2">"# Cloudflare IP Ranges"</span> <span class="o">&gt;</span> <span class="nv">$CLOUDFLARE_FILE_PATH</span>
<span class="nb">echo</span> <span class="s2">""</span> <span class="o">&gt;&gt;</span> <span class="nv">$CLOUDFLARE_FILE_PATH</span>
<span class="nb">echo</span> <span class="s2">"# - IPv4"</span> <span class="o">&gt;&gt;</span> <span class="nv">$CLOUDFLARE_FILE_PATH</span>
<span class="k">for </span>ipv4 <span class="k">in</span> <span class="si">$(</span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$cf_ips</span><span class="s2">"</span> | jq <span class="nt">-r</span> <span class="s1">'.result.ipv4_cidrs[]//""'</span> | <span class="nb">sort</span><span class="si">)</span><span class="p">;</span> <span class="k">do
    </span><span class="nb">echo</span> <span class="s2">"set_real_ip_from </span><span class="nv">$ipv4</span><span class="s2">;"</span> <span class="o">&gt;&gt;</span> <span class="nv">$CLOUDFLARE_FILE_PATH</span>
<span class="k">done
</span><span class="nb">echo</span> <span class="s2">""</span> <span class="o">&gt;&gt;</span> <span class="nv">$CLOUDFLARE_FILE_PATH</span>

<span class="nb">echo</span> <span class="s2">"# - IPv6"</span> <span class="o">&gt;&gt;</span> <span class="nv">$CLOUDFLARE_FILE_PATH</span>
<span class="k">for </span>ipv6 <span class="k">in</span> <span class="si">$(</span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$cf_ips</span><span class="s2">"</span> | jq <span class="nt">-r</span> <span class="s1">'.result.ipv6_cidrs[]//""'</span> | <span class="nb">sort</span><span class="si">)</span><span class="p">;</span> <span class="k">do
    </span><span class="nb">echo</span> <span class="s2">"set_real_ip_from </span><span class="nv">$ipv6</span><span class="s2">;"</span> <span class="o">&gt;&gt;</span> <span class="nv">$CLOUDFLARE_FILE_PATH</span>
<span class="k">done
</span><span class="nb">echo</span> <span class="s2">""</span> <span class="o">&gt;&gt;</span> <span class="nv">$CLOUDFLARE_FILE_PATH</span>

<span class="nb">echo</span> <span class="s2">"real_ip_header CF-Connecting-IP;"</span> <span class="o">&gt;&gt;</span> <span class="nv">$CLOUDFLARE_FILE_PATH</span>

nginx <span class="nt">-t</span> <span class="o">&amp;&amp;</span> systemctl reload nginx
</code></pre></div></div>

<p>Make sure the file permissions are set to <code class="language-plaintext highlighter-rouge">755</code> (<code class="language-plaintext highlighter-rouge">sudo chmod 755 /opt/scripts/cloudflare.sh</code>).</p>

<p>Add the following cronjob using <code class="language-plaintext highlighter-rouge">sudo crontab -e</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Sync Cloudflare IPs and reload Nginx</span>
0 4 <span class="k">*</span> <span class="k">*</span> <span class="k">*</span> /opt/scripts/cloudflare.sh <span class="o">&gt;</span>/dev/null 2&gt;&amp;1
</code></pre></div></div>

<p>Add this line to the <code class="language-plaintext highlighter-rouge">http</code> block in your Nginx config <code class="language-plaintext highlighter-rouge">/etc/nginx/nginx.conf</code>:</p>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">include</span> <span class="n">/etc/nginx/cloudflare</span>;
</code></pre></div></div>

<p>Now for every service you’re reverse-proxying, add this line inside <code class="language-plaintext highlighter-rouge">location</code> in the <code class="language-plaintext highlighter-rouge">server</code> block:</p>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">include</span> <span class="n">proxy_params</span>;
</code></pre></div></div>

<p>Run <code class="language-plaintext highlighter-rouge">sudo nginx -t</code> to verify the config, and reload the config using <code class="language-plaintext highlighter-rouge">sudo systemctl reload nginx</code>. Nginx will now unmask the visitors’ real IP coming through Cloudflare proxy.</p>

<h2 id="setup-cloudflare">Setup Cloudflare</h2>

<p>Free Cloudflare accounts are limited to 5 WAF rules per zone. But Cloudflare offers one free <a href="https://developers.cloudflare.com/waf/tools/lists/custom-lists/">custom list</a> that can store 10,000 IPs. This is more than enough.</p>

<p>Start by creating a custom list. Follow <a href="https://developers.cloudflare.com/waf/tools/lists/create-dashboard/">these instructions</a>. You can name it whatever you want. Make sure “Type” is set to “IP”. Select “Create” and check the URL. It will be in the following format: <code class="language-plaintext highlighter-rouge">https://dash.cloudflare.com/&lt;account-id&gt;/configurations/lists/&lt;list-id&gt;/add</code>. Note down the values in <code class="language-plaintext highlighter-rouge">account-id</code> and <code class="language-plaintext highlighter-rouge">list-id</code>, we’ll be using this later.</p>

<p>We also need an API token to edit this list. Follow <a href="https://developers.cloudflare.com/fundamentals/api/get-started/create-token/">these instructions</a>. We’ll be using a custom token. Set the permissions to “Account”, “Account Filter Lists”, and “Edit”. Note down the API token.</p>

<p>Next up, create a WAF rule. Follow <a href="https://developers.cloudflare.com/waf/custom-rules/create-dashboard/">these instructions</a>. Set “Field” to “IP Source Address” and “Operator” to “is in list”. Your list should automatically be selected. Set “Action” to “Block” and “Order” to “First”. That’s your Cloudflare setup done!</p>

<h2 id="setup-fail2ban">Setup Fail2Ban</h2>

<p>Make sure <code class="language-plaintext highlighter-rouge">jp</code> is installed in your system.</p>

<p>The action we’re going to be using is from <a href="https://gist.github.com/Xunnamius/6057a660d06bcf13cc1f478af9131423?permalink_comment_id=5049552#gistcomment-5049552">@sebres</a>. This supports both IPv4 and IPv6 addresses. Create <code class="language-plaintext highlighter-rouge">/etc/fail2ban/action.d/cloudflare-list.conf</code> with the following contents:</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[Definition]</span>
<span class="py">actionban</span> <span class="p">=</span> <span class="err">curl</span> <span class="err">-s</span> <span class="err">-o</span> <span class="err">/dev/null</span> <span class="err">-X</span> <span class="err">POST</span> <span class="err">&lt;_cf_api_prms&gt;</span> <span class="se">\
</span>                 <span class="err">-d</span> <span class="s">'[{"ip":"'"&lt;cfip&gt;"'","comment":"Created by fail2ban &lt;name&gt;"}]'</span> <span class="se">\
</span>                 <span class="err">&lt;_cf_api_url&gt;</span>
<span class="py">actionunban</span> <span class="p">=</span> <span class="py">id</span><span class="p">=</span><span class="err">$(curl</span> <span class="err">-s</span> <span class="err">-X</span> <span class="err">GET</span> <span class="err">&lt;_cf_api_prms&gt;</span> <span class="se">\
</span>                   <span class="py">"&lt;_cf_api_url&gt;?search=&lt;cfip&gt;&amp;per_page</span><span class="p">=</span><span class="mi">1</span><span class="s">" </span><span class="se">\
</span><span class="s">                   | { jp --unquoted 'result[0].id | not_null(@, `""`)' 2&gt;/dev/null; })</span><span class="err">
</span>              <span class="err">if</span> <span class="p">[</span> <span class="err">-z</span> <span class="s">"$id"</span> <span class="p">]</span><span class="err">;</span> <span class="err">then</span> <span class="err">echo</span> <span class="s">"&lt;name&gt;: id for &lt;ip&gt; cannot be found"</span><span class="err">;</span> <span class="err">exit</span> <span class="mi">0</span><span class="err">;</span> <span class="err">fi;</span>
              <span class="err">curl</span> <span class="err">-s</span> <span class="err">-o</span> <span class="err">/dev/null</span> <span class="err">-X</span> <span class="err">DELETE</span> <span class="err">&lt;_cf_api_prms&gt;</span> <span class="err">\</span>
                   <span class="err">-d</span> <span class="err">'{"items":</span><span class="nn">[{"id":"'"$id"'"}]</span><span class="err">}'</span> <span class="err">\</span>
                   <span class="err">&lt;_cf_api_url&gt;</span>
<span class="py">_cf_api_url</span> <span class="p">=</span> <span class="err">https://api.cloudflare.com/client/v</span><span class="mi">4</span><span class="err">/accounts/&lt;cfaccountid&gt;/rules/lists/&lt;cfbanlistid&gt;/items</span>
<span class="py">_cf_api_prms</span> <span class="p">=</span> <span class="err">-H</span> <span class="s">'Authorization: bearer &lt;cfapitoken&gt;'</span> <span class="err">-H</span> <span class="s">'Content-Type: application/json'</span>

<span class="nn">[Init]</span>
<span class="py">cfip</span> <span class="p">=</span> <span class="err">&lt;ip&gt;</span>

<span class="nn">[Init?family=inet6]</span>
<span class="py">cfip</span> <span class="p">=</span> <span class="err">$(fail</span><span class="mi">2</span><span class="err">ban-python</span> <span class="err">-c</span> <span class="s">'import sys; from fail2ban.server.ipdns import IPAddr; a = IPAddr(sys.argv[1]+"/"+sys.argv[2]); print(str(a))'</span> <span class="s">"&lt;ip&gt;"</span> <span class="mi">64</span><span class="err">)</span>
</code></pre></div></div>

<p>Now create <code class="language-plaintext highlighter-rouge">/etc/fail2ban/action.d/cloudflare-list.local</code> with the following contents:</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[Init]</span>
<span class="py">cfapitoken</span> <span class="p">=</span> <span class="err">&lt;api-token&gt;</span>
<span class="py">cfaccountid</span> <span class="p">=</span> <span class="err">&lt;account-id&gt;</span>
<span class="py">cfbanlistid</span> <span class="p">=</span> <span class="err">&lt;list-id&gt;</span>
</code></pre></div></div>

<p>Fill in the details, and <strong>make sure</strong> you set the file’s permissions to <code class="language-plaintext highlighter-rouge">640</code> (<code class="language-plaintext highlighter-rouge">sudo chmod 640 /etc/fail2ban/action.d/cloudflare-list.local</code>) to ensure only <em>root</em> can read this file.</p>

<p>Let’s configure Fail2Ban for Vaultwarden. <a href="https://github.com/dani-garcia/vaultwarden/wiki/Logging">Enable logging</a> in Vaultwarden. Create <code class="language-plaintext highlighter-rouge">/etc/fail2ban/filter.d/vaultwarden.local</code> with the following contents:</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[INCLUDES]</span>
<span class="py">before</span> <span class="p">=</span> <span class="err">common.conf</span>

<span class="nn">[Definition]</span>
<span class="py">failregex</span> <span class="p">=</span> <span class="err">^.*?Username</span> <span class="err">or</span> <span class="err">password</span> <span class="err">is</span> <span class="err">incorrect\.</span> <span class="err">Try</span> <span class="err">again\.</span> <span class="err">IP:</span> <span class="err">&lt;ADDR&gt;\.</span> <span class="err">Username:.*$</span>
<span class="py">ignoreregex</span> <span class="p">=</span>
</code></pre></div></div>

<p>Now create <code class="language-plaintext highlighter-rouge">/etc/fail2ban/jail.d/vaultwarden.local</code> with the following contents:</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[vaultwarden]</span>
<span class="py">enabled</span> <span class="p">=</span> <span class="kc">true</span>
<span class="py">filter</span> <span class="p">=</span> <span class="err">vaultwarden</span>
<span class="py">backend</span> <span class="p">=</span> <span class="err">auto</span>
<span class="py">logpath</span> <span class="p">=</span> <span class="err">&lt;path/to/vaultwarden.log&gt;</span>
<span class="py">action</span> <span class="p">=</span> <span class="err">cloudflare-list</span>
         <span class="err">nginx-block-map</span>
<span class="py">maxretry</span> <span class="p">=</span> <span class="mi">3</span>
<span class="py">bantime</span> <span class="p">=</span> <span class="mi">1</span><span class="err">d</span>
<span class="py">findtime</span> <span class="p">=</span> <span class="mi">15</span><span class="err">m</span>
</code></pre></div></div>

<p>Depending on your config, you might want to set <a href="https://github.com/dani-garcia/vaultwarden/wiki/Fail2Ban-Setup#note-for-docker-users">chain = FORWARD</a>. Note that we’re not banning the IP using firewall rules, because we’ll be receiving requests from Cloudflare’s IPs.</p>

<h2 id="setup-nginx">Setup Nginx</h2>

<p>Add the following line to the <code class="language-plaintext highlighter-rouge">http</code> block in your main Nginx config (<code class="language-plaintext highlighter-rouge">/etc/nginx/nginx.conf</code>):</p>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">map</span> <span class="nv">$remote_addr</span> <span class="nv">$ip_blacklisted</span> <span class="p">{</span> <span class="kn">include</span> <span class="s">blacklisted-sessions.map</span><span class="p">;</span> <span class="p">}</span>
</code></pre></div></div>

<p>Add the following line to the <code class="language-plaintext highlighter-rouge">server</code> block of services you’re reverse-proxying:</p>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="s">(</span><span class="nv">$ip_blacklisted</span><span class="s">)</span> <span class="p">{</span> <span class="kn">return</span> <span class="mi">444</span><span class="p">;</span> <span class="p">}</span>
</code></pre></div></div>

<p>The non-standard return code 444 closes a connection without sending a response header. You can also return 403 if you like.</p>

<h2 id="test-your-config">Test Your Config</h2>

<p>To verify if everything is working, go to your Vaultwarden instance and try logging in with incorrect credentials 3 times. Check <code class="language-plaintext highlighter-rouge">/var/log/fail2ban.log</code> (<code class="language-plaintext highlighter-rouge">sudo tail -f /var/log/fail2ban.log</code>), it should say that your IP has been banned. Without reloading your Vaultwarden web vault page, try logging in with the correct credentials. It won’t work, as Nginx will only return 403. If you reload the page, Cloudflare will say you’ve been banned. To unban yourself, run <code class="language-plaintext highlighter-rouge">sudo fail2ban-client set vaultwarden unbanip &lt;banned-ip&gt;</code>. Congrats! You have secured your Vaultwarden instance with two layers of protection using Fail2Ban.</p>]]></content><author><name>Ken Harris</name></author><summary type="html"><![CDATA[A guide to setting up Fail2Ban with Nginx and Cloudflare free plan with IPv6 support. Includes instructions to restore original visitor IP in Nginx.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kenhv.com/assets/images/og/posts/fail2ban-with-nginx-and-cloudflare-ipv6.png" /><media:content medium="image" url="https://kenhv.com/assets/images/og/posts/fail2ban-with-nginx-and-cloudflare-ipv6.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">I Switched to Ubuntu and You Should Too</title><link href="https://kenhv.com/blog/i-switched-to-ubuntu-and-you-should-too" rel="alternate" type="text/html" title="I Switched to Ubuntu and You Should Too" /><published>2024-08-02T00:00:00+05:30</published><updated>2024-08-28T23:29:13+05:30</updated><id>https://kenhv.com/blog/i-switched-to-ubuntu-and-you-should-too</id><content type="html" xml:base="https://kenhv.com/blog/i-switched-to-ubuntu-and-you-should-too"><![CDATA[<figure>
    <img decoding="async" fetchpriority="high" style="aspect-ratio:16/9" width="650" src="/assets/images/bell-curve-linux-distros.webp" alt="Bell curve meme about Linux distros." />
    <figcaption>Don't be the midwit.</figcaption>
</figure>

<p>After years of using Arch Linux, I’ve settled on Ubuntu. And I think you should too. “But snap bad! Canonical bad! Ubuntu is for noobs!” I don’t like snaps either, and guess what? It takes all of 60 seconds to remove it entirely. That’s a hell of a lot quicker than setting up Arch. Most people hating on Ubuntu have not used it in a long time. Everyone I’ve talked to has no solid arguments against Ubuntu other than the above. Let’s see if Ubuntu really is as horrible as it’s made out to be.</p>

<h2 id="how-i-ended-up-on-ubuntu">How I Ended Up on Ubuntu</h2>

<p>I’m tired of going to the Arch Wiki every time I need something. It was fun to tinker with my OS all day long when I was younger, but now I need something that works out of the box. I want my OS to get out of the way, and Arch got in my way one too many times. I moved to Windows 11 for 6 months and gave up after it started slowing down and heating up (as always). I decided to go back to Linux, but I needed something that <em>just works</em>.</p>

<p>Arch and derivatives (the only good one is EndeavourOS) were out of the question immediately. I also decided not to use niche distros like openSUSE for the same reason. My first choice was Fedora. DNF is terrible but I still considered it, right up until I learnt that hardware acceleration is not a thing anymore <em>OOTB</em>. Instant pass. Pop!_OS has been focused on COSMIC for a while, so that’s a no-go for now. Linux Mint is great, but I wanted newer packages and a modern DE that supports Wayland (GNOME). I have a multi-monitor setup with different refresh rates, and Wayland handles it well.</p>

<p>Then I remembered Ubuntu existed. The last time I used Ubuntu was when Canonical sent free CDs. I read about snaps being bad all the time, but apart from that, I didn’t remember any other criticism. Might as well give it a try, right? Surely it can’t be that bad. And boy oh boy, it was much better than I was expecting.</p>

<h2 id="the-ubuntu-experience">The Ubuntu Experience</h2>

<p>Right off the bat, the installation process was a breeze. I only had to tick a box for NVIDIA drivers to be set up. The same goes for codecs and hardware acceleration. I restarted after the setup and everything just worked! I was genuinely surprised. Literally everything just worked, I didn’t have to do anything.</p>

<p>I removed snapd because I prefer native packages. I found out that a lot of popular software like Firefox, Chrome, VSCode, Spotify, Discord, etc. provide official native packages/repos for Ubuntu. This is a huge win in my book. Proprietary software like this is usually only available through containerised package formats like Snaps/Flatpak or some random source (like the AUR).</p>

<p>If you don’t like Ubuntu’s customised GNOME, you can simply install gnome-session and switch to vanilla GNOME from the display manager. If you don’t like GNOME itself, you have several Ubuntu spins such as Kubuntu for KDE, Xubuntu for XFCE, etc. If you want to optimise Ubuntu further, check out <a href="/blog/linux-tweaks#ubuntu-specific-optimisations">my blog post</a>.</p>

<p>What can I say? It’s fast, it just works, and it’s widely supported. It’s a boring distro. Boring is good.</p>

<h2 id="why-the-hate">Why the Hate?</h2>

<p>First off, I strongly believe Ubuntu gets hated on so much because of how popular it is. The other arguments are just excuses. I’ll get to the other issues but I’ll address this first. This is a trend I’ve seen with GnOo/LinUx users, they feel the need to use increasingly niche tools just to feel better about themselves. Using Gentoo with an obscure window manager and Neovim won’t improve the quality of your work whatsoever. The tools you use don’t make you stand out, the work you do with them does.</p>

<p>The next big reason is <em>muh bloat</em>. Ubuntu offers several spins, and all of them have a minimal install option that comes with only the necessary programs. A minimal install of Ubuntu 24.04 takes up less than 800MB of RAM; Xubuntu 24.04, less than 500MB. Ubuntu is on par with, if not lighter than Linux Mint. It’s 2024, 8GB RAM has become the norm. Stop obsessing over saving a couple hundred megabytes of RAM. Unused RAM is wasted RAM. And honestly, CPU usage at idle is a much more important metric if you want to measure “bloat”.</p>

<p>“But snaps are slow and horrible!” If you don’t want snaps on your system, you can just remove it in a minute. It’s that easy. I removed it as well, not because I hate snaps in particular, but because I don’t like containerised package formats in general. Installing Flatpak is also trivial if you need it, but if your main complaint about snaps is its speed, stick to native packages.</p>

<p>If you genuinely have a reason to not use Ubuntu other than these, such as something not working or you or you needing a slower/faster release cycle, then this post wasn’t meant for you. This is for GnOo/LiNuX users who hate popular things because they think it’s cool.</p>

<h2 id="why-you-should-use-ubuntu">Why You Should Use Ubuntu</h2>

<p>Because it just works!</p>

<p>No other distro configures drivers and hardware acceleration properly out of the box. Doing this manually can be a pain even for long-time Linux users. Ubuntu (and by extension, Linux Mint) does this very well. The only thing holding me back from using Linux Mint is GNOME. I love GNOME. I think its the most polished desktop experience you can get on Linux (I can feel KrashDE users getting angry :P). Ubuntu offers spins for pretty much all DEs.</p>

<p>Support is amazing. I can simply Google a problem and find step-by-step instructions specifically for Ubuntu.</p>

<p>A lot of big software offer official native packages for Ubuntu. Since I dislike containerised package formats, previously my only choice was to use the AUR and trust the <code class="language-plaintext highlighter-rouge">-bin</code> package maintainers. Now I can just use official packages! Fun fact, a lot of the <code class="language-plaintext highlighter-rouge">-bin</code> packages in AUR just convert Ubuntu debs to Arch Linux pkgs.</p>

<p>Ubuntu offers full secure boot support, even when using NVIDIA. You can dual-boot with Windows and play your favourite game with kernel-level <del>rootkit</del> anti-cheat!</p>

<p>I’d much rather go with Ubuntu and <em>remove</em> one or two components I don’t like, than go with some other distro and spend hours <em>fixing</em> missing/misconfigured basic functionality.</p>

<h2 id="the-end">The End</h2>

<p>There is value in the default experience. And I believe that Ubuntu does it the best among all Linux distros. If you’re suggesting Linux for casual users, suggest Linux Mint or Ubuntu. Don’t overwhelm people with the paradox of choice.</p>]]></content><author><name>Ken Harris</name></author><summary type="html"><![CDATA[An essay on why you should be using (and recommending) Ubuntu.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kenhv.com/assets/images/bell-curve-linux-distros.webp" /><media:content medium="image" url="https://kenhv.com/assets/images/bell-curve-linux-distros.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Configuring BSNL FTTH</title><link href="https://kenhv.com/blog/configuring-bsnl-ftth" rel="alternate" type="text/html" title="Configuring BSNL FTTH" /><published>2024-07-25T00:00:00+05:30</published><updated>2024-09-20T17:01:24+05:30</updated><id>https://kenhv.com/blog/configuring-bsnl-ftth</id><content type="html" xml:base="https://kenhv.com/blog/configuring-bsnl-ftth"><![CDATA[<p>BSNL-provided routers are bad. They are horribly slow, have poor range, and are not very secure. Their routers cost ₹3,000. For the same price, you can get much better hardware. Here’s how to set up BSNL FTTH with your own hardware.</p>

<h2 id="requirements">Requirements</h2>

<ul>
  <li>ONU</li>
  <li>Router</li>
  <li>VLAN ID</li>
  <li>PPPoE username (password is always <code class="language-plaintext highlighter-rouge">password</code>)</li>
</ul>

<p>The ONU must be TEC-certified. A list of TEC-certified products can be found at the <a href="https://www.mtcte.tec.gov.in/certified_equipments">MTCTE Portal</a>. If you really don’t want to go that route, you can spoof your MAC address to match a TEC-certified ONU. I had a BSNL-provided combo device, so I spoofed my MAC to that. You can also use a TEC-certified combo device with another router by using bridge mode. You can find instructions in <a href="https://www.reddit.com/r/bsnl/comments/ht37q4/guide_for_bsnl_ftth/">this Reddit post</a>.</p>

<p>If you have a GPON connection, you’ll also need the serial number of your BSNL combo device.</p>

<p>I have a <a href="https://www.amazon.in/dp/B0CYQDZ8NC">TP-Link XZ000-G7 XPON ONU</a> and a <a href="https://www.amazon.in/dp/B07GVR9TG7">TP-Link Archer C6 AC1200 Router</a>. I’ve provided step-by-step instructions for these devices, but the configuration is the same for any ONU and router. You just have to find where each setting is located.</p>

<h2 id="onu-configuration">ONU Configuration</h2>

<p>Connect to your ONU using an ethernet cable. The TP-Link ONU doesn’t have a DHCP server by default, so you’ll have to configure IP assignment manually. Follow these <a href="https://support.microsoft.com/en-us/windows/change-tcp-ip-settings-bd0a07af-15f5-cd6a-363f-ca2b6f391ace">instructions for Windows</a>. On Linux, you can do this from NetworkManager settings.</p>

<table>
  <tbody>
    <tr>
      <td>IP Address</td>
      <td><code class="language-plaintext highlighter-rouge">192.168.1.100</code></td>
    </tr>
    <tr>
      <td>Subnet Mask</td>
      <td><code class="language-plaintext highlighter-rouge">255.255.255.0</code></td>
    </tr>
    <tr>
      <td>Gateway</td>
      <td><code class="language-plaintext highlighter-rouge">192.168.1.1</code></td>
    </tr>
  </tbody>
</table>

<p>Now go to <a href="http://192.168.1.1">192.168.1.1</a>, set a password for your ONU, and log in. Go to Advanced, Network, and select VLAN Settings. Set “VLAN mode” to “VLAN TAG mode”, enter your VLAN ID, and set “Priority” to <code class="language-plaintext highlighter-rouge">1</code>. You can also turn on the DHCP server if you want easier access to your ONU in the future. You can now disconnect the ethernet and connect your ONU to the router.</p>

<p>If you have a GPON connection, go to XPON Settings and set serial number type to ASCII+HEX, then enter your BSNL router’s serial number. Set the password to <code class="language-plaintext highlighter-rouge">1234567890</code>.</p>

<h2 id="router-configuration">Router Configuration</h2>

<p>Connect to the router using ethernet or Wi-Fi. TP-Link also has an app to configure routers, you can use it if you like. Go to <a href="http://192.168.0.1">192.168.0.1</a> and go through the setup process. Set your connection type to PPPoE and enter your PPPoE username and password (password is <code class="language-plaintext highlighter-rouge">password</code>). If you’re migrating from another router, you’ll need to change your new router’s MAC address to the old one. Go to Advanced, Network, Internet, set “Router MAC Address” to “Use Custom MAC Address”, then enter your old MAC address. Finally, you need to configure MTU. Go to Advanced, Network, Internet, Advanced Settings, and change your MTU value to <code class="language-plaintext highlighter-rouge">1492</code>. BSNL caps MTU to 1492 across India.</p>

<p>Depending on your area, you might get IPv6 support. You can use the same PPPoE credentials for IPv6 as well.</p>

<h2 id="support">Support</h2>

<p>If you have any problems or questions, check out the <a href="https://broadband.forum/forums/bsnl-broadband/">India Broadband Forum</a> or the <a href="https://t.me/BSNLTelegram">BSNL Telegram group</a>.</p>]]></content><author><name>Ken Harris</name></author><summary type="html"><![CDATA[A guide to setting up BSNL FTTH with third-party routers and modems.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kenhv.com/assets/images/og/posts/configuring-bsnl-ftth.png" /><media:content medium="image" url="https://kenhv.com/assets/images/og/posts/configuring-bsnl-ftth.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Setting Up Firefox</title><link href="https://kenhv.com/blog/setting-up-firefox" rel="alternate" type="text/html" title="Setting Up Firefox" /><published>2024-07-11T00:00:00+05:30</published><updated>2024-09-25T16:33:31+05:30</updated><id>https://kenhv.com/blog/setting-up-firefox</id><content type="html" xml:base="https://kenhv.com/blog/setting-up-firefox"><![CDATA[<p>Google is <a href="https://blog.chromium.org/2024/05/manifest-v2-phase-out-begins.html">killing Manifest v2</a> in an attempt to kill ad blockers. This change will affect not only Chrome, but browsers based on Chromium such as Edge. It’s a good time to switch to Firefox. Everything just works; I’ve faced zero breakage on any site, even with some additional privacy protection settings turned on.</p>

<p>Most Firefox guides out there recommend using <a href="https://github.com/arkenfox/user.js">hardened configs</a> or <a href="https://librewolf.net">forks</a>. The only thing they harden is your life. I want to get shit done, not fumble around with my settings trying to unbreak a site every 30 seconds. My configuration is the middle ground, where your browser protects your privacy <em>and</em> your sanity.</p>

<h2 id="settings">Settings</h2>

<h3 id="general">General</h3>

<ul>
  <li>Use autoscrolling: On</li>
  <li>Recommend extensions as you browse: Off</li>
  <li>Recommend features as you browse: Off</li>
  <li>Ask whether to open or save files: On<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup></li>
</ul>

<h3 id="home">Home</h3>

<ul>
  <li>Sponsored shortcuts: Off</li>
  <li>Recommended Stories: Off</li>
</ul>

<h3 id="privacy-and-security">Privacy and Security</h3>

<ul>
  <li>Enhanced Tracking Protection: Strict</li>
  <li>Allow Firefox to make personalized extension recommendations: Off</li>
  <li>Enable HTTPS-Only Mode in all windows: On</li>
  <li>DNS over HTTPS: Max Protection<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup></li>
</ul>

<h2 id="extensions">Extensions</h2>

<p><a href="https://addons.mozilla.org/en-US/firefox/addon/ublock-origin">uBlock Origin</a> is the best ad blocker. I enable additional filter lists: everything under “Cookie notices” and everything under “Annoyances”. I also add the <a href="https://www.i-dont-care-about-cookies.eu/abp">I don’t care about cookies</a> filter list (no need to install the extension).</p>

<p><a href="https://addons.mozilla.org/en-US/firefox/addon/darkreader">Dark Reader</a> adds a dark mode to every website. After installing it, open the extension menu, go to “Dev Tools”, “Advanced”, and click “Preview new design”.</p>

<p><a href="https://gitflic.ru/project/magnolia1234/bpc_uploads/blob/raw?file=bypass_paywalls_clean-latest.xpi&amp;inline=false">Bypass Paywalls Clean</a> lets you bypass most of the popular paywalled sites and sites that need you to login after viewing a page or two (like Quora). After installing it, go to the extension options and enable “Check for update rules at startup”. Then go to “Opt-in” and enable everything.</p>

<p><a href="https://addons.mozilla.org/en-US/firefox/addon/violentmonkey">Violentmonkey</a> is a userscript manager, which I use to install the following userscripts. <a href="https://codeberg.org/Amm0ni4/bypass-all-shortlinks-debloated/raw/branch/main/Bypass_All_Shortlinks.user.js">Bypass All Shortlinks Debloated</a> automatically skips a lot of link shorteners that make you wait or require CAPTCHAs.</p>

<p><a href="https://addons.mozilla.org/en-US/firefox/addon/no-google-search-translation">No Google Search Translation</a> disables the annoying Google feature that translates all search results.</p>

<p><a href="https://addons.mozilla.org/en-US/firefox/addon/most-recent-tab/">Most Recent Tab</a> adds a shortcut to switch to the, well, most recent tab. After installing, you can go to Manage Extensions, click the settings icon, go to Manage Extension Shortcuts, and set the shortcut to what you want. I have mine set to <code class="language-plaintext highlighter-rouge">Ctrl + Q</code>.</p>

<p><a href="https://addons.mozilla.org/en-US/firefox/addon/enforce-browser-fonts/">Enforce Browser Fonts</a> lets you disable web fonts and use the browser configured fonts (<a href="/blog/treat-yourself-to-good-typography#improving-browser-fonts">related blog post</a>). You can change the default mode to whitelist or blacklist in the Manage Extension page.</p>

<p><a href="https://addons.mozilla.org/en-US/firefox/addon/imagus-mod/">Imagus Mod</a> is a continuation of the Imagus mod. It enlarges media on hover. After installing it, go to the extension’s settings, sieve tab, click the remove all button, click the update sieve button, then save. I also set it so that it only triggers if Ctrl is pressed.</p>

<p><a href="https://addons.mozilla.org/en-US/firefox/addon/dictionary-anyvhere/">Dictionary Anywhere</a> shows you the definition when you double-click a word.</p>

<h2 id="aboutconfig">about:config</h2>

<p>Setting <code class="language-plaintext highlighter-rouge">accessibility.force_disabled</code> to <code class="language-plaintext highlighter-rouge">1</code> in helps with <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1726887">memory usage and performance</a>.</p>

<p>To disable fullscreen fade animations, set <code class="language-plaintext highlighter-rouge">full-screen-api.transition-duration.enter</code> and <code class="language-plaintext highlighter-rouge">full-screen-api.transition-duration.leave</code> to <code class="language-plaintext highlighter-rouge">0 0</code>.</p>

<p>To disable fullscreen warnings, set <code class="language-plaintext highlighter-rouge">full-screen-api.warning.timeout</code> to <code class="language-plaintext highlighter-rouge">0</code>.</p>

<p>If you don’t use Pocket, you can disable it by setting <code class="language-plaintext highlighter-rouge">extensions.pocket.enabled</code> to <code class="language-plaintext highlighter-rouge">false</code>.</p>

<p>If you want to switch tabs by scrolling on the tab bar, set <code class="language-plaintext highlighter-rouge">toolkit.tabbox.switchByScrolling</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>If you want to reduce spacing in the UI, <a href="https://support.mozilla.org/en-US/kb/compact-mode-workaround-firefox">enable compact mode</a>.</p>

<h3 id="sync-aboutconfig-tweaks">Sync about:config Tweaks</h3>

<p>Firefox lets you sync custom preferences. Let’s take <code class="language-plaintext highlighter-rouge">accessibility.force_disabled</code> as an example. Prepend the preference with <code class="language-plaintext highlighter-rouge">services.sync.prefs.sync.</code> so that it becomes <code class="language-plaintext highlighter-rouge">services.sync.prefs.sync.accessibility.force_disabled</code>. Paste it in the search box, add it as a <code class="language-plaintext highlighter-rouge">boolean</code>, and set it to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<h2 id="thoughts-on-telemetry">Thoughts on Telemetry</h2>

<p>I’ve left telemetry on, and you should consider the same for FOSS applications. This helps the developers improve their applications. Remember: telemetry is not inherently evil, privacy-invasive telemetry is. You can visit <code class="language-plaintext highlighter-rouge">about:telemetry</code> in Firefox to view all the telemetry being sent.</p>

<h2 id="changelog">Changelog</h2>

<p>Note: Changes made before <code class="language-plaintext highlighter-rouge">25 Sep 24</code> are not covered.</p>

<ul>
  <li>Added Imagus Mod and Dictionary Anywhere extensions, added tweak to disable fullscreen warning</li>
</ul>

<h2 id="footnotes">Footnotes</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>I use this to open M3U files in MPV directly, for <a href="/blog/the-comfiest-streaming-service#streaming-with-mpv">my streaming setup</a>. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>I use <a href="https://quad9.net">Quad9</a> (<code class="language-plaintext highlighter-rouge">https://dns.quad9.net/dns-query</code>) as my DNS provider. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Ken Harris</name></author><summary type="html"><![CDATA[A list of extensions and tweaks to improve Firefox without breakage.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kenhv.com/assets/images/og/posts/setting-up-firefox.png" /><media:content medium="image" url="https://kenhv.com/assets/images/og/posts/setting-up-firefox.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Highlights From Children of Dune</title><link href="https://kenhv.com/blog/highlights-from-children-of-dune" rel="alternate" type="text/html" title="Highlights From Children of Dune" /><published>2024-07-08T00:00:00+05:30</published><updated>2024-08-28T23:29:13+05:30</updated><id>https://kenhv.com/blog/highlights-from-children-of-dune</id><content type="html" xml:base="https://kenhv.com/blog/highlights-from-children-of-dune"><![CDATA[<p>I’ve never highlighted this many passages from a non-philosophy book. Half a century later, the Dune saga is more relevant than ever. A must-read for sure.</p>

<h4 id="page-154">Page 154</h4>

<blockquote>
  <p>Paul had always said that stasis was the most dangerous of those things which were not natural. The only permanence was fluid. Change was all that mattered.</p>
</blockquote>

<h4 id="page-158">Page 158</h4>

<blockquote>
  <p>“A large populace held in check by a small but powerful force is quite a common situation in our universe. And we know the major conditions wherein this large populace may turn upon its keepers— “One: When they find a leader. This is the most volatile threat to the powerful; they must retain control of leaders. “Two: When the populace recognizes its chains. Keep the populace blind and unquestioning. “Three: When the populace perceives a hope of escape from bondage. They must never even believe that escape is possible!”</p>
</blockquote>

<h4 id="page-171">Page 171</h4>

<blockquote>
  <p>“The past may show the right way to behave if you live in the past, Stil, but circumstances change.”</p>
</blockquote>

<h4 id="page-198">Page 198</h4>

<blockquote>
  <p>“To suspect your own mortality is to know the beginning of terror; to learn irrefutably that you are mortal is to know the end of terror.”</p>
</blockquote>

<h4 id="page-247">Page 247</h4>

<blockquote>
  <p>When I am weaker than you, I ask you for freedom because that is according to your principles; when I am stronger than you, I take away your freedom because that is according to my principles.</p>
</blockquote>

<h4 id="page-267">Page 267</h4>

<blockquote>
  <p>The universe is just there; that’s the only way a Fedaykin can view it and remain the master of his senses. The universe neither threatens nor promises. It holds things beyond our sway: the fall of a meteor, the eruption of a spiceblow, growing old and dying. These are the realities of this universe and they must be faced regardless of how you feel about them. You cannot fend off such realities with words. They will come at you in their own wordless way and then, then you will understand what is meant by “life and death.” Understanding this, you will be filled with joy.</p>
</blockquote>

<h4 id="page-284">Page 284</h4>

<blockquote>
  <p>Governments, if they endure, always tend increasingly toward aristocratic forms. No government in history has been known to evade this pattern. And as the aristocracy develops, government tends more and more to act exclusively in the interests of the ruling class—whether that class be hereditary royalty, oligarchs of financial empires, or entrenched bureaucracy.</p>
</blockquote>

<h4 id="page-295">Page 295</h4>

<blockquote>
  <p>He was a mind-slaver and his enslaving process could be understood with extreme simplicity: he transferred technical knowledge without a transfer of values.</p>
</blockquote>

<h4 id="page-365">Page 365</h4>

<blockquote>
  <p>If you believe certain words, you believe their hidden arguments. When you believe something is right or wrong, true or false, you believe the assumptions in the words which express the arguments. Such assumptions are often full of holes, but remain most precious to the convinced.</p>
</blockquote>

<h4 id="page-402">Page 402</h4>

<blockquote>
  <p>There’s no mystery about a human life. It’s not a problem to be solved, but a reality to be experienced.</p>
</blockquote>

<h4 id="page-457">Page 457</h4>

<blockquote>
  <p>The patterns could guide and they could trap. One had to remember that patterns change.</p>
</blockquote>

<h4 id="page-568">Page 568</h4>

<blockquote>
  <p>The child who refuses to travel in the father’s harness, this is the symbol of man’s most unique capability. “I do not have to be what my father was. I do not have to obey my father’s rules or even believe everything he believed. It is my strength as a human that I can make my own choices of what to believe and what not to believe, of what to be and what not to be.”</p>
</blockquote>

<h4 id="page-596">Page 596</h4>

<blockquote>
  <p>I command you to carry it always next to your heart as a reminder that all humans are prone to error and all leaders are human.</p>
</blockquote>

<h4 id="page-599">Page 599</h4>

<blockquote>
  <p>“There’s always a prevailing mystique in any civilization,” Leto said. “It builds itself as a barrier against change, and that always leaves future generations unprepared for the universe’s treachery. All mystiques are the same in building these barriers—the religious mystique, the hero-leader mystique, the messiah mystique, the mystique of science/technology, and the mystique of nature itself. We live in an Imperium which such a mystique has shaped, and now that Imperium is falling apart because most people don’t distinguish between mystique and their universe. You see, the mystique is like demon possession; it tends to take over the consciousness, becoming all things to the observer.”</p>
</blockquote>]]></content><author><name>Ken Harris</name></author><summary type="html"><![CDATA[A collection of my highlights from Frank Herbert's Children of Dune.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kenhv.com/assets/images/og/posts/highlights-from-children-of-dune.png" /><media:content medium="image" url="https://kenhv.com/assets/images/og/posts/highlights-from-children-of-dune.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The Comfiest Streaming Service</title><link href="https://kenhv.com/blog/the-comfiest-streaming-service" rel="alternate" type="text/html" title="The Comfiest Streaming Service" /><published>2024-06-30T00:00:00+05:30</published><updated>2024-08-28T23:29:13+05:30</updated><id>https://kenhv.com/blog/the-comfiest-streaming-service</id><content type="html" xml:base="https://kenhv.com/blog/the-comfiest-streaming-service"><![CDATA[<p>What if I told you there’s a streaming service that</p>

<ul>
  <li>Has all movies, shows, anime, documentaries, etc. ever made</li>
  <li>Has all releases/cuts</li>
  <li>Has all quality options including HDR</li>
  <li>Doesn’t lock content regionally</li>
  <li>Lets you actually download media to your device</li>
  <li>Lets you use external media players and subtitles</li>
  <li>Is available on all platforms including TVs</li>
</ul>

<p>All for the low price of $3 a month? Oh, and you can convert any torrent to a direct link and get premium links for hosters like 4Shared and RapidGator. Sounds too good to be true? Well, it’s your lucky day.</p>

<h2 id="the-catch">The Catch</h2>

<p>You can simultaneously stream from 4 devices, but the catch is that they have to be on the same network. You can only access it from one IP at a time. If you’re using this at home, with all devices on the same WiFi network, this is not a problem. But you can’t stream from two different devices with two different networks (different IPs) at the same time.</p>

<h2 id="the-components">The Components</h2>

<p>This setup involves three components: <a href="https://www.stremio.com">Stremio</a>, Torrentio and <a href="https://real-debrid.com/">Real-Debrid</a>. Don’t be alarmed, I promise it’s <strong>real</strong> simple. Stremio is a streaming app that can be extended with add-ons. It has a polished interface that lets you browse all movies and shows listed on IMDb. Torrentio is a content aggregator add-on for Stremio, which lets you stream all those movies and shows directly in Stremio. Real-Debrid provides high-quality sources to Torrentio.</p>

<h2 id="whats-real-debrid">What’s Real-Debrid?</h2>

<p><a href="https://real-debrid.com/">Real-Debrid</a> is a debrid service. A debrid service lets you convert torrents to direct links and gives you access to dozens of premium hosting services (like RapidGator, 4Shared, etc). What makes a debrid service great is that they cache torrent files. They have a <strong>massive</strong> database of torrents stored in their servers. Pretty much anything you want to watch is available in torrent sites, and pretty much everything in torrent sites are cached in Real-Debrid’s servers. It’s as low as $3 a month (their 6 month plan is €16).</p>

<h2 id="setting-it-up">Setting It Up</h2>

<p>Sign up for a <a href="https://real-debrid.com/">Real-Debrid</a> account and buy a plan. Install <a href="https://www.stremio.com/">Stremio</a> and create an account. Now go to <a href="https://torrentio.strem.fun/configure">Torrentio</a>. Here you can configure several things such as providers, quality exclusions, sorting, language priority and maximum results per quality setting. The only option I change here is sorting, I have it set to “by quality then size”. Choose Real-Debrid as your debrid provider. Get your <a href="https://real-debrid.com/apitoken">Real-Debrid API key</a> and paste it in. Now click install, it will take you to Stremio where you can install the addon. That’s all there is to it! Now you can install Stremio on any device you want, you just have to sign in to your Stremio account. You don’t have to mess with Real-Debrid if you don’t want to. You can pretty much stop reading here.</p>

<h2 id="stremio-web-for-ios">Stremio Web for iOS</h2>

<p>Stremio doesn’t have a native app for iOS (available on macOS though), but you can use the PWA (progressive web app) with an external media player. I recommend <a href="https://apps.apple.com/in/app/outplayer/id1449923287">Outplayer</a>. There is only one problem with this setup. While Outplayer will remember where you left off, it won’t be synced with Stremio. To install the PWA, go to <a href="https://web.stremio.com">Stremio Web</a>, click on the share button, and add to home screen. Open the newly added Stremio app and login using the profile icon on the top right. It’ll say streaming server is not available, just select “don’t show again”. Go to Settings, scroll down to Advanced, and choose Outplayer as the external player.</p>

<h2 id="useful-addons">Useful Addons</h2>

<p>Stremio has quite the collection of addons. Browse through the addons section and add what you like. You can find more addons <a href="https://stremio-addons.netlify.app">here</a>. There are addons to connect Stremio with Letterboxd, Simkl and more. Cyberflix is a useful addon that shows trending movies on popular streaming services.</p>

<h2 id="streaming-with-mpv">Streaming With MPV</h2>

<p>I use MPV to consume media. I’ve written a <a href="https://kenhv.com/blog/setting-up-mpv">guide to set up MPV</a> if you’re interested. Stremio is actually built with MPV, but you can’t change the configuration. Stremio doesn’t have an option to choose MPV as an external player. You can either copy the stream link and open it with MPV, or use the <a href="https://web.stremio.com">web version</a>.</p>

<p>I prefer the web version, as I don’t have to install the Stremio app on my computer. It also saves quite a bit of time. In the web version, you can set m3u playlist as the external player in Settings. You can then configure MPV to automatically open m3u files.</p>

<h2 id="thoughts-on-piracy">Thoughts on Piracy</h2>

<blockquote>
  <p>Piracy is almost always a service problem and not a pricing problem.</p>

  <p>— Gabe Newell</p>
</blockquote>

<p>I pay for music streaming because it’s convenient. It has everything I want to listen to in one place and has recommendations, curated playlists, statistics; everything I need. There is simply no movie streaming service that’s convenient. Every service has a limited and different catalogue, they don’t have all the editions of a movie, and quite a lot of content is simply not available on any service. I made a fully automated media server years ago, but that isn’t as convenient as this setup is either. Like Gabe said, it’s not a problem of pricing. I am willing to pay for convenience. It’s just that there’s nothing convenient about the streaming services available today.</p>

<p>Oh and of course, all this information is purely for educational purposes, I am not responsible, blah blah. Have a good day.</p>]]></content><author><name>Ken Harris</name></author><summary type="html"><![CDATA[The $3 streaming service that beats everything on the market combined.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kenhv.com/assets/images/og/posts/the-comfiest-streaming-service.png" /><media:content medium="image" url="https://kenhv.com/assets/images/og/posts/the-comfiest-streaming-service.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Setting up MPV - The Supreme Media Player</title><link href="https://kenhv.com/blog/setting-up-mpv" rel="alternate" type="text/html" title="Setting up MPV - The Supreme Media Player" /><published>2024-06-09T00:00:00+05:30</published><updated>2024-08-28T23:29:13+05:30</updated><id>https://kenhv.com/blog/setting-up-mpv</id><content type="html" xml:base="https://kenhv.com/blog/setting-up-mpv"><![CDATA[<p>The media player you use sucks. Switch to MPV.</p>

<h2 id="installation">Installation</h2>

<p>If you’re on Linux, you know what to do. Install it through your package manager and skip to the configuration section. If you’re on Windows, read on. Make a folder where you want to install MPV, for example <code class="language-plaintext highlighter-rouge">D:\Programs\MPV</code>. Download the <a href="https://sourceforge.net/projects/mpv-player-windows/files/bootstrapper.zip/download">MPV bootstrapper</a> and extract the contents to the folder. Run <code class="language-plaintext highlighter-rouge">updater.bat</code> as administrator. It will ask you some questions. You can choose the default options for most questions. When it asks you to choose a variant (x86_64 or x86_64-v3), choose x86_64-v3 (press 2), unless your CPU was released over 10 years ago. I also install yt-dlp and FFmpeg.</p>

<p>Once the script is finished, go to the newly created <code class="language-plaintext highlighter-rouge">installer</code> folder and run <code class="language-plaintext highlighter-rouge">mpv-install.bat</code> as administrator. This will register the application in Windows. Congrats, you have now installed MPV. If you ever want to uninstall MPV, run the uninstall script first and then simply delete the MPV folder. But before you start using it, there’s some essential configuration to be done.</p>

<h2 id="configuration">Configuration</h2>

<p>Almost all configurations you will find on the internet overcomplicate everything. My config is small, simple, fast and makes MPV very user-friendly.</p>

<p>If you’re on Linux, your config folder will be <code class="language-plaintext highlighter-rouge">~/.config/mpv</code>. For Windows, create a folder called <code class="language-plaintext highlighter-rouge">portable_config</code> inside your MPV folder, this will be your config folder. Inside your config folder, create a new file named <code class="language-plaintext highlighter-rouge">mpv.conf</code> with the following contents:</p>

<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Essentials
</span><span class="n">vo</span>=<span class="n">gpu</span>-<span class="n">next</span>
<span class="n">gpu</span>-<span class="n">api</span>=<span class="n">auto</span>
<span class="n">hwdec</span>=<span class="n">auto</span>-<span class="n">safe</span>

<span class="c"># Smoothmotion
</span><span class="n">interpolation</span>=<span class="n">yes</span>
<span class="n">tscale</span>=<span class="n">oversample</span>
<span class="n">video</span>-<span class="n">sync</span>=<span class="n">display</span>-<span class="n">resample</span>

<span class="c"># QoL
</span><span class="n">keep</span>-<span class="n">open</span>=<span class="n">yes</span>
<span class="n">save</span>-<span class="n">position</span>-<span class="n">on</span>-<span class="n">quit</span>=<span class="n">yes</span>
<span class="n">autofit</span>-<span class="n">larger</span>=<span class="m">80</span>%<span class="n">x80</span>%

<span class="c"># UOSC
</span><span class="n">osd</span>-<span class="n">bar</span>=<span class="n">no</span>
<span class="n">border</span>=<span class="n">no</span>
</code></pre></div></div>

<p>Next up, we’re gonna install <a href="https://github.com/tomasklaen/uosc">UOSC</a>. UOSC is a custom GUI for MPV; the default GUI is extremely bare-bones. On Windows, go to your MPV installation folder (the folder should have mpv.exe in it), right click and open a terminal. Now run the following commands:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Set-ExecutionPolicy</span><span class="w"> </span><span class="nx">RemoteSigned</span><span class="w"> </span><span class="nt">-Scope</span><span class="w"> </span><span class="nx">CurrentUser</span><span class="w">
</span><span class="n">irm</span><span class="w"> </span><span class="nx">https://raw.githubusercontent.com/tomasklaen/uosc/HEAD/installers/windows.ps1</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">iex</span><span class="w">
</span></code></pre></div></div>

<p>Linux users, run the following command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/bin/bash <span class="nt">-c</span> <span class="s2">"</span><span class="si">$(</span>curl <span class="nt">-fsSL</span> https://raw.githubusercontent.com/tomasklaen/uosc/HEAD/installers/unix.sh<span class="si">)</span><span class="s2">"</span>
</code></pre></div></div>

<p>And that’s it! You can now start using MPV like any other media player. Really, that’s all you <strong>need</strong>.</p>

<h2 id="beyond-the-basics">Beyond the Basics</h2>

<p>The above configuration is enough for most people, but MPV offers way more. It is scriptable, meaning the program can be extended in many ways. I will list some of them here.</p>

<h3 id="audio-compressor">Audio Compressor</h3>

<p>An audio compressor turns down the loudest parts and turns up the quietest parts. To install an audio compressor, download the script from <a href="https://github.com/mpv-player/mpv/blob/master/TOOLS/lua/acompressor.lua">here</a> and add it to the <code class="language-plaintext highlighter-rouge">scripts</code> folder inside your config directory. You can press “n” to toggle the compressor. If you want to enable it by default, open the script with a text editor and change <code class="language-plaintext highlighter-rouge">default_enable = false</code> to <code class="language-plaintext highlighter-rouge">true</code>.</p>

<h2 id="troubleshooting">Troubleshooting</h2>

<p>If MPV lags when you play media that doesn’t match your display resolution (such as 720p or 2160p on a 1080p screen), add <code class="language-plaintext highlighter-rouge">profile=fast</code> to your <code class="language-plaintext highlighter-rouge">mpv.conf</code> file. This disables MPV’s upscaling and downscaling algorithms. If that doesn’t fix it, remove the smoothmotion section.</p>

<p>If MPV lags when you play media that matches your display resolution, try removing the smoothmotion section from the config.</p>

<p>If you face crashes or weird glitches, set <code class="language-plaintext highlighter-rouge">vo=gpu</code> in your config file.</p>]]></content><author><name>Ken Harris</name></author><summary type="html"><![CDATA[The media player you use sucks. Switch to MPV.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kenhv.com/assets/images/og/posts/setting-up-mpv.png" /><media:content medium="image" url="https://kenhv.com/assets/images/og/posts/setting-up-mpv.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Treat Yourself to Good Typography</title><link href="https://kenhv.com/blog/treat-yourself-to-good-typography" rel="alternate" type="text/html" title="Treat Yourself to Good Typography" /><published>2024-06-02T00:00:00+05:30</published><updated>2024-09-24T12:46:11+05:30</updated><id>https://kenhv.com/blog/treat-yourself-to-good-typography</id><content type="html" xml:base="https://kenhv.com/blog/treat-yourself-to-good-typography"><![CDATA[<p>Ah, typography, the cornerstone of great design. Optimising your fonts and typography settings will go a long way in improving readability, especially if you’re someone who reads a lot. Here’s how I setup my devices.</p>

<h2 id="windows-vs-mac">Windows vs Mac</h2>

<p>Font rendering on Windows is handled by DirectWrite and ClearType. You might be familiar with the latter. Microsoft’s approach to rendering fonts is having “pixel-perfect” typography, literally. It aligns all glyphs to the pixel grid. The original font shapes are thrown out the window. Pun intended.</p>

<p>Mac takes a different approach. Steve Jobs was obsessed with good design and typography. Hence, Mac’s font rendering engine has a “WYSIWYG” (what you see is what you get) philosophy. It preserves the font’s original shape. Fonts look how the designers intended them to.</p>

<p>“So what are you saying, Windows bad Mac good?” Not at all. Apple has full control over their hardware, and all their displays are high DPI (dots/pixels per inch). In classic Apple fashion, they have a buzzword for it — Retina display. Most of us peasants are stuck with 96 DPI, which is pretty bad for legibility. Windows’s approach tends to do better here. Besides, all of this comes down to personal preference. Some prefer Windows’s approach, some prefer Mac’s. I won’t be going into Linux here, as it varies among distros.</p>

<h2 id="improving-typography-on-windows">Improving Typography on Windows</h2>

<p>Have you ever noticed that fonts look sharp in some applications but blurry in others? This is because of Windows’s horrible default display scaling. By default, scaling is set higher than 100% (mine was 125% for 1080p). If you revert back to 100%, the blurriness goes away but everything is too small.</p>

<p>The fix is pretty simple. Go to Settings, Display. Look at the scale percentage. Now click on Scale (not the drop-down menu, the actual Scale menu option), and it will take you to the custom scaling settings. Enter the same value here; it will ask you to sign out and sign back in. You will no longer see blurry fonts on your system.</p>

<p>Next up, let’s adjust ClearType settings. The inbuilt ClearType tuner is pretty bad. We’re going to use <a href="https://github.com/bp2008/BetterClearTypeTuner/">Better ClearType Tuner</a>, making the whole process much easier. Grab the zip from the <a href="https://github.com/bp2008/BetterClearTypeTuner/releases/tag/1.4.0.2">releases</a> section, extract it and run the program as administrator. I use RGB with maximum contrast (2200), you can play around with the values.</p>

<p>There is a program called <a href="https://github.com/snowie2000/mactype">MacType</a>, which replaces Windows’s font rendering system with a custom rendering system. It works great where it does, but is very problematic where it doesn’t. I have dabbled with it but uninstalled it at the end.</p>

<h2 id="improving-typography-on-linux">Improving Typography on Linux</h2>

<p>To get macOS-like bold fonts, add the following line to your <code class="language-plaintext highlighter-rouge">/etc/environment</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">FREETYPE_PROPERTIES</span><span class="o">=</span><span class="s2">"cff:no-stem-darkening=0 autofitter:no-stem-darkening=0"</span>
</code></pre></div></div>

<p>This enables stem darkening (credits: <a href="https://blog.aktsbot.in/no-more-blurry-fonts.html">aktsbot’s blog</a>). Stem darkening emboldens glyphs at smaller sizes to make them more readable on common low-DPI screens. I also suggest switching to grayscale antialiasing.</p>

<h2 id="improving-browser-fonts">Improving Browser Fonts</h2>

<p>I don’t like the default fonts used on most sites. How did I fix it? I wrote a userscript that overrides web fonts with the fonts that are set as default in the browser. To set this up, first configure your browser’s default fonts to your liking. I use Charter for default/proportional and serif, SF Pro Display for sans-serif, and SF Mono for monospace.</p>

<p>Download a userscript manager extension. I recommend <a href="https://violentmonkey.github.io/">ViolentMonkey</a>. Go to my script’s <a href="https://greasyfork.org/en/scripts/496839-default-fonts">GreaseFork</a> page and install the script. You can exclude the script from running on certain sites if you want to, using your userscript manager.</p>

<p>On Firefox, you can use <a href="https://addons.mozilla.org/en-US/firefox/addon/enforce-browser-fonts/">Enforce Browser Fonts</a>, which uses built-in features to achieve the same.</p>

<h2 id="android">Android</h2>

<p>Some Android manufacturers include settings to change fonts. If your OEM does not provide such options, your only choice left is to root your device. When I was using Android, I used a Magisk mod called <a href="https://gitlab.com/nongthaihoang/oh_my_font">Oh My Font</a> along with <a href="https://github.com/MrCarb0n/killgmsfont">Kill GMS Font</a>. In my opinion, this is the best typography experience you can get on Android. It uses Apple’s San Francisco fonts by default.</p>

<h2 id="the-end">The End</h2>

<p>If you made it all the way here, you might want to look into getting a shiny new high DPI monitor. If you have any suggestions, feel free to mail me.</p>]]></content><author><name>Ken Harris</name></author><summary type="html"><![CDATA[A guide to improving font rendering on all devices and browsers.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://kenhv.com/assets/images/og/posts/treat-yourself-to-good-typography.png" /><media:content medium="image" url="https://kenhv.com/assets/images/og/posts/treat-yourself-to-good-typography.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>