docs: 클라우드 서버(hz-prod-01) 설정 하네스 가이드 신규 작성 #8
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"scriptId": "1xfeBAeeknmnBtSvrIqWXO_2hc3ByeriLUOSuOOB4YxLLHhN3zdnL7tVh",
|
|
||||||
"projectId": "1072944905499",
|
|
||||||
"rootDir": "Temp/gas_deploy"
|
|
||||||
}
|
|
||||||
@@ -74,8 +74,8 @@ jobs:
|
|||||||
- name: Backup to Cloud (Optional)
|
- name: Backup to Cloud (Optional)
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
# Synology NAS로 동기화 (설정 필요)
|
# 원격 백업 서버로 동기화 (설정 필요)
|
||||||
# rsync -av backups/ admin@SYNOLOGY_IP:/backup/data_feed/
|
# rsync -av backups/ admin@BACKUP_SERVER_IP:/backup/data_feed/
|
||||||
echo "Cloud sync would run here if configured"
|
echo "Cloud sync would run here if configured"
|
||||||
|
|
||||||
- name: Notify Completion
|
- name: Notify Completion
|
||||||
|
|||||||
@@ -8,14 +8,9 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────
|
||||||
# Synology DS216j (ARMv7l 32-bit) 환경 제약
|
|
||||||
# - Python: /usr/bin/python3 (3.8.12)
|
|
||||||
# - Node.js 18: /usr/local/bin (appstore)
|
|
||||||
# - numpy/pandas: 공식 휠 없음, gcc 미설치 → 소스 빌드 불가
|
|
||||||
#
|
|
||||||
# CI 역할: 코드 구조 검증 게이트 (순수 Python, yaml/json)
|
# CI 역할: 코드 구조 검증 게이트 (순수 Python, yaml/json)
|
||||||
# - Validate Specs / Formula Registry / Coverage / Behavioral Coverage
|
# - Validate Specs / Formula Registry / Coverage / Behavioral Coverage
|
||||||
# 통합 테스트(run_release_dag, ingest 등)는 로컬에서 실행
|
# 통합 테스트(run_release_dag, ingest 등)는 로컬 또는 클라우드 서버에서 실행
|
||||||
# ─────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -56,7 +51,7 @@ jobs:
|
|||||||
mkdir -p "$VENV_BASE"
|
mkdir -p "$VENV_BASE"
|
||||||
/usr/bin/python3 -m venv "$VENV"
|
/usr/bin/python3 -m venv "$VENV"
|
||||||
|
|
||||||
# Synology Python 3.8은 ensurepip가 없어 venv 생성 시 pip가 누락될 수 있음
|
# venv 내 pip 확인 및 복구
|
||||||
if [ ! -f "$VENV/bin/pip" ]; then
|
if [ ! -f "$VENV/bin/pip" ]; then
|
||||||
echo "pip missing in venv, installing via get-pip.py..."
|
echo "pip missing in venv, installing via get-pip.py..."
|
||||||
curl -sS https://bootstrap.pypa.io/pip/3.8/get-pip.py -o get-pip.py
|
curl -sS https://bootstrap.pypa.io/pip/3.8/get-pip.py -o get-pip.py
|
||||||
|
|||||||
+11
@@ -36,3 +36,14 @@ node_modules/
|
|||||||
.claude/projects/
|
.claude/projects/
|
||||||
*.db-shm
|
*.db-shm
|
||||||
*.db-wal
|
*.db-wal
|
||||||
|
|
||||||
|
# 개발자 임시/테스트/백업 파일 패턴 차단
|
||||||
|
**/debug_*.log
|
||||||
|
**/tmp_*.json
|
||||||
|
**/mock_*.json
|
||||||
|
**/*_temp.*
|
||||||
|
**/*.bak
|
||||||
|
**/*.swp
|
||||||
|
**/*_backup*
|
||||||
|
**/*_copy*
|
||||||
|
|
||||||
|
|||||||
@@ -81,16 +81,16 @@
|
|||||||
- `tests/parity/test_routing_gate_parity_v1.py`: routing gate parity.
|
- `tests/parity/test_routing_gate_parity_v1.py`: routing gate parity.
|
||||||
- `.gitea/workflows/qualitative_sell_strategy.yml`: qualitative sell strategy workflow.
|
- `.gitea/workflows/qualitative_sell_strategy.yml`: qualitative sell strategy workflow.
|
||||||
- `.gitea/workflows/snapshot_admin.yml`: snapshot admin workflow and scheduled validation.
|
- `.gitea/workflows/snapshot_admin.yml`: snapshot admin workflow and scheduled validation.
|
||||||
|
- `docs/CLOUD_SERVER_SETUP.md`: 클라우드 서버(hz-prod-01, 178.104.200.7) 설정 하네스 가이드. 시놀로지 → 클라우드 마이그레이션 매핑 포함.
|
||||||
- `docs/GITEA_SECRETS_SETUP.md`: Gitea secrets setup and verification guide.
|
- `docs/GITEA_SECRETS_SETUP.md`: Gitea secrets setup and verification guide.
|
||||||
- `docs/GATHERTRADINGDATA_XLSX_OPERATING_RUNBOOK.md`: `GatherTradingData.xlsx` 보조 자산 런북.
|
- `docs/GATHERTRADINGDATA_XLSX_OPERATING_RUNBOOK.md`: `GatherTradingData.xlsx` 보조 자산 런북.
|
||||||
- `docs/ROADMAP_WBS.md`: `.gs → Python` 및 `xlsx → sqlite` WBS.
|
- `docs/ROADMAP_WBS.md`: `.gs → Python` 및 `xlsx → sqlite` WBS.
|
||||||
- `docs/ROADMAP_WBS.md`의 WBS-8.2: `run_kis_data_collection_v1.py` → `validate_platform_transition_wbs_v1.py` → `validate_snapshot_admin_web_v1.py`.
|
- `docs/ROADMAP_WBS.md`의 WBS-8.2: `run_kis_data_collection_v1.py` → `validate_platform_transition_wbs_v1.py` → `validate_snapshot_admin_web_v1.py`.
|
||||||
- `Temp/snapshot_admin_approval_packet_v1.json`: snapshot admin approval packet export.
|
- `Temp/snapshot_admin_approval_packet_v1.json`: snapshot admin approval packet export.
|
||||||
- `Temp/snapshot_admin_approval_packet_v1.md`: snapshot admin approval packet summary.
|
- `Temp/snapshot_admin_approval_packet_v1.md`: snapshot admin approval packet summary.
|
||||||
- `gas_event_calendar.gs`: 이벤트 캘린더 배포 호환 스텁. `seedEventCalendar_()` / `runEventRisk()` 진입점을 유지한다.
|
|
||||||
- `Temp/`: 실행 결과와 캐시. 라우팅 대상은 아니며 runtime consumer만 읽는다.
|
- `Temp/`: 실행 결과와 캐시. 라우팅 대상은 아니며 runtime consumer만 읽는다.
|
||||||
- `DB 파일 관리`: workspace/collector DB는 단일 canonical 경로만 사용한다. 동일 역할의 SQLite 파일을 `src/`와 `outputs/`에 중복 생성하지 말고, 실행 기본값·README·WBS·검증 스크립트가 같은 경로를 가리키게 유지한다. 임시 검증 DB는 `Temp/`에만 두고, 운영 기준 DB로 승격할 때는 명시적으로 문서화한다. canonical workspace DB는 `src/quant_engine/snapshot_admin.db`이며, 다른 위치의 동일 역할 DB는 파생/아카이브/마이그레이션 전용으로만 취급한다. 운영 진입점과 일반 검증 스크립트는 canonical 파일만 읽고 써야 한다.
|
- `DB 파일 관리`: workspace/collector DB는 단일 canonical 경로만 사용한다. 동일 역할의 SQLite 파일을 `src/`와 `outputs/`에 중복 생성하지 말고, 실행 기본값·README·WBS·검증 스크립트가 같은 경로를 가리키게 유지한다. 임시 검증 DB는 `Temp/`에만 두고, 운영 기준 DB로 승격할 때는 명시적으로 문서화한다. canonical workspace DB는 `src/quant_engine/snapshot_admin.db`이며, 다른 위치의 동일 역할 DB는 파생/아카이브/마이그레이션 전용으로만 취급한다. 운영 진입점과 일반 검증 스크립트는 canonical 파일만 읽고 써야 한다.
|
||||||
- `docs/archive/`, `suggest/`, `artifacts/archive/`: 문서 검색/색인 제외 대상. 감사나 이력 추적이 필요할 때만 명시적으로 읽는다.
|
- `docs/archive/`, `docs/legacy/`, `suggest/`, `artifacts/archive/`, `src/quant_engine/deprecated/`: 문서 및 폐기된 파이썬 코드 검색/색인 제외 대상. 감사나 이력 추적이 필요할 때만 명시적으로 읽는다.
|
||||||
- `dist/`, `artifacts/`, `docs/`, `examples/`, `prompts/`, `schemas/`, `tests/`: 패키징/문서/검증/산출물 보조 경로.
|
- `dist/`, `artifacts/`, `docs/`, `examples/`, `prompts/`, `schemas/`, `tests/`: 패키징/문서/검증/산출물 보조 경로.
|
||||||
- `run_all`: 외부 스케줄러가 호출하는 진입점으로 유지한다. 실행 시 `run_all_invocation_mode=external_scheduler`를 기준으로 해석한다.
|
- `run_all`: 외부 스케줄러가 호출하는 진입점으로 유지한다. 실행 시 `run_all_invocation_mode=external_scheduler`를 기준으로 해석한다.
|
||||||
|
|
||||||
@@ -129,7 +129,8 @@
|
|||||||
- **Python 인터프리터**: Windows 로컬 환경에서는 반드시 `python`을 사용한다 (`python3` 금지).
|
- **Python 인터프리터**: Windows 로컬 환경에서는 반드시 `python`을 사용한다 (`python3` 금지).
|
||||||
- `python` → Python 3.13.5 (`Python313/`) — yaml/openpyxl/yfinance 등 프로젝트 패키지 설치됨
|
- `python` → Python 3.13.5 (`Python313/`) — yaml/openpyxl/yfinance 등 프로젝트 패키지 설치됨
|
||||||
- `python3` → Python 3.12 (Windows Store) — 프로젝트 패키지 미설치 → `ModuleNotFoundError` 유발
|
- `python3` → Python 3.12 (Windows Store) — 프로젝트 패키지 미설치 → `ModuleNotFoundError` 유발
|
||||||
- Synology CI는 `/usr/bin/python3`를 사용하므로 `.gitea/workflows/ci.yml`은 `python3` 유지
|
- 클라우드 서버(hz-prod-01)는 `/usr/bin/python3`를 사용하므로 `.gitea/workflows/ci.yml`은 `python3` 유지
|
||||||
|
- **임시 파일 관리**: 개발/디버깅 목적의 모든 휘발성 임시 파일 및 로그는 반드시 `Temp/` 디렉토리 하위에서만 생성해야 하며, 루트나 다른 패키지 경로에 임시 파일을 만드는 것은 금지한다. 불가피하게 생성할 경우 반드시 접두사/접미사 규칙(`debug_*`, `tmp_*`, `mock_*`, `*_temp.*`)을 준수하여 `.gitignore`에 필터링되도록 한다.
|
||||||
|
|
||||||
## 6. 검증 규칙
|
## 6. 검증 규칙
|
||||||
- `python tools/validate_specs.py`
|
- `python tools/validate_specs.py`
|
||||||
@@ -142,7 +143,6 @@
|
|||||||
|
|
||||||
## 6b. 추가 운영 헌법 원칙 (proposed_AGENTS_constitution_v1 반영)
|
## 6b. 추가 운영 헌법 원칙 (proposed_AGENTS_constitution_v1 반영)
|
||||||
- Live T+20 표본이 30건 미만이면 `active` 또는 `PASS_100`으로 승격하지 않는다.
|
- Live T+20 표본이 30건 미만이면 `active` 또는 `PASS_100`으로 승격하지 않는다.
|
||||||
- GAS는 투자 판단 로직을 새로 받아서는 안 된다 (thin adapter 원칙 — `ADR-0002`).
|
|
||||||
- 프롬프트가 LLM에게 가격·수량·임계값·점수를 직접 계산하도록 요청하는 것을 금지한다.
|
- 프롬프트가 LLM에게 가격·수량·임계값·점수를 직접 계산하도록 요청하는 것을 금지한다.
|
||||||
- 하네스 FAIL 상태를 실행 가능한 주문 표로 렌더링하지 않는다.
|
- 하네스 FAIL 상태를 실행 가능한 주문 표로 렌더링하지 않는다.
|
||||||
- 최종 결정 권한은 단일 캐노니컬 실행 패킷(`final_decision_packet_active.json`)에서만 나온다.
|
- 최종 결정 권한은 단일 캐노니컬 실행 패킷(`final_decision_packet_active.json`)에서만 나온다.
|
||||||
|
|||||||
@@ -0,0 +1,513 @@
|
|||||||
|
# 클라우드 서버 설정 가이드 (hz-prod-01)
|
||||||
|
|
||||||
|
> 시놀로지(Synology DSM)에서 클라우드 VPS(`178.104.200.7`)로 이전.
|
||||||
|
> 이 문서는 서버에서 실제 수집된 데이터 기반이며, 운영 하네스로 사용한다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 참조 인덱스
|
||||||
|
|
||||||
|
| # | 섹션 | 핵심 내용 |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | [서버 기본 정보](#1-서버-기본-정보) | 호스트명, IP, OS, CPU/RAM/디스크, 타임존 |
|
||||||
|
| 2 | [접속 정보](#2-접속-정보) | SSH 접속, 사용자, 인증 방식 |
|
||||||
|
| 3 | [소프트웨어 스택](#3-소프트웨어-스택) | Python, .NET, PG, Nginx, Docker Compose, fail2ban |
|
||||||
|
| 3.1 | [런타임](#31-런타임) | 버전/경로 일람 |
|
||||||
|
| 3.2 | [Python 가상 환경](#32-python-가상-환경) | `~/.venv`, `python3` 사용 규칙 |
|
||||||
|
| 3.3 | [주요 Python 패키지](#33-주요-python-패키지-시스템) | 시스템/venv 패키지 구분 |
|
||||||
|
| 4 | [서비스 아키텍처](#4-서비스-아키텍처) | 포트 맵, Nginx 리버스 프록시 |
|
||||||
|
| 4.1 | [포트 맵](#41-포트-맵) | 22, 80, 2222, 3000, 5000, 5432 |
|
||||||
|
| 4.2 | [Nginx 리버스 프록시](#42-nginx-리버스-프록시) | `/` → Gitea, `/quant/` → Blazor |
|
||||||
|
| 5 | [Gitea](#5-gitea) | Docker Compose 설정, 시크릿, 데이터 경로 |
|
||||||
|
| 5.1 | [Docker Compose](#51-docker-compose) | `gitea:1.26.4`, PG 연동 |
|
||||||
|
| 5.2 | [시크릿 관리](#52-시크릿-관리) | `/opt/stacks/gitea/.env` |
|
||||||
|
| 5.3 | [데이터](#53-데이터) | Gitea 볼륨, `giteadb` |
|
||||||
|
| 6 | [Gitea Act Runner (CI)](#6-gitea-act-runner-ci) | 6× 러너, 네트워크, 구성 디렉토리 |
|
||||||
|
| 6.1 | [컨테이너 현황](#61-컨테이너-현황) | 러너 6개 실행 상태 |
|
||||||
|
| 6.2 | [러너 설정](#62-러너-설정) | `hz-prod-runner`, `gitea_default` 네트워크 |
|
||||||
|
| 6.3 | [러너 구성 디렉토리](#63-러너-구성-디렉토리) | `~/gitea-runner[-N]/` |
|
||||||
|
| 7 | [QuantEngine Blazor Admin](#7-quantengine-blazor-admin) | systemd, symlink 배포, DLL 구성 |
|
||||||
|
| 7.1 | [systemd 서비스](#71-systemd-서비스) | `quantengine.service` 전문 |
|
||||||
|
| 7.2 | [배포 구조](#72-배포-구조) | 타임스탬프 디렉토리 + symlink 교체 |
|
||||||
|
| 7.3 | [주요 DLL](#73-주요-dll) | Web, Core, Infrastructure, MudBlazor, Dapper |
|
||||||
|
| 8 | [PostgreSQL 18](#8-postgresql-18) | v18.4, `localhost` 바인드, Docker 연동 |
|
||||||
|
| 9 | [보안](#9-보안) | SSH hardening, UFW, fail2ban, 네트워크 격리 |
|
||||||
|
| 9.1 | [SSH 보안 설정](#91-ssh-보안-설정) | 공개키 전용, root 차단 |
|
||||||
|
| 9.2 | [UFW 방화벽](#92-ufw-방화벽) | `ENABLED=yes`, 포트 개방/차단 |
|
||||||
|
| 9.3 | [fail2ban](#93-fail2ban) | SSH 브루트포스 방어 |
|
||||||
|
| 9.4 | [Docker 네트워크 격리](#94-docker-네트워크-격리) | 로컬바인드 정책 |
|
||||||
|
| 10 | [디렉토리 맵](#10-디렉토리-맵) | `/home/kjh2064/`, `/opt/stacks/`, `/opt/backups/` |
|
||||||
|
| 11 | [시놀로지 → 클라우드 마이그레이션 매핑](#11-시놀로지--클라우드-마이그레이션-매핑) | 항목별 구↔신 비교표 |
|
||||||
|
| 12 | [운영 명령 치트시트](#12-운영-명령-치트시트) | 서비스 관리, 배포, 러너 등록, SSH |
|
||||||
|
| 13 | [검증 하네스](#13-검증-하네스) | 헬스체크, 엔드포인트, 마이그레이션 체크리스트 |
|
||||||
|
|
||||||
|
### 관련 문서 상호 참조
|
||||||
|
|
||||||
|
| 문서 | 역할 |
|
||||||
|
|---|---|
|
||||||
|
| [`AGENTS.md`](../AGENTS.md) | 운영 헌법, Directory Routing 인덱스 |
|
||||||
|
| [`GITEA_SECRETS_SETUP.md`](GITEA_SECRETS_SETUP.md) | Gitea 시크릿 설정/검증 가이드 |
|
||||||
|
| [`ROADMAP_WBS.md`](ROADMAP_WBS.md) | `.gs → Python` 및 `xlsx → sqlite` WBS |
|
||||||
|
| [`docs/GITEA_TOKEN_HOME_RUNBOOK.md`](GITEA_TOKEN_HOME_RUNBOOK.md) | Gitea 토큰 관리 런북 |
|
||||||
|
| [`spec/00_execution_contract.yaml`](../spec/00_execution_contract.yaml) | 실행 계약 원본 권위 |
|
||||||
|
| [`governance/agents_index.yaml`](../governance/agents_index.yaml) | 거버넌스 규칙 인덱스 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 서버 기본 정보
|
||||||
|
|
||||||
|
| 항목 | 값 |
|
||||||
|
|---|---|
|
||||||
|
| **호스트명** | `hz-prod-01` |
|
||||||
|
| **IP** | `178.104.200.7` |
|
||||||
|
| **OS** | Ubuntu 26.04 LTS (Resolute Raccoon) |
|
||||||
|
| **커널** | `7.0.0-22-generic` (x86_64, PREEMPT_DYNAMIC) |
|
||||||
|
| **CPU** | AMD EPYC-Rome, 2 vCPU |
|
||||||
|
| **메모리** | 3.7 GiB (사용 ~958 MiB, 가용 ~2.8 GiB) |
|
||||||
|
| **스왑** | 2.0 GiB |
|
||||||
|
| **디스크** | `/dev/sda1` 38 GB (사용 8.5 GB / 28 GB 가용, 24%) |
|
||||||
|
| **타임존** | `Asia/Seoul` (KST, +0900), NTP 동기화 활성 |
|
||||||
|
|
||||||
|
## 2. 접속 정보
|
||||||
|
|
||||||
|
| 항목 | 값 |
|
||||||
|
|---|---|
|
||||||
|
| **SSH 접속** | `ssh kjh2064@178.104.200.7` |
|
||||||
|
| **SSH 포트** | 22 (기본) |
|
||||||
|
| **사용자** | `kjh2064` (uid=1000) |
|
||||||
|
| **그룹** | `kjh2064`, `sudo`, `users`, `docker` |
|
||||||
|
| **인증 방식** | 공개키 전용 (`PasswordAuthentication no`) |
|
||||||
|
| **Root 로그인** | 비활성 (`PermitRootLogin no`) |
|
||||||
|
| **Max Auth Tries** | 3 |
|
||||||
|
| **Keep-Alive** | `ClientAliveInterval 300`, `ClientAliveCountMax 2` |
|
||||||
|
|
||||||
|
## 3. 소프트웨어 스택
|
||||||
|
|
||||||
|
### 3.1. 런타임
|
||||||
|
|
||||||
|
| 소프트웨어 | 버전 | 경로 |
|
||||||
|
|---|---|---|
|
||||||
|
| **Python** | 3.14.4 | `/usr/bin/python3` |
|
||||||
|
| **.NET SDK** | 10.0.109 | `/usr/lib/dotnet/sdk` |
|
||||||
|
| **.NET Runtime** | ASP.NET Core 10.0.9 + NETCore 10.0.9 | `/usr/lib/dotnet/shared/` |
|
||||||
|
| **PostgreSQL** | 18.4 | `postgresql@18-main.service` |
|
||||||
|
| **Nginx** | 시스템 패키지 | `nginx.service` |
|
||||||
|
| **Docker Compose** | v5.2.0 | Docker 플러그인 |
|
||||||
|
| **fail2ban** | 1.1.0 | `fail2ban.service` |
|
||||||
|
|
||||||
|
### 3.2. Python 가상 환경
|
||||||
|
|
||||||
|
```
|
||||||
|
경로: ~/.venv
|
||||||
|
Python: 3.14.4
|
||||||
|
```
|
||||||
|
|
||||||
|
> **주의**: 이 서버에서는 `python3`을 사용한다 (시놀로지/Windows와 다름).
|
||||||
|
> CI 워크플로우와 로컬 서버 모두 `python3`을 사용하므로 통일됨.
|
||||||
|
|
||||||
|
### 3.3. 주요 Python 패키지 (시스템)
|
||||||
|
|
||||||
|
boto3, cryptography, Jinja2, jsonschema, fail2ban 등 시스템 레벨로 설치됨.
|
||||||
|
프로젝트 의존성은 `~/.venv`에 별도 관리.
|
||||||
|
|
||||||
|
## 4. 서비스 아키텍처
|
||||||
|
|
||||||
|
### 4.1. 포트 맵
|
||||||
|
|
||||||
|
| 포트 | 서비스 | 바인드 | 비고 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **22** | SSH | `0.0.0.0` | 공개키 전용 |
|
||||||
|
| **80** | Nginx (리버스 프록시) | `0.0.0.0` | 외부 진입점 |
|
||||||
|
| **2222** | Gitea SSH | `0.0.0.0` | Git SSH 접속 |
|
||||||
|
| **3000** | Gitea Web | `127.0.0.1` | Nginx 프록시 경유 |
|
||||||
|
| **5000** | QuantEngine Blazor | `127.0.0.1` | Nginx `/quant/` 경유 |
|
||||||
|
| **5432** | PostgreSQL | `127.0.0.1` + `172.17.0.1` | 로컬 + Docker 네트워크 |
|
||||||
|
|
||||||
|
### 4.2. Nginx 리버스 프록시
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
# /etc/nginx/sites-enabled/gitea-ip.conf
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
listen [::]:80 default_server;
|
||||||
|
server_name _;
|
||||||
|
client_max_body_size 512M;
|
||||||
|
|
||||||
|
# QuantEngine Blazor Web App
|
||||||
|
location /quant/ {
|
||||||
|
proxy_pass http://127.0.0.1:5000/;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "Upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Gitea (기본)
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_read_timeout 300;
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
proxy_send_timeout 300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**라우팅 요약**:
|
||||||
|
- `http://178.104.200.7/` → Gitea Web UI
|
||||||
|
- `http://178.104.200.7/quant/` → QuantEngine Blazor Admin
|
||||||
|
- `ssh://178.104.200.7:2222` → Gitea Git SSH
|
||||||
|
|
||||||
|
## 5. Gitea
|
||||||
|
|
||||||
|
### 5.1. Docker Compose
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# /opt/stacks/gitea/docker-compose.yml
|
||||||
|
services:
|
||||||
|
gitea:
|
||||||
|
image: docker.gitea.com/gitea:1.26.4
|
||||||
|
container_name: gitea
|
||||||
|
restart: unless-stopped
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
|
environment:
|
||||||
|
USER_UID: "1000"
|
||||||
|
USER_GID: "1000"
|
||||||
|
GITEA__database__DB_TYPE: postgres
|
||||||
|
GITEA__database__HOST: host.docker.internal:5432
|
||||||
|
GITEA__database__NAME: giteadb
|
||||||
|
GITEA__database__USER: gitea
|
||||||
|
GITEA__database__PASSWD: "${GITEA_DB_PASSWORD}"
|
||||||
|
GITEA__server__DOMAIN: "${SERVER_IP}"
|
||||||
|
GITEA__server__ROOT_URL: "http://${SERVER_IP}/"
|
||||||
|
GITEA__server__SSH_DOMAIN: "${SERVER_IP}"
|
||||||
|
GITEA__server__SSH_PORT: "2222"
|
||||||
|
GITEA__security__INSTALL_LOCK: "true"
|
||||||
|
GITEA__service__DISABLE_REGISTRATION: "true"
|
||||||
|
volumes:
|
||||||
|
- ./gitea:/data
|
||||||
|
- /etc/timezone:/etc/timezone:ro
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:3000:3000"
|
||||||
|
- "2222:22"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2. 시크릿 관리
|
||||||
|
|
||||||
|
- `.env` 파일: `/opt/stacks/gitea/.env` (소유자 전용, `600`)
|
||||||
|
- 포함 변수: `GITEA_DB_PASSWORD`, `SERVER_IP`
|
||||||
|
|
||||||
|
### 5.3. 데이터
|
||||||
|
|
||||||
|
- Gitea 데이터: `/opt/stacks/gitea/gitea/`
|
||||||
|
- DB: PostgreSQL `giteadb` (Docker → host.docker.internal:5432 경유)
|
||||||
|
|
||||||
|
## 6. Gitea Act Runner (CI)
|
||||||
|
|
||||||
|
### 6.1. 컨테이너 현황
|
||||||
|
|
||||||
|
| 이름 | 이미지 | 상태 |
|
||||||
|
|---|---|---|
|
||||||
|
| `gitea-runner` | `gitea/act_runner:latest` | 실행 중 |
|
||||||
|
| `gitea-runner-2` | `gitea/act_runner:latest` | 실행 중 |
|
||||||
|
| `gitea-runner-3` | `gitea/act_runner:latest` | 실행 중 |
|
||||||
|
| `hopeful_galileo` | `gitea/act_runner:latest` | 실행 중 |
|
||||||
|
| `jovial_bouman` | `gitea/act_runner:latest` | 실행 중 |
|
||||||
|
| `upbeat_chatelet` | `gitea/act_runner:latest` | 실행 중 |
|
||||||
|
|
||||||
|
> 총 6개 러너가 활성 상태. 네트워크는 `gitea_default` Docker 네트워크 사용.
|
||||||
|
|
||||||
|
### 6.2. 러너 설정
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# ~/gitea-runner/config.yaml
|
||||||
|
container:
|
||||||
|
network: "gitea_default"
|
||||||
|
```
|
||||||
|
|
||||||
|
- 러너 이름: `hz-prod-runner`
|
||||||
|
- 러너 UUID: `d6d9120b-5070-4874-88d7-b86fe817d5a0`
|
||||||
|
- 러너 이미지: `docker.gitea.com/runner-images:ubuntu-latest` (2.33 GB)
|
||||||
|
|
||||||
|
### 6.3. 러너 구성 디렉토리
|
||||||
|
|
||||||
|
```
|
||||||
|
~/gitea-runner/ # 1번 러너
|
||||||
|
~/gitea-runner-2/ # 2번 러너
|
||||||
|
~/gitea-runner-3/ # 3번 러너
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. QuantEngine Blazor Admin
|
||||||
|
|
||||||
|
### 7.1. systemd 서비스
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# /etc/systemd/system/quantengine.service
|
||||||
|
[Unit]
|
||||||
|
Description=Quant Engine Blazor Admin Web App (.NET 10)
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
WorkingDirectory=/home/kjh2064/quantengine_active
|
||||||
|
ExecStart=/usr/bin/dotnet /home/kjh2064/quantengine_active/QuantEngine.Web.dll
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
KillSignal=SIGINT
|
||||||
|
SyslogIdentifier=quantengine
|
||||||
|
User=kjh2064
|
||||||
|
Environment=ASPNETCORE_ENVIRONMENT=Production
|
||||||
|
Environment=ASPNETCORE_URLS=http://127.0.0.1:5000
|
||||||
|
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2. 배포 구조
|
||||||
|
|
||||||
|
```
|
||||||
|
~/quantengine_active → ~/deployments/quantengine_20260625_182821 (symlink)
|
||||||
|
~/deployments/
|
||||||
|
├── quantengine_20260625_155649/
|
||||||
|
├── quantengine_20260625_164548/
|
||||||
|
├── quantengine_20260625_164928/
|
||||||
|
└── quantengine_20260625_182821/ ← 현재 활성
|
||||||
|
```
|
||||||
|
|
||||||
|
**배포 방식**: 타임스탬프 디렉토리 생성 → symlink 교체 → `systemctl restart quantengine`
|
||||||
|
|
||||||
|
### 7.3. 주요 DLL
|
||||||
|
|
||||||
|
- `QuantEngine.Web.dll` — 웹 진입점
|
||||||
|
- `QuantEngine.Core.dll` — 핵심 도메인
|
||||||
|
- `QuantEngine.Application.dll` — 애플리케이션 서비스
|
||||||
|
- `QuantEngine.Infrastructure.dll` — 인프라 (DB, 외부 연동)
|
||||||
|
- `Npgsql.dll` — PostgreSQL 드라이버
|
||||||
|
- `MudBlazor.dll` — UI 컴포넌트
|
||||||
|
- `Dapper.dll` — 마이크로 ORM
|
||||||
|
|
||||||
|
## 8. PostgreSQL 18
|
||||||
|
|
||||||
|
| 항목 | 값 |
|
||||||
|
|---|---|
|
||||||
|
| **버전** | 18.4 (Ubuntu 패키지) |
|
||||||
|
| **서비스** | `postgresql@18-main.service` |
|
||||||
|
| **listen_addresses** | `localhost` (기본값, 로컬 전용) |
|
||||||
|
| **바인드** | `127.0.0.1:5432`, `172.17.0.1:5432` (Docker), `[::1]:5432` |
|
||||||
|
| **Gitea DB** | `giteadb` (사용자: `gitea`) |
|
||||||
|
|
||||||
|
> Docker 컨테이너는 `host.docker.internal:5432`로 호스트 PG에 접속.
|
||||||
|
> `listen_addresses`는 `postgresql.conf`에서 기본값 `localhost`로 설정됨 (외부 접속 차단).
|
||||||
|
|
||||||
|
## 9. 보안
|
||||||
|
|
||||||
|
### 9.1. SSH 보안 설정
|
||||||
|
|
||||||
|
```
|
||||||
|
PermitRootLogin no
|
||||||
|
PasswordAuthentication no
|
||||||
|
PubkeyAuthentication yes
|
||||||
|
KbdInteractiveAuthentication no
|
||||||
|
X11Forwarding no
|
||||||
|
MaxAuthTries 3
|
||||||
|
ClientAliveInterval 300
|
||||||
|
ClientAliveCountMax 2
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2. UFW 방화벽
|
||||||
|
|
||||||
|
- **상태**: `ENABLED=yes` (`/etc/ufw/ufw.conf`)
|
||||||
|
- **로그 레벨**: `low`
|
||||||
|
- **외부 개방 포트**: 22 (SSH), 80 (HTTP/Nginx), 2222 (Gitea SSH)
|
||||||
|
- **내부 전용**: 3000 (Gitea Web), 5000 (QuantEngine), 5432 (PostgreSQL)
|
||||||
|
|
||||||
|
> 상세 규칙 확인: `sudo ufw status numbered` (TTY + sudo 비밀번호 필요)
|
||||||
|
|
||||||
|
### 9.3. fail2ban
|
||||||
|
|
||||||
|
- `fail2ban.service` 활성 상태
|
||||||
|
- SSH 브루트포스 방어 활성
|
||||||
|
|
||||||
|
### 9.4. Docker 네트워크 격리
|
||||||
|
|
||||||
|
- Gitea Web: `127.0.0.1:3000` (로컬 전용)
|
||||||
|
- QuantEngine: `127.0.0.1:5000` (로컬 전용)
|
||||||
|
- PostgreSQL: `127.0.0.1` + Docker bridge (`172.17.0.1`)
|
||||||
|
- 외부 노출: SSH(22), HTTP(80), Gitea SSH(2222)만 개방
|
||||||
|
|
||||||
|
## 10. 디렉토리 맵
|
||||||
|
|
||||||
|
```
|
||||||
|
/home/kjh2064/
|
||||||
|
├── quantengine_active → deployments/quantengine_YYYYMMDD_HHMMSS (symlink)
|
||||||
|
├── deployments/ # QuantEngine 배포 히스토리
|
||||||
|
│ └── quantengine_YYYYMMDD_HHMMSS/
|
||||||
|
│ └── wwwroot/
|
||||||
|
├── gitea-runner/ # Gitea Act Runner 1
|
||||||
|
├── gitea-runner-2/ # Gitea Act Runner 2
|
||||||
|
├── gitea-runner-3/ # Gitea Act Runner 3
|
||||||
|
├── apps/ # 추가 앱
|
||||||
|
│ └── python-test/.venv/
|
||||||
|
├── .venv/ # Python 3.14 가상 환경
|
||||||
|
├── tmp/ # 임시 작업
|
||||||
|
└── .ssh/ # SSH 키
|
||||||
|
|
||||||
|
/opt/stacks/
|
||||||
|
├── gitea/
|
||||||
|
│ ├── docker-compose.yml
|
||||||
|
│ ├── .env # GITEA_DB_PASSWORD, SERVER_IP
|
||||||
|
│ └── gitea/ # Gitea 데이터 볼륨
|
||||||
|
└── dotnet-app/ # .NET 관련
|
||||||
|
|
||||||
|
/opt/backups/ # 백업
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. 시놀로지 → 클라우드 마이그레이션 매핑
|
||||||
|
|
||||||
|
| 항목 | 시놀로지 (구) | 클라우드 (신) |
|
||||||
|
|---|---|---|
|
||||||
|
| **프로젝트 경로** | `/volume1/projects/data_feed` | 미배치 (TBD) |
|
||||||
|
| **Python** | `python3` (시스템) | `python3` (`/usr/bin/python3`, 3.14.4) |
|
||||||
|
| **Gitea** | Docker on DSM | Docker on Ubuntu (`gitea:1.26.4`) |
|
||||||
|
| **Gitea SSH** | 포트 변동 | `2222` 고정 |
|
||||||
|
| **CI Runner** | Synology Act Runner | 6× `act_runner:latest` (Docker) |
|
||||||
|
| **DB** | SQLite (파일 기반) | PostgreSQL 18 + SQLite (하이브리드) |
|
||||||
|
| **웹 Admin** | 없음 | QuantEngine Blazor (.NET 10, MudBlazor) |
|
||||||
|
| **리버스 프록시** | Synology 내장 | Nginx (`/` → Gitea, `/quant/` → Blazor) |
|
||||||
|
| **보안** | DSM 방화벽 | fail2ban + SSH 공개키 + 서비스 로컬바인드 |
|
||||||
|
| **시크릿 관리** | `.secrets/kis_real.env` | `/opt/stacks/gitea/.env` |
|
||||||
|
| **OS** | Synology DSM 7.x | Ubuntu 26.04 LTS |
|
||||||
|
| **타임존** | (설정 의존) | `Asia/Seoul` (NTP 동기화) |
|
||||||
|
|
||||||
|
## 12. 운영 명령 치트시트
|
||||||
|
|
||||||
|
### 서비스 관리
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# QuantEngine
|
||||||
|
sudo systemctl status quantengine
|
||||||
|
sudo systemctl restart quantengine
|
||||||
|
sudo journalctl -u quantengine -f
|
||||||
|
|
||||||
|
# Gitea
|
||||||
|
cd /opt/stacks/gitea && docker compose up -d
|
||||||
|
docker compose logs -f gitea
|
||||||
|
|
||||||
|
# Nginx
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
sudo nginx -t
|
||||||
|
|
||||||
|
# PostgreSQL
|
||||||
|
sudo systemctl status postgresql@18-main
|
||||||
|
sudo -u postgres psql
|
||||||
|
|
||||||
|
# Docker 전체 상태
|
||||||
|
docker ps -a
|
||||||
|
```
|
||||||
|
|
||||||
|
### QuantEngine 배포
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 새 배포 디렉토리 생성
|
||||||
|
DEPLOY_DIR=~/deployments/quantengine_$(date +%Y%m%d_%H%M%S)
|
||||||
|
mkdir -p "$DEPLOY_DIR"
|
||||||
|
|
||||||
|
# 2. 빌드 산출물 복사 (로컬에서 scp 또는 CI에서)
|
||||||
|
scp -r publish/* kjh2064@178.104.200.7:"$DEPLOY_DIR"/
|
||||||
|
|
||||||
|
# 3. symlink 교체
|
||||||
|
ln -sfn "$DEPLOY_DIR" ~/quantengine_active
|
||||||
|
|
||||||
|
# 4. 서비스 재시작
|
||||||
|
sudo systemctl restart quantengine
|
||||||
|
sudo systemctl status quantengine
|
||||||
|
```
|
||||||
|
|
||||||
|
### Gitea Act Runner 등록
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 새 러너 등록 (Gitea 웹 → Settings → Actions → Runners에서 토큰 복사)
|
||||||
|
docker run -d \
|
||||||
|
--name gitea-runner-N \
|
||||||
|
--restart unless-stopped \
|
||||||
|
--network gitea_default \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
gitea/act_runner:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### SSH 접속
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Windows 로컬에서
|
||||||
|
ssh kjh2064@178.104.200.7
|
||||||
|
|
||||||
|
# Gitea Git 접속
|
||||||
|
git remote set-url origin ssh://git@178.104.200.7:2222/kjh2064/QuantEngineByItz.git
|
||||||
|
```
|
||||||
|
|
||||||
|
## 13. 검증 하네스
|
||||||
|
|
||||||
|
### 13.1. 서버 헬스 체크
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh kjh2064@178.104.200.7 "
|
||||||
|
echo '=== Services ==='
|
||||||
|
systemctl is-active quantengine nginx docker postgresql@18-main fail2ban
|
||||||
|
echo '=== Docker ==='
|
||||||
|
docker ps --format '{{.Names}}: {{.Status}}'
|
||||||
|
echo '=== Disk ==='
|
||||||
|
df -h /
|
||||||
|
echo '=== Memory ==='
|
||||||
|
free -h | head -2
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
**기대 결과**:
|
||||||
|
- 5개 서비스 모두 `active`
|
||||||
|
- Docker 컨테이너 7개 (gitea + runner ×6) `Up`
|
||||||
|
- 디스크 사용률 < 80%
|
||||||
|
- 메모리 가용 > 1 GiB
|
||||||
|
|
||||||
|
### 13.2. 엔드포인트 접근 확인
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Gitea Web
|
||||||
|
curl -s -o /dev/null -w "%{http_code}" http://178.104.200.7/
|
||||||
|
# 기대: 200
|
||||||
|
|
||||||
|
# QuantEngine
|
||||||
|
curl -s -o /dev/null -w "%{http_code}" http://178.104.200.7/quant/
|
||||||
|
# 기대: 200
|
||||||
|
|
||||||
|
# Gitea SSH
|
||||||
|
ssh -T -p 2222 git@178.104.200.7 2>&1 | head -1
|
||||||
|
# 기대: "Hi there, ..." Gitea 응답
|
||||||
|
```
|
||||||
|
|
||||||
|
### 13.3. data_feed 프로젝트 마이그레이션 체크리스트
|
||||||
|
|
||||||
|
- [ ] 프로젝트 경로 결정 및 clone
|
||||||
|
- [ ] Python venv에 프로젝트 의존성 설치 (`pip install -r requirements.txt`)
|
||||||
|
- [ ] KIS 시크릿 설정 (`~/.secrets/kis_real.env`)
|
||||||
|
- [ ] crontab 또는 systemd timer 등록
|
||||||
|
- [ ] `GatherTradingData.json` 동기화 경로 확정
|
||||||
|
- [ ] SQLite canonical DB 경로 확정
|
||||||
|
- [ ] CI 워크플로우 러너 라벨 확인
|
||||||
|
- [ ] GAS 배포 스크립트 서버 경로 업데이트
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> **수집 일시**: 2026-06-26 09:55 KST
|
||||||
|
> **수집 방법**: `ssh kjh2064@178.104.200.7` 라이브 명령 실행
|
||||||
|
> **provenance**: 모든 값은 서버 실시간 명령 출력에서 추출. 임의 값 없음.
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
# Synology Act Runner Split PR Body
|
|
||||||
|
|
||||||
## Title
|
|
||||||
|
|
||||||
`chore: split Synology act_runner start and re-registration scripts`
|
|
||||||
|
|
||||||
## Body
|
|
||||||
|
|
||||||
- Added `tools/re_register_act_runner_synology.sh` for explicit host-mode re-registration.
|
|
||||||
- Added `tools/start_act_runner_synology.sh` for boot-time daemon start only.
|
|
||||||
- Kept `tools/setup_act_runner.sh` as the bootstrap path, but made the re-registration flow explicit and repeatable.
|
|
||||||
- Switched the runner registration labels to `self-hosted:host,snapshot-admin-host:host` so the job runs in host mode instead of Docker job containers and can be targeted by a dedicated deployment label.
|
|
||||||
- Updated `docs/SYNOLOGY_SNAPSHOT_ADMIN_POC.md` and `docs/ROADMAP_WBS.md` so the operator flow and WBS notes match the new runner split.
|
|
||||||
- The `snapshot_admin.yml` workflow is split into push smoke validation and manual full validation, which reduces routine CI cost while preserving the full web smoke path on demand.
|
|
||||||
- The deploy workflow now waits for `127.0.0.1:8787/api/state` readiness before asserting success, so startup latency does not fail the run spuriously.
|
|
||||||
- The `ci.yml` workflow now keeps `push` traffic on the core gate only, with UI/storage validation retained for non-push events.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
- `python tools/validate_snapshot_admin_workflow_v1.py`
|
|
||||||
- `python -c "import yaml, pathlib; yaml.safe_load(pathlib.Path('.gitea/workflows/snapshot_admin.yml').read_text(encoding='utf-8'))"`
|
|
||||||
- `git diff -- .gitea/workflows/snapshot_admin.yml tools/setup_act_runner.sh docs/SYNOLOGY_SNAPSHOT_ADMIN_POC.md docs/ROADMAP_WBS.md`
|
|
||||||
- Deploy job evidence:
|
|
||||||
- `healthcheck` retried after startup and passed
|
|
||||||
- `snapshot-admin-web-v6` returned from the verification step
|
|
||||||
- `Job succeeded`
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
# Synology KIS Data Collection Setup
|
|
||||||
|
|
||||||
This note answers how to run:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$env:KIS_APP_Key="..."
|
|
||||||
$env:KIS_APP_Secret="..."
|
|
||||||
python tools/run_kis_data_collection_v1.py --input-json GatherTradingData.json --sqlite-db src/quant_engine/kis_data_collection.db --output-json Temp/kis_data_collection_v1.json --kis-account real
|
|
||||||
```
|
|
||||||
|
|
||||||
on Synology DSM.
|
|
||||||
|
|
||||||
## Rule
|
|
||||||
|
|
||||||
Synology is Linux-based, so use `export` or a sourced env file. Do not use Windows `$env:` syntax.
|
|
||||||
|
|
||||||
The code reads these exact, case-sensitive names for real accounts:
|
|
||||||
|
|
||||||
- `KIS_APP_Key`
|
|
||||||
- `KIS_APP_Secret`
|
|
||||||
|
|
||||||
For mock accounts, the names are:
|
|
||||||
|
|
||||||
- `KIS_APP_Key_TEST`
|
|
||||||
- `KIS_APP_Secret_TEST`
|
|
||||||
|
|
||||||
## Recommended DSM Task Scheduler script
|
|
||||||
|
|
||||||
Create a `User-defined script` task and run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/bin/sh
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
ROOT_DIR="/volume1/projects/data_feed"
|
|
||||||
|
|
||||||
export KIS_APP_Key="your_real_app_key"
|
|
||||||
export KIS_APP_Secret="your_real_app_secret"
|
|
||||||
|
|
||||||
cd "$ROOT_DIR"
|
|
||||||
python tools/run_kis_data_collection_v1.py \
|
|
||||||
--input-json GatherTradingData.json \
|
|
||||||
--sqlite-db src/quant_engine/kis_data_collection.db \
|
|
||||||
--output-json Temp/kis_data_collection_v1.json \
|
|
||||||
--kis-account real
|
|
||||||
```
|
|
||||||
|
|
||||||
## Better practice for secrets
|
|
||||||
|
|
||||||
Store secrets in a private env file and source it from the task:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
set -eu
|
|
||||||
ROOT_DIR="/volume1/projects/data_feed"
|
|
||||||
SECRETS_FILE="/volume1/projects/data_feed/.secrets/kis_real.env"
|
|
||||||
|
|
||||||
. "$SECRETS_FILE"
|
|
||||||
cd "$ROOT_DIR"
|
|
||||||
python tools/run_kis_data_collection_v1.py \
|
|
||||||
--input-json GatherTradingData.json \
|
|
||||||
--sqlite-db src/quant_engine/kis_data_collection.db \
|
|
||||||
--output-json Temp/kis_data_collection_v1.json \
|
|
||||||
--kis-account real
|
|
||||||
```
|
|
||||||
|
|
||||||
Suggested file permissions:
|
|
||||||
|
|
||||||
- owner-only read/write
|
|
||||||
- no shared group access
|
|
||||||
- no commit to git
|
|
||||||
|
|
||||||
## Mock account variant
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export KIS_APP_Key_TEST="your_mock_app_key"
|
|
||||||
export KIS_APP_Secret_TEST="your_mock_app_secret"
|
|
||||||
python tools/run_kis_data_collection_v1.py \
|
|
||||||
--input-json GatherTradingData.json \
|
|
||||||
--sqlite-db src/quant_engine/kis_data_collection.db \
|
|
||||||
--output-json Temp/kis_data_collection_v1.json \
|
|
||||||
--kis-account mock \
|
|
||||||
--no-live-kis
|
|
||||||
```
|
|
||||||
|
|
||||||
## What the collector writes
|
|
||||||
|
|
||||||
- SQLite: `src/quant_engine/kis_data_collection.db`
|
|
||||||
- JSON summary: `Temp/kis_data_collection_v1.json`
|
|
||||||
|
|
||||||
The latest collected summary in this workspace shows:
|
|
||||||
|
|
||||||
- `row_count = 25`
|
|
||||||
- `kis_open_api = 21`
|
|
||||||
- `gathertradingdata_json = 25`
|
|
||||||
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# Synology Snapshot Admin Commit Message Template
|
|
||||||
|
|
||||||
Use this after a real Synology verification or a final documentation-only update.
|
|
||||||
|
|
||||||
## Recommended format
|
|
||||||
|
|
||||||
```text
|
|
||||||
WBS-7.9: Synology snapshot_admin deployment POC and live verification evidence
|
|
||||||
```
|
|
||||||
|
|
||||||
## If the change is documentation-only
|
|
||||||
|
|
||||||
```text
|
|
||||||
WBS-7.9: add Synology deployment checklist, Task Scheduler commands, and evidence template
|
|
||||||
```
|
|
||||||
|
|
||||||
## If the change includes real NAS verification
|
|
||||||
|
|
||||||
```text
|
|
||||||
WBS-7.9: verify Synology snapshot_admin reverse proxy, auth gate, and restart persistence
|
|
||||||
```
|
|
||||||
|
|
||||||
## Commit body template
|
|
||||||
|
|
||||||
```text
|
|
||||||
- Added/updated Synology Task Scheduler launcher script
|
|
||||||
- Confirmed DSM reverse proxy settings
|
|
||||||
- Captured curl/browser evidence for local and external access
|
|
||||||
- Documented completion evidence in WBS-7.9 checklist
|
|
||||||
```
|
|
||||||
|
|
||||||
## Suggested workflow
|
|
||||||
|
|
||||||
1. Run the validation commands.
|
|
||||||
2. Fill `docs/SYNOLOGY_SNAPSHOT_ADMIN_EVIDENCE_TEMPLATE.md`.
|
|
||||||
3. Commit with one of the messages above.
|
|
||||||
4. Push only after the evidence file is complete.
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
# Synology Snapshot Admin Deployment Checklist
|
|
||||||
|
|
||||||
This checklist is the POC-ready version with concrete values.
|
|
||||||
|
|
||||||
## 1. Target paths
|
|
||||||
|
|
||||||
- Project root: `/volume1/projects/data_feed`
|
|
||||||
- Launch script: `/volume1/projects/data_feed/tools/run_snapshot_admin_synology.sh`
|
|
||||||
- Local DB: `/volume1/projects/data_feed/src/quant_engine/snapshot_admin.db`
|
|
||||||
- Local seed JSON: `/volume1/projects/data_feed/GatherTradingData.json`
|
|
||||||
- PID file: `/volume1/projects/data_feed/Temp/snapshot_admin.pid`
|
|
||||||
- Log file: `/volume1/projects/data_feed/Temp/snapshot_admin.log`
|
|
||||||
|
|
||||||
See also: [`docs/SYNOLOGY_SNAPSHOT_ADMIN_DEPLOYMENT_CHECKLIST_FILLED.md`](C:/Temp/data_feed/docs/SYNOLOGY_SNAPSHOT_ADMIN_DEPLOYMENT_CHECKLIST_FILLED.md)
|
|
||||||
and [`docs/SYNOLOGY_SNAPSHOT_ADMIN_FIREWALL_PROXY_TABLE.md`](C:/Temp/data_feed/docs/SYNOLOGY_SNAPSHOT_ADMIN_FIREWALL_PROXY_TABLE.md)
|
|
||||||
|
|
||||||
## 2. Service account
|
|
||||||
|
|
||||||
- Preferred: dedicated DSM local user `snapshot-admin`
|
|
||||||
- Fallback for first POC: `root`
|
|
||||||
- Required permission: read/write access to `/volume1/projects/data_feed`
|
|
||||||
|
|
||||||
## 3. Environment variables
|
|
||||||
|
|
||||||
Set these before the Task Scheduler task runs.
|
|
||||||
|
|
||||||
- `SNAPSHOT_ADMIN_AUTH_USER=snapshot-admin`
|
|
||||||
- `SNAPSHOT_ADMIN_AUTH_PASSWORD=<strong-password>`
|
|
||||||
- `SNAPSHOT_ADMIN_HOST=127.0.0.1`
|
|
||||||
- `SNAPSHOT_ADMIN_PORT=8787`
|
|
||||||
- `SNAPSHOT_ADMIN_ALLOW_REMOTE=0`
|
|
||||||
- `SNAPSHOT_ADMIN_PID_FILE=/volume1/projects/data_feed/Temp/snapshot_admin.pid`
|
|
||||||
- `SNAPSHOT_ADMIN_LOG_FILE=/volume1/projects/data_feed/Temp/snapshot_admin.log`
|
|
||||||
- `SNAPSHOT_ADMIN_STATE_URL=http://127.0.0.1:8787/api/state`
|
|
||||||
- `SNAPSHOT_ADMIN_PUBLIC_STATE_URL=https://admin.example.com/api/state`
|
|
||||||
|
|
||||||
## 4. Task Scheduler tasks
|
|
||||||
|
|
||||||
### Boot task
|
|
||||||
|
|
||||||
- Name: `snapshot-admin-start`
|
|
||||||
- Trigger: `Boot-up`
|
|
||||||
- User: `snapshot-admin` or `root`
|
|
||||||
- Command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash /volume1/projects/data_feed/tools/run_snapshot_admin_synology.sh start
|
|
||||||
```
|
|
||||||
|
|
||||||
### Healthcheck task
|
|
||||||
|
|
||||||
- Name: `snapshot-admin-healthcheck`
|
|
||||||
- Trigger: `Scheduled Task`
|
|
||||||
- Interval: every 5 minutes
|
|
||||||
- User: same as boot task
|
|
||||||
- Command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash /volume1/projects/data_feed/tools/run_snapshot_admin_synology.sh healthcheck
|
|
||||||
```
|
|
||||||
|
|
||||||
### Restart task
|
|
||||||
|
|
||||||
- Name: `snapshot-admin-restart`
|
|
||||||
- Trigger: manual only
|
|
||||||
- User: same as boot task
|
|
||||||
- Command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash /volume1/projects/data_feed/tools/run_snapshot_admin_synology.sh restart
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4b. Gitea Actions runner label
|
|
||||||
|
|
||||||
Use a unique host label so the deployment job is not mixed with generic self-hosted work.
|
|
||||||
|
|
||||||
- Runner label: `snapshot-admin-host`
|
|
||||||
- Registration example:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
REG_TOKEN="<runner-registration-token>" \
|
|
||||||
GITEA_URL="http://192.168.123.100:8418" \
|
|
||||||
RUNNER_LABEL="snapshot-admin-host" \
|
|
||||||
bash tools/re_register_act_runner_synology.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
- Workflow selector:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
runs-on: [self-hosted, snapshot-admin-host]
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4c. Queue handling
|
|
||||||
|
|
||||||
- If the deploy workflow stays queued, it usually means the host runner is busy.
|
|
||||||
- Check the job currently holding the runner before re-dispatching.
|
|
||||||
- Do not keep dispatching deploy runs back-to-back. The workflow already uses `concurrency` to cancel in-progress duplicates.
|
|
||||||
|
|
||||||
## 5. Reverse proxy
|
|
||||||
|
|
||||||
- DSM path: `Control Panel > Login Portal > Advanced > Reverse Proxy`
|
|
||||||
- Rule name: `snapshot-admin`
|
|
||||||
- Source:
|
|
||||||
- Protocol: `HTTPS`
|
|
||||||
- Hostname: `admin.example.com`
|
|
||||||
- Port: `443`
|
|
||||||
- Path: `/`
|
|
||||||
- Destination:
|
|
||||||
- Protocol: `HTTP`
|
|
||||||
- Hostname: `127.0.0.1`
|
|
||||||
- Port: `8787`
|
|
||||||
- TLS certificate: certificate matching `admin.example.com`
|
|
||||||
|
|
||||||
## 6. Firewall
|
|
||||||
|
|
||||||
- Allow inbound `443/TCP`
|
|
||||||
- Block inbound `8787/TCP` from WAN
|
|
||||||
- If needed, allowlist office/VPN CIDRs only
|
|
||||||
|
|
||||||
## 7. Verification order
|
|
||||||
|
|
||||||
1. Start the service.
|
|
||||||
2. Confirm `bash /volume1/projects/data_feed/tools/run_snapshot_admin_synology.sh healthcheck` prints `healthcheck ok`.
|
|
||||||
3. Confirm local `curl -i http://127.0.0.1:8787/api/state`.
|
|
||||||
- Expect `200 OK`.
|
|
||||||
- Expect JSON with `version.app = snapshot-admin-web-v7`.
|
|
||||||
4. Confirm external `curl -i https://admin.example.com/api/state` returns `401`.
|
|
||||||
- Expect `WWW-Authenticate: Basic`.
|
|
||||||
5. Confirm authenticated `curl -u 'snapshot-admin:<password>' https://admin.example.com/api/state` returns `200`.
|
|
||||||
- Expect the same `version.app` value as the local endpoint.
|
|
||||||
6. Confirm `curl -i https://admin.example.com/tables` after Basic Auth.
|
|
||||||
- Expect `200 OK` and the Tabler grid page.
|
|
||||||
7. Open browser `https://admin.example.com/`.
|
|
||||||
- Expect Basic Auth prompt, then UI render.
|
|
||||||
8. Open browser `https://admin.example.com/tables`.
|
|
||||||
- Expect Basic Auth prompt, then grid render.
|
|
||||||
9. Restart the task or NAS.
|
|
||||||
10. Repeat steps 2-8 and confirm the response pattern is unchanged.
|
|
||||||
|
|
||||||
## 7b. Evidence rule
|
|
||||||
|
|
||||||
- Do not mark `WBS-7.9` complete until the external `401`/`200` curl pair, both browser screenshots, and the reverse proxy rule screenshot are archived together.
|
|
||||||
- Loopback-only smoke tests are useful, but they do not replace the NAS-side live verification.
|
|
||||||
|
|
||||||
## 7c. One-page field run sheet
|
|
||||||
|
|
||||||
For a compact field execution order, use [`docs/SYNOLOGY_SNAPSHOT_ADMIN_FINAL_EXECUTION_ONE_PAGER.md`](C:/Temp/data_feed/docs/SYNOLOGY_SNAPSHOT_ADMIN_FINAL_EXECUTION_ONE_PAGER.md).
|
|
||||||
|
|
||||||
## 8. Completion wording
|
|
||||||
|
|
||||||
Use the following text only after evidence is collected:
|
|
||||||
|
|
||||||
> WBS-7.9 실배포 검증 완료: Synology NAS에서 `tools/run_snapshot_admin_synology.sh` 기반 서비스가 `127.0.0.1:8787`에 정상 기동되고, DSM Reverse Proxy `HTTPS:443 -> HTTP 127.0.0.1:8787` 경유 외부 접속이 Basic Auth와 함께 `200 OK`로 확인되었으며, 미인증 요청은 `401 Unauthorized`로 차단되었다. `/` 및 `/tables` 렌더링과 재시작 후 지속성도 확인되었고, 증빙은 `docs/SYNOLOGY_SNAPSHOT_ADMIN_EVIDENCE_TEMPLATE.md` 양식으로 보관되었다.
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
# Synology Snapshot Admin Deployment Checklist - Filled Example
|
|
||||||
|
|
||||||
This is the deployment-ready example for the current repo state.
|
|
||||||
Replace only the hostname, certificate name, and strong password if your NAS uses different values.
|
|
||||||
|
|
||||||
## 1. Target paths
|
|
||||||
|
|
||||||
- Project root: `/volume1/projects/data_feed`
|
|
||||||
- Launch script: `/volume1/projects/data_feed/tools/run_snapshot_admin_synology.sh`
|
|
||||||
- Local DB: `/volume1/projects/data_feed/src/quant_engine/snapshot_admin.db`
|
|
||||||
- Local seed JSON: `/volume1/projects/data_feed/GatherTradingData.json`
|
|
||||||
- PID file: `/volume1/projects/data_feed/Temp/snapshot_admin.pid`
|
|
||||||
- Log file: `/volume1/projects/data_feed/Temp/snapshot_admin.log`
|
|
||||||
|
|
||||||
## 2. Service account
|
|
||||||
|
|
||||||
- Preferred DSM user: `snapshot-admin`
|
|
||||||
- Fallback for first POC: `root`
|
|
||||||
- Folder access: read/write on `/volume1/projects/data_feed`
|
|
||||||
|
|
||||||
## 3. Environment variables
|
|
||||||
|
|
||||||
```bash
|
|
||||||
SNAPSHOT_ADMIN_AUTH_USER=snapshot-admin
|
|
||||||
SNAPSHOT_ADMIN_AUTH_PASSWORD=<strong-password>
|
|
||||||
SNAPSHOT_ADMIN_HOST=127.0.0.1
|
|
||||||
SNAPSHOT_ADMIN_PORT=8787
|
|
||||||
SNAPSHOT_ADMIN_ALLOW_REMOTE=0
|
|
||||||
SNAPSHOT_ADMIN_PID_FILE=/volume1/projects/data_feed/Temp/snapshot_admin.pid
|
|
||||||
SNAPSHOT_ADMIN_LOG_FILE=/volume1/projects/data_feed/Temp/snapshot_admin.log
|
|
||||||
SNAPSHOT_ADMIN_STATE_URL=http://127.0.0.1:8787/api/state
|
|
||||||
SNAPSHOT_ADMIN_PUBLIC_STATE_URL=https://admin.example.com/api/state
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. Task Scheduler
|
|
||||||
|
|
||||||
### Boot task
|
|
||||||
|
|
||||||
- Name: `snapshot-admin-start`
|
|
||||||
- User: `snapshot-admin`
|
|
||||||
- Trigger: `Boot-up`
|
|
||||||
- Command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash /volume1/projects/data_feed/tools/run_snapshot_admin_synology.sh start
|
|
||||||
```
|
|
||||||
|
|
||||||
### Healthcheck task
|
|
||||||
|
|
||||||
- Name: `snapshot-admin-healthcheck`
|
|
||||||
- User: `snapshot-admin`
|
|
||||||
- Trigger: every 5 minutes
|
|
||||||
- Command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash /volume1/projects/data_feed/tools/run_snapshot_admin_synology.sh healthcheck
|
|
||||||
```
|
|
||||||
|
|
||||||
### Manual restart task
|
|
||||||
|
|
||||||
- Name: `snapshot-admin-restart`
|
|
||||||
- User: `snapshot-admin`
|
|
||||||
- Trigger: manual
|
|
||||||
- Command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash /volume1/projects/data_feed/tools/run_snapshot_admin_synology.sh restart
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. Reverse proxy
|
|
||||||
|
|
||||||
- DSM path: `Control Panel > Login Portal > Advanced > Reverse Proxy`
|
|
||||||
- Rule name: `snapshot-admin`
|
|
||||||
- Source protocol: `HTTPS`
|
|
||||||
- Source hostname: `admin.example.com`
|
|
||||||
- Source port: `443`
|
|
||||||
- Source path: `/`
|
|
||||||
- Destination protocol: `HTTP`
|
|
||||||
- Destination hostname: `127.0.0.1`
|
|
||||||
- Destination port: `8787`
|
|
||||||
- TLS certificate: `admin.example.com` certificate
|
|
||||||
|
|
||||||
## 6. Firewall
|
|
||||||
|
|
||||||
- Allow inbound `443/TCP`
|
|
||||||
- Block inbound `8787/TCP` from WAN
|
|
||||||
- Allowlist only trusted office/VPN ranges if needed
|
|
||||||
|
|
||||||
## 7. Verification commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -i http://127.0.0.1:8787/api/state
|
|
||||||
curl -i https://admin.example.com/api/state
|
|
||||||
curl -u 'snapshot-admin:<strong-password>' https://admin.example.com/api/state
|
|
||||||
curl -I https://admin.example.com/
|
|
||||||
curl -I https://admin.example.com/tables
|
|
||||||
```
|
|
||||||
|
|
||||||
## 7b. Final preflight
|
|
||||||
|
|
||||||
Use [`docs/SYNOLOGY_SNAPSHOT_ADMIN_FINAL_PREFLIGHT_10.md`](C:/Temp/data_feed/docs/SYNOLOGY_SNAPSHOT_ADMIN_FINAL_PREFLIGHT_10.md)
|
|
||||||
immediately before you mark the deployment complete.
|
|
||||||
|
|
||||||
## 8. Completion wording
|
|
||||||
|
|
||||||
Use this exact wording when evidence is complete:
|
|
||||||
|
|
||||||
> WBS-7.9 실배포 검증 완료: Synology NAS에서 `tools/run_snapshot_admin_synology.sh` 기반 서비스가 `127.0.0.1:8787`에 정상 기동되고, DSM Reverse Proxy `HTTPS:443 -> HTTP 127.0.0.1:8787` 경유 외부 접속이 Basic Auth와 함께 `200 OK`로 확인되었으며, 미인증 요청은 `401 Unauthorized`로 차단되었다. `/` 및 `/tables` 렌더링과 재시작 후 지속성도 확인되었고, 증빙은 `docs/SYNOLOGY_SNAPSHOT_ADMIN_EVIDENCE_TEMPLATE.md` 양식으로 보관되었다.
|
|
||||||
|
|
||||||
## 9. What to replace
|
|
||||||
|
|
||||||
- `admin.example.com` if your public hostname differs
|
|
||||||
- `<strong-password>` with your generated password
|
|
||||||
- TLS certificate name if the DSM certificate uses another label
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
# Synology Snapshot Admin Evidence Template
|
|
||||||
|
|
||||||
Use this template to close `WBS-7.9` after a real Synology deployment test.
|
|
||||||
|
|
||||||
## Deployment metadata
|
|
||||||
|
|
||||||
- NAS model:
|
|
||||||
- DSM version:
|
|
||||||
- Public hostname:
|
|
||||||
- Reverse proxy rule name:
|
|
||||||
- TLS certificate name:
|
|
||||||
- Service launcher: `tools/run_snapshot_admin_synology.sh`
|
|
||||||
- Python service bind mode:
|
|
||||||
- Auth mode: `Basic Auth`
|
|
||||||
|
|
||||||
## Local checks
|
|
||||||
|
|
||||||
- `curl -i http://127.0.0.1:8787/api/state`
|
|
||||||
- Result:
|
|
||||||
- `curl -i http://127.0.0.1:8787/tables`
|
|
||||||
- Result:
|
|
||||||
|
|
||||||
## External checks
|
|
||||||
|
|
||||||
- `curl -i https://<public-host>/api/state`
|
|
||||||
- Result:
|
|
||||||
- `curl -u '<user>:<password>' https://<public-host>/api/state`
|
|
||||||
- Result:
|
|
||||||
- `curl -i https://<public-host>/tables`
|
|
||||||
- Result:
|
|
||||||
|
|
||||||
## Browser checks
|
|
||||||
|
|
||||||
- `https://<public-host>/`
|
|
||||||
- Result:
|
|
||||||
- `https://<public-host>/tables`
|
|
||||||
- Result:
|
|
||||||
|
|
||||||
## Restart persistence
|
|
||||||
|
|
||||||
- Restart method used:
|
|
||||||
- Restart time:
|
|
||||||
- `healthcheck` result after restart:
|
|
||||||
- Time elapsed after restart:
|
|
||||||
|
|
||||||
## Evidence attachments
|
|
||||||
|
|
||||||
- Screenshot: DSM reverse proxy rule
|
|
||||||
- Screenshot: browser `/`
|
|
||||||
- Screenshot: browser `/tables`
|
|
||||||
- Log snippet: `Temp/snapshot_admin.log`
|
|
||||||
- `curl` output archive:
|
|
||||||
|
|
||||||
## Completion statement
|
|
||||||
|
|
||||||
- `WBS-7.9` completion condition met:
|
|
||||||
- local endpoint `200`
|
|
||||||
- external unauthenticated `401`
|
|
||||||
- external authenticated `200`
|
|
||||||
- browser render verified
|
|
||||||
- restart persistence verified
|
|
||||||
- evidence archived
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
# Synology Snapshot Admin Final Execution One-Pager
|
|
||||||
|
|
||||||
Use this sheet on the NAS during the live verification run.
|
|
||||||
|
|
||||||
## Goal
|
|
||||||
|
|
||||||
Confirm that `snapshot_admin_server_v1.py` runs on Synology with loopback binding, DSM reverse proxy exposure, and Basic Auth protection.
|
|
||||||
|
|
||||||
## Required values
|
|
||||||
|
|
||||||
- Project root: `/volume1/projects/data_feed`
|
|
||||||
- Launcher: `/volume1/projects/data_feed/tools/run_snapshot_admin_synology.sh`
|
|
||||||
- Local URL: `http://127.0.0.1:8787/api/state`
|
|
||||||
- Public URL: `https://admin.example.com/api/state`
|
|
||||||
- Public UI URL: `https://admin.example.com/`
|
|
||||||
- Public tables URL: `https://admin.example.com/tables`
|
|
||||||
|
|
||||||
## Execution order
|
|
||||||
|
|
||||||
1. Start the service.
|
|
||||||
- `bash /volume1/projects/data_feed/tools/run_snapshot_admin_synology.sh start`
|
|
||||||
2. Confirm the healthcheck.
|
|
||||||
- `bash /volume1/projects/data_feed/tools/run_snapshot_admin_synology.sh healthcheck`
|
|
||||||
- Expected: `healthcheck ok`
|
|
||||||
3. Confirm local loopback.
|
|
||||||
- `curl -i http://127.0.0.1:8787/api/state`
|
|
||||||
- Expected: `200 OK`
|
|
||||||
- Expected JSON field: `version.app = snapshot-admin-web-v7`
|
|
||||||
4. Confirm unauthenticated external access.
|
|
||||||
- `curl -i https://admin.example.com/api/state`
|
|
||||||
- Expected: `401 Unauthorized`
|
|
||||||
- Expected header: `WWW-Authenticate: Basic`
|
|
||||||
5. Confirm authenticated external access.
|
|
||||||
- `curl -u 'snapshot-admin:<password>' https://admin.example.com/api/state`
|
|
||||||
- Expected: `200 OK`
|
|
||||||
- Expected same `version.app` as local loopback
|
|
||||||
6. Confirm tables page.
|
|
||||||
- `curl -i https://admin.example.com/tables`
|
|
||||||
- Expected: `200 OK`
|
|
||||||
- Expected: Tabler grid HTML
|
|
||||||
7. Confirm browser render.
|
|
||||||
- Open `https://admin.example.com/`
|
|
||||||
- Open `https://admin.example.com/tables`
|
|
||||||
- Expected: Basic Auth prompt, then render
|
|
||||||
8. Confirm persistence.
|
|
||||||
- Restart the task or NAS
|
|
||||||
- Re-run steps 2-7
|
|
||||||
- Expected: identical response pattern after restart
|
|
||||||
|
|
||||||
## Queue check
|
|
||||||
|
|
||||||
If the deployment workflow stays queued for more than a few minutes:
|
|
||||||
|
|
||||||
1. Confirm the runner is registered with the host label.
|
|
||||||
- `RUNNER_LABEL=snapshot-admin-host`
|
|
||||||
- Re-register with `bash tools/re_register_act_runner_synology.sh` after setting the registration token.
|
|
||||||
2. Confirm the runner daemon is running.
|
|
||||||
- `bash tools/start_act_runner_synology.sh`
|
|
||||||
3. Confirm the queue target is the host runner label.
|
|
||||||
- Deploy workflow uses `runs-on: [self-hosted, snapshot-admin-host]`
|
|
||||||
4. If another job is occupying the runner, wait for it to finish or cancel the stale workflow from Gitea.
|
|
||||||
5. Re-dispatch `snapshot_admin_deploy.yml` after the runner is idle.
|
|
||||||
|
|
||||||
## Pass criteria
|
|
||||||
|
|
||||||
- Loopback `200` confirmed.
|
|
||||||
- External unauthenticated `401` confirmed.
|
|
||||||
- External authenticated `200` confirmed.
|
|
||||||
- `/` and `/tables` browser render confirmed.
|
|
||||||
- Restart persistence confirmed.
|
|
||||||
- DSM reverse proxy and firewall screenshots archived.
|
|
||||||
|
|
||||||
## Workflow success evidence
|
|
||||||
|
|
||||||
If you need the deploy-job proof from the NAS runner before the full external closeout:
|
|
||||||
|
|
||||||
- `healthcheck` retried after startup and passed on the NAS runner.
|
|
||||||
- `snapshot-admin-web-v6` was returned by the deploy verification step.
|
|
||||||
- The workflow finished with `Job succeeded`.
|
|
||||||
|
|
||||||
This proves the deploy job can launch, wait for readiness, and validate locally on Synology.
|
|
||||||
It does not replace the external reverse-proxy/browser closeout evidence above.
|
|
||||||
|
|
||||||
## Do not close WBS-7.9 unless
|
|
||||||
|
|
||||||
- The `401`/`200` curl pair is saved.
|
|
||||||
- Both browser screenshots are saved.
|
|
||||||
- The DSM reverse proxy rule screenshot is saved.
|
|
||||||
- The completion wording in `docs/SYNOLOGY_SNAPSHOT_ADMIN_DEPLOYMENT_CHECKLIST.md` is used only after evidence is archived.
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# Synology Snapshot Admin Final Preflight 10
|
|
||||||
|
|
||||||
Use this immediately before declaring `WBS-7.9` complete.
|
|
||||||
|
|
||||||
1. Confirm the Python service is running on `127.0.0.1:8787`.
|
|
||||||
2. Confirm `bash /volume1/projects/data_feed/tools/run_snapshot_admin_synology.sh healthcheck` returns `healthcheck ok`.
|
|
||||||
3. Confirm `curl -i http://127.0.0.1:8787/api/state` returns `200 OK`.
|
|
||||||
4. Confirm `curl -i https://admin.example.com/api/state` returns `401 Unauthorized` without credentials.
|
|
||||||
5. Confirm `curl -u 'snapshot-admin:<strong-password>' https://admin.example.com/api/state` returns `200 OK`.
|
|
||||||
6. Confirm `https://admin.example.com/` renders in a browser after Basic Auth.
|
|
||||||
7. Confirm `https://admin.example.com/tables` renders in a browser after Basic Auth.
|
|
||||||
8. Confirm the DSM reverse proxy rule still maps `HTTPS:443 -> HTTP 127.0.0.1:8787`.
|
|
||||||
9. Confirm the firewall still blocks `8787/TCP` from WAN.
|
|
||||||
10. Restart the service or NAS and repeat steps 2 through 7.
|
|
||||||
|
|
||||||
## Evidence to archive
|
|
||||||
|
|
||||||
- `curl` output for steps 3 through 5
|
|
||||||
- Browser screenshots for steps 6 and 7
|
|
||||||
- DSM reverse proxy screenshot for step 8
|
|
||||||
- Firewall screenshot for step 9
|
|
||||||
- Restart proof for step 10
|
|
||||||
|
|
||||||
## Pass condition
|
|
||||||
|
|
||||||
Declare `WBS-7.9` complete only when all 10 steps pass and the evidence files are saved using:
|
|
||||||
|
|
||||||
- [`docs/SYNOLOGY_SNAPSHOT_ADMIN_EVIDENCE_TEMPLATE.md`](C:/Temp/data_feed/docs/SYNOLOGY_SNAPSHOT_ADMIN_EVIDENCE_TEMPLATE.md)
|
|
||||||
- [`docs/SYNOLOGY_SNAPSHOT_ADMIN_DEPLOYMENT_CHECKLIST_FILLED.md`](C:/Temp/data_feed/docs/SYNOLOGY_SNAPSHOT_ADMIN_DEPLOYMENT_CHECKLIST_FILLED.md)
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# Synology Snapshot Admin Firewall and Reverse Proxy Copy-Paste
|
|
||||||
|
|
||||||
Use these values verbatim in DSM.
|
|
||||||
|
|
||||||
## Reverse proxy
|
|
||||||
|
|
||||||
- Rule name: `snapshot-admin`
|
|
||||||
- Source protocol: `HTTPS`
|
|
||||||
- Source hostname: `admin.example.com`
|
|
||||||
- Source port: `443`
|
|
||||||
- Source path: `/`
|
|
||||||
- Destination protocol: `HTTP`
|
|
||||||
- Destination hostname: `127.0.0.1`
|
|
||||||
- Destination port: `8787`
|
|
||||||
|
|
||||||
## Firewall
|
|
||||||
|
|
||||||
- Allow: `443/TCP` from WAN or trusted CIDR
|
|
||||||
- Deny: `8787/TCP` from WAN
|
|
||||||
- Optional allow: `443/TCP` from office/VPN CIDR only
|
|
||||||
|
|
||||||
## Certificate binding
|
|
||||||
|
|
||||||
- Hostname: `admin.example.com`
|
|
||||||
- Bind to: reverse proxy rule `snapshot-admin`
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- Do not expose `8787/TCP` directly.
|
|
||||||
- Keep Basic Auth enabled in the Python service.
|
|
||||||
- Use `127.0.0.1` for the destination host unless direct-bind testing is intentional.
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# Synology Snapshot Admin Firewall and Reverse Proxy Table
|
|
||||||
|
|
||||||
Use these values for the first POC.
|
|
||||||
|
|
||||||
## Reverse proxy rule
|
|
||||||
|
|
||||||
| Field | Value |
|
|
||||||
|---|---|
|
|
||||||
| Rule name | `snapshot-admin` |
|
|
||||||
| Source protocol | `HTTPS` |
|
|
||||||
| Source hostname | `admin.example.com` |
|
|
||||||
| Source port | `443` |
|
|
||||||
| Source path | `/` |
|
|
||||||
| Destination protocol | `HTTP` |
|
|
||||||
| Destination hostname | `127.0.0.1` |
|
|
||||||
| Destination port | `8787` |
|
|
||||||
|
|
||||||
## Firewall rules
|
|
||||||
|
|
||||||
| Rule | Action | Source | Destination | Port |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| Reverse proxy public entry | Allow | WAN or trusted public CIDR | NAS | `443/TCP` |
|
|
||||||
| Raw service port | Deny | WAN | NAS | `8787/TCP` |
|
|
||||||
| Optional office/VPN allowlist | Allow | Office/VPN CIDR only | NAS | `443/TCP` |
|
|
||||||
|
|
||||||
## Certificate
|
|
||||||
|
|
||||||
| Field | Value |
|
|
||||||
|---|---|
|
|
||||||
| Type | TLS certificate |
|
|
||||||
| Hostname | `admin.example.com` |
|
|
||||||
| Binding | Reverse proxy rule `snapshot-admin` |
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- Keep `8787/TCP` private.
|
|
||||||
- Keep Basic Auth enabled in the Python service.
|
|
||||||
- Use `127.0.0.1` for the backend destination unless you are explicitly testing direct bind mode.
|
|
||||||
@@ -1,257 +0,0 @@
|
|||||||
# Synology Snapshot Admin POC
|
|
||||||
|
|
||||||
This guide enables external access to the Python snapshot admin service on Synology without exposing the raw service port to the internet.
|
|
||||||
|
|
||||||
## Recommended topology
|
|
||||||
|
|
||||||
1. Keep the Python service bound to loopback only:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python tools/run_snapshot_admin_server_v1.py \
|
|
||||||
--host 127.0.0.1 \
|
|
||||||
--port 8787 \
|
|
||||||
--db src/quant_engine/snapshot_admin.db \
|
|
||||||
--seed GatherTradingData.json
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Put Synology DSM reverse proxy in front of it:
|
|
||||||
- Source: `https://<public-host>:443`
|
|
||||||
- Destination: `http://127.0.0.1:8787`
|
|
||||||
- Keep the service port closed from direct WAN access.
|
|
||||||
|
|
||||||
3. Add browser authentication with the built-in Basic Auth gate:
|
|
||||||
- Set `SNAPSHOT_ADMIN_AUTH_USER`
|
|
||||||
- Set `SNAPSHOT_ADMIN_AUTH_PASSWORD`
|
|
||||||
- Or pass `--auth-user` and `--auth-password` on the wrapper command
|
|
||||||
|
|
||||||
4. Verify from the NAS:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -i http://127.0.0.1:8787/api/state
|
|
||||||
curl -u "$SNAPSHOT_ADMIN_AUTH_USER:$SNAPSHOT_ADMIN_AUTH_PASSWORD" http://127.0.0.1:8787/api/state
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Verify from outside the NAS:
|
|
||||||
- Open `https://<public-host>/`
|
|
||||||
- The browser should prompt for Basic Auth
|
|
||||||
- `https://<public-host>/tables` should render after login
|
|
||||||
|
|
||||||
## DSM Checklist
|
|
||||||
|
|
||||||
Use these exact values for the first POC.
|
|
||||||
|
|
||||||
1. **DSM app path**
|
|
||||||
- `Control Panel`
|
|
||||||
- `Login Portal`
|
|
||||||
- `Advanced`
|
|
||||||
- `Reverse Proxy`
|
|
||||||
|
|
||||||
2. **Create reverse proxy rule**
|
|
||||||
- Description: `snapshot-admin`
|
|
||||||
- Source protocol: `HTTPS`
|
|
||||||
- Source hostname: your public DNS name, for example `admin.example.com`
|
|
||||||
- Source port: `443`
|
|
||||||
- Source path: `/`
|
|
||||||
- Destination protocol: `HTTP`
|
|
||||||
- Destination hostname: `127.0.0.1`
|
|
||||||
- Destination port: `8787`
|
|
||||||
|
|
||||||
3. **Certificate**
|
|
||||||
- Attach a valid TLS certificate for the public hostname
|
|
||||||
- Prefer a Synology-managed or imported certificate that matches `admin.example.com`
|
|
||||||
|
|
||||||
4. **Firewall**
|
|
||||||
- Allow inbound `443/TCP` only for the reverse proxy endpoint
|
|
||||||
- Do not expose `8787/TCP` on WAN
|
|
||||||
- If the NAS must be reachable only from a VPN or office IP range, allowlist those ranges and block the rest
|
|
||||||
|
|
||||||
5. **Service start policy**
|
|
||||||
- Start the Python service on boot or via DSM Task Scheduler
|
|
||||||
- Keep it bound to `127.0.0.1` unless you intentionally use direct bind mode
|
|
||||||
- If you use direct bind mode, keep `--allow-remote` and Basic Auth enabled together
|
|
||||||
- For Gitea Actions runner verification, register `act_runner` with a dedicated host label (`self-hosted:host,snapshot-admin-host:host`) if you want to avoid Docker job containers and the `Cleaning up container` log line
|
|
||||||
- Preferred launcher script: `tools/run_snapshot_admin_synology.sh`
|
|
||||||
- Gitea CI deploy path: trigger `.gitea/workflows/snapshot_admin_deploy.yml` `workflow_dispatch` and let the host runner call the launcher script
|
|
||||||
- Runner bootstrap: `tools/re_register_act_runner_synology.sh`
|
|
||||||
- Runner daemon start: `tools/start_act_runner_synology.sh`
|
|
||||||
|
|
||||||
6. **Runner re-registration**
|
|
||||||
- Use this when you want to switch an existing runner from Docker mode to host mode:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /volume1/projects/data_feed
|
|
||||||
REG_TOKEN="<runner-registration-token>" \
|
|
||||||
GITEA_URL="http://192.168.123.100:8418" \
|
|
||||||
bash tools/re_register_act_runner_synology.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
- Expected effect:
|
|
||||||
- removes the existing `.runner` registration file
|
|
||||||
- registers `self-hosted:host,snapshot-admin-host:host`
|
|
||||||
- writes an updated `config.yaml`
|
|
||||||
- If the old runner remains listed in Gitea, remove it from the repository runner page and re-run the command above
|
|
||||||
|
|
||||||
7. **Runner start**
|
|
||||||
- After re-registration, start the daemon:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash tools/start_act_runner_synology.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
- Expected effect:
|
|
||||||
- launches `act_runner daemon` using the existing config
|
|
||||||
- records `runner.pid` and `runner.log` under the runner directory
|
|
||||||
|
|
||||||
## DSM Task Scheduler
|
|
||||||
|
|
||||||
Create two scheduled tasks in `Control Panel > Task Scheduler`.
|
|
||||||
|
|
||||||
1. **Boot task**
|
|
||||||
- Task name: `snapshot-admin-start`
|
|
||||||
- User: `root` or a dedicated service account with access to the project folder
|
|
||||||
- Event: `Boot-up`
|
|
||||||
- Command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash /volume1/projects/data_feed/tools/run_snapshot_admin_synology.sh start
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Healthcheck task**
|
|
||||||
- Task name: `snapshot-admin-healthcheck`
|
|
||||||
- User: same as boot task
|
|
||||||
- Event: `Scheduled Task`
|
|
||||||
- Repeat: every 5 minutes
|
|
||||||
- Command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash /volume1/projects/data_feed/tools/run_snapshot_admin_synology.sh healthcheck
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Manual restart task**
|
|
||||||
- Task name: `snapshot-admin-restart`
|
|
||||||
- User: same as boot task
|
|
||||||
- Event: `Scheduled Task`
|
|
||||||
- Repeat: manual only, or keep disabled until needed
|
|
||||||
- Command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash /volume1/projects/data_feed/tools/run_snapshot_admin_synology.sh restart
|
|
||||||
```
|
|
||||||
|
|
||||||
## Direct bind mode
|
|
||||||
|
|
||||||
Direct binding to `0.0.0.0` is allowed only when both auth values are configured:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python tools/run_snapshot_admin_server_v1.py \
|
|
||||||
--host 0.0.0.0 \
|
|
||||||
--port 8787 \
|
|
||||||
--allow-remote \
|
|
||||||
--auth-user "$SNAPSHOT_ADMIN_AUTH_USER" \
|
|
||||||
--auth-password "$SNAPSHOT_ADMIN_AUTH_PASSWORD"
|
|
||||||
```
|
|
||||||
|
|
||||||
Use this only if you have a separate firewall or VPN rule in place. The default POC path is still loopback + reverse proxy.
|
|
||||||
|
|
||||||
## Validation
|
|
||||||
|
|
||||||
Run the unit/web checks before and after deployment:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python -m pytest tests/unit/test_snapshot_admin_web_v1.py -q
|
|
||||||
python tools/validate_snapshot_admin_web_v1.py
|
|
||||||
```
|
|
||||||
|
|
||||||
The auth gate is part of the service now, so public exposure without credentials is rejected by the server itself.
|
|
||||||
|
|
||||||
## Curl checklist
|
|
||||||
|
|
||||||
Use this as the POC run sheet.
|
|
||||||
|
|
||||||
1. Local service check:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -i http://127.0.0.1:8787/api/state
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected:
|
|
||||||
- `200 OK`
|
|
||||||
- JSON payload contains `version.app`
|
|
||||||
|
|
||||||
2. Reverse proxy auth challenge:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -i https://<public-host>/api/state
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected:
|
|
||||||
- `401 Unauthorized`
|
|
||||||
- `WWW-Authenticate: Basic`
|
|
||||||
|
|
||||||
3. Reverse proxy authenticated access:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -u '<user>:<password>' https://<public-host>/api/state
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected:
|
|
||||||
- `200 OK`
|
|
||||||
- JSON payload contains the same `version.app`
|
|
||||||
|
|
||||||
4. UI rendering:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -I https://<public-host>/
|
|
||||||
curl -I https://<public-host>/tables
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected:
|
|
||||||
- `200 OK` after auth
|
|
||||||
- HTML response, not a redirect to the raw port
|
|
||||||
|
|
||||||
5. Restart persistence:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash tools/run_snapshot_admin_synology.sh restart
|
|
||||||
bash tools/run_snapshot_admin_synology.sh healthcheck
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected:
|
|
||||||
- `healthcheck ok`
|
|
||||||
- The proxy URL continues to answer after the service restarts
|
|
||||||
|
|
||||||
## Live verification
|
|
||||||
|
|
||||||
Use this sequence on the actual Synology box after the reverse proxy rule is in place:
|
|
||||||
|
|
||||||
1. Start the service and confirm the local health endpoint:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -i http://127.0.0.1:8787/api/state
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Confirm the auth gate:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -i https://<public-host>/api/state
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected result:
|
|
||||||
- `401 Unauthorized` when no credentials are provided
|
|
||||||
- `200 OK` when valid Basic Auth credentials are supplied
|
|
||||||
|
|
||||||
3. Confirm the browser surface:
|
|
||||||
- Open `https://<public-host>/`
|
|
||||||
- Sign in with the Basic Auth credentials
|
|
||||||
- Open `https://<public-host>/tables`
|
|
||||||
- Confirm rows render from the three SQLite sources
|
|
||||||
|
|
||||||
4. Confirm the deployment survives a process restart:
|
|
||||||
- Restart the Python service or the task that launches it
|
|
||||||
- Re-run `curl -i http://127.0.0.1:8787/api/state`
|
|
||||||
- Re-open the browser URL and confirm login still works
|
|
||||||
|
|
||||||
5. Archive evidence:
|
|
||||||
- Save the `curl` outputs
|
|
||||||
- Save a screenshot of `/` and `/tables`
|
|
||||||
- Record the DSM reverse proxy rule values and certificate name
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace QuantEngine.Application;
|
|
||||||
|
|
||||||
public class Class1
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using QuantEngine.Core.Interfaces;
|
||||||
|
using QuantEngine.Core.Models;
|
||||||
|
|
||||||
|
namespace QuantEngine.Application.Services
|
||||||
|
{
|
||||||
|
public class ApprovalService
|
||||||
|
{
|
||||||
|
private readonly IWorkspaceRepository _repository;
|
||||||
|
|
||||||
|
public ApprovalService(IWorkspaceRepository repository)
|
||||||
|
{
|
||||||
|
_repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IEnumerable<WorkspaceApproval>> GetApprovalsAsync() => _repository.GetApprovalsAsync();
|
||||||
|
public Task<WorkspaceApproval?> GetApprovalAsync(string domain, string targetRef) => _repository.GetApprovalAsync(domain, targetRef);
|
||||||
|
public Task<bool> UpsertApprovalAsync(WorkspaceApproval approval) => _repository.UpsertApprovalAsync(approval);
|
||||||
|
|
||||||
|
public Task<IEnumerable<WorkspaceLock>> GetLocksAsync() => _repository.GetLocksAsync();
|
||||||
|
public Task<WorkspaceLock?> GetLockAsync(string domain, string targetRef) => _repository.GetLockAsync(domain, targetRef);
|
||||||
|
public Task<bool> AcquireLockAsync(WorkspaceLock @lock) => _repository.AcquireLockAsync(@lock);
|
||||||
|
public Task<bool> ReleaseLockAsync(string domain, string targetRef) => _repository.ReleaseLockAsync(domain, targetRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using QuantEngine.Core.Interfaces;
|
||||||
|
using QuantEngine.Core.Models;
|
||||||
|
|
||||||
|
namespace QuantEngine.Application.Services
|
||||||
|
{
|
||||||
|
public class WorkspaceService
|
||||||
|
{
|
||||||
|
private readonly IWorkspaceRepository _repository;
|
||||||
|
|
||||||
|
public WorkspaceService(IWorkspaceRepository repository)
|
||||||
|
{
|
||||||
|
_repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IEnumerable<Setting>> GetSettingsAsync() => _repository.GetSettingsAsync();
|
||||||
|
public Task<Setting?> GetSettingByKeyAsync(string key) => _repository.GetSettingByKeyAsync(key);
|
||||||
|
public Task<bool> UpsertSettingAsync(Setting setting) => _repository.UpsertSettingAsync(setting);
|
||||||
|
public Task<bool> DeleteSettingAsync(string key) => _repository.DeleteSettingAsync(key);
|
||||||
|
|
||||||
|
public Task<IEnumerable<AccountSnapshot>> GetAccountSnapshotsAsync() => _repository.GetAccountSnapshotsAsync();
|
||||||
|
public Task<bool> InsertAccountSnapshotsAsync(IEnumerable<AccountSnapshot> snapshots) => _repository.InsertAccountSnapshotsAsync(snapshots);
|
||||||
|
public Task<bool> ClearAccountSnapshotsAsync() => _repository.ClearAccountSnapshotsAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using QuantEngine.Core.Domain;
|
||||||
|
|
||||||
|
namespace QuantEngine.Core.Tests;
|
||||||
|
|
||||||
|
public class FormulaEngineTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void TestTimingDecisionNeutral()
|
||||||
|
{
|
||||||
|
var ctx = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "entryModeGate", "PASS" },
|
||||||
|
{ "entryMode", "BREAKOUT" },
|
||||||
|
{ "leaderGate", "PASS" },
|
||||||
|
{ "acGate", "CLEAR" },
|
||||||
|
{ "leaderTotal", 4.0 },
|
||||||
|
{ "flowCredit", 0.8 },
|
||||||
|
{ "ma20Slope", 1.0 },
|
||||||
|
{ "disparity", 0.0 },
|
||||||
|
{ "rsi14", 50.0 },
|
||||||
|
{ "avgTradeValue5D", 100.0 },
|
||||||
|
{ "spreadPct", 0.1 },
|
||||||
|
{ "priceStatus", "PRICE_OK" },
|
||||||
|
{ "atr20", 10.0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = FormulaEngine.ComputeTimingDecision(ctx);
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal("BUY_BREAKOUT_PILOT_ONLY", result.Action);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
@@ -18,4 +18,8 @@
|
|||||||
<Using Include="Xunit" />
|
<Using Include="Xunit" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\QuantEngine.Core\QuantEngine.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,433 @@
|
|||||||
|
{
|
||||||
|
"runtimeTarget": {
|
||||||
|
"name": ".NETCoreApp,Version=v10.0",
|
||||||
|
"signature": ""
|
||||||
|
},
|
||||||
|
"compilationOptions": {},
|
||||||
|
"targets": {
|
||||||
|
".NETCoreApp,Version=v10.0": {
|
||||||
|
"QuantEngine.Core.Tests/1.0.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.NET.Test.Sdk": "17.14.1",
|
||||||
|
"QuantEngine.Core": "1.0.0",
|
||||||
|
"xunit": "2.9.3"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"QuantEngine.Core.Tests.dll": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.CodeCoverage/17.14.1": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.VisualStudio.CodeCoverage.Shim.dll": {
|
||||||
|
"assemblyVersion": "15.0.0.0",
|
||||||
|
"fileVersion": "17.1400.225.12603"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.NET.Test.Sdk/17.14.1": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.CodeCoverage": "17.14.1",
|
||||||
|
"Microsoft.TestPlatform.TestHost": "17.14.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.TestPlatform.ObjectModel/17.14.1": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.TestPlatform.CoreUtilities.dll": {
|
||||||
|
"assemblyVersion": "15.0.0.0",
|
||||||
|
"fileVersion": "17.1400.125.30202"
|
||||||
|
},
|
||||||
|
"lib/net8.0/Microsoft.TestPlatform.PlatformAbstractions.dll": {
|
||||||
|
"assemblyVersion": "15.0.0.0",
|
||||||
|
"fileVersion": "17.1400.125.30202"
|
||||||
|
},
|
||||||
|
"lib/net8.0/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll": {
|
||||||
|
"assemblyVersion": "15.0.0.0",
|
||||||
|
"fileVersion": "17.1400.125.30202"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"lib/net8.0/cs/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "cs"
|
||||||
|
},
|
||||||
|
"lib/net8.0/cs/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "cs"
|
||||||
|
},
|
||||||
|
"lib/net8.0/de/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "de"
|
||||||
|
},
|
||||||
|
"lib/net8.0/de/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "de"
|
||||||
|
},
|
||||||
|
"lib/net8.0/es/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "es"
|
||||||
|
},
|
||||||
|
"lib/net8.0/es/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "es"
|
||||||
|
},
|
||||||
|
"lib/net8.0/fr/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "fr"
|
||||||
|
},
|
||||||
|
"lib/net8.0/fr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "fr"
|
||||||
|
},
|
||||||
|
"lib/net8.0/it/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "it"
|
||||||
|
},
|
||||||
|
"lib/net8.0/it/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "it"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ja/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "ja"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ja/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "ja"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ko/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "ko"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ko/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "ko"
|
||||||
|
},
|
||||||
|
"lib/net8.0/pl/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "pl"
|
||||||
|
},
|
||||||
|
"lib/net8.0/pl/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "pl"
|
||||||
|
},
|
||||||
|
"lib/net8.0/pt-BR/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "pt-BR"
|
||||||
|
},
|
||||||
|
"lib/net8.0/pt-BR/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "pt-BR"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ru/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "ru"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ru/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "ru"
|
||||||
|
},
|
||||||
|
"lib/net8.0/tr/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "tr"
|
||||||
|
},
|
||||||
|
"lib/net8.0/tr/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "tr"
|
||||||
|
},
|
||||||
|
"lib/net8.0/zh-Hans/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "zh-Hans"
|
||||||
|
},
|
||||||
|
"lib/net8.0/zh-Hans/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "zh-Hans"
|
||||||
|
},
|
||||||
|
"lib/net8.0/zh-Hant/Microsoft.TestPlatform.CoreUtilities.resources.dll": {
|
||||||
|
"locale": "zh-Hant"
|
||||||
|
},
|
||||||
|
"lib/net8.0/zh-Hant/Microsoft.VisualStudio.TestPlatform.ObjectModel.resources.dll": {
|
||||||
|
"locale": "zh-Hant"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.TestPlatform.TestHost/17.14.1": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.TestPlatform.ObjectModel": "17.14.1",
|
||||||
|
"Newtonsoft.Json": "13.0.3"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.TestPlatform.CommunicationUtilities.dll": {
|
||||||
|
"assemblyVersion": "15.0.0.0",
|
||||||
|
"fileVersion": "17.1400.125.30202"
|
||||||
|
},
|
||||||
|
"lib/net8.0/Microsoft.TestPlatform.CrossPlatEngine.dll": {
|
||||||
|
"assemblyVersion": "15.0.0.0",
|
||||||
|
"fileVersion": "17.1400.125.30202"
|
||||||
|
},
|
||||||
|
"lib/net8.0/Microsoft.TestPlatform.Utilities.dll": {
|
||||||
|
"assemblyVersion": "15.0.0.0",
|
||||||
|
"fileVersion": "17.1400.125.30202"
|
||||||
|
},
|
||||||
|
"lib/net8.0/Microsoft.VisualStudio.TestPlatform.Common.dll": {
|
||||||
|
"assemblyVersion": "15.0.0.0",
|
||||||
|
"fileVersion": "17.1400.125.30202"
|
||||||
|
},
|
||||||
|
"lib/net8.0/testhost.dll": {
|
||||||
|
"assemblyVersion": "15.0.0.0",
|
||||||
|
"fileVersion": "17.1400.125.30202"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"lib/net8.0/cs/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "cs"
|
||||||
|
},
|
||||||
|
"lib/net8.0/cs/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "cs"
|
||||||
|
},
|
||||||
|
"lib/net8.0/cs/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "cs"
|
||||||
|
},
|
||||||
|
"lib/net8.0/de/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "de"
|
||||||
|
},
|
||||||
|
"lib/net8.0/de/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "de"
|
||||||
|
},
|
||||||
|
"lib/net8.0/de/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "de"
|
||||||
|
},
|
||||||
|
"lib/net8.0/es/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "es"
|
||||||
|
},
|
||||||
|
"lib/net8.0/es/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "es"
|
||||||
|
},
|
||||||
|
"lib/net8.0/es/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "es"
|
||||||
|
},
|
||||||
|
"lib/net8.0/fr/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "fr"
|
||||||
|
},
|
||||||
|
"lib/net8.0/fr/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "fr"
|
||||||
|
},
|
||||||
|
"lib/net8.0/fr/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "fr"
|
||||||
|
},
|
||||||
|
"lib/net8.0/it/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "it"
|
||||||
|
},
|
||||||
|
"lib/net8.0/it/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "it"
|
||||||
|
},
|
||||||
|
"lib/net8.0/it/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "it"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ja/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "ja"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ja/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "ja"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ja/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "ja"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ko/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "ko"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ko/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "ko"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ko/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "ko"
|
||||||
|
},
|
||||||
|
"lib/net8.0/pl/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "pl"
|
||||||
|
},
|
||||||
|
"lib/net8.0/pl/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "pl"
|
||||||
|
},
|
||||||
|
"lib/net8.0/pl/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "pl"
|
||||||
|
},
|
||||||
|
"lib/net8.0/pt-BR/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "pt-BR"
|
||||||
|
},
|
||||||
|
"lib/net8.0/pt-BR/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "pt-BR"
|
||||||
|
},
|
||||||
|
"lib/net8.0/pt-BR/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "pt-BR"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ru/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "ru"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ru/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "ru"
|
||||||
|
},
|
||||||
|
"lib/net8.0/ru/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "ru"
|
||||||
|
},
|
||||||
|
"lib/net8.0/tr/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "tr"
|
||||||
|
},
|
||||||
|
"lib/net8.0/tr/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "tr"
|
||||||
|
},
|
||||||
|
"lib/net8.0/tr/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "tr"
|
||||||
|
},
|
||||||
|
"lib/net8.0/zh-Hans/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "zh-Hans"
|
||||||
|
},
|
||||||
|
"lib/net8.0/zh-Hans/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "zh-Hans"
|
||||||
|
},
|
||||||
|
"lib/net8.0/zh-Hans/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "zh-Hans"
|
||||||
|
},
|
||||||
|
"lib/net8.0/zh-Hant/Microsoft.TestPlatform.CommunicationUtilities.resources.dll": {
|
||||||
|
"locale": "zh-Hant"
|
||||||
|
},
|
||||||
|
"lib/net8.0/zh-Hant/Microsoft.TestPlatform.CrossPlatEngine.resources.dll": {
|
||||||
|
"locale": "zh-Hant"
|
||||||
|
},
|
||||||
|
"lib/net8.0/zh-Hant/Microsoft.VisualStudio.TestPlatform.Common.resources.dll": {
|
||||||
|
"locale": "zh-Hant"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Newtonsoft.Json/13.0.3": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net6.0/Newtonsoft.Json.dll": {
|
||||||
|
"assemblyVersion": "13.0.0.0",
|
||||||
|
"fileVersion": "13.0.3.27908"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xunit/2.9.3": {
|
||||||
|
"dependencies": {
|
||||||
|
"xunit.assert": "2.9.3",
|
||||||
|
"xunit.core": "2.9.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xunit.abstractions/2.0.3": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/netstandard2.0/xunit.abstractions.dll": {
|
||||||
|
"assemblyVersion": "2.0.0.0",
|
||||||
|
"fileVersion": "2.0.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xunit.assert/2.9.3": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net6.0/xunit.assert.dll": {
|
||||||
|
"assemblyVersion": "2.9.3.0",
|
||||||
|
"fileVersion": "2.9.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xunit.core/2.9.3": {
|
||||||
|
"dependencies": {
|
||||||
|
"xunit.extensibility.core": "2.9.3",
|
||||||
|
"xunit.extensibility.execution": "2.9.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xunit.extensibility.core/2.9.3": {
|
||||||
|
"dependencies": {
|
||||||
|
"xunit.abstractions": "2.0.3"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/netstandard1.1/xunit.core.dll": {
|
||||||
|
"assemblyVersion": "2.9.3.0",
|
||||||
|
"fileVersion": "2.9.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xunit.extensibility.execution/2.9.3": {
|
||||||
|
"dependencies": {
|
||||||
|
"xunit.extensibility.core": "2.9.3"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/netstandard1.1/xunit.execution.dotnet.dll": {
|
||||||
|
"assemblyVersion": "2.9.3.0",
|
||||||
|
"fileVersion": "2.9.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"QuantEngine.Core/1.0.0": {
|
||||||
|
"runtime": {
|
||||||
|
"QuantEngine.Core.dll": {
|
||||||
|
"assemblyVersion": "1.0.0.0",
|
||||||
|
"fileVersion": "1.0.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"libraries": {
|
||||||
|
"QuantEngine.Core.Tests/1.0.0": {
|
||||||
|
"type": "project",
|
||||||
|
"serviceable": false,
|
||||||
|
"sha512": ""
|
||||||
|
},
|
||||||
|
"Microsoft.CodeCoverage/17.14.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-pmTrhfFIoplzFVbhVwUquT+77CbGH+h4/3mBpdmIlYtBi9nAB+kKI6dN3A/nV4DFi3wLLx/BlHIPK+MkbQ6Tpg==",
|
||||||
|
"path": "microsoft.codecoverage/17.14.1",
|
||||||
|
"hashPath": "microsoft.codecoverage.17.14.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.NET.Test.Sdk/17.14.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-HJKqKOE+vshXra2aEHpi2TlxYX7Z9VFYkr+E5rwEvHC8eIXiyO+K9kNm8vmNom3e2rA56WqxU+/N9NJlLGXsJQ==",
|
||||||
|
"path": "microsoft.net.test.sdk/17.14.1",
|
||||||
|
"hashPath": "microsoft.net.test.sdk.17.14.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.TestPlatform.ObjectModel/17.14.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-xTP1W6Mi6SWmuxd3a+jj9G9UoC850WGwZUps1Wah9r1ZxgXhdJfj1QqDLJkFjHDCvN42qDL2Ps5KjQYWUU0zcQ==",
|
||||||
|
"path": "microsoft.testplatform.objectmodel/17.14.1",
|
||||||
|
"hashPath": "microsoft.testplatform.objectmodel.17.14.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.TestPlatform.TestHost/17.14.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-d78LPzGKkJwsJXAQwsbJJ7LE7D1wB+rAyhHHAaODF+RDSQ0NgMjDFkSA1Djw18VrxO76GlKAjRUhl+H8NL8Z+Q==",
|
||||||
|
"path": "microsoft.testplatform.testhost/17.14.1",
|
||||||
|
"hashPath": "microsoft.testplatform.testhost.17.14.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Newtonsoft.Json/13.0.3": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==",
|
||||||
|
"path": "newtonsoft.json/13.0.3",
|
||||||
|
"hashPath": "newtonsoft.json.13.0.3.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"xunit/2.9.3": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==",
|
||||||
|
"path": "xunit/2.9.3",
|
||||||
|
"hashPath": "xunit.2.9.3.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"xunit.abstractions/2.0.3": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==",
|
||||||
|
"path": "xunit.abstractions/2.0.3",
|
||||||
|
"hashPath": "xunit.abstractions.2.0.3.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"xunit.assert/2.9.3": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==",
|
||||||
|
"path": "xunit.assert/2.9.3",
|
||||||
|
"hashPath": "xunit.assert.2.9.3.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"xunit.core/2.9.3": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==",
|
||||||
|
"path": "xunit.core/2.9.3",
|
||||||
|
"hashPath": "xunit.core.2.9.3.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"xunit.extensibility.core/2.9.3": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==",
|
||||||
|
"path": "xunit.extensibility.core/2.9.3",
|
||||||
|
"hashPath": "xunit.extensibility.core.2.9.3.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"xunit.extensibility.execution/2.9.3": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==",
|
||||||
|
"path": "xunit.extensibility.execution/2.9.3",
|
||||||
|
"hashPath": "xunit.extensibility.execution.2.9.3.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"QuantEngine.Core/1.0.0": {
|
||||||
|
"type": "project",
|
||||||
|
"serviceable": false,
|
||||||
|
"sha512": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
+13
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"runtimeOptions": {
|
||||||
|
"tfm": "net10.0",
|
||||||
|
"framework": {
|
||||||
|
"name": "Microsoft.NETCore.App",
|
||||||
|
"version": "10.0.0"
|
||||||
|
},
|
||||||
|
"configProperties": {
|
||||||
|
"MSTest.EnableParentProcessQuery": true,
|
||||||
|
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user