Repository navigation
chore: Repository hygiene, real CI, installers, security cleanup #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
ef56488
chore: production cleanup and end-user test hardening
Opselon 81f3d9b
test: add cross-database end-user E2E
Opselon 4b17855
test: add SQLite and PostgreSQL cross-db compose
Opselon 292d80e
test: expand CLI end-user E2E matrix
Opselon c283301
test: add dedicated Docker end-user E2E
Opselon 69216aa
fix: make CLI matrix xunit data compile safely
Opselon f313eb0
test: assert exact labeler pull request trigger
Opselon 132009b
fix: use valid anonymous payload for CLI E2E login
Opselon 7ddc36a
test: remove obsolete labeler regression coverage
Opselon 90bd565
chore: remove unused labeler workflow
Opselon f3b735e
test: add cross-platform CLI E2E matrix
Opselon 66e6acd
test: add cross-platform filesystem runtime coverage
Opselon 25e7519
test: remove obsolete labeler regression test
Opselon d6a1c46
fix: restore regression test class closure
Opselon 97267dc
fix: restore release workflow regression helpers
Opselon 4278601
fix: reach zero warnings/errors on latest base (a90c990)
c5b591b
fix(cli): reject unknown flags, fail closed on redirected stdin, vali…
153e1d7
fix(ci): make CLI E2E + Docker smoke tests actually pass
b9ec9c2
fix(cli): add process timeouts and PowerShell Core fallback for Windo…
e94103e
fix(cli): turn corrupted vault key and backup failures into controlle…
daa0c2b
test(e2e): include CLI stdout/stderr in backup assertion failure
3694a93
fix(secrets): retry the backup file move past transient Windows file …
2128916
diag: identify which backup call fails on Windows (temporary)
d2aedfa
fix(secrets): copy the staged backup instead of moving it on Windows
447ac1e
test(e2e): serialize Docker E2E collections and assert compose up suc…
6bec373
test(e2e): authenticate before provider probe, accept absolute login …
080809d
test(e2e): force fresh-install bootstrap path and read admin password
b8c1ad2
test(e2e): surface PUT /api/secrets error body on failure
80ff2af
fix(webapi): accept enum category names in JSON payloads
19b202c
fix(auth): invalidate outstanding cookies on logout; entrypoint owns …
6337c4f
fix(auth): bootstrap admin password was unreadable (BOM) and logout n…
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| # ============================================ | ||
| # ForexTradingBot - Environment Configuration | ||
| # ============================================ | ||
| # Copy this file to .env and fill in your actual values. | ||
| # NEVER commit your real .env file to git! | ||
| # | ||
| # Values are intentionally left EMPTY here: every "example" string below a | ||
| # credential key tends to trip secret scanners. The commented lines show the | ||
| # format; paste your real values on the uncommented lines after copying. | ||
|
|
||
| # ============================================ | ||
| # Telegram Bot Configuration | ||
| # ============================================ | ||
| # From @BotFather: | ||
| # TELEGRAM_BOT_TOKEN=<paste your bot token> | ||
| TELEGRAM_BOT_TOKEN= | ||
|
|
||
| # Public HTTPS endpoint where Telegram sends updates: | ||
| # TELEGRAM_WEBHOOK_ADDRESS=https://your.domain/ | ||
| TELEGRAM_WEBHOOK_ADDRESS= | ||
|
|
||
| # Shared secret used to sign Telegram webhook payloads: | ||
| # TELEGRAM_WEBHOOK_SECRET=<choose a long random string> | ||
| TELEGRAM_WEBHOOK_SECRET= | ||
|
|
||
| # Telegram User API (if you need user-level Telegram access) | ||
| # TELEGRAM_API_ID=<numeric api id> | ||
| # TELEGRAM_API_HASH=<api hash> | ||
| TELEGRAM_API_ID= | ||
| TELEGRAM_API_HASH= | ||
| TELEGRAM_PHONE_NUMBER= | ||
|
|
||
| # Admin user IDs (comma-separated Telegram user IDs allowed to control the bot) | ||
| ADMIN_USER_IDS= | ||
|
|
||
| # ============================================ | ||
| # PostgreSQL Database Configuration | ||
| # ============================================ | ||
| # POSTGRES_PASSWORD=<choose a strong password> | ||
| POSTGRES_USER=forexbot | ||
| POSTGRES_DB=forexbotdb | ||
| POSTGRES_PASSWORD= | ||
|
|
||
| # ============================================ | ||
| # Crypto Pay Configuration | ||
| # ============================================ | ||
| # CRYPTO_PAY_API_TOKEN=<paste your api token> | ||
| CRYPTO_PAY_API_TOKEN= | ||
| CRYPTO_PAY_BASE_URL=https://pay.crypt.bot/api/ | ||
| CRYPTO_PAY_IS_TESTNET=true | ||
|
|
||
| # CRYPTO_PAY_WEBHOOK_SECRET=<choose a long random string> | ||
| CRYPTO_PAY_WEBHOOK_SECRET= | ||
|
|
||
| # ============================================ | ||
| # Forwarding Rules (Optional) | ||
| # ============================================ | ||
| # Format example (uncomment and fill in): | ||
| # FORWARDING_RULES__0__BotToken=<paste forwarding bot token> | ||
| # FORWARDING_RULES__0__SourceChannelId=-1001234567890 | ||
| # FORWARDING_RULES__0__TargetChannelId=-1009876543210 | ||
| # FORWARDING_RULES__0__IsEnabled=true | ||
|
|
||
| # ============================================ | ||
| # Development Settings (Do NOT use in Production) | ||
| # ============================================ | ||
| ASPNETCORE_ENVIRONMENT=Development | ||
| Logging__LogLevel__Default=Debug | ||
| Logging__LogLevel__Microsoft=Warning | ||
|
|
||
| # Docker sets this to a persistent application-data volume. | ||
| FOREXBOT_VAULT_DIRECTORY= |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| # Default owner for everything | ||
| * @Opselon |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| ## What does this PR do? | ||
|
|
||
| <!-- One-line summary --> | ||
|
|
||
| ## Type of change | ||
| - [ ] Bug fix | ||
| - [ ] New feature | ||
| - [ ] Refactor | ||
| - [ ] CI / tooling | ||
| - [ ] Documentation | ||
|
|
||
| ## Checklist | ||
| - [ ] `dotnet build ForexTradingBot.sln -c Release` passes | ||
| - [ ] `dotnet test ForexTradingBot.sln` passes | ||
| - [ ] No secrets/credentials committed (`.env`, tokens, keys) | ||
| - [ ] If Docker-related: `docker compose up` starts and `/healthz` returns 200 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,269 @@ | ||
| name: CI | ||
|
|
||
| on: | ||
| push: | ||
| branches: [master] | ||
| pull_request: | ||
| branches: [master] | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: ci-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| env: | ||
| DOTNET_NOLOGO: true | ||
| DOTNET_CLI_TELEMETRY_OPTOUT: true | ||
|
|
||
| jobs: | ||
| build-and-test: | ||
| name: Build & Test | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 30 | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v5 | ||
|
|
||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@v6 | ||
| with: | ||
| dotnet-version: "9.0.x" | ||
| cache: true | ||
| cache-dependency-path: "**/*.csproj" | ||
|
|
||
| - name: Restore | ||
| run: dotnet restore ForexTradingBot.sln | ||
|
|
||
| - name: Dependency vulnerability gate | ||
| shell: bash | ||
| run: | | ||
| set -euo pipefail | ||
| report="$RUNNER_TEMP/vulnerable-packages.txt" | ||
| dotnet list ForexTradingBot.sln package --vulnerable --include-transitive 2>&1 | tee "$report" | ||
| if grep -Eqi 'has (the following )?vulnerabilit|severity:[[:space:]]*(critical|high|moderate|low)' "$report"; then | ||
| echo "::error::Known vulnerable NuGet package detected. Release is blocked." | ||
| exit 1 | ||
| fi | ||
| - name: Build | ||
| run: dotnet build ForexTradingBot.sln -c Release --no-restore | ||
|
|
||
| - name: Test | ||
| run: > | ||
| dotnet test ForexTradingBot.sln -c Release --no-build | ||
| --logger "trx;LogFileName=test-results.trx" | ||
| --results-directory TestResults | ||
|
|
||
| - name: Upload test results | ||
| if: always() | ||
| uses: actions/upload-artifact@v7 | ||
| with: | ||
| name: test-results | ||
| path: TestResults | ||
| retention-days: 14 | ||
|
|
||
| e2e-end-user: | ||
| name: End-user E2E | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 45 | ||
| needs: | ||
| - build-and-test | ||
| - docker | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v5 | ||
|
|
||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@v6 | ||
| with: | ||
| dotnet-version: "9.0.x" | ||
|
|
||
| - name: Restore solution | ||
| run: dotnet restore ForexTradingBot.sln | ||
|
|
||
| - name: Restore E2E test project | ||
| run: dotnet restore tests/e2e/ForexTradingBot.EndToEnd.csproj | ||
|
|
||
| - name: Build solution | ||
| run: dotnet build ForexTradingBot.sln -c Release --no-restore | ||
|
|
||
| - name: Build E2E test project | ||
| run: dotnet build tests/e2e/ForexTradingBot.EndToEnd.csproj -c Release --no-restore | ||
|
|
||
| - name: Run end-user E2E | ||
| run: dotnet test tests/e2e/ForexTradingBot.EndToEnd.csproj -c Release --no-build --no-restore --logger "console;verbosity=normal" | ||
|
|
||
| - name: Upload E2E logs | ||
| if: always() | ||
| uses: actions/upload-artifact@v7 | ||
| with: | ||
| name: end-user-e2e-logs | ||
| path: tests/e2e/.last.log | ||
| if-no-files-found: ignore | ||
| retention-days: 14 | ||
|
|
||
| e2e-windows-cli: | ||
| name: Windows CLI E2E | ||
| runs-on: windows-latest | ||
| timeout-minutes: 20 | ||
| needs: build-and-test | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v5 | ||
|
|
||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@v6 | ||
| with: | ||
| dotnet-version: "9.0.x" | ||
|
|
||
| - name: Restore solution | ||
| shell: pwsh | ||
| run: dotnet restore ForexTradingBot.sln | ||
|
|
||
| - name: Restore E2E test project | ||
| shell: pwsh | ||
| run: dotnet restore tests/e2e/ForexTradingBot.EndToEnd.csproj | ||
|
|
||
| - name: Build solution | ||
| shell: pwsh | ||
| run: dotnet build ForexTradingBot.sln -c Release --no-restore | ||
|
|
||
| - name: Build E2E test project | ||
| shell: pwsh | ||
| run: dotnet build tests/e2e/ForexTradingBot.EndToEnd.csproj -c Release --no-restore | ||
|
|
||
| - name: Run CLI E2E | ||
| shell: pwsh | ||
| run: dotnet test tests/e2e/ForexTradingBot.EndToEnd.csproj -c Release --no-build --no-restore --filter "Surface=CLI" --logger "console;verbosity=normal" | ||
|
|
||
| e2e-cli-platform-matrix: | ||
| name: CLI E2E (${{ matrix.os }}) | ||
| runs-on: ${{ matrix.os }} | ||
| timeout-minutes: 25 | ||
| needs: build-and-test | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| os: [ubuntu-latest, windows-latest, macos-latest] | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v5 | ||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@v6 | ||
| with: | ||
| dotnet-version: "9.0.x" | ||
| - name: Restore | ||
| run: dotnet restore ForexTradingBot.sln | ||
| - name: Restore E2E project | ||
| run: dotnet restore tests/e2e/ForexTradingBot.EndToEnd.csproj | ||
| - name: Build solution | ||
| run: dotnet build ForexTradingBot.sln -c Release --no-restore | ||
| - name: Build E2E project | ||
| run: dotnet build tests/e2e/ForexTradingBot.EndToEnd.csproj -c Release --no-restore | ||
| - name: CLI E2E | ||
| run: dotnet test tests/e2e/ForexTradingBot.EndToEnd.csproj -c Release --no-build --no-restore --filter "Surface=CLI" --logger "console;verbosity=normal" | ||
|
|
||
| docker: | ||
| name: Docker build & smoke | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 30 | ||
| needs: build-and-test | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v5 | ||
|
|
||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@v4 | ||
|
|
||
| - name: Build image (load into local daemon) | ||
| uses: docker/build-push-action@v7 | ||
| with: | ||
| context: . | ||
| push: false | ||
| load: true | ||
| tags: forexbot:ci | ||
| cache-from: type=gha | ||
| cache-to: type=gha,mode=max | ||
|
|
||
| - name: Smoke test — real Postgres + Redis, API against them, /healthz = 200 | ||
| run: | | ||
| set -euo pipefail | ||
| docker network create smokenet | ||
| docker run -d --name smoke-pg --network smokenet \ | ||
| -e POSTGRES_USER=forexbot -e POSTGRES_PASSWORD=ci-smoke-password -e POSTGRES_DB=forexbotdb \ | ||
| --health-cmd "pg_isready -U forexbot -d forexbotdb" \ | ||
| --health-interval 5s --health-timeout 5s --health-retries 20 \ | ||
| postgres:15-alpine | ||
| docker run -d --name smoke-redis --network smokenet redis:7-alpine | ||
| echo "Waiting for Postgres health..." | ||
| pg_ok=0 | ||
| for i in $(seq 1 30); do | ||
| st=$(docker inspect -f '{{.State.Health.Status}}' smoke-pg 2>/dev/null || echo starting) | ||
| if [ "$st" = "healthy" ]; then echo "Postgres healthy after ${i} checks"; pg_ok=1; break; fi | ||
| sleep 5 | ||
| done | ||
| if [ "$pg_ok" != "1" ]; then echo "::error::Postgres never became healthy"; docker logs smoke-pg | tail -50; docker rm -f smoke-api smoke-pg smoke-redis 2>/dev/null || true; docker network rm smokenet 2>/dev/null || true; exit 1; fi | ||
|
|
||
| # CI=true puts the app in smoke mode (wizard bypass + forced SQLite). | ||
| # To validate the REAL Postgres path we clear CI and set IsSmokeTest=false, | ||
| # so the app runs the same configuration a production container would. | ||
| docker rm -f smoke-api 2>/dev/null || true | ||
| docker run -d --name smoke-api --network smokenet \ | ||
| -e CI=false \ | ||
| -e IsSmokeTest=false \ | ||
| -e DatabaseSettings__DatabaseProvider=postgres \ | ||
| -e 'ConnectionStrings__DefaultConnection=Host=smoke-pg;Port=5432;Database=forexbotdb;Username=forexbot;Password=ci-smoke-password;Include Error Detail=true' \ | ||
| -e ConnectionStrings__Redis=smoke-redis:6379,abortConnect=false \ | ||
| -e TelegramPanel__BotToken= \ | ||
| -e CryptoPay__ApiToken= \ | ||
| -e ASPNETCORE_URLS=http://+:8080 \ | ||
| -p 8080:8080 \ | ||
| forexbot:ci | ||
| ok=0 | ||
| for i in $(seq 1 60); do | ||
| code=$(curl -s -o /dev/null -w '%{http_code}' -m 3 http://localhost:8080/healthz || true) | ||
| if [ "$code" = "200" ]; then | ||
| echo "API /healthz returned 200 after ${i} checks" | ||
| ok=1; break | ||
| fi | ||
| sleep 5 | ||
| done | ||
| if [ "$ok" != "1" ]; then | ||
| echo "::error::API /healthz never returned 200" | ||
| docker logs smoke-api | tail -100 | ||
| docker rm -f smoke-api smoke-pg smoke-redis || true | ||
| docker network rm smokenet || true | ||
| exit 1 | ||
| fi | ||
| docker rm -f smoke-api smoke-pg smoke-redis || true | ||
| docker network rm smokenet || true | ||
|
|
||
| - name: Smoke test — SQLite variant | ||
| if: always() | ||
| run: | | ||
| set -euo pipefail | ||
| docker rm -f smoke-sqlite 2>/dev/null || true | ||
| docker run -d --name smoke-sqlite \ | ||
| -e CI=false \ | ||
| -e IsSmokeTest=false \ | ||
| -e DatabaseSettings__DatabaseProvider=sqlite \ | ||
| -e 'ConnectionStrings__DefaultConnection=Data Source=/tmp/forexbot.db' \ | ||
| -e TelegramPanel__BotToken= \ | ||
| -e CryptoPay__ApiToken= \ | ||
| -e ASPNETCORE_URLS=http://+:8080 \ | ||
| -p 8081:8080 \ | ||
| forexbot:ci | ||
| ok=0 | ||
| for i in $(seq 1 40); do | ||
| code=$(curl -s -o /dev/null -w '%{http_code}' -m 3 http://localhost:8081/healthz || true) | ||
| if [ "$code" = "200" ]; then | ||
| echo "SQLite variant /healthz returned 200 after ${i} checks" | ||
| ok=1; break | ||
| fi | ||
| sleep 5 | ||
| done | ||
| docker logs smoke-sqlite | tail -60 || true | ||
| docker rm -f smoke-sqlite || true | ||
| [ "$ok" = "1" ] || { echo "::error::SQLite variant never became healthy"; exit 1; } | ||
|
github-advanced-security[bot] marked this conversation as resolved.
Fixed
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.