diff options
author | 2022-12-11 20:03:30 +0000 | |
---|---|---|
committer | 2022-12-11 20:03:30 +0000 | |
commit | c395cb2d37c727fc647386be9b59f1a688dde496 (patch) | |
tree | ffb45ec88035d2fd6534af32f28bf76e9ed68c67 | |
parent | Reviews fully resolved, but unsure about defer (diff) | |
parent | Merge pull request #811 from python-discord/dependabot/pip/flake8-bugbear-22.... (diff) |
Merge branch 'main' into main
12 files changed, 500 insertions, 111 deletions
diff --git a/poetry.lock b/poetry.lock index c17c3286..7caa8a40 100644 --- a/poetry.lock +++ b/poetry.lock @@ -61,7 +61,7 @@ yaml = ["PyYAML"] [[package]] name = "certifi" -version = "2022.9.24" +version = "2022.12.7" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -145,7 +145,7 @@ python-versions = "*" [[package]] name = "django" -version = "4.1.3" +version = "4.1.4" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." category = "main" optional = false @@ -248,16 +248,16 @@ testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pyt [[package]] name = "flake8" -version = "5.0.4" +version = "6.0.0" description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.8.1" [package.dependencies] mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.9.0,<2.10.0" -pyflakes = ">=2.5.0,<2.6.0" +pycodestyle = ">=2.10.0,<2.11.0" +pyflakes = ">=3.0.0,<3.1.0" [[package]] name = "flake8-annotations" @@ -285,7 +285,7 @@ flake8 = ">=5.0.0" [[package]] name = "flake8-bugbear" -version = "22.10.27" +version = "22.12.6" description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." category = "dev" optional = false @@ -312,7 +312,7 @@ pydocstyle = ">=2.1" [[package]] name = "flake8-import-order" -version = "0.18.1" +version = "0.18.2" description = "Flake8 and pylama plugin that checks the ordering of import statements." category = "dev" optional = false @@ -422,7 +422,7 @@ socks = ["socksio (>=1.0.0,<2.0.0)"] [[package]] name = "httpx" -version = "0.23.0" +version = "0.23.1" description = "The next generation HTTP client." category = "main" optional = false @@ -430,7 +430,7 @@ python-versions = ">=3.7" [package.dependencies] certifi = "*" -httpcore = ">=0.15.0,<0.16.0" +httpcore = ">=0.15.0,<0.17.0" rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} sniffio = "*" @@ -587,7 +587,7 @@ python-versions = ">=3.6" [[package]] name = "pycodestyle" -version = "2.9.1" +version = "2.10.0" description = "Python style guide checker" category = "dev" optional = false @@ -625,7 +625,7 @@ python-versions = ">=3.7" [[package]] name = "pyflakes" -version = "2.5.0" +version = "3.0.1" description = "passive checker of Python programs" category = "dev" optional = false @@ -650,7 +650,7 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pymdown-extensions" -version = "9.8" +version = "9.9" description = "Extension pack for Python Markdown." category = "main" optional = false @@ -735,7 +735,7 @@ idna2008 = ["idna"] [[package]] name = "sentry-sdk" -version = "1.11.0" +version = "1.11.1" description = "Python client for Sentry (https://sentry.io)" category = "main" optional = false @@ -912,7 +912,7 @@ brotli = ["Brotli"] [metadata] lock-version = "1.1" python-versions = "3.10.*" -content-hash = "bd31b8df83e8098e6a18a2cddc41ef40215cc0e20269900bedd59330a7363951" +content-hash = "97d11b3c40201adbd9bc794c09f4fc3999c77fc2715725c964976ffcfedc8ef2" [metadata.files] anyio = [ @@ -932,8 +932,8 @@ bandit = [ {file = "bandit-1.7.4.tar.gz", hash = "sha256:2d63a8c573417bae338962d4b9b06fbc6080f74ecd955a092849e1e65c717bd2"}, ] certifi = [ - {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, - {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, ] cffi = [ {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, @@ -1098,8 +1098,8 @@ distlib = [ {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, ] django = [ - {file = "Django-4.1.3-py3-none-any.whl", hash = "sha256:6b1de6886cae14c7c44d188f580f8ba8da05750f544c80ae5ad43375ab293cd5"}, - {file = "Django-4.1.3.tar.gz", hash = "sha256:678bbfc8604eb246ed54e2063f0765f13b321a50526bdc8cb1f943eda7fa31f1"}, + {file = "Django-4.1.4-py3-none-any.whl", hash = "sha256:0b223bfa55511f950ff741983d408d78d772351284c75e9f77d2b830b6b4d148"}, + {file = "Django-4.1.4.tar.gz", hash = "sha256:d38a4e108d2386cb9637da66a82dc8d0733caede4c83c4afdbda78af4214211b"}, ] django-distill = [ {file = "django-distill-3.0.1.tar.gz", hash = "sha256:8bbac5e45d2afc61cc718d587c6026267c985305f5e599465f2ebc4b0cba9ebf"}, @@ -1129,8 +1129,8 @@ filelock = [ {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"}, ] flake8 = [ - {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, - {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, + {file = "flake8-6.0.0-py2.py3-none-any.whl", hash = "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7"}, + {file = "flake8-6.0.0.tar.gz", hash = "sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181"}, ] flake8-annotations = [ {file = "flake8-annotations-2.9.1.tar.gz", hash = "sha256:11f09efb99ae63c8f9d6b492b75fe147fbc323179fddfe00b2e56eefeca42f57"}, @@ -1141,16 +1141,16 @@ flake8-bandit = [ {file = "flake8_bandit-4.1.1.tar.gz", hash = "sha256:068e09287189cbfd7f986e92605adea2067630b75380c6b5733dab7d87f9a84e"}, ] flake8-bugbear = [ - {file = "flake8-bugbear-22.10.27.tar.gz", hash = "sha256:a6708608965c9e0de5fff13904fed82e0ba21ac929fe4896459226a797e11cd5"}, - {file = "flake8_bugbear-22.10.27-py3-none-any.whl", hash = "sha256:6ad0ab754507319060695e2f2be80e6d8977cfcea082293089a9226276bd825d"}, + {file = "flake8-bugbear-22.12.6.tar.gz", hash = "sha256:4cdb2c06e229971104443ae293e75e64c6107798229202fbe4f4091427a30ac0"}, + {file = "flake8_bugbear-22.12.6-py3-none-any.whl", hash = "sha256:b69a510634f8a9c298dfda2b18a8036455e6b19ecac4fe582e4d7a0abfa50a30"}, ] flake8-docstrings = [ {file = "flake8-docstrings-1.6.0.tar.gz", hash = "sha256:9fe7c6a306064af8e62a055c2f61e9eb1da55f84bb39caef2b84ce53708ac34b"}, {file = "flake8_docstrings-1.6.0-py2.py3-none-any.whl", hash = "sha256:99cac583d6c7e32dd28bbfbef120a7c0d1b6dde4adb5a9fd441c4227a6534bde"}, ] flake8-import-order = [ - {file = "flake8-import-order-0.18.1.tar.gz", hash = "sha256:a28dc39545ea4606c1ac3c24e9d05c849c6e5444a50fb7e9cdd430fc94de6e92"}, - {file = "flake8_import_order-0.18.1-py2.py3-none-any.whl", hash = "sha256:90a80e46886259b9c396b578d75c749801a41ee969a235e163cfe1be7afd2543"}, + {file = "flake8-import-order-0.18.2.tar.gz", hash = "sha256:e23941f892da3e0c09d711babbb0c73bc735242e9b216b726616758a920d900e"}, + {file = "flake8_import_order-0.18.2-py2.py3-none-any.whl", hash = "sha256:82ed59f1083b629b030ee9d3928d9e06b6213eb196fe745b3a7d4af2168130df"}, ] flake8-string-format = [ {file = "flake8-string-format-0.3.0.tar.gz", hash = "sha256:65f3da786a1461ef77fca3780b314edb2853c377f2e35069723348c8917deaa2"}, @@ -1184,8 +1184,8 @@ httpcore = [ {file = "httpcore-0.15.0.tar.gz", hash = "sha256:18b68ab86a3ccf3e7dc0f43598eaddcf472b602aba29f9aa6ab85fe2ada3980b"}, ] httpx = [ - {file = "httpx-0.23.0-py3-none-any.whl", hash = "sha256:42974f577483e1e932c3cdc3cd2303e883cbfba17fe228b0f63589764d7b9c4b"}, - {file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"}, + {file = "httpx-0.23.1-py3-none-any.whl", hash = "sha256:0b9b1f0ee18b9978d637b0776bfd7f54e2ca278e063e3586d8f01cda89e042a8"}, + {file = "httpx-0.23.1.tar.gz", hash = "sha256:202ae15319be24efe9a8bd4ed4360e68fde7b38bcc2ce87088d416f026667d19"}, ] identify = [ {file = "identify-2.5.8-py2.py3-none-any.whl", hash = "sha256:48b7925fe122720088aeb7a6c34f17b27e706b72c61070f27fe3789094233440"}, @@ -1355,8 +1355,8 @@ psycopg2-binary = [ {file = "psycopg2_binary-2.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:484405b883630f3e74ed32041a87456c5e0e63a8e3429aa93e8714c366d62bd1"}, ] pycodestyle = [ - {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, - {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, + {file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"}, + {file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"}, ] pycparser = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, @@ -1371,16 +1371,16 @@ pyfakefs = [ {file = "pyfakefs-5.0.0.tar.gz", hash = "sha256:19d1d8f1ee520891d78b6ed05c2078e0792d545f83dee33461fbaa5cc72e187d"}, ] pyflakes = [ - {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, - {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, + {file = "pyflakes-3.0.1-py2.py3-none-any.whl", hash = "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf"}, + {file = "pyflakes-3.0.1.tar.gz", hash = "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"}, ] PyJWT = [ {file = "PyJWT-2.6.0-py3-none-any.whl", hash = "sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14"}, {file = "PyJWT-2.6.0.tar.gz", hash = "sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd"}, ] pymdown-extensions = [ - {file = "pymdown_extensions-9.8-py3-none-any.whl", hash = "sha256:8e62688a8b1128acd42fa823f3d429d22f4284b5e6dd4d3cd56721559a5a211b"}, - {file = "pymdown_extensions-9.8.tar.gz", hash = "sha256:1bd4a173095ef8c433b831af1f3cb13c10883be0c100ae613560668e594651f7"}, + {file = "pymdown_extensions-9.9-py3-none-any.whl", hash = "sha256:ac698c15265680db5eb13cd4342abfcde2079ac01e5486028f47a1b41547b859"}, + {file = "pymdown_extensions-9.9.tar.gz", hash = "sha256:0f8fb7b74a37a61cc34e90b2c91865458b713ec774894ffad64353a5fce85cfc"}, ] python-dotenv = [ {file = "python-dotenv-0.21.0.tar.gz", hash = "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"}, @@ -1445,8 +1445,8 @@ rfc3986 = [ {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, ] sentry-sdk = [ - {file = "sentry-sdk-1.11.0.tar.gz", hash = "sha256:e7b78a1ddf97a5f715a50ab8c3f7a93f78b114c67307785ee828ef67a5d6f117"}, - {file = "sentry_sdk-1.11.0-py2.py3-none-any.whl", hash = "sha256:f467e6c7fac23d4d42bc83eb049c400f756cd2d65ab44f0cc1165d0c7c3d40bc"}, + {file = "sentry-sdk-1.11.1.tar.gz", hash = "sha256:675f6279b6bb1fea09fd61751061f9a90dca3b5929ef631dd50dc8b3aeb245e9"}, + {file = "sentry_sdk-1.11.1-py2.py3-none-any.whl", hash = "sha256:8b4ff696c0bdcceb3f70bbb87a57ba84fd3168b1332d493fcd16c137f709578c"}, ] setuptools = [ {file = "setuptools-65.5.0-py3-none-any.whl", hash = "sha256:f62ea9da9ed6289bfe868cd6845968a2c854d1427f8548d52cae02a42b4f0356"}, diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/asking-good-questions.md b/pydis_site/apps/content/resources/guides/pydis-guides/asking-good-questions.md index 971989a9..b08ba7c6 100644 --- a/pydis_site/apps/content/resources/guides/pydis-guides/asking-good-questions.md +++ b/pydis_site/apps/content/resources/guides/pydis-guides/asking-good-questions.md @@ -26,7 +26,7 @@ If none of the above steps help you or you're not sure how to do some of the abo # A Good Question -When you're ready to ask a question, there's a few things you should have to hand before forming a query. +When you're ready to ask a question, there are a few things you should have to hand before forming a query. * A code example that illustrates your problem * If possible, make this a minimal example rather than an entire application diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/contributing-guidelines/commit-messages.md b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/commit-messages.md index ba476b65..ba476b65 100644 --- a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/contributing-guidelines/commit-messages.md +++ b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/commit-messages.md diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/contributing-guidelines.md b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/contributing-guidelines.md index 73c5dcab..d1e4250d 100644 --- a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/contributing-guidelines.md +++ b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/contributing-guidelines.md @@ -12,7 +12,7 @@ We have simple but strict style rules that are enforced through linting. Not all of the style rules are enforced by linting, so make sure to read the [style guide](../style-guide/) as well. 2. **Make great commits.** Great commits should be atomic, with a commit message explaining what and why. -Check out [Writing Good Commit Messages](./commit-messages) for details. +Check out [Writing Good Commit Messages](../commit-messages/) for details. 3. **Do not open a pull request if you aren't assigned to the issue.** If someone is already working on it, consider offering to collaborate with that person. 4. **Use assets licensed for public use.** diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/contributing-guidelines/_info.yml b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/contributing-guidelines/_info.yml deleted file mode 100644 index 80c8e772..00000000 --- a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/contributing-guidelines/_info.yml +++ /dev/null @@ -1,2 +0,0 @@ -title: Contributing Guidelines -description: Guidelines to adhere to when contributing to our projects. diff --git a/pydis_site/apps/content/resources/guides/python-guides/docker-hosting-guide.md b/pydis_site/apps/content/resources/guides/python-guides/docker-hosting-guide.md new file mode 100644 index 00000000..57d86e99 --- /dev/null +++ b/pydis_site/apps/content/resources/guides/python-guides/docker-hosting-guide.md @@ -0,0 +1,323 @@ +--- +title: How to host a bot with Docker and GitHub Actions on Ubuntu VPS +description: This guide shows how to host a bot with Docker and GitHub Actions on Ubuntu VPS +--- + +## Contents + +1. [You will learn](#you-will-learn) +2. [Introduction](#introduction) +3. [Installing Docker](#installing-docker) +4. [Creating Dockerfile](#creating-dockerfile) +5. [Building Image and Running Container](#building-image-and-running-container) +6. [Creating Volumes](#creating-volumes) +7. [Using GitHub Actions for full automation](#using-github-actions-for-full-automation) + +## You will learn how to + +- write Dockerfile +- build Docker image and run the container +- use Docker Compose +- make docker keep the files throughout the container's runs +- parse environment variables into container +- use GitHub Actions for automation +- set up self-hosted runner +- use runner secrets + +## Introduction + +Let's say you have got a nice discord bot written in python and you have a VPS to host it on. Now the only question is +how to run it 24/7. You might have been suggested to use *screen multiplexer*, but it has some disadvantages: + +1. Every time you update the bot you have to SSH to your server, attach to screen, shutdown the bot, run `git pull` and + run the bot again. You might have good extensions management that allows you to update the bot without restarting it, + but there are some other cons as well +2. If you update some dependencies, you have to update them manually +3. The bot doesn't run in an isolated environment, which is not good for security. + +But there's a nice and easy solution to these problems - **Docker**! Docker is a containerization utility that automates +some stuff like dependencies update and running the application in the background. So let's get started. + +## Installing Docker + +The best way to install Docker is to use +the [convenience script](https://docs.docker.com/engine/install/ubuntu/#install-using-the-convenience-script) provided +by Docker developers themselves. You just need 2 lines: + +```shell +$ curl -fsSL https://get.docker.com -o get-docker.sh +$ sudo sh get-docker.sh +``` + +## Creating Dockerfile + +To tell Docker what it has to do to run the application, we need to create a file named `Dockerfile` in our project's +root. + +1. First we need to specify the *base image*, which is the OS that the docker container will be running. Doing that will + make Docker install some apps we need to run our bot, for + example the Python interpreter + +```dockerfile +FROM python:3.10-bullseye +``` + +2. Next, we need to copy our Python project's external dependencies to some directory *inside the container*. Let's call + it `/app` + +```dockerfile +COPY requirements.txt /app/ +``` + +3. Now we need to set the directory as working and install the requirements + +```dockerfile +WORKDIR /app +RUN pip install -r requirements.txt +``` + +4. The only thing that is left to do is to copy the rest of project's files and run the main executable + +```dockerfile +COPY . . +CMD ["python3", "main.py"] +``` + +The final version of Dockerfile looks like this: + +```dockerfile +FROM python:3.10-bullseye +COPY requirements.txt /app/ +WORKDIR /app +RUN pip install -r requirements.txt +COPY . . +CMD ["python3", "main.py"] +``` + +## Building Image and Running Container + +Now update the project on your VPS, so we can run the bot with Docker. + +1. Build the image (dot at the end is very important) + +```shell +$ docker build -t mybot . +``` + +- the `-t` flag specifies a **tag** that will be assigned to the image. With it, we can easily run the image that the + tag was assigned to. +- the dot at the end is basically the path to search for Dockerfile. The dot means current directory (`./`) + +2. Run the container + +```shell +$ docker run -d --name mybot mybot:latest +``` + +- `-d` flag tells Docker to run the container in detached mode, meaning it will run the container in the background of + your terminal and not give us + any output from it. If we don't + provide it, the `run` will be giving us the output until the application exits. Discord bots aren't supposed to exit + after certain time, so we do need this flag +- `--name` assigns a name to the container. By default, container is identified by id that is not human-readable. To + conveniently refer to container when needed, + we can assign it a name +- `mybot:latest` means "latest version of `mybot` image" + +3. Read bot logs (keep in mind that this utility only allows to read STDERR) + +```shell +$ docker logs -f mybot +``` + +- `-f` flag tells the docker to keep reading logs as they appear in container and is called "follow mode". To exit + press `CTRL + C`. + +If everything went successfully, your bot will go online and will keep running! + +## Using Docker Compose + +Just 2 commands to run a container is cool, but we can shorten it down to just 1 simple command. For that, create +a `docker-compose.yml` file in project's root and fill it with the following contents: + +```yml +version: "3.8" +services: + main: + build: . + container_name: mybot +``` + +- `version` tells Docker what version of Compose to use. You may check all the + versions [here](https://docs.docker.com/compose/compose-file/compose-versioning/) +- `services` contains services to build and run. Read more about + services [here](https://docs.docker.com/compose/compose-file/#services-top-level-element) +- `main` is a service. We can call it whatever we would like to, not necessarily `main` +- `build: .` is a path to search for Dockerfile, just like `docker build` command's dot +- `container_name: mybot` is a container name to use for a bot, just like `docker run --name mybot` + +Update the project on VPS, remove the previous container with `docker rm -f mybot` and run this command + +```shell +docker compose up -d --build +``` + +Now the docker will automatically build the image for you and run the container. + +### Why docker-compose + +The main purpose of Compose is to run several services at once. Mostly we +don't need this in discord bots, however. +For us, it has the following benefits: + +- we can build and run the container with just one command +- if we need to parse some environment variables or volumes (more about them further in tutorial) our run command would + look like this + +```shell +$ docker run -d --name mybot -e TOKEN=... -e WEATHER_API_APIKEY=... -e SOME_USEFUL_ENVIRONMENT_VARIABLE=... --net=host -v /home/exenifix/bot-data/data:/app/data -v /home/exenifix/bot-data/images:/app/data/images +``` + +This is pretty long and unreadable. Compose allows us to transfer those flags into single config file and still +use just one short command to run the container. + +## Creating Volumes + +The files creating during container run are destroyed after its recreation. To prevent some files from getting +destroyed, we need to use *volumes* that basically save the files from directory inside of container somewhere on drive. + +1. Create a new directory somewhere and copy path to it + +```shell +$ mkdir mybot-data +$ echo $(pwd)/mybot-data +``` + +My path is `/home/exenifix/mybot-data`, yours is most likely **different**! + +2. In your project, store the files that need to be persistent in a separate directory (eg. `data`) +3. Add `volumes` to `docker-compose.yaml` so it looks like this: + +```yml +version: "3.8" +services: + main: + build: . + container_name: mybot + volumes: + - /home/exenifix/mybot-data:/app/data +``` + +The path before the colon `:` is the directory *on server's drive, outside of container*, and the second path is the +directory *inside of container*. +All the files saved in container in that directory will be saved on drive's directory as well and Docker will be +accessing them *from drive*. + +## Using GitHub Actions for full automation + +Now it's time to fully automate the process and make Docker update the bot automatically on every commit or release. For +that, we will use a **GitHub Actions workflow**, which basically runs some commands when we need to. You may read more +about them [here](https://docs.github.com/en/actions/using-workflows). + +### Create repository secret + +We will not have the ability to use `.env` files with the workflow, so it's better to store the environment variables +as **actions secrets**. Let's add your discord bot's token as a secret + +1. Head to your repository page -> Settings -> Secrets -> Actions +2. Press `New repository secret` +3. Give it a name like `TOKEN` and paste the token. + Now we will be able to access its value in workflow like `${{ secrets.TOKEN }}`. However, we also need to parse the + variable into container now. Edit `docker-compose` so it looks like this: + +```yml +version: "3.8" +services: + main: + build: . + container_name: mybot + volumes: + - /home/exenifix/mybot-data:/app/data + environment: + - TOKEN +``` + +### Setup self-hosted runner + +To run the workflow on our VPS, we will need to register it as *self-hosted runner*. + +1. Head to Settings -> Actions -> Runners +2. Press `New self-hosted runner` +3. Select runner image and architecture +4. Follow the instructions but don't run the runner +5. Instead, create a service + +```shell +$ sudo ./svc.sh install +$ sudo ./svc.sh start +``` + +Now we have registered our VPS as a self-hosted runner and we can run the workflow on it now. + +### Write a workflow + +Create a new file `.github/workflows/runner.yml` and paste the following content into it. Please pay attention to +the `branches` instruction. +The GitHub's standard main branch name is `main`, however it may be named `master` or something else if you edited its +name. Make sure to put +the correct branch name, otherwise it won't work. More about GitHub workflows +syntax [here](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions) + +```yml +name: Docker Runner + +on: + push: + branches: [ main ] + +jobs: + run: + runs-on: self-hosted + environment: production + + steps: + - uses: actions/checkout@v3 + + - name: Run Container + run: docker compose up -d --build + env: + TOKEN: ${{ secrets.TOKEN }} + + - name: Cleanup Unused Images + run: docker image prune -f +``` + +Run `docker rm -f mybot` (it only needs to be done once) and push to GitHub. Now if you open `Actions` tab on your +repository, you should see a workflow running your bot. Congratulations! + +### Displaying logs in actions terminal + +There's a nice utility for reading docker container's logs and stopping upon meeting a certain phrase and it might be +useful for you as well. + +1. Install the utility on your VPS with + +```shell +$ pip install exendlr +``` + +2. Add a step to your workflow that would show the logs until it meets `"ready"` phrase. I recommend putting it before + the cleanup. + +```yml +- name: Display Logs + run: python3 -m exendlr mybot "ready" +``` + +Now you should see the logs of your bot until the stop phrase is met. + +**WARNING** +> The utility only reads from STDERR and redirects to STDERR, if you are using STDOUT for logs, it will not work and +> will be waiting for stop phrase forever. The utility automatically exits if bot's container is stopped (e.g. error +> occurred during starting) or if a log line contains a stop phrase. Make sure that your bot 100% displays a stop phrase +> when it's ready otherwise your workflow will get stuck. diff --git a/pydis_site/apps/content/resources/guides/python-guides/keeping-tokens-safe.md b/pydis_site/apps/content/resources/guides/python-guides/keeping-tokens-safe.md new file mode 100644 index 00000000..9d523b4b --- /dev/null +++ b/pydis_site/apps/content/resources/guides/python-guides/keeping-tokens-safe.md @@ -0,0 +1,29 @@ +--- +title: Keeping Discord Bot Tokens Safe +description: How to keep your bot tokens safe and safety measures you can take. +--- +It's **very** important to keep a bot token safe, +primarily because anyone who has the bot token can do whatever they want with the bot -- +such as destroying servers your bot has been added to and getting your bot banned from the API. + +# How to Avoid Leaking your Token +To help prevent leaking your token, +you should ensure that you don't upload it to an open source program/website, +such as replit and github, as they show your code publicly. +The best practice for storing tokens is generally utilising .env files +([click here](https://vcokltfre.dev/tips/tokens/.) for more information on storing tokens safely). + +# What should I do if my token does get leaked? + +If for whatever reason your token gets leaked, you should immediately follow these steps: +- Go to the list of [Discord Bot Applications](https://discord.com/developers/applications) you have and select the bot application that had the token leaked. +- Select the Bot (1) tab on the left-hand side, next to a small image of a puzzle piece. After doing so you should see a small section named TOKEN (under your bot USERNAME and next to his avatar image) +- Press the Regenerate button to regenerate your bot token and invalidate the old one. + + + +Following these steps will create a new token for your bot, making it secure again and terminating any connections from the leaked token. +The old token will stop working though, so make sure to replace the old token with the new one in your code if you haven't already. + +# Summary +Make sure you keep your token secure by storing it safely, not sending it to anyone you don't trust, and regenerating your token if it does get leaked. diff --git a/pydis_site/apps/content/resources/guides/python-guides/proper-error-handling.md b/pydis_site/apps/content/resources/guides/python-guides/proper-error-handling.md new file mode 100644 index 00000000..74b0f59b --- /dev/null +++ b/pydis_site/apps/content/resources/guides/python-guides/proper-error-handling.md @@ -0,0 +1,70 @@ +--- +title: Proper error handling in discord.py +description: Are you not getting any errors? This might be why! +--- +If you're not recieving any errors in your console, even though you know you should be, try this: + +# With bot subclass: +```py +import discord +from discord.ext import commands + +import traceback +import sys + +class MyBot(commands.Bot): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + async def on_command_error(self, ctx: commands.Context, error): + # Handle your errors here + if isinstance(error, commands.MemberNotFound): + await ctx.send("I could not find member '{error.argument}'. Please try again") + + elif isinstance(error, commands.MissingRequiredArgument): + await ctx.send(f"'{error.param.name}' is a required argument.") + else: + # All unhandled errors will print their original traceback + print(f'Ignoring exception in command {ctx.command}:', file=sys.stderr) + traceback.print_exception(type(error), error, error.__traceback__, file=sys.stderr) + +bot = MyBot(command_prefix="!", intents=discord.Intents.default()) + +bot.run("token") +``` + +# Without bot subclass +```py +import discord +from discord.ext import commands + +import traceback +import sys + +async def on_command_error(self, ctx: commands.Context, error): + # Handle your errors here + if isinstance(error, commands.MemberNotFound): + await ctx.send("I could not find member '{error.argument}'. Please try again") + + elif isinstance(error, commands.MissingRequiredArgument): + await ctx.send(f"'{error.param.name}' is a required argument.") + else: + # All unhandled errors will print their original traceback + print(f'Ignoring exception in command {ctx.command}:', file=sys.stderr) + traceback.print_exception(type(error), error, error.__traceback__, file=sys.stderr) + +bot = commands.Bot(command_prefix="!", intents=discord.Intents.default()) +bot.on_command_error = on_command_error + +bot.run("token") +``` + + +Make sure to import `traceback` and `sys`! + +------------------------------------------------------------------------------------------------------------- + +Useful Links: +- [FAQ](https://discordpy.readthedocs.io/en/latest/faq.html) +- [Simple Error Handling](https://gist.github.com/EvieePy/7822af90858ef65012ea500bcecf1612) diff --git a/pydis_site/apps/content/resources/guides/python-guides/vps-services.md b/pydis_site/apps/content/resources/guides/python-guides/vps-services.md index 0acd3e55..710fd914 100644 --- a/pydis_site/apps/content/resources/guides/python-guides/vps-services.md +++ b/pydis_site/apps/content/resources/guides/python-guides/vps-services.md @@ -1,9 +1,12 @@ --- -title: VPS Services -description: On different VPS services +title: VPS and Free Hosting Service for Discord bots +description: This article lists recommended VPS services and covers the disasdvantages of utilising a free hosting service to run a discord bot. +toc: 2 --- -If you need to run your bot 24/7 (with no downtime), you should consider using a virtual private server (VPS). This is a list of VPS services that are sufficient for running Discord bots. +## Recommended VPS services + +If you need to run your bot 24/7 (with no downtime), you should consider using a virtual private server (VPS). Here is a list of VPS services that are sufficient for running Discord bots. * Europe * [netcup](https://www.netcup.eu/) @@ -25,7 +28,31 @@ If you need to run your bot 24/7 (with no downtime), you should consider using a * [OVHcloud](https://www.ovhcloud.com/) * [Vultr](https://www.vultr.com/) ---- -# Free hosts -There are no reliable free options for VPS hosting. If you would rather not pay for a hosting service, you can consider self-hosting. -Any modern hardware should be sufficient for running a bot. An old computer with a few GB of ram could be suitable, or a Raspberry Pi. + +## Why not to use free hosting services for bots? +While these may seem like nice and free services, it has a lot more caveats than you may think. For example, the drawbacks of using common free hosting services to host a discord bot are discussed below. + +### Replit + +- The machines are super underpowered, resulting in your bot lagging a lot as it gets bigger. + +- You need to run a webserver alongside your bot to prevent it from being shut off. This uses extra machine power. + +- Repl.it uses an ephemeral file system. This means any file you saved through your bot will be overwritten when you next launch. + +- They use a shared IP for everything running on the service. +This one is important - if someone is running a user bot on their service and gets banned, everyone on that IP will be banned. Including you. + +### Heroku +- Bots are not what the platform is designed for. Heroku is designed to provide web servers (like Django, Flask, etc). This is why they give you a domain name and open a port on their local emulator. + +- Heroku's environment is heavily containerized, making it significantly underpowered for a standard use case. + +- Heroku's environment is volatile. In order to handle the insane amount of users trying to use it for their own applications, Heroku will dispose your environment every time your application dies unless you pay. + +- Heroku has minimal system dependency control. If any of your Python requirements need C bindings (such as PyNaCl + binding to libsodium, or lxml binding to libxml), they are unlikely to function properly, if at all, in a native + environment. As such, you often need to resort to adding third-party buildpacks to facilitate otherwise normal + CPython extension functionality. (This is the reason why voice doesn't work natively on heroku) + +- Heroku only offers a limited amount of time on their free programme for your applications. If you exceed this limit, which you probably will, they'll shut down your application until your free credit resets. diff --git a/pydis_site/apps/content/resources/guides/python-guides/vps_services.md b/pydis_site/apps/content/resources/guides/python-guides/vps_services.md deleted file mode 100644 index 710fd914..00000000 --- a/pydis_site/apps/content/resources/guides/python-guides/vps_services.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: VPS and Free Hosting Service for Discord bots -description: This article lists recommended VPS services and covers the disasdvantages of utilising a free hosting service to run a discord bot. -toc: 2 ---- - -## Recommended VPS services - -If you need to run your bot 24/7 (with no downtime), you should consider using a virtual private server (VPS). Here is a list of VPS services that are sufficient for running Discord bots. - -* Europe - * [netcup](https://www.netcup.eu/) - * Germany & Austria data centres. - * Great affiliate program. - * [Yandex Cloud](https://cloud.yandex.ru/) - * Vladimir, Ryazan, and Moscow region data centres. - * [Scaleway](https://www.scaleway.com/) - * France data centre. - * [Time 4 VPS](https://www.time4vps.eu/) - * Lithuania data centre. -* US - * [GalaxyGate](https://galaxygate.net/) - * New York data centre. - * Great affiliate program. -* Global - * [Linode](https://www.linode.com/) - * [Digital Ocean](https://www.digitalocean.com/) - * [OVHcloud](https://www.ovhcloud.com/) - * [Vultr](https://www.vultr.com/) - - -## Why not to use free hosting services for bots? -While these may seem like nice and free services, it has a lot more caveats than you may think. For example, the drawbacks of using common free hosting services to host a discord bot are discussed below. - -### Replit - -- The machines are super underpowered, resulting in your bot lagging a lot as it gets bigger. - -- You need to run a webserver alongside your bot to prevent it from being shut off. This uses extra machine power. - -- Repl.it uses an ephemeral file system. This means any file you saved through your bot will be overwritten when you next launch. - -- They use a shared IP for everything running on the service. -This one is important - if someone is running a user bot on their service and gets banned, everyone on that IP will be banned. Including you. - -### Heroku -- Bots are not what the platform is designed for. Heroku is designed to provide web servers (like Django, Flask, etc). This is why they give you a domain name and open a port on their local emulator. - -- Heroku's environment is heavily containerized, making it significantly underpowered for a standard use case. - -- Heroku's environment is volatile. In order to handle the insane amount of users trying to use it for their own applications, Heroku will dispose your environment every time your application dies unless you pay. - -- Heroku has minimal system dependency control. If any of your Python requirements need C bindings (such as PyNaCl - binding to libsodium, or lxml binding to libxml), they are unlikely to function properly, if at all, in a native - environment. As such, you often need to resort to adding third-party buildpacks to facilitate otherwise normal - CPython extension functionality. (This is the reason why voice doesn't work natively on heroku) - -- Heroku only offers a limited amount of time on their free programme for your applications. If you exceed this limit, which you probably will, they'll shut down your application until your free credit resets. diff --git a/pydis_site/static/images/content/regenerating_token.jpg b/pydis_site/static/images/content/regenerating_token.jpg Binary files differnew file mode 100644 index 00000000..7b2588dc --- /dev/null +++ b/pydis_site/static/images/content/regenerating_token.jpg diff --git a/pyproject.toml b/pyproject.toml index 79f2ecc0..dfc69ac9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,32 +7,32 @@ license = "MIT" [tool.poetry.dependencies] python = "3.10.*" -django = "4.1.3" +django = "4.1.4" django-environ = "0.9.0" django-filter = "22.1" djangorestframework = "3.14.0" psycopg2-binary = "2.9.5" django-simple-bulma = "2.5.0" whitenoise = "6.2.0" -httpx = "0.23.0" +httpx = "0.23.1" pyyaml = "6.0" gunicorn = "20.1.0" -sentry-sdk = "1.11.0" +sentry-sdk = "1.11.1" markdown = "3.4.1" python-frontmatter = "1.0.0" django-prometheus = "2.2.0" django-distill = "3.0.1" PyJWT = {version = "2.6.0", extras = ["crypto"]} -pymdown-extensions = "9.8" +pymdown-extensions = "9.9" [tool.poetry.dev-dependencies] coverage = "6.5.0" -flake8 = "5.0.4" +flake8 = "6.0.0" flake8-annotations = "2.9.1" flake8-bandit = "4.1.1" -flake8-bugbear = "22.10.27" +flake8-bugbear = "22.12.6" flake8-docstrings = "1.6.0" -flake8-import-order = "0.18.1" +flake8-import-order = "0.18.2" flake8-tidy-imports = "4.8.0" flake8-string-format = "0.3.0" flake8-todo = "0.7" |