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:
parent
4b5ab1d928
commit
4792575405
3 changed files with 468 additions and 11 deletions
|
@ -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
59
templates/blog/_toc.html
Normal 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>
|
|
@ -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 %}
|
||||||
|
|
Loading…
Reference in a new issue