3 Commits
3 changed files with 102 additions and 136 deletions
+100
View File
@@ -0,0 +1,100 @@
schema: gitea-agent/release/v1
version:
sources:
- path: pyproject.toml
format: toml
selector: project.version
lockfiles:
- path: uv.lock
package: jarca-ledger-mcp
tag_template: "v{version}"
prepare:
runtime:
python: "3.12"
commands:
- name: sync
argv: [uv, sync, --python, "3.12", --frozen, --extra, dev, --extra, dashboard]
- name: lint
argv: [uv, run, ruff, check, .]
- name: test
argv: [uv, run, pytest]
- name: dashboard-http-smoke
argv: [uv, run, python, scripts/ci_dashboard_http_smoke.py, --host, 127.0.0.1, --port, "18501", --timeout, "45"]
- name: db-safety-smoke
argv: [uv, run, python, scripts/ci_db_safety_smoke.py, --timeout, "45"]
artifacts:
- type: container
name: jarca-ledger-mcp
platform: linux/arm64
build:
adapter: docker-buildx
context: .
dockerfile: Dockerfile
pull: true
local_tag_template: "jarca-ledger-mcp:release-{commit}"
labels:
org.opencontainers.image.source: "https://gitea.jsfamily.quest/genichin/jarca-ledger-mcp"
org.opencontainers.image.revision: "{commit}"
org.opencontainers.image.version: "{version}"
smoke:
- type: image-platform
expected_os: linux
expected_architecture: arm64
- type: container-command
environment:
JARCA_LEDGER_DB_PATH: /tmp/ledger.sqlite3
argv:
- python
- -c
- >-
import platform; from jarca_ledger_mcp import service;
result = service.health_check();
assert platform.machine() == "aarch64";
assert result["success"], result
- type: container-health
name_template: "jarca-mcp-{run_id}"
command: default
expected: healthy
timeout_seconds: 90
- type: container-http
name_template: "jarca-dashboard-{run_id}"
command:
- python
- -m
- streamlit
- run
- dashboard/app.py
- --server.address
- 0.0.0.0
- --server.port
- "8501"
- --server.headless
- "true"
- --browser.gatherUsageStats
- "false"
container_port: 8501
path: /_stcore/health
expected_status: 200
expected_body: ok
timeout_seconds: 90
publish:
adapter: gitea-container-registry
registry: gitea.jsfamily.quest
repository: genichin/jarca-ledger-mcp
credentials:
username: authenticated-gitea-login
token_env: GITEA_TOKEN
tags:
- "{tag}"
- "sha-{commit}"
overwrite: false
verify:
- digest
- platform
- org.opencontainers.image.revision
-134
View File
@@ -1,134 +0,0 @@
name: Verify and publish ARM64 image
on:
pull_request:
paths:
- Dockerfile
- .dockerignore
- pyproject.toml
- uv.lock
- jarca_ledger_mcp/**
- dashboard/**
- .github/workflows/publish-image.yml
push:
tags:
- "v*"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: image-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
verify-arm64:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v3
with:
platforms: arm64
- uses: docker/setup-buildx-action@v3
- name: Build ARM64 verification image
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/arm64
load: true
push: false
tags: jarca-ledger-mcp:arm64-verify
cache-from: type=gha,scope=jarca-ledger-arm64
cache-to: type=gha,mode=max,scope=jarca-ledger-arm64
- name: Verify image platform, imports, and SQLite
run: |
set -euo pipefail
test "$(docker image inspect jarca-ledger-mcp:arm64-verify --format '{{.Architecture}}')" = arm64
docker run --rm --platform linux/arm64 \
-e JARCA_LEDGER_DB_PATH=/tmp/ledger.sqlite3 \
jarca-ledger-mcp:arm64-verify \
python -c 'import platform, cryptography, numpy, pandas, pyarrow, pymupdf; from jarca_ledger_mcp import service; result=service.health_check(); assert platform.machine() == "aarch64"; assert result["success"], result; print(result["schema_version"])'
- name: Verify MCP and Dashboard startup
run: |
set -euo pipefail
trap 'docker rm -f jarca-mcp-smoke jarca-dashboard-smoke >/dev/null 2>&1 || true' EXIT
docker run -d --name jarca-mcp-smoke --platform linux/arm64 \
jarca-ledger-mcp:arm64-verify
for _ in $(seq 1 60); do
status=$(docker inspect --format '{{.State.Health.Status}}' jarca-mcp-smoke)
test "$status" = healthy && break
test "$status" = unhealthy && { docker logs jarca-mcp-smoke; exit 1; }
sleep 1
done
test "$(docker inspect --format '{{.State.Health.Status}}' jarca-mcp-smoke)" = healthy
docker run -d --name jarca-dashboard-smoke --platform linux/arm64 \
-p 127.0.0.1::8501 jarca-ledger-mcp:arm64-verify \
python -m streamlit run dashboard/app.py \
--server.address 0.0.0.0 --server.port 8501 \
--server.headless true --browser.gatherUsageStats false
port=$(docker port jarca-dashboard-smoke 8501/tcp | sed 's/.*://')
code=000
for _ in $(seq 1 90); do
code=$(curl -sS -o /tmp/dashboard-health -w '%{http_code}' \
"http://127.0.0.1:${port}/_stcore/health" || true)
test "$code" = 200 && break
sleep 1
done
test "$code" = 200
test "$(tr -d '\n' < /tmp/dashboard-health)" = ok
publish:
if: startsWith(github.ref, 'refs/tags/v')
needs: verify-arm64
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v3
with:
platforms: arm64
- uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and publish immutable ARM64 image
id: build
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/arm64
push: true
tags: |
ghcr.io/genichin/jarca-ledger-mcp:${{ github.ref_name }}
ghcr.io/genichin/jarca-ledger-mcp:sha-${{ github.sha }}
labels: |
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.version=${{ github.ref_name }}
cache-from: type=gha,scope=jarca-ledger-arm64
cache-to: type=gha,mode=max,scope=jarca-ledger-arm64
provenance: mode=max
sbom: true
- name: Report published digest
run: |
echo "ghcr.io/genichin/jarca-ledger-mcp:${GITHUB_REF_NAME}@${{ steps.build.outputs.digest }}"
+2 -2
View File
@@ -90,11 +90,11 @@ Deployment: jarca-ledger (replicas: 1)
- MCP와 frontend는 목적별 Kubernetes Service로 분리합니다. MCP는 `8000`, frontend는 `8501`을 대상으로 합니다.
- 외부 공개가 필요하면 일반적으로 frontend Service만 Ingress 또는 NodePort에 연결하고, MCP Service는 필요한 범위에서만 노출합니다.
- ARM64 worker에서만 실행하도록 Pod의 `nodeSelector` 또는 node affinity를 설정합니다.
- 배포 이미지는 가변 태그 대신 검증된 GHCR digest로 고정합니다.
- 배포 이미지는 가변 태그 대신 `gitea.jsfamily.quest/genichin/jarca-ledger-mcp`에서 확인한 Gitea Container Registry digest로 고정합니다.
Streamlit이 SQLite에 직접 접근하지 않고 MCP/API만 호출하도록 변경하거나 네트워크 DB로 전환한 뒤에는 MCP와 frontend를 각각 독립 Deployment로 분리할 수 있습니다.
로컬 `docker-compose.yml`도 동일한 애플리케이션 이미지를 두 컨테이너로 실행하는 구조입니다. 현재 Compose 설정은 로컬에서 빌드한 `jarca-ledger:latest`를 사용하며, MCP는 호스트 `8000` 포트로, Streamlit은 호스트 `40059`에서 컨테이너 `8501` 포트로 연결됩니다. Compose 실행 전 host environment 또는 안전한 `.env` 전달 경계에 두 Dashboard credential 변수를 설정해야 하며 누락 시 Compose가 시작을 거부합니다. GHCR 릴리즈를 배포할 때는 두 서비스의 `image`를 검증된 동일 digest로 지정해야 합니다.
로컬 `docker-compose.yml`도 동일한 애플리케이션 이미지를 두 컨테이너로 실행하는 구조입니다. 현재 Compose 설정은 로컬에서 빌드한 `jarca-ledger:latest`를 사용하며, MCP는 호스트 `8000` 포트로, Streamlit은 호스트 `40059`에서 컨테이너 `8501` 포트로 연결됩니다. Compose 실행 전 host environment 또는 안전한 `.env` 전달 경계에 두 Dashboard credential 변수를 설정해야 하며 누락 시 Compose가 시작을 거부합니다. Gitea Container Registry 릴리즈를 배포할 때는 두 서비스의 `image`를 `gitea.jsfamily.quest/genichin/jarca-ledger-mcp`의 검증된 동일 digest로 지정해야 합니다.
## 주요 도구