diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..a501852
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,3 @@
+# Binance API credentials
+BINANCE_API_KEY=your_api_key_here
+BINANCE_SECRET=your_secret_key_here
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..5b88d81
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,93 @@
+name: BinanceBot Build Pipeline
+
+on:
+ workflow_dispatch:
+ push:
+ pull_request:
+ branches: [ "master" ]
+
+permissions:
+ contents: read
+
+concurrency:
+ group: build-${{ github.ref }}
+ cancel-in-progress: true
+
+defaults:
+ run:
+ working-directory: ./src
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ configuration: [Debug, Release]
+ dotnet-version: ["9.0.x"]
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up .NET SDK
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: ${{ matrix.dotnet-version }}
+ cache: true
+ cache-dependency-path: |
+ src/**/*.csproj
+ src/**/global.json
+ src/**/NuGet.Config
+
+ - name: .NET info
+ run: dotnet --info
+
+ - name: Restore dependencies
+ run: dotnet restore --verbosity minimal
+
+ - name: Build
+ run: dotnet build --no-restore --configuration ${{ matrix.configuration }} --nologo
+
+ - name: Test
+ run: dotnet test --no-build --configuration ${{ matrix.configuration }} --collect:"XPlat Code Coverage" --logger "trx;LogFileName=test_results.trx" || echo "No tests found"
+
+ - name: Upload test results (TRX)
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: test-results-${{ matrix.configuration }}
+ path: |
+ **/TestResults/**/*.trx
+ if-no-files-found: warn
+
+ - name: Upload code coverage (Cobertura)
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: coverage-cobertura-${{ matrix.configuration }}
+ path: |
+ **/TestResults/**/coverage.cobertura.xml
+ if-no-files-found: warn
+
+ - name: Publish artifacts (Release only)
+ if: matrix.configuration == 'Release'
+ run: |
+ dotnet publish BinanceBot.MarketBot.Console/BinanceBot.MarketBot.Console.csproj \
+ -c Release \
+ -o ./publish/MarketBot \
+ --no-build
+ dotnet publish BinanceBot.MarketViewer.Console/BinanceBot.MarketViewer.Console.csproj \
+ -c Release \
+ -o ./publish/MarketViewer \
+ --no-build
+
+ - name: Upload published artifacts
+ if: matrix.configuration == 'Release'
+ uses: actions/upload-artifact@v4
+ with:
+ name: binance-bot-binaries
+ path: |
+ src/publish/MarketBot/
+ src/publish/MarketViewer/
+ retention-days: 7
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..bb0d106
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,92 @@
+name: BinanceBot Release Pipeline
+
+on:
+ workflow_dispatch:
+ push:
+ branches: [ "master" ]
+ tags:
+ - 'v*.*.*'
+
+permissions:
+ contents: read
+ packages: write
+ id-token: write
+
+concurrency:
+ group: release-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build-and-push-images:
+
+ name: Build and push Docker images
+
+ runs-on: ubuntu-latest
+
+ env:
+ DOCKER_BUILDKIT: 1
+
+ strategy:
+ matrix:
+ app:
+ - name: marketbot
+ dockerfile: Dockerfile.marketbot
+ project: BinanceBot.MarketBot.Console
+ - name: marketviewer
+ dockerfile: Dockerfile.marketviewer
+ project: BinanceBot.MarketViewer.Console
+
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to GitHub Container Registry
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.CONTAINER_REGISTRY_TOKEN }}
+
+ - name: Docker metadata
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: |
+ ghcr.io/${{ github.repository_owner }}/binancebot-${{ matrix.app.name }}
+ tags: |
+ type=raw,value=latest
+ type=ref,event=branch
+ type=semver,pattern={{version}}
+ type=semver,pattern={{major}}.{{minor}}
+ type=sha
+ labels: |
+ org.opencontainers.image.source=${{ github.repository }}
+ org.opencontainers.image.created=${{ github.event.head_commit.timestamp }}
+ org.opencontainers.image.revision=${{ github.sha }}
+ org.opencontainers.image.title=BinanceBot ${{ matrix.app.name }}
+ org.opencontainers.image.description=BinanceBot ${{ matrix.app.project }}
+
+ - name: Build and push
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ file: ./${{ matrix.app.dockerfile }}
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ cache-from: type=gha,scope=${{ matrix.app.name }}
+ cache-to: type=gha,mode=max,scope=${{ matrix.app.name }}
+ build-args: |
+ PROJECT_NAME=${{ matrix.app.project }}
+
+ - name: Run Trivy vulnerability scanner
+ uses: aquasecurity/trivy-action@0.31.0
+ with:
+ image-ref: ghcr.io/${{ github.repository_owner }}/binancebot-${{ matrix.app.name }}:latest
+ format: 'table'
+ ignore-unfixed: true
+ vuln-type: 'library'
+ severity: 'CRITICAL,HIGH'
diff --git a/Dockerfile.marketbot b/Dockerfile.marketbot
new file mode 100644
index 0000000..0638cd7
--- /dev/null
+++ b/Dockerfile.marketbot
@@ -0,0 +1,51 @@
+# Build stage
+FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build
+ARG BUILD_CONFIGURATION=Release
+ARG PROJECT_NAME=BinanceBot.MarketBot.Console
+WORKDIR /src
+
+# Copy solution and project files for better layer caching
+COPY src/*.sln ./
+COPY src/BinanceBot.Market/*.csproj ./BinanceBot.Market/
+COPY src/BinanceBot.MarketBot.Console/*.csproj ./BinanceBot.MarketBot.Console/
+COPY src/BinanceBot.MarketViewer.Console/*.csproj ./BinanceBot.MarketViewer.Console/
+
+# Restore dependencies as a separate layer
+RUN dotnet restore "${PROJECT_NAME}/${PROJECT_NAME}.csproj" \
+ --runtime linux-musl-x64
+
+# Copy remaining source files
+COPY src/. ./
+
+# Build and publish
+WORKDIR /src/${PROJECT_NAME}
+RUN dotnet publish "${PROJECT_NAME}.csproj" \
+ -c $BUILD_CONFIGURATION \
+ -o /app/publish \
+ --no-restore \
+ --runtime linux-musl-x64 \
+ --self-contained false \
+ /p:UseAppHost=false
+
+# Runtime stage
+FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS final
+ARG PROJECT_NAME=BinanceBot.MarketBot.Console
+WORKDIR /app
+
+# Create non-root user
+RUN addgroup -g 1000 appuser && \
+ adduser -u 1000 -G appuser -s /bin/sh -D appuser && \
+ chown -R appuser:appuser /app
+
+# Copy published output
+COPY --from=build --chown=appuser:appuser /app/publish .
+
+# Security: Run as non-root
+USER appuser
+
+# Set environment variables
+ENV DOTNET_RUNNING_IN_CONTAINER=true \
+ DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
+
+# Note: Set BINANCE_API_KEY and BINANCE_SECRET via environment variables or .env file at runtime
+ENTRYPOINT dotnet "${PROJECT_NAME}.dll"
diff --git a/Dockerfile.marketviewer b/Dockerfile.marketviewer
new file mode 100644
index 0000000..11d619e
--- /dev/null
+++ b/Dockerfile.marketviewer
@@ -0,0 +1,51 @@
+# Build stage
+FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build
+ARG BUILD_CONFIGURATION=Release
+ARG PROJECT_NAME=BinanceBot.MarketViewer.Console
+WORKDIR /src
+
+# Copy solution and project files for better layer caching
+COPY src/*.sln ./
+COPY src/BinanceBot.Market/*.csproj ./BinanceBot.Market/
+COPY src/BinanceBot.MarketBot.Console/*.csproj ./BinanceBot.MarketBot.Console/
+COPY src/BinanceBot.MarketViewer.Console/*.csproj ./BinanceBot.MarketViewer.Console/
+
+# Restore dependencies as a separate layer
+RUN dotnet restore "${PROJECT_NAME}/${PROJECT_NAME}.csproj" \
+ --runtime linux-musl-x64
+
+# Copy remaining source files
+COPY src/. ./
+
+# Build and publish
+WORKDIR /src/${PROJECT_NAME}
+RUN dotnet publish "${PROJECT_NAME}.csproj" \
+ -c $BUILD_CONFIGURATION \
+ -o /app/publish \
+ --no-restore \
+ --runtime linux-musl-x64 \
+ --self-contained false \
+ /p:UseAppHost=false
+
+# Runtime stage
+FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS final
+ARG PROJECT_NAME=BinanceBot.MarketViewer.Console
+WORKDIR /app
+
+# Create non-root user
+RUN addgroup -g 1000 appuser && \
+ adduser -u 1000 -G appuser -s /bin/sh -D appuser && \
+ chown -R appuser:appuser /app
+
+# Copy published output
+COPY --from=build --chown=appuser:appuser /app/publish .
+
+# Security: Run as non-root
+USER appuser
+
+# Set environment variables
+ENV DOTNET_RUNNING_IN_CONTAINER=true \
+ DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
+
+# Note: Set BINANCE_API_KEY and BINANCE_SECRET via environment variables or .env file at runtime
+ENTRYPOINT dotnet "${PROJECT_NAME}.dll"
diff --git a/compose.yml b/compose.yml
new file mode 100644
index 0000000..01575a9
--- /dev/null
+++ b/compose.yml
@@ -0,0 +1,28 @@
+services:
+ marketbot:
+ image: ghcr.io/${GITHUB_REPOSITORY_OWNER:-codez0mb1e}/binancebot-marketbot:latest
+ container_name: binancebot-marketbot
+ restart: unless-stopped
+ environment:
+ - BINANCE_API_KEY=${BINANCE_API_KEY}
+ - BINANCE_SECRET=${BINANCE_SECRET}
+ env_file:
+ - .env
+ networks:
+ - binancebot-network
+
+ marketviewer:
+ image: ghcr.io/${GITHUB_REPOSITORY_OWNER:-codez0mb1e}/binancebot-marketviewer:latest
+ container_name: binancebot-marketviewer
+ restart: unless-stopped
+ environment:
+ - BINANCE_API_KEY=${BINANCE_API_KEY}
+ - BINANCE_SECRET=${BINANCE_SECRET}
+ env_file:
+ - .env
+ networks:
+ - binancebot-network
+
+networks:
+ binancebot-network:
+ driver: bridge
diff --git a/src/BinanceBot.MarketBot.Console/.env.example b/src/BinanceBot.MarketBot.Console/.env.example
deleted file mode 100644
index 0252472..0000000
--- a/src/BinanceBot.MarketBot.Console/.env.example
+++ /dev/null
@@ -1,5 +0,0 @@
-# Binance API Credentials
-# Copy this file to .env and fill in your actual credentials
-
-BINANCE_API_KEY=your_api_key_here
-BINANCE_SECRET=your_secret_key_here
diff --git a/src/BinanceBot.MarketBot.Console/BinanceBot.MarketBot.Console.csproj b/src/BinanceBot.MarketBot.Console/BinanceBot.MarketBot.Console.csproj
index 5409c87..11affff 100644
--- a/src/BinanceBot.MarketBot.Console/BinanceBot.MarketBot.Console.csproj
+++ b/src/BinanceBot.MarketBot.Console/BinanceBot.MarketBot.Console.csproj
@@ -22,8 +22,4 @@
PreserveNewest
-
-
-
-
diff --git a/src/BinanceBot.MarketViewer.Console/.env.example b/src/BinanceBot.MarketViewer.Console/.env.example
deleted file mode 100644
index 0252472..0000000
--- a/src/BinanceBot.MarketViewer.Console/.env.example
+++ /dev/null
@@ -1,5 +0,0 @@
-# Binance API Credentials
-# Copy this file to .env and fill in your actual credentials
-
-BINANCE_API_KEY=your_api_key_here
-BINANCE_SECRET=your_secret_key_here
diff --git a/src/BinanceBot.MarketViewer.Console/BinanceBot.MarketViewer.Console.csproj b/src/BinanceBot.MarketViewer.Console/BinanceBot.MarketViewer.Console.csproj
index 3631770..88bff0f 100644
--- a/src/BinanceBot.MarketViewer.Console/BinanceBot.MarketViewer.Console.csproj
+++ b/src/BinanceBot.MarketViewer.Console/BinanceBot.MarketViewer.Console.csproj
@@ -23,7 +23,4 @@
PreserveNewest
-
-
-