From 549baa9ba9593b211cf68d42055c0974a529a0fc Mon Sep 17 00:00:00 2001 From: Dan Fredrik Stokka <9398849+hackandhack@users.noreply.github.com> Date: Fri, 29 May 2026 23:51:35 +0200 Subject: [PATCH 1/2] ci: add docker-compose, monitoring configs, and update CI workflow to reference secrets and push image on branch ci/update-secrets-and-compose --- .github/workflows/ci-cd-pipeline.yml | 113 ++++++++++++++++++++------- LOCAL_DEVELOPMENT.md | 34 ++++++++ alertmanager/config.yml | 15 ++++ docker-compose.yml | 31 ++++++++ prometheus/prometheus.yml | 8 ++ 5 files changed, 174 insertions(+), 27 deletions(-) create mode 100644 LOCAL_DEVELOPMENT.md create mode 100644 alertmanager/config.yml create mode 100644 docker-compose.yml create mode 100644 prometheus/prometheus.yml diff --git a/.github/workflows/ci-cd-pipeline.yml b/.github/workflows/ci-cd-pipeline.yml index 9245bdc..cc6bad5 100644 --- a/.github/workflows/ci-cd-pipeline.yml +++ b/.github/workflows/ci-cd-pipeline.yml @@ -2,56 +2,115 @@ name: CI/CD Pipeline on: push: - branches: [ main ] + branches: + - main + - master pull_request: - branches: [ main ] + branches: + - main + - master + workflow_dispatch: {} + +permissions: + contents: read + packages: write + id-token: write + statuses: write + +env: + APP_IMAGE: ghcr.io/hackandhack/ai-talk-model jobs: build: runs-on: ubuntu-latest - + outputs: + sha: ${{ steps.get_sha.outputs.sha }} steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up JDK 17 uses: actions/setup-java@v4 with: + distribution: temurin java-version: '17' - distribution: 'temurin' - cache: maven - - name: Build with Maven - run: mvn clean verify --batch-mode + - name: Cache Maven packages + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Get short SHA + id: get_sha + run: echo "::set-output name=sha::$(git rev-parse --short HEAD)" - - name: Build JAR - run: mvn package -DskipTests --batch-mode + - name: Build (Maven) + run: mvn -B -DskipTests clean package - docker: + test: needs: build runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' - steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '17' + + - name: Cache Maven packages + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Run tests + run: mvn test + + docker-build-and-push: + needs: [build, test] + runs-on: ubuntu-latest + environment: production + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v2 - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 + - name: Log in to GHCR + uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Login to Hugging Face - run: echo ${{ secrets.HF_TOKEN }} | huggingface-cli login --token-stdin || echo 'HF_TOKEN not set, skipping login' + - name: Build and push Docker image + env: + HF_TOKEN: ${{ secrets.HF_TOKEN }} + SMTP_USER: ${{ secrets.SMTP_USER }} + SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }} + SMTP_HOST: ${{ secrets.SMTP_HOST }} + SMTP_PORT: ${{ secrets.SMTP_PORT }} + EMAIL_FROM: ${{ secrets.EMAIL_FROM }} + run: | + docker buildx build \ + --build-arg HF_TOKEN="$HF_TOKEN" \ + --tag $APP_IMAGE:latest \ + --tag $APP_IMAGE:${{ github.sha }} \ + --push . - - name: Build and Push Docker Image - uses: docker/build-push-action@v6 - with: - context: . - push: true - tags: ghcr.io/${{ github.repository }}:latest - cache-from: type=gha - cache-to: type=gha,mode=max \ No newline at end of file + - name: Output image + run: echo "Image pushed: $APP_IMAGE:latest and $APP_IMAGE:${{ github.sha }}" diff --git a/LOCAL_DEVELOPMENT.md b/LOCAL_DEVELOPMENT.md new file mode 100644 index 0000000..753da1e --- /dev/null +++ b/LOCAL_DEVELOPMENT.md @@ -0,0 +1,34 @@ +LOCAL DEVELOPMENT + +This file describes how to run the application and the monitoring stack locally using docker compose. + +Preparations +- Create a file named `.env` in the repo root (do NOT commit it). Add: + + HF_TOKEN=your_huggingface_token + +- If the image is private on GHCR, login locally: + + echo | docker login ghcr.io -u --password-stdin + +Running locally +- Start the stack: + + docker compose up -d + +- Services: + - App (Spring Boot): http://localhost:8080 (Actuator: /actuator/health, /actuator/prometheus) + - Prometheus: http://localhost:9090 + - Alertmanager: http://localhost:9093 + +Repository secrets (add these in GitHub: Settings → Secrets and variables → Actions) +- HF_TOKEN — Hugging Face token +- SMTP_USER — SMTP username (e.g. your Gmail address) +- SMTP_PASSWORD — Gmail App Password or other SMTP password +- SMTP_HOST — smtp.gmail.com +- SMTP_PORT — 587 +- EMAIL_FROM — no-reply@yourdomain + +Notes +- The CI workflow references these secrets but does NOT store values. Add them in the repo before merging. +- The Docker build in CI uses the repository GITHUB_TOKEN to authenticate to ghcr.io. Ensure the repository allows packages: write for workflows if your organization blocks default tokens. diff --git a/alertmanager/config.yml b/alertmanager/config.yml new file mode 100644 index 0000000..e553d6e --- /dev/null +++ b/alertmanager/config.yml @@ -0,0 +1,15 @@ +global: + resolve_timeout: 5m + +route: + receiver: 'email-notifications' + +receivers: + - name: 'email-notifications' + email_configs: + - to: 'ops@example.com' # <-- Replace with real address + from: 'no-reply@example.com' + smarthost: 'smtp.gmail.com:587' + auth_username: 'your-smtp-user' + auth_password: 'your-smtp-password' + require_tls: true diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f16707e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,31 @@ +version: "3.8" + +services: + app: + image: ghcr.io/hackandhack/ai-talk-model:latest + restart: unless-stopped + env_file: .env + environment: + SPRING_PROFILES_ACTIVE: prod + HF_TOKEN: ${HF_TOKEN} + ports: + - "8080:8080" + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:8080/actuator/health || exit 1"] + interval: 30s + timeout: 10s + retries: 5 + + prometheus: + image: prom/prometheus:latest + volumes: + - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro + ports: + - "9090:9090" + + alertmanager: + image: prom/alertmanager:latest + volumes: + - ./alertmanager/config.yml:/etc/alertmanager/config.yml:ro + ports: + - "9093:9093" diff --git a/prometheus/prometheus.yml b/prometheus/prometheus.yml new file mode 100644 index 0000000..de5a2db --- /dev/null +++ b/prometheus/prometheus.yml @@ -0,0 +1,8 @@ +global: + scrape_interval: 15s + +scrape_configs: + - job_name: 'ai-talk-model' + metrics_path: '/actuator/prometheus' + static_configs: + - targets: ['app:8080'] From 507745ca99d2fa73e16d8477f6ca4d1c5ebd3001 Mon Sep 17 00:00:00 2001 From: Dan Fredrik Stokka <9398849+hackandhack@users.noreply.github.com> Date: Sat, 30 May 2026 00:34:54 +0200 Subject: [PATCH 2/2] chore(ci): trigger workflow and add /api/ai/chat alias + local README updates --- LOCAL_DEVELOPMENT.md | 27 ++++++++++++++++- TRIGGER.txt | 3 ++ .../assistant/AiChatController.java | 30 +++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 TRIGGER.txt create mode 100644 src/main/java/no/hackandhack/assistant/AiChatController.java diff --git a/LOCAL_DEVELOPMENT.md b/LOCAL_DEVELOPMENT.md index 753da1e..fa012b4 100644 --- a/LOCAL_DEVELOPMENT.md +++ b/LOCAL_DEVELOPMENT.md @@ -6,23 +6,48 @@ Preparations - Create a file named `.env` in the repo root (do NOT commit it). Add: HF_TOKEN=your_huggingface_token + OPENAI_API_KEY=your_openai_api_key (optional, only if using openai provider) - If the image is private on GHCR, login locally: echo | docker login ghcr.io -u --password-stdin Running locally -- Start the stack: +- Build and run using Maven wrapper: + + ./mvnw spring-boot:run + +- Or build jar and run: + + ./mvnw -DskipTests package + java -jar target/*.jar + +- Start the stack (compose) if you want metrics and alerting: docker compose up -d - Services: - App (Spring Boot): http://localhost:8080 (Actuator: /actuator/health, /actuator/prometheus) + - Assistant endpoint: POST http://localhost:8080/api/assistant + - Alias endpoint: POST http://localhost:8080/api/ai/chat - Prometheus: http://localhost:9090 - Alertmanager: http://localhost:9093 +Example POST to assistant (Hugging Face Grok 4-3 is default): + +curl -s -X POST http://localhost:8080/api/assistant \ + -H "Content-Type: application/json" \ + -d '{"prompt":"Say hello","provider":"hf"}' + +Or to the alias path expected by your tests: + +curl -s -X POST http://localhost:8080/api/ai/chat \ + -H "Content-Type: application/json" \ + -d '{"prompt":"Say hello","provider":"hf"}' + Repository secrets (add these in GitHub: Settings → Secrets and variables → Actions) - HF_TOKEN — Hugging Face token +- OPENAI_API_KEY — OpenAI API key (optional) - SMTP_USER — SMTP username (e.g. your Gmail address) - SMTP_PASSWORD — Gmail App Password or other SMTP password - SMTP_HOST — smtp.gmail.com diff --git a/TRIGGER.txt b/TRIGGER.txt new file mode 100644 index 0000000..d6ec0b6 --- /dev/null +++ b/TRIGGER.txt @@ -0,0 +1,3 @@ +chore(ci): trigger workflow - small README note + +This commit was created by the assistant to trigger the CI pipeline and validate the recent changes. diff --git a/src/main/java/no/hackandhack/assistant/AiChatController.java b/src/main/java/no/hackandhack/assistant/AiChatController.java new file mode 100644 index 0000000..a5f2ba7 --- /dev/null +++ b/src/main/java/no/hackandhack/assistant/AiChatController.java @@ -0,0 +1,30 @@ +package no.hackandhack.assistant; + +import no.hackandhack.assistant.dto.AssistantRequest; +import no.hackandhack.assistant.dto.AssistantResponse; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +@RestController +@RequestMapping("/api/ai") +public class AiChatController { + private final AssistantService service; + + public AiChatController(AssistantService service) { + this.service = service; + } + + @PostMapping(value = "/chat", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public Mono> chat(@RequestBody AssistantRequest request) { + return service.generate(request) + .map(resp -> ResponseEntity.ok() + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .body(resp)); + } +}