blog/self-host/index.html (view raw)
1<!doctype html><html lang=en><head><meta charset=UTF-8><meta content="IE=edge" http-equiv=X-UA-Compatible><meta content="text/html; charset=UTF-8" http-equiv=content-type><meta content="width=device-width,initial-scale=1,user-scalable=no" name=viewport><meta content="index, follow" name=robots><title>Self-hosting Extravaganza</title><meta content="Self-hosting Extravaganza" name=title><meta content="Tech and privacy ramblings from a random italian dude." name=description><meta content=website property=og:type><meta content=https://birabittoh.github.io/blog/self-host/ property=og:url><meta content=BiRabittoh property=og:site_name><meta content="Self-hosting Extravaganza" property=og:title><meta content="Tech and privacy ramblings from a random italian dude." property=og:description><meta content=summary_large_image property=twitter:card><meta content=https://birabittoh.github.io/blog/self-host/ property=twitter:url><meta content="Self-hosting Extravaganza" property=twitter:title><meta content="Tech and privacy ramblings from a random italian dude." property=twitter:description><link href=https://birabittoh.github.io/blog/self-host/ rel=canonical><link href=https://birabittoh.github.io/atom.xml rel=alternate title=RSS type=application/atom+xml><link href=https://birabittoh.github.io/css/style.css rel=stylesheet><body><div class=wrapper><header><nav class=navBar><a href=/>/home/</a><a href=/about>/about/</a><a href=/links>/links/</a><a href=/blog>/blog/</a><div class=themeSwitch><button class="themeButton light" onclick="setTheme('light')" title="Light mode">◐</button><button class="themeButton dark" onclick="setTheme('dark')" title="Dark mode">◑</button></div><script>const setTheme=a=>{document.documentElement.className=a;localStorage.setItem(`theme`,a)};const getTheme=()=>{const a=localStorage.getItem(`theme`);const b=a?a:`dark`;setTheme(b)};getTheme()</script></nav><div><a href=..>..</a>/<span class=metaData>self-host</span></div><time datetime=2023-07-16>Published on: <span class=metaData>2023-07-16</span></time><h1>Self-hosting Extravaganza</h1></header><main><p>Lately, more and more companies are putting their services behind paywalls, usage limits and closed APIs. Some examples are <a href=https://nitter.it/elonmusk/status/1675187969420828672 rel=noopener target=_blank>Twitter</a> limiting the number of tweets a non-paying user can read, <a href=https://www.redditinc.com/blog/2023apiupdates rel=noopener target=_blank>Reddit</a> increasing their API price to an extent that’s unbearable for any normal individual and <a href=https://libreddit.kavin.rocks/r/youtube/comments/14kmd07/youtube_cracking_down_on_if_youre_not_paying_them/ rel=noopener target=_blank>YouTube</a> starting to block their service towards anyone using an adblock extension.<h2 id=there-must-be-a-better-way>There must be a better way</h2><p>Luckily, I’ve been interested in <a href=https://github.com/mendel5/alternative-front-ends rel=noopener target=_blank>alternative front-ends</a> for a while. These services allow you to get the same (or better) functionality as their corporate counterpart without giving away any of your information in return. Some of these even offer their own free APIs.<p>Here’s my favorite instances with respect to the service they provide:<table><thead><tr><th>Service<th>PC<th>Mobile<tbody><tr><td>YouTube<td><a href=https://y.com.sb/ rel=noopener target=_blank>Invidious</a><td><a href=https://apt.izzysoft.de/fdroid/index/apk/org.polymorphicshade.newpipe rel=noopener target=_blank>NewPipe</a><tr><td>Twitter<td><a href=https://nitter.it rel=noopener target=_blank>Nitter</a><td><a href=https://apt.izzysoft.de/fdroid/index/apk/org.ca.squawker rel=noopener target=_blank>Squawker</a><tr><td>Reddit<td><a href=https://libreddit.kavin.rocks rel=noopener target=_blank>LibReddit</a><td><a href=https://libreddit.kavin.rocks rel=noopener target=_blank>LibReddit</a><tr><td>Medium<td><a href=https://scribe.rip rel=noopener target=_blank>Scribe</a><td><a href=https://scribe.rip/ rel=noopener target=_blank>Scribe</a></table><h2 id=drawbacks>Drawbacks</h2><p>Of course, this is not a perfect solution. There are a lot of problems to be discussed.<h3 id=privacy>Privacy</h3><p>First and foremost, these instances do not make any profit. This is not a problem until you really think about it. Can you really trust a random developer offering a (paid) service for thousands of users out of their own kindness? The answer is “probably yes”, but are you willing to take this risk?<p>Instance admins could easily edit the upstream source code to make it so they can track their users indefinetly and sell usage data without them even realizing. This is a given if you use any “normal” (not self-hosted) service, but the difference is big companies are <em>required</em> by GDPR to protect collected user data in a certain way and keep them for a maximum set amount of time.<p>The same cannot be assured for individuals who apparently don’t even make a profit for what they’re doing.<h3 id=scaling>Scaling</h3><p>This buzzword has become a meme in the programming world, but it’s been shown how important it is to consider when dealing with large userbases that can grow exponentially without any warning.<p>Think about the amount of users who migrated to Mastodon immediately after Elon Musk acquired Twitter. Instance admins were used to having a couple hundred users, so hundred of thousands of new signups made a lot of popular instances slow down or even temporarily shut down while they migrated to new (and more expensive) hardware.<p>Anything public you use can be subject to this phenomenon, leading to poor user experience, as you’ll be one of the many people wondering why your feed takes one minute to load.<h2 id=fine-i-ll-do-it-myself>Fine, I’ll do it myself</h2><p>Since joining the world of minimalism, I had always considered Docker as a bloated way to run multiple virtual machines. I read about people complaining that even simple Python scripts were providing <code>Dockerfile</code> and <code>docker-compose.yml</code> files and I started seeing it as a bloaty way to achieve the same result.<p>Whenever I wanted to host anything by myself, I used to SSH into my VPS with password authentication (!!!) and expose a public port for each service (!!!). I used my public IP address to log into my services, so I had to resort to sending cleartext passwords through HTTP (!!!) since TLS was not an option.<p>Of course, this is possibly the most insecure way to host services on a public server, but I felt that was “secure enough” and nobody would ever be interested in hacking me (!!! × ∞).<p>Nonetheless, I used to <code>cat /var/log/auth.log</code> to see all the failed login attempts, and pray that nobody actually got my password right. Nowadays, I look back and laugh at my previous config; at least I’m (almost) sure that nobody actually managed to get in.<h2 id=the-right-way>The right way</h2><p>Since I started my new job, I also began experimenting with Docker and found out it’s not as bad as I thought it’d be. I will now let my previous config serve as the perfect example of how NOT to secure your VPS correctly for any self-hosting configuration.<h3 id=ditch-password-authentication>Ditch password authentication</h3><p>First of all, password authentication. You’ll be a lot safer as soon as you disable it.<p>Having it enabled means you’re vulnerable to dictionary and bruteforce attacks. Also, if some new vulnerability is published, the password field is one more way the attacker could send a malicious string to get inside (see <a href=https://scribe.rip/geekculture/the-log4j-incident-explained-ed0ce6d36df2 rel=noopener target=_blank>the log4j incident</a>).<p>A better way of logging into your VPS is through public key authentication.<p>First, generate a key on your own PC:<pre><code>ssh-keygen -t ed25519 -a 100
2</code></pre><p>This will create two files: <code>~/.ssh/id_ed25519.pub</code> and <code>~/.ssh/id_ed25519</code><p>Now, use the following command to copy your key over to the VPS:<pre><code>ssh-copy-id -i ~/.ssh/id_ed25519 <user>@<host>
3</code></pre><p>At this point, if everything went correctly, just add or change the following line in <code>/etc/ssh/sshd_config</code>:<pre><code>PasswordAuthentication no
4</code></pre><p>At this point, you should be able to log into your VPS without the need to input your password, which is more secure as well as more convenient.<p>I keep the content of my public and private ssh key files saved as secure notes in my BitWarden account, so I can take them to any PC I want to access my VPS from. People say this is bad practice (you should only have a key for each host), but I personally feel like it’s not that big of a deal compared to the security mess I had going on before.<h3 id=containerize-your-applications>Containerize your applications</h3><p>Now that you have a safe way to SSH into your machine, you can start hosting your own services.<p>First, some terminology:<ul><li><code>Dockerfile</code> files are like a list of ingredients. They contain every dependency needed to build a minimal operating system dedicated to running a program. They’re used to build images.<li><code>Images</code> are like recipes. You can create some yourself from a Dockerfile or download them from an external repository. They can be instantiated as containers.<li><code>Containers</code> are like courses. You can instantiate multiple equal courses from the same image and you can actually eat (use) them! They can be managed through <code>docker-compose</code>.<li><code>docker-compose.yml</code> files are like menus. They’re a convenient way to instantiate and deinstantiate multiple containers in a specific and reproducible configuration. If you’re not a developer, you’ll be mainly working on these files.</ul><p>To get started with Docker, install <code>docker</code> and <code>docker-compose</code> via your package manager of choice. If you want an easy start, you can follow <a href=https://docs.invidious.io/installation/#docker-compose-method-production rel=noopener target=_blank>this guide</a> to host our own Invidious instance.<p>It’s not that hard, but you might need to read the official <a href=https://docs.docker.com/compose/ rel=noopener target=_blank>Docker Compose documentation</a> if something doesn’t go as planned.<p>My advice is to generate an <code>hmac_key</code> using <code>pwgen 20 1</code> or <code>openssl rand -hex 20</code> and insert it in the correct spot inside <code>docker-compose.yml</code>.<p>Also, remove the <code>127.0.0.1:</code> part in the <code>ports</code> section since we don’t have a reverse proxy set up (yet).<p>After you’re done configuring, you can type <code>docker-compose up -d</code> to pull all required images and instantiate your containers, and <code>docker-compose down</code> if you want to stop and remove everything.<h3 id=use-a-reverse-proxy>Use a reverse proxy</h3><p>If you’ve followed that guide correctly, you should now have two containers that communicate through a network. You can find out their names by running <code>docker ps -a</code>. Take note of the name of your main invidious container, which will be referred as <code>invidious</code> for the rest of this guide.<p>Problem is, you’re still using an IP address and communicating in cleartext through HTTP! This means your ISP can read every single detail in every single request you make.<p>Luckily, there is a way to get a cool domain name for free that also happens to include free and auto-generated TLS certificates.<p>First, create an account on <a href=https://www.duckdns.org/ rel=noopener target=_blank>DuckDNS</a> and set up a free domain.<p>Just make a new directory near the one you used for Invidious and create a new <code>docker-compose.yml</code>:<pre><code>mkdir swag
5cd swag
6nano docker-compose.yml
7</code></pre><p>You can paste and edit accordingly the lines in <a href=https://docs.linuxserver.io/general/swag#creating-a-swag-container rel=noopener target=_blank>this guide</a>.<p>For example, instead of <code>DNSPLUGIN=cloudflare</code> you should have <code>DNSPLUGIN=duckdns</code>.<p>When you’re done, start your container with <code>docker-compose up -d</code>. This will create the config folder in <code>/etc/config/swag</code> as well as a new network called <code>swag_default</code>.<p>Now we need to create a custom subdomain for Invidious. You can do it by creating the following file: <code>/etc/config/swag/nginx/proxy-confs/invidious.subdomain.conf</code> with this content:<pre><code>server {
8 listen 443 ssl http2;
9 listen [::]:443 ssl http2;
10
11 server_name y.*;
12
13 include /config/nginx/ssl.conf;
14
15 client_max_body_size 0;
16
17 location / {
18 include /config/nginx/proxy.conf;
19 include /config/nginx/resolver.conf;
20 set $upstream_app invidious;
21 set $upstream_port 3000;
22 set $upstream_proto http;
23 proxy_pass $upstream_proto://$upstream_app:$upstream_port;
24 }
25}
26</code></pre><p>Where:<ul><li><code>server_name yt.*</code>: <code>yt</code> is the subdomain of choice;<li><code>set $upstream_app invidious;</code>: <code>invidious</code> is the name of the main Invidious container;<li><code>set $upstream_port 3000;</code>: <code>3000</code> is the Invidious port.</ul><p>There’s one last step remaining. Invidious and Swag are two separate containers, so they cannot communicate unless they’re connected to the same network. You can connect Invidious to Swag’s network with the following command, where <code>invidious</code> is the name of your main Invidious container.<pre><code>docker network connect swag_default invidious
27</code></pre><p>Finally, you can visit https://yt.<your-domain>.duckdns.org/ and check if you can access Invidious through HTTPS.<p>Note: now that you have a reverse proxy set up, you can remove your <code>ports:</code> section entirely from Invidious’ <code>docker-compose.yml</code>. You can do this because the containers are communicating internally to the <code>swag_default</code> network, without the need to expose any ports to the outside. After you’re done, remember to reload your configuration by running <code>docker-compose restart</code> in your Invidious folder.<p>Ideally, the only container with exposed ports in your VPS should be Swag exposing ports 80 (HTTP) and 443 (HTTPS).<h2 id=conclusion>Conclusion</h2><p>Self-hosting is not easy. It’s been my <a href=https://wiki.froth.zone/wiki/Camino_de_Santiago rel=noopener target=_blank>Camino de Santiago</a>: a long path of redemption for the sins I have committed in my young age. Even if I made a lot of mistakes, in the end I’ve learned a lot about dev-ops and cybersecurity, as well as precious skills that proved themselves useful for my engineering job.<p>You can find a full list of self-hostable services <a href=https://github.com/awesome-selfhosted/awesome-selfhosted rel=noopener target=_blank>here</a>!</main><footer><p class=tagsData><a href=/tags/advice>#advice</a> <a href=/tags/foss>#foss</a> <a href=/tags/privacy>#privacy</a><hr><div class=footContainer><div class=footLeft><p>Licensed under <a rel="noopener noreferrer" href=https://fr.wikipedia.org/wiki/Licence_MIT target=_blank>MIT</a><br> Built with <a rel="noopener noreferrer" href=https://www.getzola.org target=_blank>Zola</a> using <a rel="noopener noreferrer" href=https://github.com/BiRabittoh/anemone target=_blank>anemone</a> theme & <a rel="noopener noreferrer" href=https://github.com/Speyll/veqev target=_blank>veqev</a> colors.<br></div><div class=footRight><a href=https://wobble.town/visit/528 target=_blank><img class="footGif noStyle" alt=footGif loading=lazy src=https://wobble.town/visit/528/wobble.gif></a><a rel="noopener noreferrer" title="Subscribe via RSS for updates." class=metaData href=https://birabittoh.github.io/atom.xml target=_blank>RSS</a></div></div></footer></div>