-
Notifications
You must be signed in to change notification settings - Fork 0
DEPLOYMENT
๋ชจ๋ธ: ๋ชจ๋ ์๋น์ค๋ ์ ์์ฅ ๋ก์ปฌ PC์์ ์คํ. ๋์ปค ์ด๋ฏธ์ง ๋น๋๋ง GitHub Actions(ํด๋ผ์ฐ๋)์์ ์ํํ๊ณ ์ปจํ ์ด๋ ๋ ์ง์คํธ๋ฆฌ์ push. ๋ก์ปฌ PC๋ ํ(pin)๋ ๋ฒ์ ์ pullํด ์คํ.
๋ณธ ์์คํ ์ ๋จ์ผ ์ฌ์ดํธ ํ ๋ก์ปฌ ์ด์ + ๋์ ์ฒดํ์ 3๋ช + ์นด๋ฉ๋ผ ~8๋ + ๋ก๋ด 2๋ ๊ธฐ์ค.
| ๋ ธ๋ | ์ฉ๋ | ๊ถ์ฅ ์ฌ์ | ์ถ์ ๋น์ฉ๋ |
|---|---|---|---|
| GPU ๋ ธ๋ | Frigate(๊ฐ์ฒด ํ์ง) | CPU 8C16T (Intel i7/Ryzen 7), RAM 32GB, GPU NVIDIA RTX 4060 8GB VRAM ๋๋ USB Coral TPU, NVMe(NVM Express, ๊ณ ์ ์ ์ฅ ์ธํฐํ์ด์ค) 1TB | ์ค์ |
| CPU ๋ ธ๋ | mosquitto, HA, orchestrator, bridge, mcp-*, ๊ด์ธก ์คํ | CPU 4C8T (Intel i5/Ryzen 5), RAM 16GB, NVMe 512GB | ์ค |
๋๋ ํตํฉํ(๊ถ์ฅ): ์ ๋์ ํ๋์ ์ํฌ์คํ ์ด์ ์ผ๋ก ๋ฌถ์ด ๋จ์ผ ๋ ธ๋๋ก ์ด์.
- CPU 8C16T, RAM 32GB, GPU RTX 4060 8GB, NVMe 1TB, 1Gbps Ethernet ร2.
- ์ปจํ
์ด๋ ๋ฆฌ์์ค ๋ถ๋ฆฌ๋ docker compose์
deploy.resources๋๋ Linux cgroup์ผ๋ก.
| ์ปจํ ์ด๋ | RAM ์ถ์ | CPU ์ถ์ | GPU |
|---|---|---|---|
| Frigate (8 cam, 5fps detect) | 1.5~2 GB | 2 ์ฝ์ด | TensorRT ๋๋ Coral 1๊ฐ โ 8 GPU stream ๊ฐ๋ฅ |
| Home Assistant (recorder + entities) | 600 MB ~ 1 GB | 0.5 ์ฝ์ด | โ |
| Mosquitto | 50 MB | 0.1 ์ฝ์ด | โ |
| orchestrator (LangGraph) | 400 MB | 0.5 ์ฝ์ด (idle) ~ 2 ์ฝ์ด (Claude ํธ์ถ burst) | โ |
| bridge (ReID ์๋ฒ ๋ฉ) | 800 MB ~ 1.5 GB | 1 ์ฝ์ด (5Hz position publish) | (์ ํ) GPU 0.5 GB for OSNet |
| mcp-robot-adapter | 300 MB | 0.5 ์ฝ์ด | โ |
| mcp-tablet-gateway | 200 MB | 0.2 ์ฝ์ด | โ |
| Prometheus + Loki + Grafana | 1.5 GB | 0.5 ์ฝ์ด | โ |
| ํฉ๊ณ (ํค๋๋ฃธ 30%) | ~16 GB | ~8 ์ฝ์ด | ~8 GB VRAM |
| ๋ฐ์ดํฐ | ์์ ์ฌ์ฉ | ๋ณด์กด |
|---|---|---|
| HA recorder DB | < 500 MB / 3์ผ (purge_keep_days: 3) |
3์ผ |
| Loki ๋ก๊ทธ | 5 GB / 7์ผ | 7์ผ |
| Prometheus ๋ฉํธ๋ฆญ | 10 GB / 15์ผ | 15์ผ |
| Frigate ์์ ํด๋ฆฝ | 0 (record.enabled: false) |
โ |
| ์ปจํ ์ด๋ ์ด๋ฏธ์ง | 15~20 GB (8 ์ปจํ ์ด๋) | โ |
| ์์คํ + ์ฌ์ | ~50 GB | โ |
| ํฉ๊ณ |
| ํธ๋ํฝ | ์ถ์ ๋์ญํญ |
|---|---|
| RTSP(Real Time Streaming Protocol, ์ค์๊ฐ ์คํธ๋ฆฌ๋ฐ ํ๋กํ ์ฝ) ์ ๋ ฅ (8 ์นด๋ฉ๋ผ ร 720p ร ~3Mbps) | ~24 Mbps |
| MQTT(Message Queuing Telemetry Transport, ๋ฉ์์ง ํ ํ ๋ ๋ฉํธ๋ฆฌ ์ ์ก ํ๋กํ ์ฝ) (๋ชจ๋ ๋๋ฉ์ธ ํ ํฝ ํฉ์ฐ, ํผํฌ) | < 1 Mbps |
| Anthropic API (LLM(Large Language Model, ๋๊ท๋ชจ ์ธ์ด ๋ชจ๋ธ) ํธ์ถ) | ๊ฐ๋ณ, ๋ณดํต < 5 Mbps |
| ghcr.io(GitHub Container Registry, GitHub ์ปจํ ์ด๋ ๋ ์ง์คํธ๋ฆฌ) pull (๋ฐฐํฌ ์๋ง) | ๊ฐ๋ณ |
| ์ด์์ ๋์๋ณด๋ viewing | < 5 Mbps |
๊ถ์ฅ: 1 Gbps Ethernet ร 2 (์นด๋ฉ๋ผ segment + ์ผ๋ฐ segment ๋ถ๋ฆฌ) + 5GHz Wi-Fi AP 2๋ (ํ๋ธ๋ฆฟ์ฉ ๋ณ๋ SSID(Service Set Identifier, ๋ฌด์ ๋คํธ์ํฌ ์๋ณ์)).
- ๋ฉ์ธ ๋ ธ๋ + ๋คํธ์ํฌ ์ค์์น + AP๋ UPS(Uninterruptible Power Supply, ๋ฌด์ ์ ์ ์๊ณต๊ธ์ฅ์น) ๋ณดํธ ํ์ (์ ์ ์ด์ ์ค ์ ์ โ ๋ฐ์ดํฐ ์์ ๋ฐฉ์ง).
- UPS ์ฉ๋: ๋ ธ๋ ~150W + ๋คํธ์ํฌ ~50W = 200W ร 5๋ถ ๋ฐฑ์ โ ์ต์ 600VA.
- ์นด๋ฉ๋ผ๋ PoE(Power over Ethernet, ์ด๋๋ท ์ ๋ ฅ ๊ณต๊ธ) ์ค์์น ์ฌ์ฉ ์ ์๋ ๋ณดํธ.
- ๋ก๋ด์ ์์ฒด ๋ฐฐํฐ๋ฆฌ โ ์ํฅ ์์.
- ๋ ธ๋ ๋ฐ์ดยท์์: ์ ์์ฅ ๋ฐฑ์คํผ์ค(๊ด๋๊ฐ ๋น๊ณต๊ฐ)์ ์ค์น, ์ง์ฌ๊ด์ ยท์๋ถ ํํผ.
- ์ผ์ด๋ธ: RTSPยท๋ก๋ด ํต์ ์ ๊ฐ๋ฅํ ์ ์ , ํ๋ธ๋ฆฟ๋ง ๋ฌด์ .
- ์นด๋ฉ๋ผ ์ผ์ด๋ธ ๊ธธ์ด > 50m ์ ๊ด ๋ณํ ๋๋ PoE+ ์ฌ์ฉ.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ GitHub (์ฝ๋ ์ ์ฅ์) โ
โ โ โ
โ โ git push โ
โ โผ โ
โ GitHub Actions (CI/CD) โ
โ โ docker build โ
โ โผ โ
โ ghcr.io (Container Registry) โ
โ โ ghcr.io/OWNER/zerone/<svc>:TAG โ
โโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โ docker pull (์ ์์ฅ์์ ํธ๋ฆฌ๊ฑฐ)
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ์ ์์ฅ ๋ก์ปฌ PC (Edge) โ
โ โ
โ docker-compose.yml โ
โ โโโ mosquitto (์ธ๋ถ ์ด๋ฏธ์ง) โ
โ โโโ frigate (์ธ๋ถ ์ด๋ฏธ์ง) โ
โ โโโ home-assistant (์ธ๋ถ ์ด๋ฏธ์ง) โ
โ โโโ bridge (ghcr.io) โ
โ โโโ orchestrator (ghcr.io) โ
โ โโโ mcp-robot-adapter (ghcr.io) โ
โ โโโ mcp-tablet-gateway(ghcr.io) โ
โ โ
โ ์ธ๋ถ ์์กด: Anthropic API + ์ธํฐ๋ท ํ์ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
์์น:
- ๋น๋(ํด๋ผ์ฐ๋)์ ์คํ(๋ก์ปฌ)์ด ์์ ํ ๋ถ๋ฆฌ.
- ๋ก์ปฌ์ ์ด๋ฏธ์ง๋ฅผ ๊ฐ์ ธ์ ์คํ๋ง ํจ โ ๋น๋ ๋๊ตฌยท์์ค์ฝ๋ ์์.
- ๋ฒ์ ์ ๋ช ์์ ํ๊ทธ๋ก ํ(pin) โ ์ ์ ์ด์ ์ค ์๋์น ์์ ์๋ ๊ฐฑ์ ๋ฐฉ์ง.
์ ghcr.io:
- GitHub Actions์ ๋ค์ดํฐ๋ธ ํตํฉ (
GITHUB_TOKEN๋ง์ผ๋ก push ์ธ์ฆ). - ํผ๋ธ๋ฆญ/ํ๋ผ์ด๋น ๋ชจ๋ ๋ฌด๋ฃ.
- AWS ECR๊ณผ ๋ฌ๋ฆฌ IAMยทVPC ์ค์ ์์ด ๋ฐ๋ก ์ฌ์ฉ ๊ฐ๋ฅ.
- ํ(pull) ์ธ์ฆ๋ PAT(Personal Access Token, ๊ฐ์ธ ์ก์ธ์ค ํ ํฐ) 1๊ฐ๋ก ์ฒ๋ฆฌ.
๋์:
- Docker Hub: ๋ฌด๋ฃ ํฐ์ด pull rate limit์ด ์์ด ๋ก์ปฌ์์ ์์ฃผ pull ์ ๋งํ ์ ์์ โ ๋น์ถ.
- AWS ECR: AWS ๊ณ์ ยทIAM์ด ํ์ โ ๋จ๋ ์ฌ์ฉ์ ๊ณผ๋.
| ํ๊ทธ | ์๋ฏธ | ์ธ์ ์์ฑ |
|---|---|---|
sha-<short-sha> |
ํน์ ์ปค๋ฐ | ๋ชจ๋ main ๋ธ๋์น push |
main |
main์ ์ต์ | ๋ชจ๋ main ๋ธ๋์น push (์ด์ main ํ๊ทธ ๋ฎ์ด์) |
vX.Y.Z |
๋ฆด๋ฆฌ์ฆ | git tag v* push |
latest |
์ต์ ์์ ๋ฆด๋ฆฌ์ฆ | git tag v* push |
์ ์ ์ด์ ๊ถ์ฅ: docker-compose์์ vX.Y.Z ๋๋ sha-<short-sha>๋ก ๋ช
์์ ํ. latest/main์ dev ํ๊ฒฝ์์๋ง.
์ด์ : ์ ์ ๋์ค ๋๊ตฐ๊ฐ docker compose pull && up -d ์คํํ์ ๋ ์๋์น ์์ ์ ๋ฒ์ ์ด ๋ค์ด์ ๋ผ์ด๋ธ ์์ฐ์ด ๊นจ์ง๋ ์ฌ๊ณ ๋ฅผ ๋ฐฉ์ง.
# .github/workflows/build-images.yml
name: build-images
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_PREFIX: ${{ github.repository }} # OWNER/zerone
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
services: ${{ steps.filter.outputs.changes }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
orchestrator: services/orchestrator/**
bridge: services/bridge/**
mcp-robot-adapter: services/mcp-robot-adapter/**
mcp-tablet-gateway: services/mcp-tablet-gateway/**
build:
needs: detect-changes
if: needs.detect-changes.outputs.services != '[]'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write # ghcr.io push ๊ถํ
strategy:
fail-fast: false
matrix:
service: ${{ fromJSON(needs.detect-changes.outputs.services) }}
steps:
- uses: actions/checkout@v4
- name: Log in to ghcr.io
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Compute tags
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/${{ matrix.service }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix=sha-,format=short
type=semver,pattern={{version}}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
- uses: docker/setup-buildx-action@v3
- name: Build & push
uses: docker/build-push-action@v5
with:
context: services/${{ matrix.service }}
platforms: linux/amd64 # ์ ์์ฅ PC ์ํคํ
์ฒ
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max-
services/orchestrator/**๋ณ๊ฒฝ + main push โghcr.io/OWNER/zerone/orchestrator:main,:sha-abc1234๋ ํ๊ทธ. -
git tag v1.2.0 && git push --tagsโ:v1.2.0,:latest์ถ๊ฐ push. - PR์ ๋น๋๋ง ํ๊ณ push ์ ํจ (dry-run ๊ฒ์ฆ).
๋น๋ ์งํ Trivy๋ก ์ทจ์ฝ์ ์ค์บ ์ถ๊ฐ:
- name: Trivy scan
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/${{ matrix.service }}@${{ steps.meta.outputs.digest }}
format: 'sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'๋ ํฌ์ deploy/ ๋๋ ํฐ๋ฆฌ์ ๋ฐฐ์น โ ๋น๋ ์ปจํ
์คํธ๋ ์์ค์ฝ๋ ์์ด compose ํ์ผ๊ณผ ์ค์ ๋ง.
# deploy/docker-compose.yml
version: "3.8"
x-image-prefix: &image-prefix ghcr.io/OWNER/zerone
networks:
edge:
driver: bridge
volumes:
ha_config:
frigate_media:
mosquitto_data:
services:
mosquitto:
image: eclipse-mosquitto:2.0
restart: always
ports: ["1883:1883"]
volumes:
- ./mosquitto/config:/mosquitto/config:ro
- mosquitto_data:/mosquitto/data
networks: [edge]
frigate:
image: ghcr.io/blakeblackshear/frigate:0.14.1
restart: always
shm_size: "256mb"
devices:
- /dev/dri:/dev/dri
env_file: [./.env, ./.env.secret] # FRIGATE_MQTT_PASSWORD, FRIGATE_RTSP_USER/PASSWORD
volumes:
- ./frigate/config.yml:/config/config.yml:ro
- frigate_media:/media/frigate
ports: ["5000:5000"]
networks: [edge]
depends_on: [mosquitto]
home-assistant:
image: ghcr.io/home-assistant/home-assistant:2026.1
restart: always
network_mode: host
volumes:
- ha_config:/config
bridge:
image: ghcr.io/OWNER/zerone/bridge:${BRIDGE_TAG:-v1.0.0}
restart: always
env_file: [./.env, ./.env.secret] # MOSQUITTO_PWD_BRIDGE
networks: [edge]
depends_on: [mosquitto]
orchestrator:
image: ghcr.io/OWNER/zerone/orchestrator:${ORCHESTRATOR_TAG:-v1.0.0}
restart: always
env_file: [./.env, ./.env.secret]
extra_hosts:
- "host.docker.internal:host-gateway" # HA(host network)์ ๋๋ฌ
networks: [edge]
depends_on: [mosquitto]
mcp-robot-adapter:
image: ghcr.io/OWNER/zerone/mcp-robot-adapter:${ROBOT_ADAPTER_TAG:-v1.0.0}
restart: always
env_file: [./.env, ./.env.secret] # MOSQUITTO_PWD_ROBOT_ADAPTER, SPOT_USERNAME/PASSWORD
networks: [edge]
depends_on: [mosquitto]
mcp-tablet-gateway:
image: ghcr.io/OWNER/zerone/mcp-tablet-gateway:${TABLET_GATEWAY_TAG:-v1.0.0}
restart: always
ports: ["8080:8080"] # ํ๋ธ๋ฆฟ WSS
env_file: [./.env, ./.env.secret] # MOSQUITTO_PWD_TABLET_GATEWAY
networks: [edge]
depends_on: [mosquitto]๋ฒ์ ์ .env๋ก ๋ถ๋ฆฌ (๋ฐฐํฌ ๋จ์ ๊ด๋ฆฌ):
# deploy/.env (git์ ์ปค๋ฐ, ์ํฌ๋ฆฟ ์๋)
BRIDGE_TAG=v1.0.0
ORCHESTRATOR_TAG=v1.0.0
ROBOT_ADAPTER_TAG=v1.0.0
TABLET_GATEWAY_TAG=v1.0.0์ํฌ๋ฆฟ์ ๋ณ๋ (.env.secret, gitignore):
ANTHROPIC_API_KEY=sk-ant-...
SPOT_USERNAME=...
SPOT_PASSWORD=...
HA_TOKEN=...
HA_URL=http://host.docker.internal:8123 # HA๊ฐ host network์ด๋ฏ๋ก host gateway๋ก ๋๋ฌ
FRIGATE_MQTT_PASSWORD=...
FRIGATE_RTSP_USER=...
FRIGATE_RTSP_PASSWORD=...
MOSQUITTO_PWD_ORCHESTRATOR=...
MOSQUITTO_PWD_BRIDGE=...
MOSQUITTO_PWD_ROBOT_ADAPTER=...
MOSQUITTO_PWD_TABLET_GATEWAY=...
MOSQUITTO_PWD_FRIGATE=...
MOSQUITTO_PWD_HOMEASSISTANT=...docker-compose.yml์์ env_file: [./.env, ./.env.secret]๋ก ๋ ๋ค ๋ก๋.
HA ๋คํธ์ํฌ ๋๋ฌ ์ฃผ์:
home-assistant์ปจํ ์ด๋๋ mDNSยทSSDP ํธํ์ ์ํดnetwork_mode: host๋ก ๋์ํ๋ฏ๋ก, ๊ฐ์ compose์ ๋ค๋ฅธ ์๋น์ค๊ฐhome-assistantํธ์คํธ๋ช ์ผ๋ก ์ ๊ทผ ๋ถ๊ฐ. ์์ฒ๋ผ ๋ชจ๋ ํด๋ผ์ด์ธํธ๋host.docker.internal:8123์ ์ฌ์ฉํด์ผ ํ๋ค(extra_hosts๋ก ๋งคํ).
# 1. Docker + Compose ์ค์น
sudo apt-get install -y docker.io docker-compose-plugin
# 2. ghcr.io ํ๋ผ์ด๋น ์ด๋ฏธ์ง pull ๊ถํ (ํผ๋ธ๋ฆญ ๋ ํฌ๋ฉด ์๋ต ๊ฐ๋ฅ)
# GitHub PAT ์์ฑ (read:packages ๊ถํ๋ง)
echo $GHCR_TOKEN | docker login ghcr.io -u GITHUB_USERNAME --password-stdin
# 3. ๋ฐฐํฌ ๋๋ ํฐ๋ฆฌ๋ง ํด๋ก (์๋น์ค ์์ค ๋ถํ์)
git clone --depth 1 --filter=blob:none --sparse \
https://github.com/OWNER/zerone.git
cd zerone
git sparse-checkout set deploy
# 4. ์ํฌ๋ฆฟ ์์ฑ
cp deploy/.env.secret.example deploy/.env.secret
$EDITOR deploy/.env.secret
# 5. ์ฒซ ๋ถํ
cd deploy
docker compose pull
docker compose up -d
docker compose logs -f์ต์ : ์์ค์ฝ๋ ์ ์ฒด๊ฐ ํ์ ์์ผ๋ฉด
deploy/๋๋ ํฐ๋ฆฌ๋ง ๋ณ๋ ๋ ํฌ๋ก ๋ถ๋ฆฌํด ๋ ๋จ์ํ ๊ฐ๋ฅ.
cd deploy
# 1. ๋ณ๊ฒฝํ ๋ฒ์ ์ .env์์ ๋ช
์์ ์ผ๋ก ๊ฐฑ์
$EDITOR .env # ORCHESTRATOR_TAG=v1.1.0
# 2. ์ ์ด๋ฏธ์ง๋ง pull
docker compose pull
# 3. ๋ณ๊ฒฝ๋ ์ปจํ
์ด๋๋ง ๊ต์ฒด (์์กด์ฑ ์๋)
docker compose up -d
# 4. ํฌ์ค ํ์ธ
docker compose ps
docker compose logs --tail=50 orchestratordocker compose pull orchestrator
docker compose up -d --no-deps orchestrator$EDITOR .env # ์ด์ ๋ฒ์ SHA ๋๋ ํ๊ทธ๋ก ๋๋๋ฆผ
docker compose pull && docker compose up -d์ด๋ฏธ์ง๊ฐ ghcr.io์ ๋ณด์กด๋๋ฏ๋ก ๋กค๋ฐฑ์ 1๋ถ ๋ด.
Watchtower ๋ฑ์ผ๋ก ์๋ pull ๊ธ์ง โ ์ ์ ๋์ค ๋ฌด์ธ ๊ฐฑ์ ์ ์ฌ๊ณ ์ํ. ํญ์ ๋ช ์์ ๋ช ๋ น์ผ๋ก.
์ compose์์ 2.0, 0.14.1, 2026.1์ฒ๋ผ ๋ช
์์ ๋ฒ์ ์ ์ ์๋ค. ์ด์ :
-
latestํ๊ทธ๋ ์ธ๋ถ ๋ฉ์ธํ ์ด๋์ ๋ฆด๋ฆฌ์ฆ ์์ ์ ๋ฐ๋ผ ๊ฐ์๊ธฐ ๊ฐฑ์ ๋จ โ ์ ์ ์ค ํ๊ท ์ํ. - ์ ๊ทธ๋ ์ด๋๋ ์ฌ์ ๊ฒ์ฆ ํ ์๋์ ์ผ๋ก.
์ ๊ทธ๋ ์ด๋ ์ ์ฐจ:
- ์คํ ์ด์ง ํ๊ฒฝ(๋๋ ์ฌ๋ฌด์ค ๋ฏธ๋ฌ)์์ ์ ๋ฒ์ ๊ฒ์ฆ.
- ๋์ ํ์ธ๋๋ฉด compose ์์ ยท์ปค๋ฐยท์ ์์ฅ PC์ ๋ฐฐํฌ.
| ํ๊ฒฝ | ๋น๋ฐ | ์ ์ฅ ์์น | ํ์ ์ฃผ๊ธฐ |
|---|---|---|---|
| GitHub Actions (๋น๋) | (์์) | โ | โ |
| GitHub Actions (push) |
GITHUB_TOKEN (์๋) |
GitHub ๋ด์ฅ | ์๋ (per-job) |
| ๋ก์ปฌ PC (pull) | ghcr PAT (read:packages) |
~/.docker/config.json |
90์ผ |
| ๋ก์ปฌ PC (์คํ) | API keyยท๋ก๋ด ์๊ฒฉยทHA ํ ํฐยทMQTT ๋น๋ฒ |
deploy/.env.secret (git ignore, chmod 600) |
ํญ๋ชฉ๋ณ (์๋) |
| Mosquitto ๋น๋ฒ | hashed in passwd ํ์ผ |
./mosquitto/config/passwd (root:1883, 0600) |
๋ถ๊ธฐ ํ์ |
| Frigate UI ์ธ์ฆ | ๊ธฐ๋ณธ ๋ฏธ์ง์ โ reverse proxy ์ธ์ฆ | HA Authelia ๋๋ LAN ACL(Access Control List, ์ ๊ทผ ์ ์ด ๋ชฉ๋ก) | โ |
์์น: ๋น๋ฐ์ ๋น๋ ์ด๋ฏธ์ง์ ํฌํจ ๊ธ์ง โ ํญ์ ๋ฐํ์ ํ๊ฒฝ๋ณ์๋ก ์ฃผ์ .
-
๋ฐ๊ธ: console.anthropic.com โ API Keys โ ์ ๊ท ๋ฐ๊ธ, name=
zerone-prod. -
ํ์ : ๋ถ๊ธฐ 1ํ. ์ ๊ท ๋ฐ๊ธ โ
.env.secret๊ฐฑ์ โdocker compose up -d orchestrator๋ฌด์ค๋จ ๊ต์ฒด โ ๊ตฌ ํค ํ๊ธฐ. - ๋ชจ๋ํฐ๋ง: ์ฌ์ฉ๋ ์๋ฆผ ์๊ณ ์ค์ (์: ์ผ $50 ์ด๊ณผ ์ ์ด๋ฉ์ผ).
-
๋ฐ๊ธ ์ ์ฐจ (HA ์ฒซ ๋ถํ
ํ):
- HA์์ ์ด์์ ๊ณ์ ์์ฑ (
zerone-ops). - ์ฐ์ธก ์๋จ ํ๋กํ ์์ด์ฝ โ "Long-Lived Access Tokens" ์น์ .
- "Create Token" ํด๋ฆญ, name=
orchestrator-prod์ ๋ ฅ. - ๋ฐ๊ธ๋ ํ ํฐ ์ฆ์
.env.secret์HA_TOKEN์ ๊ธฐ์ (ํ ๋ฒ๋ง ํ์๋จ).
- HA์์ ์ด์์ ๊ณ์ ์์ฑ (
- ํ์ : 6๊ฐ์ 1ํ ๋๋ ์ด์์ ์ธ์์ธ๊ณ ์. ์ ๊ท ๋ฐ๊ธ โ ๊ฐฑ์ โ ๊ตฌ ํ ํฐ revoke.
-
๊ถํ: HA๋ ํ ํฐ์ ๋ฐ๊ธํ ์ฌ์ฉ์์ ๊ถํ์ ๊ทธ๋๋ก ๋ฐ์ผ๋ฏ๋ก
zerone-ops๊ณ์ ์ ์ต์ ๊ถํ๋ง ๋ถ์ฌ.
-
๋ฐ๊ธ: github.com/settings/tokens (classic) โ
read:packages๋จ์ผ ์ค์ฝํ, expiration 90์ผ. -
๊ฐฑ์ : ๋ง๋ฃ 7์ผ ์ ์๋ฆผ โ ์ ๊ท ๋ฐ๊ธ โ
docker login ghcr.io์ฌ์คํ โ~/.docker/config.json์๋ ๊ฐฑ์ . - ์ด์ ์๋ํ: ๋ง๋ฃ์ผ์ ์บ๋ฆฐ๋์ ๋ฑ๋ก + Slack/์ด๋ฉ์ผ ์๋ฆผ.
-
service account ์์ฑ: SPOT ๊ด๋ฆฌ ์ฝ์์์ ์ด์์ ๋ณธ์ธ ๊ณ์ ์ด ์๋ dedicated service account ์์ฑ:
- ์ด๋ฆ:
zerone-spot-svc - ๊ถํ:
Operator(์ด๋ยท๋์ ๋ช ๋ น) +EStop(์์ ).Admin๊ถํ ๋ถ์ฌ ๊ธ์ง.
- ์ด๋ฆ:
- ํ์ : 6๊ฐ์ 1ํ.
-
์์ฑ:
mosquitto_passwd -c /mosquitto/config/passwd <user>(๋ํํ). ์๋ํ ์mosquitto_passwd -b passwd <user> <pwd>. -
passwdํ์ผ ๊ถํ:chown 1883:1883 passwd && chmod 0600 passwd. -
ํ์ : ๋ถ๊ธฐ 1ํ. ์ฌ์ฉ์๋ณ
mosquitto_passwd -b๊ฐฑ์ ํ mosquitto reload (docker kill -s HUP mosquitto).
- ํ์: opaque random 32-byte URL-safe (JWT ๋ถํ์ โ ๋จ์ผ ์ฌ์ดํธ, ๊ฒ์ฆ ๋จ์).
- TTL: 4์๊ฐ (์ ์ 1์ธ์ ์ถฉ๋ถ, ๋ถ์ค ์ ์งง์ ์๋).
- ๋ฐ๊ธ:
mcp-tablet-gateway์/pairREST(Representational State Transfer, ํํ ์ํ ์ ์ด) โAuthorization: Bearer <token>ํค๋๋ก ํ์ WSS(WebSocket Secure, ๋ณด์ ์น์์ผ) ์ธ์ฆ. - ๋ง๋ฃ ์: ์๋ ํ์ด๋ง ํ๋ฉด์ผ๋ก ๋ณต๊ท โ ์ฌ๋ฐ๊ธ.
- ์ ์ฅ:
mcp-tablet-gatewayin-memory๋ง (Redis ๋ฑ ์ธ๋ถ ์ ์ฅ X). ์ปจํ ์ด๋ ์ฌ๊ธฐ๋ ์ ๋ชจ๋ ํ์ด๋ง ๋ฌดํจ โ ์ด์ ์ค ์ํฅ ๋ฏธ๋ฏธ.
- Frigate ์์ฒด์๋ ๊ธฐ๋ณธ ์ธ์ฆ ์์. ๋ค์ ์ค ํ๋:
-
๊ถ์ฅ: Frigate๋ฅผ LAN ๋ด๋ถ IP์๋ง ๋ฐ์ธ๋ฉ(
127.0.0.1:5000) + HA์ ingress add-on์ ํตํด ๋ ธ์ถ โ HA ์ธ์ฆ์ผ๋ก ๋ณดํธ. - ๋๋ Cloudflare Tunnel + Cloudflare Access(์ด๋ฉ์ผยทSSO(Single Sign-On, ๋จ์ผ ๋ก๊ทธ์ธ ์ธ์ฆ)).
- ๋๋ LAN ์์ฒด๋ฅผ ์ ๋ขฐ ์์ญ์ผ๋ก ๋๊ณ ๋ฏธ์ธ์ฆ (์ด์์๋ง LAN ์ ๊ทผ).
-
๊ถ์ฅ: Frigate๋ฅผ LAN ๋ด๋ถ IP์๋ง ๋ฐ์ธ๋ฉ(
- HA ๊ธฐ๋ณธ username/password + MFA (HA ๋ด์ฅ TOTP(Time-based One-Time Password, ์๊ฐ ๊ธฐ๋ฐ ์ผํ์ฉ ๋น๋ฐ๋ฒํธ) ์ง์ โ ์ฌ์ฉ์ ํ๋กํ์์ "Enable two-factor authentication").
- ์ด์์ ์ธ์ 5๋ช ์ดํ๋ฉด ์ถฉ๋ถ. ๊ทธ ์ด์์ด๋ฉด Authelia add-on์ผ๋ก SSOยทLDAP(Lightweight Directory Access Protocol, ๊ฒฝ๋ ๋๋ ํฐ๋ฆฌ ์ ๊ทผ ํ๋กํ ์ฝ) ์ฐ๋ ๊ฒํ .
- Lovelace ์นด๋ ๋จ์ ๊ถํ์ HA์
auth_provider+ custom_card ๊ถํ ์ค์ ์ผ๋ก.
- ghcr PAT ๋ ธ์ถ โ ์ฆ์ GitHub์์ revoke โ ์ ๊ท ๋ฐ๊ธ.
- HA ํ ํฐ ๋ ธ์ถ โ HA Profile์์ ํ ํฐ revoke.
- Anthropic API key ๋ ธ์ถ โ console์์ ์ฆ์ revoke + ์ฌ์ฉ๋ ๊ฐ์ฌ โ ์ ๊ท ๋ฐ๊ธ.
- Mosquitto ๋น๋ฒ ๋ ธ์ถ โ ๋ชจ๋ ์ฌ์ฉ์ ํ์ + ์งง์ ์๊ฐ ๋ด ACL ๋ฏธ๊ฒฝ์ publish ๋ก๊ทธ ํ์ธ.
- ์ฌ๊ณ ๊ธฐ๋ก์ ์ด์ ์ผ์ง์ ๋จ๊ธฐ๊ณ ํ์ ์ฃผ๊ธฐ ์ผ์ ๋จ์ถ.
- PR ์ ๋น๋๋ง ํ๊ณ push ์ ํจ (์ ์ํฌํ๋ก ์ด๋ฏธ ๋ฐ์).
- ๋น๋ ์คํจ = ๋จธ์ง ์ฐจ๋จ (branch protection rule).
- uses: anchore/sbom-action@v0
with:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/${{ matrix.service }}:sha-${{ github.sha }}
format: spdx-json- Cosign + GitHub OIDC(OpenID Connect, OpenID ์ฐ๊ฒฐ ์ธ์ฆ ํ๋กํ ์ฝ)๋ก keyless signing.
- ๋ก์ปฌ pull ์
cosign verify๋ก ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ.
CI๊ฐ ์ธ๋ถ ๋๊ตฌ ํธ์ถ ์ ์ฌ์ฉํ๋ ์ํฌ๋ฆฟ์ GitHub repo Settings โ Secrets and variables โ Actions์ ๋ฑ๋ก.
| Secret ์ด๋ฆ | ์ฉ๋ | ๋ฐ๊ธ ์์น | ํ์ ์ฌ๋ถ |
|---|---|---|---|
GITHUB_TOKEN |
ghcr.io push ์ธ์ฆ | ์๋(GitHub Actions ๋ด์ฅ) | ํ์ (์๋) |
TRIVY_DB_TOKEN |
์ฌ์ค vuln DB ์ ๊ทผ (์ฌ์ฉ ์) | aquasec.com | ์ ํ |
COSIGN_KEY |
(keyed signing ์) | ๋ก์ปฌ cosign generate-key-pair
|
์ ํ |
COSIGN_PASSWORD |
์ ํค ์ํธ | ์์ ํจ๊ป | ์ ํ |
SLACK_WEBHOOK_URL |
๋น๋ ์คํจ ์๋ฆผ | Slack ์ฑ โ Incoming Webhook | ์ ํ |
SBOM_UPLOAD_URL |
SBOM ์ธ๋ถ ์ ์ฅ์ | ๋ณด์ํ endpoint | ์ ํ |
Keyless cosign (๊ถ์ฅ): GitHub OIDC๋ก ํค ์์ด ์๋ช
โ COSIGN_KEY/PASSWORD ๋ถํ์.
permissions:
id-token: write # OIDC
packages: write
contents: read
# build job ๋ง์ง๋ง์
- name: Sign image (keyless)
env:
COSIGN_EXPERIMENTAL: "true"
run: |
cosign sign --yes \
${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/${{ matrix.service }}@${{ steps.build.outputs.digest }}์ํฌ๋ฆฟ ํ์ :
- ํ์ ์ GitHub UI์์ ๊ฐ ๊ฐฑ์ โ ์ํฌํ๋ก ๋ณ๊ฒฝ ๋ถํ์.
- ๋ง๋ฃ ๋ชจ๋ํฐ๋ง์ ์ด์์ ์บ๋ฆฐ๋์ ๊ธฐ๋ก.
ํ๊ฒฝ๋ณ ๋ถ๋ฆฌ (์ด์/์คํ ์ด์ง):
- GitHub Environment ๊ธฐ๋ฅ์ผ๋ก production/staging ๋ถ๋ฆฌ.
- production ์ํฌ๋ฆฟ์ main ๋ธ๋์น push ์์๋ง ๋ ธ์ถ (require reviewers).
docker-compose.yml์ ๊ด์ธก ์คํ์ ํจ๊ป ์ถ๊ฐ:
prometheus:
image: prom/prometheus:v2.55.0
volumes: [./prometheus.yml:/etc/prometheus/prometheus.yml:ro]
ports: ["9090:9090"]
networks: [edge]
grafana:
image: grafana/grafana:11.3.0
volumes: [./grafana:/etc/grafana/provisioning:ro]
ports: ["3000:3000"]
networks: [edge]
loki:
image: grafana/loki:3.2.0
volumes: [./loki-config.yml:/etc/loki/local-config.yaml:ro]
networks: [edge]์ด์์ PC๊ฐ ๊ฐ์ LAN์ด๋ฉด http://EDGE_IP:3000 ์ผ๋ก Grafana ์ ์.
์๊ฒฉ ๋ชจ๋ํฐ๋ง์ด ํ์ํ๋ฉด Cloudflare Tunnel ๋๋ Tailscale๋ก ์ ํฌํธ๋ง ์ธ๋ถ ๋ ธ์ถ(๋ฌด๋ฃ ํฐ์ด๋ก ์ถฉ๋ถ).
| ์์ฐ | ์์น | ํ๋ฐ์ฑ | ๋ฐฑ์ ์ฐ์ ์์ |
|---|---|---|---|
| HA recorder DB | ha_config ๋ณผ๋ฅจ/home-assistant_v2.db |
๋จ๊ธฐ ์์ (3์ผ ์๋ purge) | ๋ฎ์ (์ฌ๊ตฌ์ถ ๊ฐ๋ฅ) |
| HA configuration (yamlยท์๋ํยทdashboard) | ha_config ๋ณผ๋ฅจ/* |
์์ | ๋์ |
| Mosquitto passwdยทaclยทdata |
mosquitto_data ๋ณผ๋ฅจ + ./mosquitto/config/
|
์์ (passwd) / ํ๋ฐ (data) | ๋์ (passwd) |
| Frigate config |
./frigate/config.yml (git์ ์ปค๋ฐ) |
git ๊ด๋ฆฌ | git๋ง |
| Frigate clips | (๋ฏธ์ ์ฅ) | โ | โ |
์บ๋ฆฌ๋ธ๋ ์ด์
(config/calibration/*.yaml) |
git์ ์ปค๋ฐ | git ๊ด๋ฆฌ | git๋ง |
.envยท.env.secret
|
ํธ์คํธ ๋์คํฌ | ์์ | ๋์ (์ํฌ๋ฆฟ) |
| ์ปจํ ์ด๋ ์ด๋ฏธ์ง | ghcr.io | ํด๋ผ์ฐ๋ ๋ณด์กด | ๋ถํ์ (registry๊ฐ SoT) |
| ์์ฒด ์๋น์ค ๋ฉ๋ชจ๋ฆฌ ์ํ | RAM only | ํ๋ฐ | ๋ถํ์ |
-
์ผ์ผ ๋ฐฑ์
(cron ๋งค์ผ 03:00):
# /etc/cron.d/zerone-backup 0 3 * * * root /usr/local/bin/zerone-backup.sh
# zerone-backup.sh set -euo pipefail STAMP=$(date +%Y%m%d-%H%M%S) DEST=/mnt/nas/zerone-backup tar -czf "$DEST/ha-$STAMP.tar.gz" -C /var/lib/docker/volumes/zerone_ha_config _data cp /opt/zerone/deploy/mosquitto/config/passwd "$DEST/passwd-$STAMP" cp /opt/zerone/deploy/mosquitto/config/acl "$DEST/acl-$STAMP" # .env.secret์ ๋ณ๋ vault(์: ์ด์์ 1Password) ์๋ ๋ณด๊ด โ cron ๋ฐฑ์ X find "$DEST" -type f -mtime +14 -delete # 14์ผ ๋ณด์กด
- NAS ๋ฏธ๋ฌ: NAS ์์ฒด๊ฐ RAID + off-site sync (์ด์์ ์ฑ ์ ์ธ๋ถ ์ ์ฑ ).
-
๊ฐ์ธ์ ๋ณด ์ํฅ: HA recorder๋ zone ์ ์ ยท๋ก๋ด ์ํ๋ง โ ์๋ณ์ ์์. ๊ทธ๋๋ก ๋ฐฑ์
๊ฐ๋ฅ.
.env.secret์ ๋ณ๋ ๋ณด๊ด.
| ์๋๋ฆฌ์ค | RPO (๋ฐ์ดํฐ ์์ค ํ์ฉ) | RTO (๋ณต๊ตฌ ๋ชฉํ ์๊ฐ) |
|---|---|---|
| ์ปจํ ์ด๋ ๋จ์ผ ํฌ๋์ | 0 (in-memory๋ง ํ๋ฐ) | < 30s (restart: always) |
| ํธ์คํธ ๋์คํฌ ์ผ๋ถ ์์ | < 24์๊ฐ (๋ง์ง๋ง cron ๋ฐฑ์ ) | < 1์๊ฐ (NAS์์ ๋ณต์) |
| ํธ์คํธ ์ ์ฒด ์ฅ์ | < 24์๊ฐ | < 4์๊ฐ (๋์ฒด ํ๋์จ์ด ๋ถํ + ๋ณต์ + ์บ๋ฆฌ๋ธ๋ ์ด์ ์ผ๋ถ) |
| ์ํฌ๋ฆฟ ๋ ธ์ถ | ์ฆ์ | < 30๋ถ (ํ์ ์ ์ฐจ ยง9.2) |
| ์ ์ ์์ D-day ์ฅ์ | 0 (์์ ๋ชป ํจ) | (ํด๋น ์ผ ์์ฐ ์ทจ์) |
# 1. ์ ๋์คํฌ์ OSยทDocker ์ค์น
# 2. NAS ๋ง์ดํธ
sudo mount /mnt/nas
# 3. ๊ฐ์ฅ ์ต๊ทผ ๋ฐฑ์
์ถ์ถ
LATEST=$(ls -t /mnt/nas/zerone-backup/ha-*.tar.gz | head -1)
docker volume create zerone_ha_config
tar -xzf "$LATEST" -C /var/lib/docker/volumes/zerone_ha_config/_data --strip 1
# 4. mosquitto ๋น๋ฐ
cp /mnt/nas/zerone-backup/passwd-* deploy/mosquitto/config/passwd
chown 1883:1883 deploy/mosquitto/config/passwd
# 5. .env.secret์ ์ด์์๊ฐ ์๋ ๋ณต๊ตฌ (1Password ๋ฑ)
# 6. ๋ถํ
cd deploy && docker compose pull && docker compose up -d
# 7. ์บ๋ฆฌ๋ธ๋ ์ด์
๊ฒ์ฆ (ํธ์คํธ๊ฐ ๊ฐ์ ์์น๋ฉด ๊ทธ๋๋ก ์ ํจ)-
config/calibration/cameras.yamlยทrobots.yaml์ git์ ์ปค๋ฐ๋์ด ์์ด git checkout์ผ๋ก ๋ณต๊ตฌ. - ์นด๋ฉ๋ผ๊ฐ ๋ฌผ๋ฆฌ์ ์ผ๋ก ์์น ์ ๋ฐ๋์์ผ๋ฉด ๊ทธ๋๋ก ์ฌ์ฉ. ์ฌ์บ๋ฆฌ๋ธ๋ ์ด์ ํ์ ์ SYSTEM_DESIGN.md ยง8.3 ์ ์ฐจ ๋ฐ๋ฆ.
- ๋ถ๊ธฐ 1ํ ์ฌ๋ฌด์ค ๋ฏธ๋ฌ ํ๊ฒฝ์์ ๋ฐฑ์ โ ๋ณต์ โ ํฌ์ค์ฒดํฌ ์ํ์ค dry-run.
- ๊ฒฐ๊ณผ๋ฅผ ์ด์ ์ผ์ง์ ๊ธฐ๋ก (๋ง์ง๋ง dry-run ์ผ์ + ๋ฐ๊ฒฌ๋ ์ด์).
| ์ฅ์ | ๋์ |
|---|---|
| Anthropic API ๋จ์ | HA ์๋ํ์ rule-based ํด๋ฐฑ, ์ด์์ ์๋ฆผ |
| ์ธํฐ๋ท ๋จ์ | ์์ ๋์ผ (LLM ์ธ ๊ธฐ๋ฅ์ ์ํฅ ์์) |
| ghcr.io ๋จ์ | ์ ๋ฐฐํฌ๋ง ๋งํ, ์คํ ์ค ์ปจํ ์ด๋๋ ์ ์ |
| Edge PC ์ ์ | UPS ํ systemd๊ฐ docker compose ์ฌ์์ |
| ์ปจํ ์ด๋ ํฌ๋์ |
restart: always๋ก ์๋ ๋ณต๊ตฌ |
| ๋ก๋ด fault | ์์ ์ธํฐ๋ก์ผ๋ก ๋ช ๋ น ๊ฑฐ๋ถ, ๋ค๋ฅธ ๋ก๋ด์ผ๋ก ์๋๋ฆฌ์ค ๋์ฒด |
- Phase 0 โ ๋ก์ปฌ ๋จ๋ (1์ฃผ): ์ต์ ์ปดํฌ์ฆ(MQTT + HA + bridge + orchestrator), ๋๋ฏธ ์นด๋ฉ๋ผ/๋ก๋ด.
- Phase 1 โ ์ค ์นด๋ฉ๋ผ + Frigate (1์ฃผ): 1๊ฐ zone enter/exit ๊ฒ์ฆ.
- Phase 2 โ SPOT ํตํฉ (2์ฃผ): mcp-robot-adapter, ์์ ์ธํฐ๋ก, e-stop(emergency stop, ๊ธด๊ธ ์ ์ง).
- Phase 3 โ LLMยทํ๋ธ๋ฆฟ (2์ฃผ): orchestrator + Claude tool use, 1๊ฐ zone playbook ์ข ๋จ.
- Phase 4 โ 4 zone + 2๋ฒ์งธ ๋ก๋ด (3์ฃผ): cross-camera ReID, ๋ฉํฐ ๋ก๋ด.
- Phase 5 โ CI/CD(Continuous Integration / Continuous Deployment, ์ง์ ํตํฉ/์ง์ ๋ฐฐํฌ) ์๋ํ (1์ฃผ): GitHub Actionsยท์ด๋ฏธ์ง ํ๊น ๋์ . ๊ทธ ์ ๊น์ง๋ ๋ก์ปฌ ๋น๋ OK.
- Phase 6 โ ๊ด์ธกยท์ด์ ๊ฐํ (1์ฃผ): Prometheus/Grafana/Loki, ์๋ฆผ.
CI/CD๋ Phase 5์ ๋์
ํด๋ ์ถฉ๋ถ. ๊ทธ ์ ์ ๋ก์ปฌ์์ docker compose build๋ก ์์
.
-
.env์ ๋ชจ๋ ํ๊ทธ๊ฐ ๋ฆด๋ฆฌ์ฆ ๋ฒ์ (vX.Y.Z)์ผ๋ก ํ(pin)๋์ด ์์ -
.env.secret์ด ์ฑ์์ ธ ์๊ณ ๊ถํ 600 -
docker compose ps๋ชจ๋ ์๋น์ค healthy - Anthropic API key ์์ฌ ํ๋ ํ์ธ
- HA ์๋ํ ํด๋ฐฑ ์๋๋ฆฌ์ค dry-run
- ๋ก๋ด e-stop ๋ฌผ๋ฆฌ ํ ์คํธ
- ์นด๋ฉ๋ผ๋ณ zone polygon ์บ๋ฆฌ๋ธ๋ ์ด์ ๊ฒ์ฆ
- ํ๋ธ๋ฆฟ ํ์ด๋ง โ ์ธํฐ๋์ ์ข ๋จ ์๋๋ฆฌ์ค 1ํ
- UPS ์์ฌ ์ฉ๋ + ๋ก๋ด ๋ฐฐํฐ๋ฆฌ 100%
- Grafana ๋์๋ณด๋ ์ด์์ PC์์ ์ ๊ทผ ๊ฐ๋ฅ
- ๋กค๋ฐฑ ์ ์ฐจ ๋ฌธ์ ์ด์์์๊ฒ ๊ณต์ (
.env์ด์ ํ๊ทธ๋ก ๋ณ๊ฒฝ โ pull โ up -d)
- Google ADK ๊ฒํ (ํ๋ ์์ํฌยท๋ชจ๋ธ ๋น๊ต)
- ํ๋ฅด์๋ + ์ํฉ ์นด๋ ๋ชจ๋ธ (2์ฐจ ์๋๋ฆฌ์ค ๊ตฌ๋ ๋ฐฉ์)
- Mac Studio ๋ก์ปฌ LLM + Claude Opus ํ์ด๋ธ๋ฆฌ๋ ๋ผ์ฐํ ์ ๋ต
- ๊ฐ๋ฐ ์ผ์ ยทํ ์ผ ์ ๊ฒํ (๋จ๊ณ๋ณ ์งํ ์ ๊ฒ)
- ๋ก๋ด Adapter ๊ฐ๋ฐ ์จ๋ณด๋ฉ (HAยทMQTT ๊ณ์ ๋ฑ๋ก, ํ ํฝ ์ ์ฉ, ํ ์คํธ ์ก์์ , ์ํ ํ์ธ)