Files
KArtSell.Aegis/docs/SECRETS_LOCAL_DEVELOPMENT.md
kjh2064 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
docs: Add NuGet.config setup and local build instructions (AGENTS.md §6)
- 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>
2026-08-03 13:01:44 +09:00

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:

  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)

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:

  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:

    public class NewProviderSettings
    {
        public string ApiKey { get; set; } = string.Empty;
        public string BaseUrl { get; set; } = "https://api.provider.com";
    }
    
  2. Add to appsettings.json:

    "ExternalApis": {
      "NewProvider": {
        "ApiKey": "${NEW_PROVIDER_API_KEY}",
        "BaseUrl": "https://api.provider.com"
      }
    }
    
  3. Set locally:

    dotnet user-secrets set "ExternalApis:NewProvider:ApiKey" "your-key"
    
  4. Add to Gitea Secrets:

  5. 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)

  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