Squashed commit of the following:

commit 1241888f29ec90c03df4ffba7e250c8b42a0a45c
Author: realaravinth <realaravinth@batsense.net>
Date:   Mon Sep 12 14:51:34 2022 +0530

    feat: set draft=false

commit ba3d8eab8f8f410cb9ff5d7529f11d5da4d91097
Author: realaravinth <realaravinth@batsense.net>
Date:   Sun Sep 11 19:13:52 2022 +0530

    fix: more typos and errors

commit 310950ab62bef46edfcfc86d00bb586a5ad2c3a9
Author: realaravinth <realaravinth@batsense.net>
Date:   Sun Sep 11 19:13:39 2022 +0530

    feat: add toc to blog

commit 9fe83cdba08194f7c59cb4b36c45f5091caf1c15
Author: realaravinth <realaravinth@batsense.net>
Date:   Sun Sep 11 18:35:08 2022 +0530

    fix: styling and typo errors

commit 6070f79b9be9ebc30c983651db94f0f7e76204cf
Author: realaravinth <realaravinth@batsense.net>
Date:   Sun Sep 11 18:33:11 2022 +0530

    feat: first draft of manual website deployment blog post
This commit is contained in:
Aravinth Manivannan 2022-09-12 14:52:18 +05:30
parent 4b5ab1d928
commit 4792575405
Signed by: realaravinth
GPG key ID: AD9F0F08E855ED88
3 changed files with 468 additions and 11 deletions

View file

