🔗 Personal URL shortener on your domain with automation through GitHub Actions
Tags: tutorial, github, beginners, showdev
Published: 2020-06-17 23:33:50

## Introduction

Hey, hey! 👋 Are you ready to continue learning how to work with GitHub Actions and get to know it better? **Perfect!** Today, we will create your own link shortener to simplify interaction on the Internet.

_So where's GitHub Actions?_ Almost everywhere, because that's _main part_! We'll use it at its **full power** to add new links, reboot a web server and deploy a _friendly_ index page of your shortener website.

<a name="toc"></a>
### 📝 Table of contents

- [What will we build?](#ch-1)
  - [Repository structure](#ch-1-1)
- [Register domain name](#ch-2)
- [Configure remote virtual server](#ch-3)
- [Configure GitHub Actions for deploys](#ch-4)
  - [GitHub secrets](#ch-4-1)
  - [List of shorted URLs](#ch-4-2)
  - [Website index page](#ch-4-3)
  - [Let's deploy it!](#ch-4-4)
  - [Add new link](#ch-4-5)
- [Questions for better understanding](#ch-5)
- [Exercises for independent execution](#ch-6)

<a name="ch-1"></a>
## 🤔 What will we build?

We will build the **URL shortener** for only important links, that you always use and which you won't change often.

> 👀 For example, for a slide in your presentation with contact information or for downloading a useful snippet from your repository (_for easily `wget` manipulations_) or for a shorter version of link to YouTube video... and so on.

All right, here's our plan:

- Register domain name
- Configure remote virtual server
- Configure a separate branches of the same GitHub repository
- Configure GitHub Actions for deploys
- Push-to-Deploy

[↑ Table of contents](#toc)

<a name="ch-1-1"></a>
### Repository structure

As I said earlier, we will split our repository into two branches: 

1. `master` branch will contain the main scenario (_a list of shortened URLs_)
2. `website` branch will contain the index page of your website (_a "hello" message for curious users_)

> ☝️ **Please note**: in this case, the unfolding of two branches will go independently of each other. Thank you, GitHub Actions!

Structure of `master` branch:

```bash
.
├── .github
│   └── workflows
│       └── urls_deploy.yml  # <-- GitHub Action
└── short_urls_list.conf     # <-- List of shorted URLs (NGINX config)
```

Structure of `website` branch:

```bash
.
├── .github
│   └── workflows
│       └── website_deploy.yml  # <-- GitHub Action
└── index.html                  # <-- Index page of website
```

[↑ Table of contents](#toc)

<a name="ch-2"></a>
## 🌐 Register domain name

At the _very_ beginning of our exciting journey, let's define the domain name for your project. I was lucky enough to register the domain name [`shrts.website`](https://shrts.website/), which fully matches to our idea: _make shorten links to websites_.

> 😉 **Fun fact**: I've been using this [domain name registrar](https://www.reg.com/domain/new/?rlink=reflink-5542869) for over 14 years (_to be exact, since October 2006_) and I'm still _fully_ satisfied with its prices and support.

You can play with different combinations and abbreviations, but the main purpose of the domain for a such project&nbsp;→ **simplicity** and **ease** of remembering!

[↑ Table of contents](#toc)

<a name="ch-3"></a>
## ⚙️ Configure remote virtual server

To avoid repeating myself, I'm not going to describe in detail the process of configuring the virtual server and just give you links to my articles, where I already described it:

1. [Create a new droplet on DigitalOcean](https://dev.to/koddr/automate-that-a-practical-guide-to-github-actions-build-deploy-a-static-11ty-website-to-remote-virtual-server-after-push-d19#ch-2-2)
2. [Setup virtual server via `snippets-deploy`](https://github.com/truewebartisans/snippets-deploy)
3. [Create a private SSH key for GitHub Actions deploys](https://dev.to/koddr/automate-that-a-practical-guide-to-github-actions-build-deploy-a-static-11ty-website-to-remote-virtual-server-after-push-d19#ch-2-3)

[↑ Table of contents](#toc)

<a name="ch-4"></a>
## 🤖 Configure GitHub Actions for deploys

Let's start with the most interesting part: _setting up GitHub Actions_.

[↑ Table of contents](#toc)

<a name="ch-4-1"></a>
### GitHub secrets

Here's a list of all the secrets we need:

- `REMOTE_HOST` – IP address of your virtual server
- `REMOTE_USER` – user on your virtual server (not `root`, please)
- `REMOTE_NGINX_DIR` – full path to your NGINX folder for place snippets (by default, it should be `/etc/nginx/snippets`)
- `REMOTE_WEBSITE_DIR` – full path to your website root folder (if you setup your virtual server via [`snippets-deploy`](https://github.com/truewebartisans/snippets-deploy), it should be `/var/www/<domain>/html`)
- `SSH_KEY` — the contents of a **PRIVATE** part (`~/.ssh/gha_rsa`) of the SSH key, that we generated in [Private key for SSH](#ch-2-3) section of my previous article
- `SSH_KEY_PASSPHRASE` — the passphrase of the SSH key, that we entered, when generating the SSH key

![GitHub secrets](https://dev-to-uploads.s3.amazonaws.com/i/dnulkj4eclvuv39ybiqn.png)

> ☝️ Please read part of my previous article entitled "[_Understanding the GitHub secrets_](https://dev.to/koddr/automate-that-a-practical-guide-to-github-actions-build-deploy-a-static-11ty-website-to-remote-virtual-server-after-push-d19#ch-4-1)", if you want to know more about it.

[↑ Table of contents](#toc)

<a name="ch-4-2"></a>
### List of shorted URLs

Let's get started with NGINX config for your `<domain>` on remote virtual server. 

Connect to server and open config in `nano` editor. Go to main `server {...}` section and add `include snippets/short_urls_list.conf;` line after `index index.html;`, like this:

```conf
# /etc/nginx/sites-available/<domain>.conf

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name <domain>;
    root /var/www/<domain>/html;
    index index.html;

    include snippets/short_urls_list.conf; # <-- include file with URLs redirects

    ssl_certificate /etc/letsencrypt/live/<domain>/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/<domain>/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/<domain>/chain.pem;

    # ...
```

> ❗️ **Attention**: do not forget to change `<domain>` to yours.

Good! 👍 Now, create snippet with shorten URLs in your GitHub repository:

```conf
# short_urls_list.conf

rewrite /link-1 https://huge-example-1.com/very-long/link/1 permanent;
```

As you can see, these are common [`301 Moved Permanently`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/301) redirect from `https://<domain>/link-1` to another link (`https://huge-example-1.com/very-long/link/1` in this example).

Now, we've to create GitHub Action config for `master` branch:

```yml
# .github/workflows/urls_deploy.yml

name: Deploy new short URLs via SSH

on:
  push:
    branches: [master] # triggered only if pushed to master branch

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      # Checks-out repository under $GITHUB_WORKSPACE
      - uses: actions/checkout@master

      # Copying files to server via SSH
      - name: Copying files to server
        uses: appleboy/scp-action@master
        with:
          host: ${{ secrets.REMOTE_HOST }}
          username: ${{ secrets.REMOTE_USER }}
          key: ${{ secrets.SSH_KEY }}
          passphrase: ${{ secrets.SSH_KEY_PASSPHRASE }}
          source: "short_urls_list.conf"
          target: "${{ secrets.REMOTE_NGINX_DIR }}"

      # Restart NGINX after deploy
      - name: Restart NGINX
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.REMOTE_HOST }}
          username: ${{ secrets.REMOTE_USER }}
          key: ${{ secrets.SSH_KEY }}
          passphrase: ${{ secrets.SSH_KEY_PASSPHRASE }}
          script: sudo nginx -t && sudo systemctl restart nginx
```

[↑ Table of contents](#toc)

<a name="ch-4-3"></a>
### Website index page

Switch to `website` branch and create an index page (simple `index.html` file) for the website of your taste. 

> 👌 It could be some kind of _welcome text describing your shortener_ or whatever you want. Fantasize about it!

And, _finally_, we're ready to create GitHub Action for `website` branch:

```yml
# .github/workflows/website_deploy.yml

name: Deploy website via SSH

on:
  push:
    branches: [website] # triggered only if pushed to website branch

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      # Checks-out repository under $GITHUB_WORKSPACE
      - uses: actions/checkout@master

      # Copying files to server via SSH
      - name: Copying files to server
        uses: appleboy/scp-action@master
        with:
          host: ${{ secrets.REMOTE_HOST }}
          username: ${{ secrets.REMOTE_USER }}
          key: ${{ secrets.SSH_KEY }}
          passphrase: ${{ secrets.SSH_KEY_PASSPHRASE }}
          source: "index.html"
          target: "${{ secrets.REMOTE_WEBSITE_DIR }}"
```

<a name="ch-4-4"></a>
### Let's deploy it!

Commit & push changes to your repository (_for both branches_) and visit `https://<domain>` to see website index page, like this:

![shrts.website screen](https://dev-to-uploads.s3.amazonaws.com/i/2u5homjf4d8p3dweg4mx.png)

Now, hit your shortened link to make sure everything works as intended. 

**Yep, it just works!** 🎉 

<a name="ch-4-5"></a>
### Add new link

To add a new link, all you need to do is add it to the `short_urls_list.conf` file (in the `master` branch) and make push.

That's all! Cool, isn't it? 😎

[↑ Table of contents](#toc)

<a name="ch-5"></a>
## 💬 Questions for better understanding

1. What should be the domain name for the link shortener?
2. Due to what redirect from a short link to the original occurs?
3. Which parameter in the GitHub Actions config is responsible for triggering job by branch name?

[↑ Table of contents](#toc)

<a name="ch-6"></a>
## ✏️ Exercises for independent execution

- Repeat everything you have seen in the article, but with your own domain and virtual server. _Please, write about your result & link to your shorter website in the comments below!_
- Try to deploy `index.html` to GitHub Pages (at your repository) instead of the virtual server, using the right action. _See `gh-pages` docs [here](https://pages.github.com/)_.

[↑ Table of contents](#toc)

<a name=""></a>
## Photos/Images by

- True web artisans ([link](https://github.com/truewebartisans))
- GitHub repository settings ([link](https://github.com))

<a name=""></a>
## P.S.

If you want more articles (like this) on this blog, then post a comment below and subscribe to me. Thanks! 😻

And of course, you can help me make developers' lives even better! Just connect to one of my projects as a contributor. It's easy!

My projects that need your help (and stars) 👇

- 🔥 [gowebly][gowebly_url]: A next-generation CLI tool for easily build amazing web applications with Go on the backend, using htmx & hyperscript and the most popular atomic/utility-first CSS frameworks on the frontend.
- ✨ [create-go-app][cgapp_url]: Create a new production-ready project with Go backend, frontend and deploy automation by running one CLI command.
- 🏃 [yatr][yatr_url]: Yet Another Task Runner allows you to organize and automate your routine operations that you normally do in Makefile (or else) for each project.
- 📚 [gosl][gosl_url]: The Go Snippet Library provides snippets collection for working with routine operations in your Go programs with a super user-friendly API and the most efficient performance.
- 🏄‍♂️ [csv2api][csv2api_url]: The parser reads the CSV file with the raw data, filters the records, identifies fields to be changed, and sends a request to update the data to the specified endpoint of your REST API.
- 🚴 [json2csv][json2csv_url]: The parser can read given folder with JSON files, filtering and qualifying input data with intent & stop words dictionaries and save results to CSV files by given chunk size.

<!-- Footer links -->

[gowebly_url]: https://github.com/gowebly
[cgapp_url]: https://github.com/create-go-app
[yatr_url]: https://github.com/koddr/yatr
[gosl_url]: https://github.com/koddr/gosl
[csv2api_url]: https://github.com/koddr/csv2api
[json2csv_url]: https://github.com/koddr/json2csv