System information: Debian GNU/Linux 12 (bookworm)

Installing

Oh My Zsh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apt-get update
# install ZSH
# apt-get update
apt install zsh -y
zsh --version # Expected result: zsh 5.0.8 or more recent.
# zsh 5.9 (x86_64-debian-linux-gnu)
chsh -s $(which zsh) # set zsh as default shell
# sudo chsh -s /bin/zsh <myUserName>
# new session
echo $SHELL # test
# /usr/bin/zsh

# install git
apt install git -y
git version
# git version 2.39.2

# install Oh My Zsh
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

# install plugins
## zsh-autosuggestions
## https://github.com/zsh-users/zsh-autosuggestions/blob/master/INSTALL.md#oh-my-zsh
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
## add zsh-autosuggestions to the list of plugins (~/.zshrc)

source ~/.zshrc

Docker

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# https://docs.docker.com/engine/install/debian/#install-using-the-repository
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Add the repository to Apt sources:
echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

# install the latest version
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# verify that the installation
sudo docker run hello-world
docker version
# ...
# Version:           24.0.6
# ...

HTTPS

Register and auto-renew SSL certificates with acme.sh.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# install acm.sh (change email)
curl https://get.acme.sh | sh -s email=my@example.com
acme.sh --version
# https://github.com/acmesh-official/acme.sh
# v3.0.7
crontab -l # a cron job is automatically set up
# run every day of every month at 7:19 PM
# 7 19 * * * "/$USER/.acme.sh"/acme.sh --cron --home "/$USER/.acme.sh" > /dev/null

# add DNS records on CloudFlare
# @ and www

# https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA
# register an account from ZeroSSL.com CA to issue free SSL certificates
## email must be the same as the one used to register the account
acme.sh --register-account -m my@example.com --server zerossl

# https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode
# https://github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_cf
# acm.sh with DNS API
# CloudFlare Multiple DNS zones (change CF_Account_ID and CF_Token)
export CF_Account_ID="xxx"
export CF_Token="xxx"
acme.sh --issue --dns dns_cf -d example.com -d "*.example.com" --force

NGINX

Set up NGINX to serve the website.

  • Turn off Clouflare proxy for the domain.

nginx.conf:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# flush DNS cache if necessary

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;

    server {
        listen 80;
        server_name www.exampleho.com;
        return 301 $scheme://exampleho.com$request_uri;
    }
    server {
        listen 443 ssl;
        server_name www.exampleho.com;

        ssl_certificate /etc/ssl/fullchain.cer;
        ssl_certificate_key /etc/ssl/exampleho.com.key;

        return 301 https://exampleho.com$request_uri;
    }

    server {
        listen 80;
        server_name exampleho.com;
        location / {
            return 301 https://$host$request_uri;
        }
    }
    server {
        listen 443 ssl;
        server_name exampleho.com;

        ssl_certificate /etc/ssl/fullchain.cer;
        ssl_certificate_key /etc/ssl/exampleho.com.key;

        location / {
            root /usr/share/nginx/html; 
            index index.html;
            try_files $uri $uri/ =404;
        }
    }

    server {
        server_name _;
        listen       80  default_server;
        return       404;
    }
    # https visits to non-configured domains cannot be redirected to 404 page using similar methods
    # (GPT:  When a client makes a request over HTTPS, the SSL handshake happens before the HTTP request is processed. This means that the certificate for example.com is being used, even if the request is for abc.example.com.)
}

docker-compose-nginx.yaml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
version: '3'
services:
  nginx:
    image: nginx:mainline-alpine3.18
    container_name: nginx
    volumes:
      - ~/hosting/nginx.conf:/etc/nginx/nginx.conf
      - ~/.acme.sh/exampleho.com_ecc:/etc/ssl
      - ~/resumes:/resumes
      - ~/hosting/homepage:/usr/share/nginx/html
    ports:
      - 80:80
      - 443:443

Github Actions for Automatic Deployment

Github repo - Settings - Actions - Runners - New self-hosted runner:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

# export RUNNER_ALLOW_RUNASROOT="1"

# This runner will have the following labels: 'self-hosted', 'Linux', 'X64' 

./svc.sh install
./svc.sh start
./svc.sh status
# ./svc.sh stop
# https://docs.github.com/en/free-pro-team@latest/actions/hosting-your-own-runners/configuring-the-self-hosted-runner-application-as-a-service

Add VPS public key to Github SSH keys:

1
2
3
4
5
6
7
8
9
# https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent?platform=linux
# generate SSH key pair on VPS
ssh-keygen -t ed25519 -C "yourmai_el@example.com"

# add to Github SSH keys

# pull the repo to VPS first
git clone --recursive <repo_name>
git clone --recursive git@github.com:funfungho/abitinterference.git

Github workflow config:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
name: CI
on:
  # Triggers the workflow on push or pull request events but only for the "main" branch
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]
  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  build:
    # The type of runner that the job will run on
    runs-on: self-hosted
    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      - name: Run remote build
        run: cd ~/<repo_name> && git pull --recurse-submodules && . ~/<repo_name>/auto_deploy.sh

# auto_deploy.sh
# cd ~/<repo_name>
# rm -rf ~/<repo_name>/public && hugo
# cd ~/hosting && docker compose -f docker-compose-nginx.yaml restart