- 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>
7.6 KiB
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
cd src/KArtSell.Host
dotnet user-secrets init
This creates ~/.microsoft/usersecrets/<PROJECT_GUID>/secrets.json (not in git).
2. Store Secrets Locally
# 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:
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
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
# 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
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:
- Loads appsettings.json (has
${KARTSELL_POSTGRES}placeholders) - Overlays user-secrets (if in Development)
- 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:
- Sets
KARTSELL_POSTGRESandKRX_API_KEYas environment variables - Code reads from environment (highest priority in ResolveSecret)
- Never stores secrets in build artifacts
Verify Setup Works
1. Start PostgreSQL (SSH Tunnel)
ssh -L 5432:127.0.0.1:5432 kjh2064@178.104.200.7
Keep this running in a separate terminal.
2. Run Application
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
curl http://localhost:5000/health
# Expected: 200 OK
Troubleshooting
Issue: "ConnectionStrings:Postgres is required"
Cause: User secrets not set or not loaded
Fix:
# 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:
# 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:
- Check application doesn't log Configuration
- Check Serilog is not in Verbose mode
- 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
.gitignoreexcludingsecrets.json - Rotate API keys quarterly
❌ DON'T
- Commit secrets to git (even accidentally)
- Store credentials in appsettings.Development.json
- Commit
.envfiles - 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):
-
Add to ExternalApiOptions.cs:
public class NewProviderSettings { public string ApiKey { get; set; } = string.Empty; public string BaseUrl { get; set; } = "https://api.provider.com"; } -
Add to appsettings.json:
"ExternalApis": { "NewProvider": { "ApiKey": "${NEW_PROVIDER_API_KEY}", "BaseUrl": "https://api.provider.com" } } -
Set locally:
dotnet user-secrets set "ExternalApis:NewProvider:ApiKey" "your-key" -
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
-
Add to CI/CD workflow:
env: NEW_PROVIDER_API_KEY: ${{ secrets.NEW_PROVIDER_API_KEY }}
Rotating Secrets
Local Secrets
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)
- Go to: https://gitea.taxbaik.com/kjh2064/KArtSell.Aegis/settings/actions/secrets
- Click on secret → "Update"
- Enter new value
- Save
- Next CI/CD run uses new secret automatically
See Also
docs/CLAUDE.md— Project instructions and architectureGATE_3_EXECUTION_GUIDE.md— Setting up Gate 3 shadow run (uses same secrets).gitea/workflows/secrets-injection.yml— CI/CD workflow with secret injection