diff --git a/.ci/deploy.py b/.ci/deploy.py new file mode 100644 index 0000000..7269d60 --- /dev/null +++ b/.ci/deploy.py @@ -0,0 +1,94 @@ +import requests +import os + +host = os.environ['PORTAINER_HOST'] +endpointId = os.environ['PORTAINER_ENDPOINT_ID'] +stack_name = 'capivara' +stack_file_path = './docker-compose.yaml' +access_token = os.environ['PORTAINER_ACCESS_TOKEN'] + +def get_envs(): + env_keys = [ + 'CAPIVARA_IMAGE_TAG', + 'DISCORD_TOKEN', + 'LOG_CHANNEL_ID', + 'CURUPIRA_RESET', + 'DATABASE_DRIVER', + 'DATABASE_DIALECT', + 'DATABASE_URL', + 'DATABASE_USERNAME', + 'DATABASE_PASSWORD', + 'JAVA_ARGS', + ] + + return [{'name': key, 'value': os.environ[key]} for key in env_keys] + +def get_stack_id(stack_name): + get_stack_res = requests.get( + f'{host}/api/stacks', + headers={'X-API-KEY': access_token}, + verify=False + ) + if get_stack_res.ok: + payload = get_stack_res.json() + for stack in payload: + if stack['Name'] == stack_name: + return stack['Id'] + + raise Exception('Stack not found') + else: + raise Exception('Failed to get stack id') + +def setup_stack(): + envs = get_envs() + with open(stack_file_path) as f: + file_content = f.read() + + create_stack_res = requests.post( + f'{host}/api/stacks/create/standalone/string?endpointId={endpointId}', + json={ + "name": stack_name, + "stackFileContent": file_content, + "env": envs + }, + headers={'X-API-KEY': access_token}, + verify=False + ) + if create_stack_res.ok: + print('Created stack with success') + return + + if create_stack_res.status_code == 401: + raise Exception('Unauthorized') + + if create_stack_res.status_code == 409: + payload = create_stack_res.json() + if payload['message'] == f'A stack with the normalized name \'{stack_name}\' already exists': + update_stack(file_content, envs) + return + + raise Exception('Unable to create and then update stack') + +def update_stack(file_content, envs): + stack_id = get_stack_id(stack_name) + update_stack_res = requests.put( + f'{host}/api/stacks/{stack_id}?endpointId={endpointId}', + json={ + "stackFileContent": file_content, + "env": envs + }, + headers={'X-API-KEY': access_token}, + verify=False + ) + + if update_stack_res.ok: + print('Updated stack with success') + return + + if update_stack_res.status_code == 401: + raise Exception('Unauthorized') + + raise Exception('Unable to update stack', update_stack_res.status_code) + +if __name__ == '__main__': + setup_stack() \ No newline at end of file diff --git a/.ci/docker-compose.yaml b/.ci/docker-compose.yaml new file mode 100644 index 0000000..2cc5ec3 --- /dev/null +++ b/.ci/docker-compose.yaml @@ -0,0 +1,32 @@ +services: + capivarabot: + image: docker.io/eduardoferro/capivara:${CAPIVARA_IMAGE_TAG} + restart: unless-stopped + environment: + - JAVA_ARGS=${JAVA_ARGS} + - LOG_CHANNEL_ID=${LOG_CHANNEL_ID} + - DISCORD_TOKEN=${DISCORD_TOKEN} + - CURUPIRA_RESET=${CURUPIRA_RESET} + - DATABASE_DRIVER=${DATABASE_DRIVER} + - DATABASE_DIALECT=${DATABASE_DIALECT} + - DATABASE_URL=${DATABASE_URL} + - DATABASE_USERNAME=${DATABASE_USERNAME} + - DATABASE_PASSWORD=${DATABASE_PASSWORD} + networks: + - metrics_metrics + - database_database + deploy: + resources: + limits: + cpus: '0.5' + memory: 400M + expose: + - 8080 + +networks: + capivara: + driver: bridge + database_database: + external: true + metrics_metrics: + external: true \ No newline at end of file diff --git a/.github/workflows/deploy-homolog.yaml b/.github/workflows/deploy-homolog.yaml deleted file mode 100644 index e2837e8..0000000 --- a/.github/workflows/deploy-homolog.yaml +++ /dev/null @@ -1,98 +0,0 @@ -name: homolog-deploy -on: - workflow_dispatch: - -concurrency: - group: "deploy-homolog" - cancel-in-progress: false - -jobs: - build: - environment: PUB - env: - GITHUB_USER: ${{ secrets._GITHUB_USER }} - GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }} - runs-on: ubuntu-latest - steps: - - name: 'Check out repository' - uses: actions/checkout@v2 - - - name: 'Download latest JDK 17' - run: wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.tar.gz - - - name: 'Set up JDK' - uses: actions/setup-java@v2 - with: - java-version: '17' - distribution: 'jdkfile' - jdkFile: jdk-17_linux-x64_bin.tar.gz - cache: 'gradle' - - - name: 'Grant execute permission for gradlew' - run: chmod +x gradlew - - - name: 'Build and Generate JAR' - run: ./gradlew build deployHomolog -P version=${{github.ref_name}} - - - uses: actions/upload-artifact@master - with: - name: build-libs - path: build/libs/CapivaraBotHomolog.jar - retention-days: 1 - - deploy: - needs: build - environment: PUB - env: - GITHUB_USER: ${{ secrets._GITHUB_USER }} - GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }} - runs-on: ubuntu-latest - steps: - - name: 'Checkout' - uses: actions/checkout@v2 - - - uses: actions/download-artifact@master - with: - name: build-libs - path: build/libs/ - - - name: 'Wait for SSH' - run: | - while ! nc -w5 -z ${{ secrets.INSTANCE_IP }} 22; do - sleep 5 - echo "SSH not available..." - done; echo "SSH ready!" - - - name: 'Stop and Delete Previous App' - uses: appleboy/ssh-action@master - continue-on-error: true - with: - host: ${{ secrets.INSTANCE_IP }} - username: ${{ secrets.SSH_USERNAME }} - passphrase: ${{ secrets.VM_SSH_PRIVATE_KEY_PASSPHRASE }} - key: ${{ secrets.VM_SSH_PRIVATE_KEY }} - script: | - sudo pkill -f 'java -jar .*CapivaraBotHomolog.*\.jar' - rm -rf ~/capivara-homolog/CapivaraBotHomolog.jar - - - name: 'Push Repo' - uses: appleboy/scp-action@master - with: - host: ${{ secrets.INSTANCE_IP }} - username: ${{ secrets.SSH_USERNAME }} - passphrase: ${{ secrets.VM_SSH_PRIVATE_KEY_PASSPHRASE }} - key: ${{ secrets.VM_SSH_PRIVATE_KEY }} - source: "./build/libs/CapivaraBotHomolog.jar" - target: /home/${{ secrets.SSH_USERNAME }}/capivara-homolog/ - strip_components: 3 - - - name: 'Start BOT' - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.INSTANCE_IP }} - username: ${{ secrets.SSH_USERNAME }} - passphrase: ${{ secrets.VM_SSH_PRIVATE_KEY_PASSPHRASE }} - key: ${{ secrets.VM_SSH_PRIVATE_KEY }} - script: | - cd /home/${{ secrets.SSH_USERNAME }}/capivara-homolog - log_directory=/home/${{ secrets.SSH_USERNAME }}/capivara-homolog/logs nohup java -jar ~/capivara-homolog/CapivaraBotHomolog.jar --spring.config.location=file:/home/${{ secrets.SSH_USERNAME }}/capivara-homolog/config/main.properties --curupira.reset=true> nohup.out 2> nohup.err < /dev/null & diff --git a/.github/workflows/deploy-prod.yaml b/.github/workflows/deploy-prod.yaml index 6a04bd4..b6b0870 100644 --- a/.github/workflows/deploy-prod.yaml +++ b/.github/workflows/deploy-prod.yaml @@ -16,6 +16,13 @@ jobs: - name: 'Check out repository' uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: 'Install Python requests' + run: pip install requests + - name: Log in to Docker Hub uses: docker/login-action@v3.2.0 with: @@ -32,12 +39,12 @@ jobs: push: ${{ github.event_name != 'pull_request' }} tags: eduardoferro/capivara:${{ github.sha }},${{ github.event_name == 'release' && format('eduardoferro/capivara:latest,eduardoferro/capivara:{0}', github.ref_name) || '' }} - - name: 'Wait for SSH' + - name: 'Wait for host' run: | SLEEP=1 TRIES=0 MAX_TRIES=10 - while ! nc -w5 -z $INSTANCE_IP 22; do + while ! nc -w5 -z $INSTANCE_IP 9443; do echo "SSH not available..." if [[ $TRIES -eq $MAX_TRIES ]]; then echo "Max tries reached, exiting" @@ -45,28 +52,28 @@ jobs: fi ((TRIES += 1)) sleep $SLEEP - done; echo "SSH ready!" + done; echo "Host ready!" env: INSTANCE_IP: ${{ secrets.INSTANCE_IP }} - - name: 'Push start-container.sh' - uses: appleboy/scp-action@v0.1.7 - with: - host: ${{ secrets.INSTANCE_IP }} - username: ${{ secrets.SSH_USERNAME }} - passphrase: ${{ secrets.VM_SSH_PRIVATE_KEY_PASSPHRASE }} - key: ${{ secrets.VM_SSH_PRIVATE_KEY }} - source: "./start-container.sh" - target: /home/${{ secrets.SSH_USERNAME }}/capivara/ - strip_components: 1 - - - name: 'Start BOT' - uses: appleboy/ssh-action@v0.1.7 - with: - host: ${{ secrets.INSTANCE_IP }} - username: ${{ secrets.SSH_USERNAME }} - passphrase: ${{ secrets.VM_SSH_PRIVATE_KEY_PASSPHRASE }} - key: ${{ secrets.VM_SSH_PRIVATE_KEY }} - script: | - cd /home/${{ secrets.SSH_USERNAME }}/capivara - DISCORD_TOKEN='${{ secrets.DISCORD_BOT_TOKEN }}' LOG_CHANNEL_ID='${{ secrets.DISCORD_LOG_CHANNEL_ID }}' CURUPIRA_RESET='true' DATABASE_DRIVER='org.postgresql.Driver' DATABASE_DIALECT='org.hibernate.dialect.PostgreSQL95Dialect' DATABASE_URL='jdbc:postgresql://capivara_database:5432/capivara' DATABASE_USERNAME='${{ secrets.DATABASE_USERNAME }}' DATABASE_PASSWORD='${{ secrets.DATABASE_PASSWORD }}' JAVA_ARGS='-Xmx350M' /bin/bash start-container.sh docker.io/eduardoferro/capivara:${{ github.sha }} + - name: 'Deploy to Portainer' + working-directory: ./.ci + run: | + python3 ./deploy.py + env: + # Portainer + PORTAINER_HOST: ${{ secrets.PORTAINER_HOST }} + PORTAINER_ENDPOINT_ID: ${{ secrets.PORTAINER_ENDPOINT_ID }} + PORTAINER_ACCESS_TOKEN: ${{ secrets.PORTAINER_ACCESS_TOKEN }} + # Image + CAPIVARA_IMAGE_TAG: ${{ github.sha }} + # Bot + DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }} + LOG_CHANNEL_ID: ${{ secrets.DISCORD_LOG_CHANNEL_ID }} + CURUPIRA_RESET: 'true' + DATABASE_DRIVER: 'org.postgresql.Driver' + DATABASE_DIALECT: 'org.hibernate.dialect.PostgreSQL95Dialect' + DATABASE_URL: 'jdbc:postgresql://postgres-db:5432/capivara' + DATABASE_USERNAME: ${{ secrets.DATABASE_USERNAME }} + DATABASE_PASSWORD: ${{ secrets.DATABASE_PASSWORD }} + JAVA_ARGS: '-Xmx350M' diff --git a/prometheus.yml b/prometheus.yml new file mode 100644 index 0000000..31638b3 --- /dev/null +++ b/prometheus.yml @@ -0,0 +1,23 @@ +global: + scrape_interval: 1m + +scrape_configs: + - job_name: 'prometheus' + scrape_interval: 10s + static_configs: + - targets: ['localhost:9090'] + + - job_name: 'capivara' + scrape_interval: 10s + static_configs: + - targets: ['capivara:9090'] + + - job_name: 'node-exporter' + scrape_interval: 10s + static_configs: + - targets: ['node-exporter:9100'] + + - job_name: postgres-exporter + scrape_interval: 10s + static_configs: + - targets: ["postgres-exporter:9187"] \ No newline at end of file diff --git a/start-container.sh b/start-container.sh deleted file mode 100644 index 78b0a97..0000000 --- a/start-container.sh +++ /dev/null @@ -1,68 +0,0 @@ -if [ -z "${1}" ]; then - echo "Missing container repository and tag" - exit 1; -else - echo "Container repository and tag: $1" -fi -CONTAINER_REPOSITORY=${1} - -if [[ -z "${DISCORD_TOKEN}" ]] || [[ -z "${LOG_CHANNEL_ID}" ]] ; then - echo 'Missing required environment variable: DISCORD_TOKEN, LOG_CHANNEL_ID' - exit 1 -fi - -CONTAINER_NAME="capivarabot" -OLD_CONTAINER_ID=$(podman ps --all --quiet --filter "name=$CONTAINER_NAME") - -if [[ -z "${OLD_CONTAINER_ID}" ]]; then - echo "Container not found" -else - echo "Found container $OLD_CONTAINER_ID" - echo "Stopping..." - WAIT=10 # seconds - podman stop $OLD_CONTAINER_ID -t $WAIT || { echo 'Failed to stop container'; exit 1; } - echo "Removing..." - podman rm --force $OLD_CONTAINER_ID || { echo 'Failed to remove container'; exit 1; } -fi - -echo "Starting container..." -podman run -d \ - -e DISCORD_TOKEN=$DISCORD_TOKEN \ - -e LOG_CHANNEL_ID=$LOG_CHANNEL_ID \ - -e CURUPIRA_RESET=${CURUPIRA_RESET:-false} \ - -e DATABASE_DRIVER=${DATABASE_DRIVER:-org.h2.Driver} \ - -e DATABASE_DIALECT=${DATABASE_DIALECT:-org.hibernate.dialect.H2Dialect} \ - -e DATABASE_URL=${DATABASE_URL:-jdbc:h2:file:./banco_h2;DB_CLOSE_DELAY=-1} \ - -e DATABASE_USERNAME=${DATABASE_USERNAME:-sa} \ - -e DATABASE_PASSWORD=${DATABASE_PASSWORD:-sa} \ - -e JAVA_ARGS=${JAVA_ARGS:--Xmx200M} \ - --cpus 0.5 \ - --memory 400M \ - --name $CONTAINER_NAME \ - --restart always \ - --network metrics \ - $CONTAINER_REPOSITORY || { echo 'Failed to start container failed'; exit 1; } - - -SLEEP=10 -CHECKS=0 -MAX_CHECKS=6 -echo "Starting 'healthcheck'" -while true; do - NEW_CONTAINER_ID=$(podman ps --quiet --filter "name=$CONTAINER_NAME") - if [[ -z "${NEW_CONTAINER_ID}" ]]; then - echo "Container died? exiting" - exit 1 - fi - - if [[ CHECKS -eq MAX_CHECKS ]]; then - break - fi - ((CHECKS += 1)) - sleep $SLEEP -done; - -echo "Container should be alive" - -echo "Cleaning images..." -podman image prune -a -f || { echo 'Failed to start clean images'; exit 1; }