@ -0,0 +1,397 @@
+++
title = "How to deploy a website WITHOUT LibrePages"
date = "2022-09-10"
description = "Automation services like LibrePages exist to make lives easier but how do you do the same manually, on self-hosted hardware, or in the cloud?"
draft=false
[taxonomies]
tags = ['bare-metal', 'nginx', 'JAMStack', 'git', 'self-hosting']
[extra]
author = 'realaravinth'
+++
In this ~~blog post~~ tutorial, I'll show you how to deploy a personal
website. LibrePages automates everything that is discussed in this
tutorial and lets you focus on creating content. Automation is good
but knowing how to do it manually using industry standard
technologies always helps!
We will be using the following technologies to deploy our website:
1. GNU/Linux server(Debian)
2. Nginx (webs server)
3. Let's Encrypt (for HTTPS)
4. Gitea (but any Git hosting works)
Let's get started!
## 1. Setup Debian GNU/Linux
We are going to start with a fresh GNU/Linux installation, you could get
one from a cloud provider like [Digital
Ocean](https://www.digitalocean.com) (not affiliated).
### 1.1) Give your account `sudo` privileges
On GNU/Linux systems, the `root` account is the most powerful user account.
It is good practice to avoid working as `root` since a careless mistake
could wipe the entire system out.
`sudo` give the ability to execute commands with `root` capabilities
from a lower-privileged account. Let's make our account sudo capable:
```bash
su # become root
# add `realaravinth`, my account` to `sudo` group to be able to use `sudo`
usermod -aG sudo realaravinth # my account is called `realaravinth`, replace it with yours
exit
$ exit
```
Log out and log back in.
### 1.2) Install and setup firewall(`ufw`)
Uncomplicated Firewall(`ufw`) is a popular firewall that is easy to
set up and maintain. For most installations, this should be enough.
System administrators use firewalls to open only the ports that they
think should receive traffic from external networks. Without it, all
ports will be open, causing a security nightmare.
We will require standard SSH (22), and the standard web ports (80 and
443). A comprehensive list of services and the list of ports the listen
on is available at `/etc/services.
```bash $ sudo apt update && apt upgrade # update system $ sudo apt
install ufw # we are using `ufw` for the firewall
$ sudo ufw allow ssh # allow SSH traffic on port 22, required to log into the server
$ sudo ufw enable # deploy firewall
```
### 1.3) Secure SSH
SSH allows remote access to our servers over secure, encrypted
channels. By default, users can log in with their password
using SSH. But password authentication is susceptible to brute force attacks, so we should disable password logins on our server and only allow public-key authentication only.
### 1.3.1) Generate key pair
On your local computer, generate an SSH key pair:
```bash
$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/realaravinth/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/realaravinth/.ssh/id_rsa
Your public key has been saved in /home/realaravinth/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:i2DE1b9BQb9DqV0r6O9MfPeVqUwfww1/T8wIXL2Xqdo realaravinth@myserver.com
The key's random art image is:
+---[RSA 3072]----+
| .. .o. |
| . . . .. . . |
| o o + o .|
| . o* + .+|
| o S ooB o+.|
| . . . o.. +o*=|
| . . . ooo*X|
| +=.ooB|
| o+E .o|
+----[SHA256]-----+
```
Set a strong password the program prompts for one and save it somewhere
safe. Your public key will be at `~/.ssh/id_rsa.pub` and your private key at
`~/.ssh/id_rsa`. **Never share the private key with anyone**.
### 1.3.2) Setup public-key authentication
We have to copy the public key that we generated in the previous setup
onto our server:
```bash
$ ssh-copy-id -i ~/.ssh/id_rsa.pub myserver.com
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/realaravinth/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
realaravinth@myserver.com's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh 'myserver.com'"
and check to make sure that only the key(s) you wanted were added.
```
### 1.3.3) Disable SSH password authentication
> **NOTE: Verify you can log into your account before proceeding**
Now that we have a private-key authentication setup on both the client and
the server, let's disable password authentication on the server:
Open `/etc/ssh/sshd_config` and add the following lines:
```
PubkeyAuthentication yes
PasswordAuthentication no
```
And restart the SSH server:
```bash
$ sudo systemctl restart sshd
```
### 1.3) Install and setup `fail2ban`
We will be using `fail2ban` for intrusion prevention by blackiisting entities (users, bots, etc.) based on failed login attempts.
#### 1.3.1) Install `fail2ban`
```bash
$ sudo apt install fail2ban
```
#### 1.3.2) Enable `fail2ban` for `sshd`
```yml
[sshd]
enabled = true
```
#### 1.3.3) Configure `fail2ban` to start on boot
```bash
$ sudo systemctl enable fail2ban
$ sudo systemctl start fail2ban
```
### 1.4) Install and setup `nginx`
`nginx` is a popular web server that can be used to serve static sites.
It is fast, stable, and easy to set up.
To install, run the following command:
#### 1.4.1) Install `nginx`:
```bash
$ sudo apt install nginx
```
#### 1.4.2) Allow web traffic: open ports `80` and `443`
Ports `80` is the default for HTTP and `443` for HTTPS. To serve
web traffic, we'll have to Configure `ufw` to accept traffic on them:
```bash
$ sudo ufw allow 80 # open ports 80 HTTP traffic
$ sudo ufw allow 443 # open ports 443 for HTTPS traffic
```
#### 1.4.2) Configure `nginx` to start on boot
```bash
$ sudo systemtl enable nginx # automatically start nginx on boot
$ sudo systemtl start nginx # start nginx server
```
And verify it works:
```bash
$ curl localhost
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
```
`nginx` is working!
## 2) Deploy website
For this demo, we'll deploy a single file(`index.html`)
HTML website.
### 2.1) Install the webpage on the server
Edit `/var/www/html/index.html` and add the following HTML to it:
```html
<!DOCTYPE html>
<html>
<head>
<title>My cool website!</title>
</head>
<body>
<h1>Welcome to my website! o/</h1>
</body>
</html>
```
The webpage should now be available on localhost, and we should see it when we run the following command:
```bash
$ curl localhost
<!DOCTYPE html>
<html>
<head>
<title>My cool website!</title>
</head>
<body>
<h1>Welcome to my website! o/</h1>
</body>
</html>
```
### 2.2) Serve webpage on a custom domain
#### 2.2.1) Buy a domain if you don't own one already
#### 2.2.2) Go to the domain's DNS dashboard and add the following record
```
@ A 300 <your server IP address>
```
#### 2.2.3) Setup `nginx` to serve the website at `http://<your-domain.`
Open `/etc/nginx/sites-available/your-domain` and add the following:
```
server {
# serve website on port 80
listen [::]:80;
listen 80;
# write error logs to file
error_log /var/log/nginx/<your-domain>.error.log;
# write access logs to file
access_log /var/log/nginx/<your-domain>.access.log;
# serve only on this domain:
server_name <your-domain>; # replace me
# use files from this directory
root /var/www/html/;
# remove .html from URL; it is cleaner this way
rewrite ^(/.*)\.html(\?.*)?$ $1$2 permanent;
# when a request is received, try the index.html in the directory
# or $uri.html
try_files $uri/index.html $uri.html $uri/ $uri =404;
}
```
It is good practice to have all `nginx` deployment configurations in
`/etc/nginx/sites-available/` directory and link production websites to
`/etc/nginx/sites-enabled directory. Doing so allows you to
work-in-progress configurations or delete deployments without losing
the configuration files.
Let's enable `<your-domain>`
```bash
$ sudo ln -s /etc/nginx/sites-available/<your-domain> /etc/nginx/sites-available/<your-domain>
```
Verify configurations before deploying, `nginx` has a command
to do it:
```bash
$ sudo nginx -t
```
If there are no errors, reload `nginx` to deploy the website:
```bash
$ sudo nginx -s reload
```
Your webpage should now be accessible at `http://<your-domain>`!
### 2.3) Install `certbot` to set up HTTPS
HTTP is insecure. We'll have to set up SSL to serve our website using
HTTPS. To do that, we will be using [Let's
Encrypt](https://letsencrypt.org/) a popular nonprofit certificate
authority to get our SSL certificates.
SSL certificates come with set lifetimes, so we renew them before they expire. The process, when done manually, is demanding: you
will have to log in every three months and renew the
certificate. If you fail or forget it, your visitors will see security
warnings on your website.
Thankfully, Let's Encrypt provides automation through `certbot`
#### 2.3.1) Install `certbot`:
```bash
$ sudo apt install certbot python3-certbot-nginx
```
#### 2.3.2) Get a certificate for `<your-domain>`
```bash
$ sudo certbot --nginx -d <your-domain>
```
`certbot` will prompt you for an email ID, and ask you to accept their
terms and conditions, privacy policy, etc. Be sure to read them before
agreeing to them. It will then try to authenticate your domain ownership
using the [ACME
protocol](https://en.wikipedia.org/wiki/Automatic_Certificate_Management_Environment).
By configuring the DNS to point to our server and by telling `nginx` at
that domain.
When it has verified ownership, it will automatically issue, deploy the
certificate on `nginx` and setup redirects.
#### 2.3.3) Setup cronjob to automate SSL certificate renewals
Become root and edit crontab
```bash
$ su
crontab -e
```
Add the following job and exit:
```
0 */12 * * * certbot -n --nginx renew
```
It will attempt to renew SSL certificates every 12 hours. If a the
certificate is due for renewal, `certbot` will go through the ACME
challenge, get the new certificates and automatically deploy them for
you.
Now our GNU/Linux server is configured and ready to serve our website at
`http://<your-website>`!

59
templates/blog/_toc.html Normal file
View file

@ -0,0 +1,59 @@
<aside class="toc">
<h2>Table of Contents</h2>
{% if page.toc %}
<ul>
{% for h1 in page.toc %}
<li>
<a href="{{ h1.permalink | safe }}">{{ h1.title }}</a>
{% if h1.children %}
<ul>
{% for h2 in h1.children %}
<li>
<a href="{{ h2.permalink | safe }}">{{ h2.title }}</a>
{% if h2.children %}
<ul>
{% for h3 in h2.children %}
<li>
<a href="{{ h3.permalink | safe }}">{{ h3.title }}</a>
{% if h3.children %}
<ul>
{% for h4 in h3.children %}
<li>
<a href="{{ h4.permalink | safe }}">{{ h4.title }}</a>
{% if h4.children %}
<ul>
{% for h5 in h4.children %}
<li>
<a href="{{ h5.permalink | safe }}">{{ h5.title }}</a>
{% if h5.children %}
<ul>
{% for h6 in h5.children %}
<li>
<a href="{{ h6.permalink | safe }}">{{ h6.title }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
</aside>

View file

@ -3,19 +3,20 @@
endblock meta %} {% block content %} endblock meta %} {% block content %}
<div class="page__container"> <div class="page__container">
<h1 class="page__group-title">{{ page.title }}</h1> <h1 class="page__group-title">{{ page.title }}</h1>
{% include "blog/_meta.html" %} {% include "blog/_meta.html" %}
<div class="blog__content"> <div class="blog__content">
{% include "blog/_toc.html" %}
{{ page.content | safe }} {{ page.content | safe }}
</div> </div>
<br /> <br />
<br /> <br />
<div class="blog__post-tag-container"> <div class="blog__post-tag-container">
{% for t in page.taxonomies.tags %} {% for t in page.taxonomies.tags %}
<a class="blog__post-tag" href="/tags/{{t | slugify }}">#{{ t }}</a> <a class="blog__post-tag" href="/tags/{{t | slugify }}">#{{ t }}</a>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
{% endblock content %} {% endblock content %}