diff --git a/.env-example b/.env-example index d2ac0cd2..30338ddb 100755 --- a/.env-example +++ b/.env-example @@ -2,15 +2,22 @@ # export LOG_LEVEL=DEBUG export TZ=America/Los_Angeles export LOG_LEVEL=INFO -# Tokens + +# 3rd-party API Tokens export DISCORD_TOKEN= export TMDB_KEY= export NASA_TOKEN= export GIT_TOKEN= +export GOOGLE_API_KEY= +export GOOGLE_CX= +export WOLFRAM_ALPHA_ID= # Mysql DB Creds export DB_SEED_FILEPATH=./data/seed-db.sql -export DB_HOST=db +# Uncomment next line to run mysql in docker +export DB_HOST=host.docker.internal +# Uncomment next line to run mysql in kubernetes +# export DB_HOST=mysql-service.agimus.svc.cluster.local export DB_NAME=FoD export DB_PASS=password export DB_USER=root @@ -18,12 +25,5 @@ export DB_DUMP_FILENAME=bot-dump.sql export DB_CONTAINER_NAME=fodmysql # Path to configuration json -export BOT_CONTAINER_NAME=fodbot +export BOT_CONTAINER_NAME=agimus export BOT_CONFIGURATION_FILEPATH=./configuration.json - -# Google Search Credentials for generate_episode_python.json -export GOOGLE_API_KEY= -export GOOGLE_CX= - -# Wolfram Alpha Credentials for "Computer:" prompts -export WOLFRAM_ALPHA_ID= \ No newline at end of file diff --git a/.github/workflows/build-and-release.yaml b/.github/workflows/build-and-release.yaml new file mode 100644 index 00000000..d0f6f36c --- /dev/null +++ b/.github/workflows/build-and-release.yaml @@ -0,0 +1,63 @@ +# Name: build-and-release.yaml +# Author: Mathew Fleisch +# Description: This action will build and push a docker container, when triggered by +# pushing to the main branch +name: Release Container and Helm Chart +on: + push: + branches: + - main + +jobs: + + release: + name: Release Containers and Helm Chart + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - + name: Set tag environment variable + run: echo "RELEASE_VERSION=$(make version)" >> $GITHUB_ENV + - + name: Set up QEMU + id: qemu + uses: docker/setup-qemu-action@v1 + - + name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v1 + - + name: Docker Login + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - + name: Build and Push Containers + id: docker_build + uses: docker/build-push-action@v3 + with: + push: true + context: . + platforms: linux/amd64,linux/arm64 + tags: | + ghcr.io/${{ secrets.REPO_OWNER }}/agimus:latest + ghcr.io/${{ secrets.REPO_OWNER }}/agimus:${{ env.RELEASE_VERSION }} + cache-from: type=registry,ref=ghcr.io/${{ secrets.REPO_OWNER }}/agimus:latest + cache-to: type=inline + - + name: Configure Git + run: | + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + + - + name: Run chart-releaser + uses: helm/chart-releaser-action@v1.1.0 + env: + CR_TOKEN: "${{ secrets.GIT_TOKEN }}" diff --git a/.github/workflows/pr-test-and-build.yaml b/.github/workflows/pr-test-and-build.yaml new file mode 100644 index 00000000..538a086f --- /dev/null +++ b/.github/workflows/pr-test-and-build.yaml @@ -0,0 +1,132 @@ +# Name: pr-test-and-build.yaml +# Author: Mathew Fleisch +# Description: This action will run go lint/unit tests as well as +# build a docker container and test it against a KinD cluster. +# See Makefile for more details. +name: PR CI +on: + pull_request: + branches: + - main + +jobs: + unit_test: + name: Lint and Unit Tests + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - + name: Install stuff with asdf + uses: asdf-vm/actions/install@v1 + with: + tool_versions: | + action-validator 0.1.2 + dockle 0.4.5 + helm 3.8.1 + yq 4.22.1 + - + name: Lint Actions + run: make --no-print-directory lint-actions + # - + # name: Lint Container Using Dockle + # env: + # DOCKER_CONTENT_TRUST: 1 + # run: | + # make --no-print-directory docker-build + # make --no-print-directory docker-lint + + integration_test: + name: KinD Integration Tests + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - + name: Install stuff with asdf + uses: asdf-vm/actions/install@v1 + with: + tool_versions: | + helm 3.8.1 + yq 4.22.1 + - + name: KinD Tests + shell: bash + env: + CONFIGJSON: ${{ secrets.CONFIGJSON }} + ENVFILE: ${{ secrets.ENVFILE }} + run: | + if [ -z "${CONFIGJSON}" ]; then + echo "Loading fallback configuration" \ + && cp configuration.json local.json; + else + echo "Loading override configuration" \ + && echo "${{ secrets.CONFIGJSON }}" | base64 -d | sed -e 's/\r//' > local.json; + fi + if [ -z "${ENVFILE}" ]; then + echo "Problem loading github secret ENVFILE" \ + && echo "${{ secrets.ENVFILE }}" | base64 -d | sed -e 's/\r//' > .env \ + && cat .env + else + echo "Loading secrets" \ + && echo "${{ secrets.ENVFILE }}" | base64 -d | sed -e 's/\r//' > .env \ + && source .env + fi + ls -alF + echo "DB_SEED_FILEPATH=$DB_SEED_FILEPATH" + echo "DB_HOST=$DB_HOST" + echo "DB_NAME=$DB_NAME" + echo "DB_PASS=$DB_PASS" + echo "DB_USER=$DB_USER" + echo "DB_DUMP_FILENAME=$DB_DUMP_FILENAME" + echo "DB_CONTAINER_NAME=$DB_CONTAINER_NAME" + echo "REPO_OWNER=$REPO_OWNER" + echo "BOT_CONTAINER_NAME=$BOT_CONTAINER_NAME" + echo "BOT_CONFIGURATION_FILEPATH=$BOT_CONFIGURATION_FILEPATH" + make help + echo "Building KinD cluster" \ + && make --no-print-directory kind-create + echo "Building docker container and load into KinD cluster" \ + && make --no-print-directory kind-load + echo "Running tests..." \ + && make --no-print-directory kind-test + sleep 5 + kubectl --namespace agimus delete pod $(kubectl --namespace agimus get pods | grep agimus | awk '{print $1}') || true + sleep 10 + kubectl --namespace agimus get deployments -o wide + sleep 10 + kubectl --namespace agimus get pods -o wide + sleep 10 + kubectl --namespace agimus describe pod $(kubectl --namespace agimus get pods | grep agimus | awk '{print $1}') || true + sleep 10 + kubectl --namespace agimus get deployments -o wide + kubectl --namespace agimus get pods -o wide + kubectl --namespace agimus get secrets + kubectl --namespace agimus get configmaps + kubectl --namespace agimus get configmaps agimus-config -o yaml + kubectl --namespace agimus logs $(kubectl --namespace agimus get pods | grep agimus | awk '{print $1}') || true + - + name: Debug failure + if: ${{ failure() }} + shell: bash + env: + CONFIGJSON: ${{ secrets.CONFIGJSON }} + run: | + make help + ls -alF + kubectl --namespace agimus get deployments -o wide + kubectl --namespace agimus get pods -o wide + kubectl --namespace agimus get secrets + kubectl --namespace agimus get configmaps + kubectl --namespace agimus get configmaps agimus-config -o yaml + kubectl --namespace agimus describe pod $(kubectl --namespace agimus get pods | grep agimus | awk '{print $1}') || true + kubectl --namespace agimus logs $(kubectl --namespace agimus get pods | grep agimus | awk '{print $1}') || true + kubectl --namespace agimus describe pod $(kubectl --namespace agimus get pods | grep mysql | awk '{print $1}') || true + kubectl --namespace agimus logs $(kubectl --namespace agimus get pods | grep mysql | awk '{print $1}') || true + diff --git a/.github/workflows/update-tgg.yaml b/.github/workflows/update-tgg.yaml index b89f7422..fed12765 100755 --- a/.github/workflows/update-tgg.yaml +++ b/.github/workflows/update-tgg.yaml @@ -12,7 +12,7 @@ on: - cron: '15 21 * * 4' repository_dispatch: types: - - trigger-tgg-update + - update-shows jobs: build: name: Update The Greatest Generation Metadata diff --git a/Dockerfile b/Dockerfile index bda938b5..f7aba8cf 100755 --- a/Dockerfile +++ b/Dockerfile @@ -6,21 +6,24 @@ RUN rm /bin/sh && ln -s /bin/bash /bin/sh \ && apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y \ curl wget apt-utils python3 python3-pip make build-essential fonts-noto-color-emoji locales openssl git jq tzdata sudo \ + && touch /etc/sudoers.d/bot-user \ + && echo "bot ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/bot-user \ && useradd -ms /bin/bash bot \ && usermod -aG sudo bot \ && python3 -m pip install --upgrade --force pip \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ && ln -s /usr/bin/python3 /usr/local/bin/python -# configure timezone +# configure timezone and set UTF8 charset ENV TZ="America/Los_Angeles" -RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone -RUN dpkg-reconfigure --frontend noninteractive tzdata - -# set UTF8 charset -RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \ - locale-gen +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \ + && echo $TZ > /etc/timezone \ + && dpkg-reconfigure --frontend noninteractive tzdata \ + && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen \ + && locale-gen ENV LC_ALL en_US.UTF-8 -ENV LANG en_US.UTF-8 +ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en # Use 'bot' user to avoid pip warning messages diff --git a/Makefile b/Makefile index c4661cf6..a94ca529 100644 --- a/Makefile +++ b/Makefile @@ -1,63 +1,187 @@ -REPO_OWNER:=jp00p -REPO_NAME:=agimus +REPO_OWNER?=jp00p +REPO_NAME?=AGIMUS +BOT_CONTAINER_NAME?=agimus +LOCAL_KIND_CONFIG?=kind-config.yaml +namespace?=agimus +SHELL=/bin/bash ifneq (,$(wildcard ./.env)) include .env export endif -.PHONY: db-mysql -db-mysql: - @docker-compose exec db mysql -u"${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" - -.PHONY: db-bash -db-bash: - @docker-compose exec db bash - -.PHONY: db-dump -db-dump: - @docker-compose exec db bash -c 'mysqldump -u"${DB_USER}" -p"${DB_PASS}" -B ${DB_NAME} 2>/dev/null' > ./${DB_DUMP_FILENAME} +.PHONY: help +help: ## Displays this help dialog (to set repo/fork ownker REPO_OWNWER=[github-username]) + @echo "Friends of DeSoto Bot - github.com/$$REPO_OWNER/$$REPO_NAME:$(shell make --no-print-directory version)" + @cat banner.txt + @echo "" + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) -.PHONY: db-load -db-load: - @docker-compose exec -T db sh -c 'exec mysql -u"${DB_USER}" -p"${DB_PASS}" "${DB_NAME}"' < ./${DB_DUMP_FILENAME} +##@ Python stuff .PHONY: setup -setup: - @pip install -q -r requirements.txt +setup: ## Install python dependencies via requirements.txt + @pip install -q -r requirements.txt --no-warn-script-location @pip install --upgrade --no-deps --force-reinstall git+https://github.com/Pycord-Development/pycord .PHONY: start -start: setup +start: setup ## Start the bot via python @python main.py +##@ Docker stuff + .PHONY: docker-build -docker-build: +docker-build: ## Build the docker containers for the bot and the database @docker-compose build +.PHONY: docker-pull +docker-pull: ## Pull the defined upstream containers for BOT_CONTAINER_NAME and BOT_CONTAINER_VERSION + @docker-compose pull + .PHONY: docker-start -docker-start: +docker-start: ## Start the docker containers for the bot and the database @docker-compose up -.PHONY: docker-start-exec -docker-start-exec: - docker run --rm -it -v ${PWD}:/bot ghcr.io/$(REPO_OWNER)/$(REPO_NAME):latest bash - -.PHONY: docker-logs -docker-logs: - @docker-compose logs -f - .PHONY: docker-stop -docker-stop: +docker-stop: ## Stop the docker containers for the bot and the database @docker-compose down .PHONY: docker-restart -docker-restart: +docker-restart: ## Restart the docker containers running mysql and AGIMUS @docker-compose down && docker-compose up -.PHONY: update-tgg-metadata -update-tgg-metadata: +.PHONY: docker-logs +docker-logs: ## Tail the logs of running containers + @docker-compose logs -f + +.PHONY: docker-lint +docker-lint: ## Lint the container with dockle + dockle --version + dockle --exit-code 1 $(BOT_CONTAINER_NAME):latest + +##@ MySQL stuff + +.PHONY: db-mysql +db-mysql: ## MySQL session in running db container + @docker-compose exec db mysql -u"${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" + +.PHONY: db-bash +db-bash: ## Bash session in running db container + @docker-compose exec db bash + +.PHONY: db-dump +db-dump: ## Dump the database to a file at ./$DB_DUMP_FILENAME + @docker-compose exec db bash -c 'mysqldump -u"${DB_USER}" -p"${DB_PASS}" -B ${DB_NAME} 2>/dev/null' > ./${DB_DUMP_FILENAME} + +.PHONY: db-load +db-load: ## Load the database from a file at ./$DB_DUMP_FILENAME + @docker-compose exec -T db sh -c 'exec mysql -u"${DB_USER}" -p"${DB_PASS}" "${DB_NAME}"' < ./${DB_DUMP_FILENAME} + +# mysql session in pod +# kubectl exec -it my-cluster-mysql-0 -c mysql -- mysql -uroot -ppassword +# Run sql file in pod +# kubectl exec my-cluster-mysql-0 -c mysql -- mysql -uroot -ppassword < bot-dump.sql + +##@ Kubernetes in Docker (KinD) stuff + +.PHONY: kind-create +kind-create: ## Create a KinD cluster with local config-yaml + kind create cluster --config $(LOCAL_KIND_CONFIG) -v 5 || true + +.PHONY: kind-load +kind-load: ## Load $BOT_CONTAINER_NAME into a running kind cluster + @BOT_CONTAINER_VERSION=local make --no-print-directory docker-build + kind load docker-image $(BOT_CONTAINER_NAME):local + @kubectl create namespace $(namespace) || true + @make helm-config + +.PHONY: kind-test +kind-test: ## Install AGIMUS into a running KinD cluster with helm + helm upgrade --install --debug --wait \ + --namespace $(namespace) \ + --set image.repository=$(BOT_CONTAINER_NAME) \ + --set image.tag=local \ + agimus charts/agimus + sleep 30 + kubectl --namespace $(namespace) get pods -o wide + @make helm-db-load + +.PHONY: kind-destroy +kind-destroy: ## Tear the KinD cluster down + @kind delete cluster + +##@ Helm stuff + +.PHONY: helm-config +helm-config: ## Install the configmaps and secrets from .env and $(BOT_CONFIGURATION_FILEPATH) using helm + @kubectl --namespace $(namespace) create configmap agimus-dotenv --from-file=.env || true + @kubectl --namespace $(namespace) create configmap agimus-config --from-file=$(BOT_CONFIGURATION_FILEPATH) || true + @kubectl --namespace $(namespace) create secret generic mysql-secret --from-literal=MYSQL_ROOT_PASSWORD=$(DB_PASS) || true + +.PHONY: helm-config-rm +helm-config-rm: ## Delete configmaps and secrets + @kubectl --namespace $(namespace) delete configmap agimus-dotenv --ignore-not-found=true + @kubectl --namespace $(namespace) delete configmap agimus-config --ignore-not-found=true + @kubectl --namespace $(namespace) delete secret mysql-secret --ignore-not-found=true + +.PHONY: helm-install +helm-install: helm-config ## Install AGIMUS helm chart + helm upgrade --install --debug --wait \ + --create-namespace \ + --namespace $(namespace) \ + --set image.repository=$(BOT_CONTAINER_NAME) \ + --set image.tag=$(shell make --no-print-directory version) \ + agimus charts/agimus + +.PHONY: helm-uninstall +helm-uninstall: helm-config-rm ## Remove AGIMUS helm chart + @helm --namespace $(namespace) delete agimus + +.PHONY: helm-db-load +helm-db-load: ## Load the database from a file at ./$DB_DUMP_FILENAME + @kubectl --namespace $(namespace) exec -i $(shell make --no-print-directory helm-db-pod) \ + -- bash -c 'exec mysql -h127.0.0.1 -u"${DB_USER}" -p"${DB_PASS}"' < ${DB_DUMP_FILENAME} + +.PHONY: helm-db-mysql +helm-db-mysql: ## Mysql session in mysql pod + @kubectl --namespace $(namespace) exec -it $(shell make --no-print-directory helm-db-pod) \ + -- mysql -u"${DB_USER}" -p"${DB_PASS}" + +.PHONY: helm-db-forward +helm-db-forward: ## Forward the mysql port 3306 + @kubectl --namespace $(namespace) port-forward svc/mysql-service 3306 + +.PHONY: helm-db-pod +helm-db-pod: ## Display the pod name for mysql + @kubectl --namespace $(namespace) get pods --template '{{range .items}}{{.metadata.name}}{{end}}' --selector=app=mysql + +.PHONY: helm-agimus-pod +helm-agimus-pod: ## Display the pod name for AGIMUS + @kubectl --namespace $(namespace) get pods --template '{{range .items}}{{.metadata.name}}{{end}}' --selector=app=agimus + +##@ Miscellaneous stuff + +.PHONY: update-shows +update-shows: ## Update the TGG metadata in the database via github action @curl -s -H "Accept: application/vnd.github.everest-preview+json" \ -H "Authorization: token $(GIT_TOKEN)" \ --request POST \ - --data '{"event_type": "trigger-tgg-update"}' \ - https://api.github.com/repos/$(REPO_OWNER)/$(REPO_NAME)/dispatches \ No newline at end of file + --data '{"event_type": "update-shows"}' \ + https://api.github.com/repos/$(REPO_OWNER)/$(REPO_NAME)/dispatches + +.PHONY: lint-actions +lint-actions: ## Run .gihtub/workflows/*.yaml|yml through action-valdator tool + find .github/workflows -type f \( -iname \*.yaml -o -iname \*.yml \) \ + | xargs -I action_yaml action-validator --verbose action_yaml + +.PHONY: version +version: ## Print the version of the bot from the helm chart (requires yq) + @yq e '.version' charts/agimus/Chart.yaml + +.PHONY: encode-config +encode-config: ## Print the base64 encoded contents of $(BOT_CONFIGURATION_FILEPATH) (Pro-Tip: pipe to pbcopy on mac) + @cat $(BOT_CONFIGURATION_FILEPATH) | base64 + +.PHONY: encode-env +encode-env: ## Print the base64 encoded contents of the .env file (Pro-Tip: pipe to pbcopy on mac) + @cat .env | base64 + diff --git a/README.md b/README.md index bbeccf30..095a62a8 100755 --- a/README.md +++ b/README.md @@ -1,14 +1,22 @@ -# Friends of DeSoto Bot +# AGIMUS The Friends of DeSoto are a group of fans of Star Trek and [The Greatest Generation podcast](http://gagh.biz). -## Usage +## Makefile + +Provided in this repository is a makefile to aid in building, testing and running AGIMUS in a variety of deployment environments. To see all available makefile targets, clone the repository and run `make help` in a terminal. + +### Docker Usage + +Dependencies + +- [Docker](https://docs.docker.com/get-docker/) This discord bot is built with python using the [discord.py library](https://discordpy.readthedocs.io/en/stable/api.html) and requires a mysql db with credentials stored in a .env file ([.env example](.env-example)). To develop locally, docker is used to standardize infrastructure and dependencies. ```bash -# Clone FoDBot source -git clone https://github.com/jp00p/FoDBot-SQL.git && cd FoDBot-SQL +# Clone AGIMUS source +git clone https://github.com/jp00p/AGIMUS.git && cd AGIMUS # Fill out .env vars... cp .env-example .env @@ -35,7 +43,34 @@ make docker-stop UPDATE users SET score=42069, spins=420, jackpots=69, wager=25, high_roller=1 WHERE id=1; ``` -## Permissions +### Kubernetes Usage + +Dependencies + +- [Docker](https://docs.docker.com/get-docker/) +- [KinD](https://kind.sigs.k8s.io/docs/user/quick-start/#installing-from-release-binaries) +- [Helm](https://helm.sh/docs/intro/install/) + +AGIMUS can also be deployed in kubernetes. The provided helm chart includes a persistent volume claim for mysql to run in a pod, and the agimus container itself. To run AGIMUS in a KinD cluster, use the following makefile targets: + +```bash +# Clone AGIMUS source +git clone https://github.com/jp00p/AGIMUS.git && cd AGIMUS + +# Fill out .env vars... +cp .env-example .env + +# Create a KinD cluster +make kind-create + +# Build AGIMUS, and load it into the running KinD cluster +make kind-load + +# Install AGIMUS via helm and +make kind-test +``` + +## Discord Permissions First you will need a discord app and bot token to send messages. See this youtube playlist to learn how: https://www.youtube.com/playlist?list=PLRqwX-V7Uu6avBYxeBSwF48YhAnSn_sA4 @@ -52,8 +87,11 @@ Instructions for how to do this are available through this video at the 58 secon The bot now requires `Intents.members` and `Intents.presences`. You must enable this through the "Privileged Gateway Intents" page on the Application page of the Discord developer's portal. +![AGIMUS permissions](https://i.imgur.com/rcZnQCo.png) -## Commands +![AGIMUS "intents"](https://i.imgur.com/bGl9pf9.png) + +## AGIMUS Commands and Triggers Bot commands are triggered by typing an exclamation point followed by a command. Commands must be defined in the [configuration.json](configuration.json) file, a python file in the [commands directory](commands), and an import line added to [main.py](main.py). @@ -86,7 +124,7 @@ Bot commands are triggered by typing an exclamation point followed by a command. | `!tuvix` | [tuvix.py](commands/tuvix.py) | Return 2 random trek characters as discussion prompt | | `!update_status [game \| listen \| watch] ` | [update_status.py](commands/update_status.py) | Update the bot's server profile status | -## Slash Commands +### Slash Commands Slash commands are triggered by typing a forward slash (`/`) followed by the command text. The same basic rules apply as the regular ! commands above as far as the info necessary in the [configuration.json](configuration.json) file, python file in the [commands directory](commands), and import line in [main.py](main.py). @@ -98,14 +136,15 @@ Slash commands are triggered by typing a forward slash (`/`) followed by the com | `/drops` | [drop.py](commands/drop.py) | Replies with a message to the user with a full list of the drops available from `/drop` | | `/profile` | [profile.py](commands/profile.py) | Generate profile card with user statistics/options | -## "Computer:"/"AGIMUS:" Prompt +### "Computer:"/"AGIMUS:" Prompt In addition to the `/` and `!` commands we have a special case for handling messages that begin with a "Computer:" or "AGIMUS:" prompt. It has an entry within `configuration.json` and the same rules apply to it as the `!` commands. Extending the feature should be done within `commands/computer.py`. | Command | File | Description | | :--------------------------------------- | :-------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- | | `[Computer:\|AGIMUS:] ` | [computer.py](commands/computer.py) | Runs the user's query against Wolfram Alpha to provide a Star Trek "Computer"-like experience with context-aware responses. | -### Generating a Wolfram Alpha API ID +#### Generating a Wolfram Alpha API ID + In order to use the "Computer:"/"AGIMUS:" prompt you'll need to also provide a Wolfram Alpha API ID which can be obtained from their site at https://products.wolframalpha.com/api . Full instructions for obtaining the API ID can be found in their [documentation](https://products.wolframalpha.com/api/documentation/). A development ID key is free and supports up to 2000 queries per month. @@ -113,7 +152,6 @@ A development ID key is free and supports up to 2000 queries per month. Once generated it should be placed in your `.env` file as per the section in `.env-example`: ```bash -# Wolfram Alpha Credentials for "Computer:" prompts export WOLFRAM_ALPHA_ID=YOURKEYHERE ``` @@ -137,6 +175,7 @@ The [configuration.json](configuration.json) file defines metadata about each co ``` The file also provides the "Guild ID" for your server, note this is required in order for the slash commands to register properly and will cause a permissions error on startup if not provided! + ```json "guild_ids": [ 820440093898440756 @@ -149,7 +188,6 @@ Each command requires a python script that accepts a discord message as input wh ```python from .common import * - # setwager() - Entrypoint for !setwager command # message[required]: discord.Message # This function is the main entrypoint of the !setwager command @@ -173,7 +211,6 @@ async def setwager(message:discord.Message): msg = f"{message.author.mention}: Wager must be a whole number between `{min_wager}` and `{max_wager}`\nYour current wager is: `{current_wager}`" await message.channel.send(msg) - # set_player_wager(discord_id, amt) # discord_id[required]: int # amt[required]: int @@ -200,14 +237,21 @@ Each command requires an explicit import in the [main.py](main.py) script. from commands.setwager import setwager ``` -### Utils +## Automation + +The automation detailed below run in github action runners. + +### Pull Requests and Merges + +Pull requests to the main branch of the AGIMUS repository will automatically build a container and attempt to run the bot in a [KinD cluster](https://kind.sigs.k8s.io/docs/user/quick-start/#installing-from-release-binaries) and it uses [helm](https://helm.sh/docs/intro/install/) to install the kubernetes manifests into a running cluster. There are also make targets to assist in building and running AGIMUS in a KinD cluster locally. On merges to the main branch, another action will run to build and push the AGIMUS container to the github container registry and release a helm chart hosted as a github pages deployment. + +### generate_episode_json.py -#### generate_episode_json.py The repo also currently provides a way to automatically generate the files for the Greatest Gen `.json` files located under `data/episodes/` (such as `tgg_voy.json` for example). The utility is under `utils` as `generate_episode_json.py`. The script uses Google to gather some of the metadata necessary for each entry, so you'll need to provide two additional ENV variables if you'd like to use this script. -``` +```bash export GOOGLE_API_KEY= export GOOGLE_CX= ``` @@ -218,4 +262,4 @@ Once those have been placed in your .env file, you can execute the script by pro ```bash python utils/generate_episode_json.py -p VOY -o data/episodes/voy.json -``` \ No newline at end of file +``` diff --git a/banner.txt b/banner.txt new file mode 100644 index 00000000..35c6da2e --- /dev/null +++ b/banner.txt @@ -0,0 +1,8 @@ + _____ + __...---'-----`---...__ + _=============================== + ______________,/' `---..._______...---' +(____________LL). . ,--' + / /.---' `. / +'--------_ - - - - _/ + `~~~~~~~~' \ No newline at end of file diff --git a/charts/agimus/.helmignore b/charts/agimus/.helmignore new file mode 100644 index 00000000..0e8a0eb3 --- /dev/null +++ b/charts/agimus/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/agimus/Chart.yaml b/charts/agimus/Chart.yaml new file mode 100644 index 00000000..a1426253 --- /dev/null +++ b/charts/agimus/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: agimus +description: A helm chart for a discord bot that also runs a mysql db +type: application +version: v0.1.0 +appVersion: v0.1.0 diff --git a/charts/agimus/templates/_helpers.tpl b/charts/agimus/templates/_helpers.tpl new file mode 100644 index 00000000..b11fd4db --- /dev/null +++ b/charts/agimus/templates/_helpers.tpl @@ -0,0 +1,63 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "agimus.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "agimus.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "agimus.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "agimus.labels" -}} +helm.sh/chart: {{ include "agimus.chart" . }} +{{ include "agimus.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Selector labels +*/}} +{{- define "agimus.selectorLabels" -}} +app.kubernetes.io/name: {{ include "agimus.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + +{{/* +Create the name of the service account to use +*/}} +{{- define "agimus.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "agimus.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} diff --git a/charts/agimus/templates/agimus-deployment.yaml b/charts/agimus/templates/agimus-deployment.yaml new file mode 100644 index 00000000..06305a5b --- /dev/null +++ b/charts/agimus/templates/agimus-deployment.yaml @@ -0,0 +1,50 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "agimus.fullname" . }} + labels: + {{- include "agimus.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicas }} + strategy: + type: Recreate + selector: + matchLabels: + {{- include "agimus.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "agimus.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + volumes: + - name: agimus-dotenv + configMap: + name: agimus-dotenv + - name: agimus-config + configMap: + name: agimus-config + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + # command: + # - bash + # - -c + # - 'sleep 9999;' + volumeMounts: + - name: agimus-dotenv + mountPath: /bot/.env + subPath: .env + - name: agimus-config + mountPath: /bot/local.json + subPath: local.json diff --git a/charts/agimus/templates/mysql-deployment.yaml b/charts/agimus/templates/mysql-deployment.yaml new file mode 100644 index 00000000..79972662 --- /dev/null +++ b/charts/agimus/templates/mysql-deployment.yaml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mysql + labels: + app: mysql +spec: + replicas: 1 + selector: + matchLabels: + app: mysql + template: + metadata: + labels: + app: mysql + spec: + volumes: + - name: mysql-data-storage + persistentVolumeClaim: + claimName: mysql-data-pv-claim + containers: + - name: mysql + image: mysql:oracle + ports: + - containerPort: 3306 + name: mysql + env: + - name: MYSQL_DATABASE + value: FoD + - name: MYSQL_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: mysql-secret + key: MYSQL_ROOT_PASSWORD + volumeMounts: + - mountPath: "/var/lib/mysql" + name: mysql-data-storage \ No newline at end of file diff --git a/charts/agimus/templates/pv.yaml b/charts/agimus/templates/pv.yaml new file mode 100644 index 00000000..604a640a --- /dev/null +++ b/charts/agimus/templates/pv.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: mysql-data-pv-volume + labels: + type: mysql +spec: + storageClassName: manual + capacity: + storage: 256Mi + accessModes: + - ReadWriteOnce + hostPath: + path: "/usr/local/mysql" + # persistentVolumeReclaimPolicy: Retain +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: mysql-data-pv-claim + labels: + type: mysql +spec: + storageClassName: manual + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 256Mi \ No newline at end of file diff --git a/charts/agimus/templates/service.yaml b/charts/agimus/templates/service.yaml new file mode 100644 index 00000000..95044954 --- /dev/null +++ b/charts/agimus/templates/service.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Service +metadata: + name: mysql-service +spec: + ports: + - port: 3306 + selector: + app: mysql + clusterIP: None \ No newline at end of file diff --git a/charts/agimus/values.yaml b/charts/agimus/values.yaml new file mode 100644 index 00000000..e2b1f082 --- /dev/null +++ b/charts/agimus/values.yaml @@ -0,0 +1,18 @@ +# Default values for AGIMUS. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# To enable bot, set to 1. To disable bot, set to 0. +replicas: 1 + + +image: + repository: ghcr.io/mathew-fleisch/agimus + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + # tag: "" + + +# Run as root +# podSecurityContext: +# runAsUser: 0 \ No newline at end of file diff --git a/commands/clip.py b/commands/clip.py index 593f718c..823c6fd5 100644 --- a/commands/clip.py +++ b/commands/clip.py @@ -76,3 +76,4 @@ async def clip_post(ctx:discord.ApplicationContext, query:str, private:bool): await ctx.respond(f"{emojis.get('ezri_frown_sad')} Clip not found! To get a list of clips run: /clips", ephemeral=True) else: await ctx.respond(f"{emojis.get('ohno')} Someone in the channel has already posted a clip too recently. Please wait a minute before another clip!", ephemeral=True) + \ No newline at end of file diff --git a/commands/drop.py b/commands/drop.py index 097fc9a4..18d57205 100755 --- a/commands/drop.py +++ b/commands/drop.py @@ -78,3 +78,4 @@ async def drop_post(ctx:discord.ApplicationContext, query:str, private:bool): await ctx.respond(f"{emojis.get('ezri_frown_sad')} Drop not found! To get a list of drops run: /drops", ephemeral=True) else: await ctx.respond(f"{emojis.get('ohno')} Someone in the channel has already dropped too recently. Please wait a minute before another drop!", ephemeral=True) + \ No newline at end of file diff --git a/commands/reports.py b/commands/reports.py index 2a5cde0f..8d76d258 100644 --- a/commands/reports.py +++ b/commands/reports.py @@ -186,4 +186,4 @@ def generate_report_card(title:str, description:str, rows:list): counter += 1 image.save("./images/reports/report.png") - return discord.File("./images/reports/report.png") + return discord.File("./images/reports/report.png") \ No newline at end of file diff --git a/data/drops/.gitignore b/data/drops/.gitignore new file mode 100644 index 00000000..26416673 --- /dev/null +++ b/data/drops/.gitignore @@ -0,0 +1 @@ +*.mp4 diff --git a/docker-compose.yml b/docker-compose.yml index cb8ce109..9f568dbf 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: "3.8" services: app: - image: ${BOT_CONTAINER_NAME:-fodbot}:latest + image: ${BOT_CONTAINER_NAME:-agimus}:${BOT_CONTAINER_VERSION:-latest} build: context: . dockerfile: Dockerfile diff --git a/images/reports/xp-report.png b/images/reports/xp-report.png index f25728f9..8f3a8aaf 100644 Binary files a/images/reports/xp-report.png and b/images/reports/xp-report.png differ diff --git a/kind-config.yaml b/kind-config.yaml new file mode 100644 index 00000000..11eb4140 --- /dev/null +++ b/kind-config.yaml @@ -0,0 +1,7 @@ +apiVersion: kind.x-k8s.io/v1alpha4 +kind: Cluster +nodes: + - role: control-plane + extraMounts: + - hostPath: /usr/local/mysql + containerPath: /usr/local/mysql