diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..937ec93 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +# Keep the build context small and reproducible — everything the image needs is +# rebuilt inside the Dockerfile. +.git +.gradle +build/ +.idea +*.iml +.env +.env.* +lavalink/plugins/ +catchphrases.txt +always100.txt +always90.txt diff --git a/.gitea/workflows/ci-cd.yaml b/.gitea/workflows/ci-cd.yaml new file mode 100644 index 0000000..fa2f503 --- /dev/null +++ b/.gitea/workflows/ci-cd.yaml @@ -0,0 +1,49 @@ +name: CI/CD + +on: + push: + branches: [main, dev] + pull_request: + branches: [main, dev] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 25 + + - name: Build + run: | + chmod +x gradlew + ./gradlew --no-daemon build + + deploy: + runs-on: ubuntu-latest + needs: build + if: gitea.ref == 'refs/heads/main' + steps: + - uses: actions/checkout@v4 + + - name: Build & push Docker image + run: | + echo "${{ secrets.REGISTRY_PASSWORD }}" | \ + docker login git.${{ secrets.DOMAIN }} \ + -u ${{ secrets.REGISTRY_USER }} --password-stdin + docker build -t git.${{ secrets.DOMAIN }}/${{ secrets.REGISTRY_USER }}/botinator:latest . + docker push git.${{ secrets.DOMAIN }}/${{ secrets.REGISTRY_USER }}/botinator:latest + + - name: Deploy to VPS + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/key + chmod 600 ~/.ssh/key + ssh-keyscan -H ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts + + ssh -i ~/.ssh/key \ + ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} \ + "cd ~/programs/botinator && docker compose pull && docker compose up -d" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bb4a790 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +# syntax=docker/dockerfile:1 + +# ---- Build stage: compile and assemble a runnable distribution ---- +FROM eclipse-temurin:25-jdk AS build +WORKDIR /app + +# Copy the Gradle wrapper and build scripts first so dependency resolution is +# cached in its own layer and only re-runs when the build files change. +COPY gradlew settings.gradle.kts build.gradle.kts ./ +COPY gradle ./gradle +RUN chmod +x gradlew && ./gradlew --no-daemon dependencies > /dev/null 2>&1 || true + +# Now the source. `installDist` produces build/install/Botinator with a launcher +# script and all dependency jars under lib/. +COPY src ./src +RUN ./gradlew --no-daemon --stacktrace installDist + +# ---- Runtime stage: JRE only, no build tooling ---- +FROM eclipse-temurin:25-jre +WORKDIR /app + +# Run as an unprivileged user. +RUN useradd --system --create-home --uid 10001 botinator +USER botinator + +# The assembled app (bin/Botinator launcher + lib/*.jar). +COPY --from=build --chown=botinator:botinator /app/build/install/Botinator/ ./ + +# BOT_TOKEN etc. and the catchphrases file are supplied at deploy time +# (env_file / -e for config, a mounted volume for catchphrases.txt). +ENTRYPOINT ["./bin/Botinator"] diff --git a/docker-compose.yaml b/docker-compose.yaml index 027e9f0..2dbf6c8 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,10 +1,23 @@ -# Runs a Lavalink v4 server for the bot to connect to. -# docker compose up -d # start in background -# docker compose logs -f # watch logs -# docker compose down # stop +# Runs the bot together with a Lavalink v4 server for music playback. +# docker compose up -d --build # build the bot image locally and start # -# The bot connects to ws://localhost:2333 with the password below (see .env). services: + bot: + image: ${BOT_IMAGE:-botinator:latest} + build: . + container_name: botinator + restart: unless-stopped + depends_on: + - lavalink + # BOT_TOKEN, GUILD_ID, LAVALINK_PASSWORD, ... come from .env. + env_file: + - .env + environment: + - LAVALINK_URI=ws://lavalink:2333 + # Catchphrases are supplied at deploy time + volumes: + - ./catchphrases.txt:/app/catchphrases.txt:ro + lavalink: image: ghcr.io/lavalink-devs/lavalink:4 container_name: lavalink