diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..d872363ff --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +data +.git +.dockerignore +Dockerfile +config.ini +config.ini.example +dockerbuild.sh diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..12f862bb0 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,58 @@ +name: 'ci' + +on: + push: + branches: port + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - name: Set up Docker Buildx + uses: docker/setup-qemu-action@v1 + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + # github username and personal access token + registry: ghcr.io + username: ${{ secrets.CR_USERNAME }} + password: ${{ secrets.CR_PAT }} + - name: Build Docker image + id: docker_build + uses: docker/build-push-action@v2 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64 + push: false + # Can't auto-push with v2 right now... + tags: ghcr.io/nh-server/kurisu:latest + build-args: COMMIT=${{ github.sha }},BRANCH=${{ github.ref }} + - name: Image digest + run: echo ${{ steps.docker_build.outputs.digest }} + - name: Push Docker image + run: docker push ghcr.io/nh-server/kurisu:latest +# https://stackoverflow.com/questions/60477061/github-actions-how-to-deploy-to-remote-server-using-ssh/60479844#60479844 +# - name: Create SSH key +# run: | +# mkdir -p ~/.ssh/ +# chmod 700 ~/.ssh/ +# echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa +# chmod 600 ~/.ssh/id_rsa +# echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts +# shell: bash +# env: +# SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} +# SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS }} +# - name: Pull on server +# run: docker-compose pull +# env: +# DOCKER_HOST: ${{ secrets.SSH_HOST }} +# - name: Run on server +# run: docker-compose up -d +# env: +# DOCKER_HOST: ${{ secrets.SSH_HOST }} diff --git a/.gitignore b/.gitignore index 366366b9f..6b2161048 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.ini *.sh +!dockerbuild.sh *.log *.json *.png diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..a700f1298 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.8.5-slim-buster +LABEL org.opencontainers.image.source https://github.com/nh-server/Kurisu +ENV HOME /home/kurisu +RUN useradd -m -d $HOME -s /bin/sh -u 2849 kurisu +WORKDIR $HOME +COPY ./requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +ENV IS_DOCKER=1 +ARG COMMIT="unknown" +ARG BRANCH="unknown" +ENV COMMIT_SHA=${COMMIT} +ENV COMMIT_BRANCH=${BRANCH} +USER kurisu +COPY --chown=2849:2849 . . +CMD ["python3", "kurisu.py"] diff --git a/cogs/events.py b/cogs/events.py index 773791abc..bf35c980b 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -314,13 +314,14 @@ async def channel_spam_check(self, message): async def on_message(self, message): if isinstance(message.channel, discord.abc.PrivateChannel): return - if message.author.name == "GitHub" and message.author.discriminator == "0000": - if message.embeds[0].title.startswith('[Kurisu:port]'): - await self.bot.channels['helpers'].send("Automatically pulling changes!") - call(['git', 'pull']) - await self.bot.channels['helpers'].send("Restarting bot...") - await self.bot.close() - return + if not self.bot.IS_DOCKER: + if message.author.name == "GitHub" and message.author.discriminator == "0000": + if message.embeds[0].title.startswith('[Kurisu:port]'): + await self.bot.channels['helpers'].send("Automatically pulling changes!") + call(['git', 'pull']) + await self.bot.channels['helpers'].send("Restarting bot...") + await self.bot.close() + return await self.bot.wait_until_all_ready() if message.author == message.guild.me or await check_staff_id(self, 'Helper', message.author.id) or await self.check_nofilter(message.channel): # don't process messages by the bot or staff or in the helpers channel return diff --git a/cogs/mod.py b/cogs/mod.py index d6d6b290a..d3bbf7d23 100644 --- a/cogs/mod.py +++ b/cogs/mod.py @@ -27,10 +27,14 @@ async def quit(self, ctx): @commands.command() async def pull(self, ctx): """Pull new changes from GitHub and restart.""" - await ctx.send("Pulling changes...") - call(['git', 'pull']) - await ctx.send("👋 Restarting bot!") - await self.bot.close() + if self.bot.IS_DOCKER: + await ctx.send("Pull isn't used when running from a Docker container!") + return + else: + await ctx.send("Pulling changes...") + call(['git', 'pull']) + await ctx.send("👋 Restarting bot!") + await self.bot.close() @is_staff("Helper") @commands.guild_only() @@ -580,7 +584,7 @@ async def updatechannel(self, ctx, name, channel: discord.TextChannel): await ctx.send("Invalid channel name!") return self.bot.channel_config['Channels'][name] = str(channel.id) - with open('channels.ini', 'w', encoding='utf-8') as f: + with open('data/channels.ini', 'w', encoding='utf-8') as f: ctx.bot.channel_config.write(f) self.bot.channels[name] = channel await ctx.send(f"Changed {name} channel to {channel.mention} | {channel.id}") diff --git a/config.ini.example b/data/config.ini.example similarity index 100% rename from config.ini.example rename to data/config.ini.example diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..1cee25953 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,7 @@ +version: "3.8" +services: + kurisu: + image: "ghcr.io/nh-server/kurisu" + volumes: + - /opt/kurisudata:/home/kurisu/data + restart: always diff --git a/dockerbuild.sh b/dockerbuild.sh new file mode 100755 index 000000000..ef2381edd --- /dev/null +++ b/dockerbuild.sh @@ -0,0 +1,2 @@ +#!/bin/sh +docker build --build-arg COMMIT=$(git rev-parse HEAD) --build-arg BRANCH=$(git rev-parse --abbrev-ref HEAD) -t ghcr.io/nh-server/kurisu . diff --git a/kurisu.py b/kurisu.py index 3a9b1e37a..4ede05e19 100644 --- a/kurisu.py +++ b/kurisu.py @@ -19,13 +19,15 @@ from utils.database import ConnectionHolder from utils.manager import WordFilterManager, InviteFilterManager +IS_DOCKER = os.environ.get('IS_DOCKER', 0) + # sets working directory to bot's folder dir_path = os.path.dirname(os.path.realpath(__file__)) os.chdir(dir_path) # Load config config = ConfigParser() -config.read("config.ini") +config.read("data/config.ini") database_name = 'data/kurisu.sqlite' @@ -68,11 +70,14 @@ async def get_user(self, userid: int): class Kurisu(commands.Bot): """Its him!!.""" + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.startup = datetime.now() self.channel_config = ConfigParser() - self.channel_config.read("channels.ini", encoding='utf-8') + self.channel_config.read("data/channels.ini", encoding='utf-8') + + self.IS_DOCKER = IS_DOCKER self.roles = { 'Helpers': None, @@ -139,7 +144,6 @@ def __init__(self, *args, **kwargs): self._is_all_ready = Event(loop=self.loop) os.makedirs("data", exist_ok=True) - os.makedirs("data/ninupdates", exist_ok=True) async def get_context(self, message, *, cls=CustomContext): return await super().get_context(message, cls=cls) @@ -165,7 +169,7 @@ def load_channels(self): print(f"Failed to find channel {n}") continue self.channel_config['Channels'][n] = str(self.channels[n].id) - with open('channels.ini', 'w', encoding='utf-8') as f: + with open('data/channels.ini', 'w', encoding='utf-8') as f: self.channel_config.write(f) def load_roles(self): @@ -330,18 +334,22 @@ def main(): print('Kurisu requires 3.8 or later.') return 2 - # attempt to get current git information - try: - commit = check_output(['git', 'rev-parse', 'HEAD']).decode('ascii')[:-1] - except CalledProcessError as e: - print(f'Checking for git commit failed: {type(e).__name__}: {e}') - commit = "" - - try: - branch = check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD']).decode()[:-1] - except CalledProcessError as e: - print(f'Checking for git branch failed: {type(e).__name__}: {e}') - branch = "" + if not IS_DOCKER: + # attempt to get current git information + try: + commit = check_output(['git', 'rev-parse', 'HEAD']).decode('ascii')[:-1] + except CalledProcessError as e: + print(f'Checking for git commit failed: {type(e).__name__}: {e}') + commit = "" + + try: + branch = check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD']).decode()[:-1] + except CalledProcessError as e: + print(f'Checking for git branch failed: {type(e).__name__}: {e}') + branch = "" + else: + commit = os.environ.get('COMMIT_SHA') + branch = os.environ.get('COMMIT_BRANCH') bot = Kurisu(('.', '!'), description="Kurisu, the bot for Nintendo Homebrew!", allowed_mentions=discord.AllowedMentions(everyone=False, roles=False)) bot.help_command = commands.DefaultHelpCommand(dm_help=None) diff --git a/requirements.txt b/requirements.txt index 6b01d5e76..df9862a35 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -discord.py -aiosqlite3 -Pillow -xkcd -pytz +discord.py==1.4.1 +aiosqlite3==0.3.0 +Pillow==7.2.0 +xkcd==2.4.2 +pytz==2020.1