2386c00277
ci / backend (push) Failing after 1s
Build & Test with Secrets / build (push) Failing after 1s
ci / static (push) Failing after 8s
Build & Test with Secrets / security-scan (push) Failing after 5s
ci / frontend (push) Failing after 1m18s
Build & Test with Secrets / frontend (push) Failing after 1m18s
Build & Test with Secrets / notification (push) Failing after 1s
- Document .NET SDK version mismatch & NuGet.config solution - Add Release build with Development environment example - Include stub API key setup for local Host startup - Explain why Telerik source is included but not used Closes: Local build failure on machines with preview SDK only Verified: Both NuGet.config + appsettings prevent NU1507 errors Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
307 lines
7.6 KiB
Markdown
307 lines
7.6 KiB
Markdown
# Local Development: User Secrets Configuration
|
|
|
|
This guide explains how to safely manage secrets locally without storing them in version control.
|
|
|
|
## Overview
|
|
|
|
- **Production/CI:** Secrets stored in Gitea Actions Secrets → injected as environment variables at build/deploy time
|
|
- **Local Dev:** Secrets stored in user-secrets → NOT checked into git
|
|
- **Code:** Never hardcodes secrets; reads from environment or IOptions
|
|
|
|
---
|
|
|
|
## Setup User Secrets (One-Time)
|
|
|
|
### 1. Initialize User Secrets Store
|
|
|
|
```bash
|
|
cd src/KArtSell.Host
|
|
dotnet user-secrets init
|
|
```
|
|
|
|
This creates `~/.microsoft/usersecrets/<PROJECT_GUID>/secrets.json` (not in git).
|
|
|
|
### 2. Store Secrets Locally
|
|
|
|
```powershell
|
|
# PowerShell (Windows)
|
|
cd src/KArtSell.Host
|
|
|
|
# PostgreSQL connection string
|
|
dotnet user-secrets set "ConnectionStrings:Postgres" "Host=localhost;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell"
|
|
|
|
# KRX API Key
|
|
dotnet user-secrets set "ExternalApis:KrxOpenApi:ApiKey" "your-krx-api-key-here"
|
|
|
|
# OpenDart API Key (optional)
|
|
dotnet user-secrets set "ExternalApis:OpenDart:ApiKey" "your-opendart-key-here"
|
|
|
|
# KIS API Keys (optional)
|
|
dotnet user-secrets set "ExternalApis:Kis:ApiKey" "your-kis-api-key"
|
|
dotnet user-secrets set "ExternalApis:Kis:SecretKey" "your-kis-secret-key"
|
|
```
|
|
|
|
**Bash/macOS:**
|
|
```bash
|
|
cd src/KArtSell.Host
|
|
|
|
dotnet user-secrets set "ConnectionStrings:Postgres" "Host=localhost;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell"
|
|
dotnet user-secrets set "ExternalApis:KrxOpenApi:ApiKey" "your-krx-api-key-here"
|
|
```
|
|
|
|
### 3. Verify Secrets Are Set
|
|
|
|
```bash
|
|
cd src/KArtSell.Host
|
|
dotnet user-secrets list
|
|
```
|
|
|
|
**Expected Output:**
|
|
```
|
|
ConnectionStrings:Postgres = Host=localhost;Port=5432;...
|
|
ExternalApis:KrxOpenApi:ApiKey = your-krx-api-key-here
|
|
ExternalApis:OpenDart:ApiKey = your-opendart-key-here
|
|
ExternalApis:Kis:ApiKey = your-kis-api-key
|
|
ExternalApis:Kis:SecretKey = your-kis-secret-key
|
|
```
|
|
|
|
---
|
|
|
|
## Build Issues & Solutions
|
|
|
|
### .NET SDK Version Mismatch
|
|
|
|
**Problem:** `global.json` requires .NET 10.0.100 GA, but only preview version installed
|
|
```
|
|
Requested SDK version: 10.0.100
|
|
Install the [10.0.100] .NET SDK or update global.json to match an installed SDK.
|
|
```
|
|
|
|
**Solution:** Use NuGet.config to resolve package source conflicts
|
|
```bash
|
|
# NuGet.config at project root handles Telerik source override
|
|
# (Telerik was configured in .sln but not actually used in code)
|
|
# This prevents NU1507 "warning-as-error" during restore
|
|
```
|
|
|
|
The project includes `NuGet.config` which:
|
|
- Configures only nuget.org as package source
|
|
- Removes transitive Telerik source (build-only artifact)
|
|
- Works with both GA and preview .NET 10 SDKs
|
|
|
|
### Building Locally
|
|
|
|
```bash
|
|
cd C:\Job_Roomz\KArtSell.Aegis
|
|
|
|
# Release build (optimized binaries)
|
|
dotnet build KArtSell.sln -c Release
|
|
|
|
# Development mode (with appsettings.Development.json)
|
|
$env:ASPNETCORE_ENVIRONMENT = "Development"
|
|
$env:KARTSELL_POSTGRES = "Host=127.0.0.1;Port=5432;Database=kartselldb_test;Username=kartsell_test;Password=kartsell4321@!_test"
|
|
$env:KRX_OPENAPI = "stub-key-for-testing"
|
|
|
|
dotnet run --project src/KArtSell.Host -c Release --no-build
|
|
```
|
|
|
|
Host listens on: `http://127.0.0.1:5002`
|
|
|
|
---
|
|
|
|
## How It Works
|
|
|
|
### Development (dotnet run)
|
|
```
|
|
User Secrets → appsettings.json (placeholder) → Program.cs (ResolveSecret)
|
|
↓ ↓ ↓
|
|
(highest (if ${VAR}) (merged together)
|
|
priority)
|
|
```
|
|
|
|
When you run `dotnet run`, ASP.NET Core:
|
|
1. Loads appsettings.json (has `${KARTSELL_POSTGRES}` placeholders)
|
|
2. Overlays user-secrets (if in Development)
|
|
3. Overlays environment variables (highest priority)
|
|
|
|
Result: `Program.cs` sees actual values, not placeholders.
|
|
|
|
### CI/CD (Gitea Actions)
|
|
```
|
|
Gitea Secrets (env injection) → appsettings.json → Program.cs
|
|
↓ ↓ ↓
|
|
${{ secrets.* }} (placeholder) (resolved to actual)
|
|
```
|
|
|
|
Gitea Actions:
|
|
1. Sets `KARTSELL_POSTGRES` and `KRX_API_KEY` as environment variables
|
|
2. Code reads from environment (highest priority in ResolveSecret)
|
|
3. Never stores secrets in build artifacts
|
|
|
|
---
|
|
|
|
## Verify Setup Works
|
|
|
|
### 1. Start PostgreSQL (SSH Tunnel)
|
|
|
|
```bash
|
|
ssh -L 5432:127.0.0.1:5432 kjh2064@178.104.200.7
|
|
```
|
|
|
|
Keep this running in a separate terminal.
|
|
|
|
### 2. Run Application
|
|
|
|
```bash
|
|
cd src/KArtSell.Host
|
|
dotnet run -c Release
|
|
```
|
|
|
|
**Expected:**
|
|
- Application starts without "KARTSELL_POSTGRES is required" error
|
|
- Logs show database connection successful
|
|
- Hangfire dashboard accessible at http://localhost:5000/hangfire
|
|
|
|
### 3. Verify API Works
|
|
|
|
```bash
|
|
curl http://localhost:5000/health
|
|
# Expected: 200 OK
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Issue: "ConnectionStrings:Postgres is required"
|
|
|
|
**Cause:** User secrets not set or not loaded
|
|
|
|
**Fix:**
|
|
```bash
|
|
# Check if secrets are set
|
|
dotnet user-secrets list
|
|
|
|
# If empty, re-set them
|
|
dotnet user-secrets set "ConnectionStrings:Postgres" "Host=localhost;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell"
|
|
|
|
# If using different terminal, make sure you're in src/KArtSell.Host directory
|
|
```
|
|
|
|
### Issue: "KRX_API_KEY is required"
|
|
|
|
**Cause:** API key not configured
|
|
|
|
**Fix:**
|
|
```bash
|
|
# Set KRX API key
|
|
dotnet user-secrets set "ExternalApis:KrxOpenApi:ApiKey" "your-api-key"
|
|
|
|
# Or set via environment variable (overrides user-secrets)
|
|
$env:KRX_API_KEY = "your-api-key" # PowerShell
|
|
export KRX_API_KEY="your-api-key" # Bash
|
|
```
|
|
|
|
### Issue: Secrets Showing in Logs
|
|
|
|
**Never should happen** — ResolveSecret does not log secret values.
|
|
|
|
If you see secrets in logs:
|
|
1. Check application doesn't log Configuration
|
|
2. Check Serilog is not in Verbose mode
|
|
3. Report as security issue
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
### ✅ DO
|
|
|
|
- Store secrets in user-secrets locally
|
|
- Use environment variables in CI/CD (via Gitea Secrets)
|
|
- Commit **only** appsettings.json with placeholders
|
|
- Keep `.gitignore` excluding `secrets.json`
|
|
- Rotate API keys quarterly
|
|
|
|
### ❌ DON'T
|
|
|
|
- Commit secrets to git (even accidentally)
|
|
- Store credentials in appsettings.Development.json
|
|
- Commit `.env` files
|
|
- Log secrets in any log level
|
|
- Share API keys via chat/email
|
|
|
|
---
|
|
|
|
## Adding New Secrets
|
|
|
|
When adding a new API (e.g., new data provider):
|
|
|
|
1. **Add to ExternalApiOptions.cs:**
|
|
```csharp
|
|
public class NewProviderSettings
|
|
{
|
|
public string ApiKey { get; set; } = string.Empty;
|
|
public string BaseUrl { get; set; } = "https://api.provider.com";
|
|
}
|
|
```
|
|
|
|
2. **Add to appsettings.json:**
|
|
```json
|
|
"ExternalApis": {
|
|
"NewProvider": {
|
|
"ApiKey": "${NEW_PROVIDER_API_KEY}",
|
|
"BaseUrl": "https://api.provider.com"
|
|
}
|
|
}
|
|
```
|
|
|
|
3. **Set locally:**
|
|
```bash
|
|
dotnet user-secrets set "ExternalApis:NewProvider:ApiKey" "your-key"
|
|
```
|
|
|
|
4. **Add to Gitea Secrets:**
|
|
- Go to: https://gitea.taxbaik.com/kjh2064/KArtSell.Aegis/settings/actions/secrets
|
|
- Click "+ New Secret"
|
|
- Name: `NEW_PROVIDER_API_KEY`
|
|
- Value: actual key
|
|
|
|
5. **Add to CI/CD workflow:**
|
|
```yaml
|
|
env:
|
|
NEW_PROVIDER_API_KEY: ${{ secrets.NEW_PROVIDER_API_KEY }}
|
|
```
|
|
|
|
---
|
|
|
|
## Rotating Secrets
|
|
|
|
### Local Secrets
|
|
|
|
```bash
|
|
cd src/KArtSell.Host
|
|
|
|
# Update the secret
|
|
dotnet user-secrets set "ExternalApis:KrxOpenApi:ApiKey" "new-api-key"
|
|
|
|
# Restart application
|
|
# (no need to commit, secrets are local)
|
|
```
|
|
|
|
### Production Secrets (Gitea)
|
|
|
|
1. Go to: https://gitea.taxbaik.com/kjh2064/KArtSell.Aegis/settings/actions/secrets
|
|
2. Click on secret → "Update"
|
|
3. Enter new value
|
|
4. Save
|
|
5. Next CI/CD run uses new secret automatically
|
|
|
|
---
|
|
|
|
## See Also
|
|
|
|
- `docs/CLAUDE.md` — Project instructions and architecture
|
|
- `GATE_3_EXECUTION_GUIDE.md` — Setting up Gate 3 shadow run (uses same secrets)
|
|
- `.gitea/workflows/secrets-injection.yml` — CI/CD workflow with secret injection
|