Multiple Developers; One Discord Bot?
Tags: python, github, discord
Published: 2022-03-08 16:56:00

I had a problem: 

> How do I keep a Python Discord bot online, allow multiple people to contribute to it and then deploy it when new features are added?

Several Google searches had come up short, partial solutions; no clear answer.

Here is my attempt at a solution (_though no promise that it is the smartest, or best.. it has just worked for me_ ¯\\_\(ツ)\_/¯)

## Hosting and Deployment

This project uses discord.py and [discord-py-slash-command](https://pypi.org/project/discord-py-slash-command/) to patch the missing slash command functionality and is hosted [here](https://github.com/harveychurch/beeb-bot).

The aim of the project was to allow for any member of a private Discord to contribute custom functionality, test locally and then deploy for all to use.

---

![From Local Development to Deployment](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m3ovqs4085ivb2i5oge5.png)

1.  Code can be developed locally, and continually pushed to a GitHub repo.
2.  No code is pushed to the "Live Server" until a git tag is created for a specific release
3.  This tag triggers a GitHub Action that SSH's into the "live server" and deploys the code
4.  For a user on a Discord server the bot is part of, can now run commands.
5.  Developers can continue to test/create features on a locally hosted bot, and push them _without_ the live bot being effected

For hosting, I am using [DigitalOcean](https://www.digitalocean.com/).

 I am sure any other cloud hosting provider will work, just a few changes in the GitHub action script would be required.

## GitHub Actions & Server Setup

### The Script

To actually upload the code and implement the triggering of the workflow when a tag is created, I have used the following GitHub workflow script.

```
name: Deploy Code to Digital Ocean

on:
  push: 
    branches:
    - main
    tags:
     - 'v0.*'

jobs:
  create_release:
    if: github.ref_type == 'tag' && contains(github.ref_name,'v')
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2.4.0
      - name: Create Release on GitHub
        uses: softprops/action-gh-release@v1
        with:
           tag_name: ${{ github.ref_name }}
           token: ${{ secrets.GITHUB_TOKEN }}
  push_to_digital_ocean:
    needs: create_release
    runs-on: ubuntu-latest
    steps:
     - name: SSH into Droplet and push
       uses: garygrossgarten/github-action-ssh@release
       with:
        host: ${{ secrets.HOST }}
        username: ${{ secrets.USERNAME }}
        passphrase: ${{ secrets.PASSWORD }}
        privateKey: ${{secrets.SSHKEY}}
        command: |
         cd beeb-bot/
         echo "Running Startup Script"
         pm2 stop main_bot
         git pull
         # For if there have been any changes to the pipefile/dependancies
         pipenv install
         pm2 start main_bot
                
 ```
The core of the script is the SSH portion. GitHub actions are designed for the repeating of others code. This action makes use of Gary Großgarten's ["Github Action SSH"](https://github.com/garygrossgarten/github-action-ssh). 

A private key is stored _within_ the repository, with the matching Public Key on the server.

### The Juicy Part

The important part of the script is the following commands:

```
  cd beeb-bot/
         echo "Running Startup Script"
         pm2 stop main_bot
         git pull
         # For if there have been any changes to the pipefile/dependancies
         pipenv install
         pm2 start main_bot
```

`pm2` in the script refers to a [process manager](https://pm2.keymetrics.io/), mainly used for Node.js projects -- however it can run Python scripts. 

`pm2` is used used because just running something like `python3 main.py` locks up the action, and it can never exit. Using this means the script no longer locks up, and the action succeeds/fails.

`main_bot` is a short hand for `pm2 start "pipenv run python3 main.py" --name main_bot --watch`. This project makes use of pipenv to manage dependencies, and to make sure the virtual environment is used, `pipenv run` is called.

A git instance of repo is stored on the server. `git pull` gets a copy from GitHub (which should be the latest release).

`pipenv install` is run to ensure the virtual environment on the server matches the needed packages for the project.

`pm2 start main_bot` is called to start the process again and run the freshly pulled `main.py` script.

### On the Server (_specifically for this project_)

The initial set-up on the server has a few requirements. `pipenv` and `pm2` must then be installed.

Following this:

1. The user must clone the repo on the server
2. Run `pipenv install` to establish the same Python packages that the project is expecting (mainly discord.py and discord-py-slash-command)
3. Have a token stored in the `.env` file so that when the bot is run, it can talk to Discord and authenticate.
4. Create a pm2 proccess -- `pm2 start "pipenv run python3 main.py" --name main_bot` -- _Note: This project expects the proccess to be called "main_bot", but in reality, any name can be used._

## Final Remarks

This is not _entirely_ comprehensive, but hopefully is a good jumping off point for others. 

I am certain that it is not perfect, I have missed something obvious or that there is something simpler. 

One big gap, the release process relying on tags is very manual. The usage of Semantic Version could improve this.