diff --git a/README.md b/README.md index 7e5d459..0d5de57 100644 --- a/README.md +++ b/README.md @@ -57,9 +57,14 @@ cp .env.example .env # MINIMAX_API_KEY 필수, TELEGRAM_* 선택 | 환경 | 방법 | |---|---| | macOS | `bash deploy/install-launchd.sh` (launchd, KeepAlive) | +| macOS (Apple container) | `bash deploy/install-apple-container.sh` — 데몬+대시보드 빌드·기동. `--boot`를 붙이면 로그인 시 자동 기동(LaunchAgent) 등록 | | Linux | `deploy/us2kr.service` → `systemctl enable --now us2kr` | | Docker | `docker compose -f deploy/compose.yaml up -d --build` | +Apple container(`brew install container`)는 restart 정책이 없어 재부팅 후 자동 기동은 `--boot`의 +LaunchAgent(`container system start` + `container start us2kr`)가 담당합니다. +`data/` `reports/` `logs/`는 호스트 디렉터리를 그대로 마운트하므로 launchd 방식과 산출물 위치가 같습니다. + > macOS 노트북은 **잠자기 중에는 실행되지 않습니다.** 06~07시에 깨어 있도록 > `sudo pmset repeat wakeorpoweron MTWRFS 06:00:00` 로 예약 기동을 걸거나, 항상 켜진 머신/서버에서 돌리세요. @@ -76,6 +81,8 @@ cp .env.example .env # MINIMAX_API_KEY 필수, TELEGRAM_* 선택 | `US2KR_CATCHUP_WINDOW_HOURS` | `3` | 재시작 시 따라잡기 허용 창 | | `US2KR_SKIP_WHEN_KRX_CLOSED` | `false` | 한국 휴장일에 다음 미국 세션이 먼저 마감되어 브리핑이 무의미해질 때만 스킵(금요일 세션은 항상 발송) | | `US2KR_NEWS_LOOKBACK_HOURS` / `US2KR_NEWS_MAX_ITEMS` | `18` / `40` | 뉴스 수집 범위 | +| `SEARXNG_BASE_URL` | — | 있으면 RSS 외에 SearXNG 뉴스 검색 결과도 병합 (인스턴스에 `json` 포맷 활성화 필요) | +| `US2KR_SEARXNG_QUERIES` | 기본 검색어 3개 | SearXNG 검색어 CSV (기본: `us2kr/collectors/searx.py`의 `DEFAULT_QUERIES`) | | `US2KR_KRX_EXTRA_HOLIDAYS` | — | 캘린더에 없는 한국 임시휴장일 CSV (예: `2026-06-03`) | ## 산출물 @@ -84,6 +91,22 @@ cp .env.example .env # MINIMAX_API_KEY 필수, TELEGRAM_* 선택 - `data/us2kr.sqlite3` — 시세 스냅샷·리포트·발송 기록(전일 대비 비교, 중복 방지) - `logs/us2kr.log` — 일자별 회전, API 키 마스킹 +## 대시보드 (dashboard/) + +쌓인 브리핑을 보는 웹 대시보드(TypeScript, 런타임 의존성 없는 Node 24 서버 + 번들 클라이언트). +`reports/*.json`만 읽는 읽기 전용 뷰라 데몬의 SQLite(WAL)와 절대 경합하지 않습니다. + +- 최신 세션 요약 타일(S&P·나스닥·SOX·VIX·달러/원·10년물), 종목별 종가 추이 차트(표 전환 가능) +- 브리핑 목록/본문(Markdown 렌더, sanitize), 데이터 결측·오류 로그 표시 +- Apple container 배포 시 함께 기동: `http://localhost:8090` (포트: `US2KR_DASHBOARD_PORT`) + +```bash +cd dashboard +npm install +npm run build # tsc 타입체크 + esbuild 번들 +US2KR_REPORTS_DIR=../reports npm start # http://localhost:8090 +``` + ## 개발 ```bash diff --git a/deploy/com.us2kr.container.plist b/deploy/com.us2kr.container.plist new file mode 100644 index 0000000..45a58ae --- /dev/null +++ b/deploy/com.us2kr.container.plist @@ -0,0 +1,18 @@ + + + + + + Labelcom.us2kr.container + ProgramArguments + + /bin/bash + -c + __CONTAINER__ system start && __CONTAINER__ start us2kr; __CONTAINER__ start us2kr-dashboard + + RunAtLoad + StandardOutPath__ROOT__/logs/container-boot.log + StandardErrorPath__ROOT__/logs/container-boot.log + + diff --git a/deploy/install-apple-container.sh b/deploy/install-apple-container.sh new file mode 100644 index 0000000..55e1196 --- /dev/null +++ b/deploy/install-apple-container.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# macOS: Apple container(https://github.com/apple/container)로 빌드·상주 실행. +# 실행: bash deploy/install-apple-container.sh # 이미지 빌드 + 컨테이너 (재)기동 +# bash deploy/install-apple-container.sh --boot # + 로그인 시 자동 기동(LaunchAgent) 등록 +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +NAME=us2kr + +CONTAINER_BIN="$(command -v container || true)" +if [ -z "$CONTAINER_BIN" ]; then + echo "Apple container CLI가 없습니다: brew install container (macOS 15+, Apple Silicon)" >&2 + exit 1 +fi +if [ ! -f "$ROOT/.env" ]; then + echo "$ROOT/.env 가 없습니다: cp .env.example .env 후 MINIMAX_API_KEY를 넣으세요" >&2 + exit 1 +fi + +DASHBOARD=us2kr-dashboard +DASHBOARD_PORT="${US2KR_DASHBOARD_PORT:-8090}" + +mkdir -p "$ROOT/data" "$ROOT/reports" "$ROOT/logs" +container system start +container build -t "$NAME" -f "$ROOT/deploy/Dockerfile" "$ROOT" +container build -t "$DASHBOARD" -f "$ROOT/dashboard/Dockerfile" "$ROOT/dashboard" + +# 기존 컨테이너는 갈아끼운다 (이미지 갱신 반영). container에는 restart 정책이 없어 +# 자동 기동은 --boot 의 LaunchAgent가 담당한다. +container stop "$NAME" "$DASHBOARD" >/dev/null 2>&1 || true +container delete "$NAME" "$DASHBOARD" >/dev/null 2>&1 || true +container run --detach --name "$NAME" \ + --env-file "$ROOT/.env" \ + --env US2KR_DATA_DIR=/app/data \ + --env US2KR_REPORTS_DIR=/app/reports \ + --env US2KR_LOG_DIR=/app/logs \ + --volume "$ROOT/data:/app/data" \ + --volume "$ROOT/reports:/app/reports" \ + --volume "$ROOT/logs:/app/logs" \ + "$NAME" +container run --detach --name "$DASHBOARD" \ + --volume "$ROOT/reports:/data/reports:ro" \ + --publish "$DASHBOARD_PORT:8090" \ + "$DASHBOARD" + +if [ "${1:-}" = "--boot" ]; then + PLIST_SRC="$ROOT/deploy/com.us2kr.container.plist" + PLIST_DST="$HOME/Library/LaunchAgents/com.us2kr.container.plist" + mkdir -p "$HOME/Library/LaunchAgents" + sed -e "s#__CONTAINER__#$CONTAINER_BIN#g" -e "s#__ROOT__#$ROOT#g" "$PLIST_SRC" > "$PLIST_DST" + launchctl bootout "gui/$(id -u)/com.us2kr.container" 2>/dev/null || true + launchctl bootstrap "gui/$(id -u)" "$PLIST_DST" + echo "boot agent: $PLIST_DST (로그인 시 container system start + container start $NAME $DASHBOARD)" +fi + +echo "dashboard: http://localhost:$DASHBOARD_PORT" +echo "status: container list --all" +echo "logs: container logs --follow $NAME (파일: tail -f $ROOT/logs/us2kr.log)" +echo "stop: container stop $NAME $DASHBOARD" +echo "remove: container stop $NAME $DASHBOARD && container delete $NAME $DASHBOARD"