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 %}
|
||||
|
||||
<div class="page__container">
|
||||
<h1 class="page__group-title">{{ page.title }}</h1>
|
||||
{% include "blog/_meta.html" %}
|
||||
<h1 class="page__group-title">{{ page.title }}</h1>
|
||||
{% include "blog/_meta.html" %}
|
||||
|
||||
<div class="blog__content">
|
||||
<div class="blog__content">
|
||||
{% include "blog/_toc.html" %}
|
||||
{{ page.content | safe }}
|
||||
</div>
|
||||
<br />
|
||||
<br />
|
||||
<div class="blog__post-tag-container">
|
||||
{% for t in page.taxonomies.tags %}
|
||||
<a class="blog__post-tag" href="/tags/{{t | slugify }}">#{{ t }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<br />
|
||||
<div class="blog__post-tag-container">
|
||||
{% for t in page.taxonomies.tags %}
|
||||
<a class="blog__post-tag" href="/tags/{{t | slugify }}">#{{ t }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
|
|
Loading…
Reference in a new issue