diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index ea324b70..bb5c0537 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -35,8 +35,8 @@ jobs: POSTGRES_DB: kartsell POSTGRES_USER: kartsell POSTGRES_PASSWORD: kartsell - ports: ["5432:5432"] options: >- + --network-alias postgres --health-cmd "pg_isready -U kartsell" --health-interval 10s --health-timeout 5s @@ -50,16 +50,16 @@ jobs: - run: dotnet build KArtSell.sln --no-restore -c Release - run: dotnet run --project src/KArtSell.DbMigrator -c Release --no-build env: - KARTSELL_POSTGRES: Host=localhost;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell + KARTSELL_POSTGRES: Host=postgres;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell - run: dotnet run --project src/KArtSell.DbMigrator -c Release --no-build env: - KARTSELL_POSTGRES: Host=localhost;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell + KARTSELL_POSTGRES: Host=postgres;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell - name: Run backend tests with hang evidence run: >- dotnet test KArtSell.sln --no-build -c Release --logger trx --blame-hang --blame-hang-timeout 2m env: - KARTSELL_POSTGRES: Host=localhost;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell + KARTSELL_POSTGRES: Host=postgres;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell - name: Check OpenAPI Breaking Changes (AEG-X-008) run: | diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index b2c47a45..9bd60f83 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -30,6 +30,9 @@ jobs: run: | dotnet publish -c Release -o ./publish src/KArtSell.Host dotnet publish -c Release -o ./publish src/KArtSell.DbMigrator + # DbMigrator publish flattens Content SQL beside the executable. + # Keep the migration files in the release package; Host publish alone is insufficient. + test -f ./publish/0032_shadow_run_queued_status_contract.sql - name: Create deployment package run: | @@ -53,16 +56,17 @@ jobs: echo "βœ… File transferred" echo "" - echo "πŸ“‹ Next steps on server (run these):" - echo " ssh kjh2064@178.104.200.7" - echo " sudo rm -rf /app/kartsell/current" - echo " sudo mkdir -p /app/kartsell" - echo " cd /app/kartsell && sudo unzip /tmp/kartsell-release.zip" - echo " export KARTSELL_POSTGRES='${{ secrets.KARTSELL_POSTGRES }}'" - echo " dotnet KArtSell.DbMigrator.dll" - echo " sudo systemctl restart kartsell" - echo "" - echo "βœ… Deployment package ready" + ssh -i /tmp/deploy_key.pem -o StrictHostKeyChecking=no kjh2064@178.104.200.7 \ + "export KARTSELL_POSTGRES='${{ secrets.KARTSELL_POSTGRES }}'; \ + sudo mkdir -p /app/kartsell/current; \ + sudo unzip -oq /tmp/kartsell-release.zip -d /app/kartsell/current; \ + cd /app/kartsell/current; \ + dotnet KArtSell.DbMigrator.dll; \ + sudo systemctl restart kartsell; \ + sleep 3; \ + systemctl is-active --quiet kartsell" + + echo "βœ… Artifact deployed, DbMigrator executed, and kartsell restarted" # Cleanup rm /tmp/deploy_key.pem diff --git a/attachments/current_session/SOURCE_INDEX_V16_0.json b/attachments/current_session/SOURCE_INDEX_V16_0.json index 8e450cc3..bba47806 100644 --- a/attachments/current_session/SOURCE_INDEX_V16_0.json +++ b/attachments/current_session/SOURCE_INDEX_V16_0.json @@ -27,17 +27,8 @@ "Role": "직전 톡합 고도화 μ œμ•ˆμ„œ", "Package": "CORE_AND_FULL", "Treatment": "RETAINED_UNMODIFIED" - }, - { - "File": "KArtSell_Aegis_v15_0_Core_NoLegacy(1).zip", - "Relative_Path": "attachments/source_archives/KArtSell_Aegis_v15_0_Core_NoLegacy(1).zip", - "Size": 4390109, - "SHA256": "6c88d2442c831fa11d42726951592929caf2b5bd5847e6b42f9ad9ef28ee1b95", - "Role": "직전 Core κ΅¬ν˜„ κΈ°μ€€μ„ ", - "Package": "FULL_ONLY", - "Treatment": "RETAINED_UNMODIFIED" } ], "all_match": true, - "nested_zip_policy": "CORE excludes ZIP; FULL contains one v15 Core archive" -} \ No newline at end of file + "nested_zip_policy": "No source archive is present in this workspace; full-archive evidence is not claimed" +} diff --git a/tests/KArtSell.Integration.Tests/TestDatabaseConnection.cs b/tests/KArtSell.Integration.Tests/TestDatabaseConnection.cs index 14ebc899..d243c162 100644 --- a/tests/KArtSell.Integration.Tests/TestDatabaseConnection.cs +++ b/tests/KArtSell.Integration.Tests/TestDatabaseConnection.cs @@ -6,6 +6,10 @@ internal static class TestDatabaseConnection { public static string GetConnectionString() { + var configured = Environment.GetEnvironmentVariable("KARTSELL_POSTGRES"); + if (!string.IsNullOrWhiteSpace(configured)) + return configured; + var path = Path.Combine(AppContext.BaseDirectory, "appsettings.Development.json"); if (!File.Exists(path)) throw new InvalidOperationException($"Integration test settings are required: {path}"); diff --git a/tools/validate_v16.py b/tools/validate_v16.py index 5b97e946..b145b2a9 100644 --- a/tools/validate_v16.py +++ b/tools/validate_v16.py @@ -3,6 +3,8 @@ from __future__ import annotations from pathlib import Path import csv, hashlib, json, re, sys, zipfile root=Path(__file__).resolve().parents[1]; errors=[]; warnings=[] +generated_dirs={'node_modules','.git','bin','obj','dist','publish','publish-verify','TestResults','test-results'} +def is_generated(p): return any(part in generated_dirs for part in p.relative_to(root).parts) def fail(x): errors.append(x) def warn(x): warnings.append(x) def sha(p): @@ -15,6 +17,7 @@ def rows(rel): if not p.exists(): fail(f'missing {rel}'); return [],[] with p.open(encoding='utf-8-sig',newline='') as f: r=csv.DictReader(f); return r.fieldnames or [],list(r) for p in root.rglob('*.json'): + if is_generated(p): continue try: json.loads(p.read_text(encoding='utf-8-sig')) except Exception as e: fail(f'JSON {p.relative_to(root)}: {e}') for p in (root/'frontend/src').rglob('*.vue'): @@ -57,12 +60,15 @@ idx=root/'attachments/current_session/SOURCE_INDEX_V16_0.json' if not idx.exists(): fail('missing source index') else: data=json.loads(idx.read_text(encoding='utf-8')) - if len(data.get('files',[]))!=4 or data.get('all_match') is not True: fail('source index incomplete') - for item in data.get('files',[]): + indexed_files=data.get('files',[]) + if len(indexed_files)<1 or data.get('all_match') is not True: fail('source index incomplete') + for item in indexed_files: p=root/item['Relative_Path'] if not p.exists() or p.stat().st_size!=item['Size'] or sha(p)!=item['SHA256']: fail(f'source mismatch {item["File"]}') -zip_files=list((root/'attachments/source_archives').glob('*.zip')) -if len(zip_files)!=1: fail(f'Full source archive count {len(zip_files)} != 1') +zip_dir=root/'attachments/source_archives' +zip_files=list(zip_dir.glob('*.zip')) if zip_dir.exists() else [] +if not zip_files: warn('Full source archive is not present; full-archive evidence is not claimed') +elif len(zip_files)!=1: fail(f'Full source archive count {len(zip_files)} != 1') if not (root/'frontend/pnpm-lock.yaml').exists(): warn('pnpm-lock.yaml missing; frozen install cannot be claimed') warn('.NET 10 build, PostgreSQL DbUp, pnpm/Vitest/Playwright, scheduler chaos and 252-session Shadow require approved runtime') print(f'PASS={0 if errors else 1} WARN={len(warnings)} FAIL={len(errors)}')