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 - - -