-
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 (9 ์ปจํ ์ด๋, cctv-relay ํฌํจ โ go2rtc ์ด๋ฏธ์ง ~50MB) | โ |
| ์์คํ + ์ฌ์ | ~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 |
cctv-relay ๋ผ์ด๋ธ ์์ (WebRTC โ ์ ์ ํ๋ฉดยทํ๋ธ๋ฆฟ, H.264 720p ร ๋์ ๋ทฐ n) |
~2โ3 Mbps / ๋ทฐ (LAN egress) |
| RTSP ์ ๋ ฅ ์ค๋ณต (cctv-relay ๋ ๋ฆฝ pull, Frigate detect์ ๋ณ๊ฐ) | +์ต๋ ~24 Mbps (LAN ingress) |
WebRTC ๋ฏธ๋์ด๋ ๋ธ๋ผ์ฐ์ โ
cctv-relayP2P(LAN ๋ด๋ถ)๋ก ํ๋ฅด๋ฏ๋ก ์ธ๋ถ ํ์ ๋์ญํญ์๋ ์ํฅ์ด ์๋ค.cctv-relay๋ Frigate์ ๋ถ๋ฆฌ๋์ด RTSP๋ฅผ ๋ ๋ฆฝ pull ํ๋ฏ๋ก ์นด๋ฉ๋ผ ์ ๋ ฅ์ด detect ๊ฒฝ๋ก์ ์ค๋ณต๋๋ค(์ฅ์ ๊ฒฉ๋ฆฌ์ ๋๊ฐ). ์นด๋ฉ๋ผ ๋์ ์ฐ๊ฒฐยทLAN ๋์ญํญ์ด ๋นก๋นกํ๋ฉด ๋จ์ผ-pull ๋ณํ ๊ฒํ (SERVICES.md ยง2.5). ๊ทผ๊ฑฐ: SYSTEM_DESIGN.md ยง8.4.
๊ถ์ฅ: 1 Gbps Ethernet ร 2 (์นด๋ฉ๋ผ segment + ์ผ๋ฐ segment ๋ถ๋ฆฌ) + 5GHz Wi-Fi AP 2๋ (ํ๋ธ๋ฆฟ์ฉ ๋ณ๋ SSID(Service Set Identifier, ๋ฌด์ ๋คํธ์ํฌ ์๋ณ์)).
๊ฐ๋ฐฉ ํฌํธ(์ ์์ฅ LAN ๋ด๋ถ): Frigate UI 5000, cctv-relay WebRTC ๋ฏธ๋์ด 8555/tcp+udp(๋ธ๋ผ์ฐ์ โrelay), cctv-relay API 1984/tcp(BFF ์๊ทธ๋๋ง โ ์ธ๋ถ ๋น๊ณต๊ฐ). WebRTC๊ฐ LAN host candidate(8555)๋ก ํ์๋๋ฏ๋ก STUN/TURN ์๋ฒ๋ ๋์ง ์๋๋ค.
- ๋ฉ์ธ ๋ ธ๋ + ๋คํธ์ํฌ ์ค์์น + 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 (์ธ๋ถ ์ด๋ฏธ์ง) โ
โ โโโ cctv-relay (์ธ๋ถ ์ด๋ฏธ์ง) โ
โ โโโ 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๋ก ๋งคํ).
์ ์์ฅ PC์ฉ ํ์คํ(์ ยง5)์ LinuxยทGPUยทnetwork_mode: host๋ฅผ ์ ์ ํ๋ฏ๋ก macOS ๊ฐ๋ฐ ๋จธ์ ์์ ๊ทธ๋๋ก ๋จ์ง ์๋๋ค. ์๋๋ Mac์์ ๋ผ์ด๋ธ ์์ ์ค๊ณ ๊ฒฝ๋ก(๋ธ๋ผ์ฐ์ โ cctv-relay)๋ฅผ ๋น ๋ฅด๊ฒ ๊ฒ์ฆํ๊ธฐ ์ํ ์ต์ ์คํ์ด๋ค โ mosquitto + home-assistant + cctv-relay(go2rtc) ์ธ ๊ฐ๋ง ๋์ด๋ค. CCTV "์ฐ๊ณ ์๋น์ค"๋ detect/์ด๋ฒคํธ์ฉ Frigate๊ฐ ์๋๋ผ ๋ธ๋ผ์ฐ์ ๋ผ์ด๋ธ ์์์ ์ฑ
์์ง๋ cctv-relay ๋ค(SERVICES.md ยง2.5, ์ค๊ณ ๊ทผ๊ฑฐ SYSTEM_DESIGN.md ยง8.4).
ํ์คํ(ยง5)๊ณผ์ ์ฐจ์ด
| ํญ๋ชฉ | ์ ์์ฅ ํ์คํ (ยง5) | Mac ๊ฐ๋ฐ ์คํ (ยง5.1) |
|---|---|---|
| ์๋น์ค ์ | mosquittoยทfrigateยทHAยทbridgeยทorchestratorยท์ด๋ํฐยทgateway | mosquittoยทHAยทcctv-relay 3๊ฐ |
| CCTV ๊ฒฝ๋ก | Frigate(detect/์ด๋ฒคํธ) + cctv-relay(๋ผ์ด๋ธ) | cctv-relay(๋ผ์ด๋ธ)๋ง |
| HA ๋คํธ์ํฌ | network_mode: host |
ํฌํธ ๋งคํ 8123:8123 (Mac์ host ๋คํธ์ํฌ ๋ฏธ์ง์) |
| HW ๊ฐ์ |
/dev/dri ํจ์ค์ค๋ฃจ |
๋ถํ์ โ relay๋ detect๋ฅผ ํ์ง ์์(zero-copy ํจ์ค์ค๋ฃจ) |
| ์ฉ๋ | ์ด์ ๋ฐฐํฌ | ๋ผ์ด๋ธ ์ค๊ณยทHA ๋์๋ณด๋ ๋ก์ปฌ ๊ฒ์ฆ |
Mac ๊ฐ๋ฐ ๋จธ์ ์์๋ ์ด
docker-compose.yml์ ยง5์ ํ์คํ ๋์ ์ด๋ค(๊ฐ์ ๊ฒฝ๋ก, ํ ๋จธ์ ์ ๋ ์ค ํ๋๋ง). ์ฒ์ ๊ตฌ์ฑํ ์ต์ compose(mosquitto+home-assistant)์cctv-relay์๋น์ค๋ฅผ ๋ํ ํํ๋ค.
๋๋ ํฐ๋ฆฌ ๋ ์ด์์ (deploy/ ๊ธฐ์ค)
deploy/
โโโ docker-compose.yml # ์๋ compose (Mac ๊ฐ๋ฐ ๋จธ์ ์ฉ ์ต์ ์คํ)
โโโ .env.secret # RTSP_USER / RTSP_PASSWORD (gitignore)
โโโ mosquitto/config/mosquitto.conf
โโโ config/cctv-relay.yaml # go2rtc streams ๋งต
โโโ ha-config/ # HA ์ฒซ ๋ถํ
์ ์๋ ์์ฑ
deploy/docker-compose.yml โ ์ฒ์ ๊ตฌ์ฑํ ์ต์ compose์ cctv-relay(go2rtc) ์ถ๊ฐ
# deploy/docker-compose.yml
services:
mosquitto:
image: eclipse-mosquitto:2.0
restart: unless-stopped
ports: ["1883:1883"]
volumes:
- ./mosquitto/config:/mosquitto/config
- mosquitto_data:/mosquitto/data
networks: [edge]
cctv-relay: # โ ์ถ๊ฐ: ๋ธ๋ผ์ฐ์ ์ฉ CCTV ๋ผ์ด๋ธ ์ค๊ณ (go2rtc)
image: ghcr.io/alexxit/go2rtc:1.9.x
restart: unless-stopped
command: ["-config", "/config/cctv-relay.yaml"]
env_file: [./.env.secret] # RTSP_USER, RTSP_PASSWORD
volumes:
- ./config/cctv-relay.yaml:/config/cctv-relay.yaml:ro
ports:
- "8555:8555/tcp" # WebRTC ๋ฏธ๋์ด (๋ธ๋ผ์ฐ์ โ relay, ICE)
- "8555:8555/udp"
- "1984:1984" # go2rtc API/Web UI โ ๊ฐ๋ฐ ์ค ์ง์ ํ
์คํธ์ฉ
networks: [edge]
# detect/MQTT๋ฅผ ํ์ง ์์ผ๋ฏ๋ก mosquitto์ depends_on ํ์ง ์์ (๋ณ๋ ๋ฏธ๋์ด ํ๋ฉด)
home-assistant:
image: ghcr.io/home-assistant/home-assistant:2026.1
restart: unless-stopped
# network_mode: host โ Mac์์๋ ์ ๊ฑฐ
ports: ["8123:8123"] # โ ๋์ ๋ช
์์ ํฌํธ ๋งคํ
volumes:
- ./ha-config:/config
networks: [edge]
depends_on: [mosquitto]
networks:
edge:
driver: bridge
volumes:
mosquitto_data:์ค์ ํ์ผ
deploy/mosquitto/config/mosquitto.conf (๊ฐ๋ฐ์ฉ โ ์ต๋ช
ํ์ฉ, LAN ํ์ ):
listener 1883
allow_anonymous true
persistence true
persistence_location /mosquitto/data/
โ ๏ธ allow_anonymous true๋ ๋ก์ปฌ ๊ฐ๋ฐ ํ์ ์ด๋ค. ์ด์ ACLยท๋น๋ฐ๋ฒํธ ์ ์ฑ ์ SERVICES.md ยง1์ ๋ฐ๋ฅธ๋ค.
deploy/config/cctv-relay.yaml (go2rtc โ ์์ค RTSP ํ ์ค๋น ์คํธ๋ฆผ 1๊ฐ):
# deploy/config/cctv-relay.yaml
streams:
cafe_cam_01: rtsp://${RTSP_USER}:${RTSP_PASSWORD}@192.168.0.11:554/stream
# H.265 ์นด๋ฉ๋ผ๋ ๋ธ๋ผ์ฐ์ ํธํ(H.264)์ผ๋ก ํธ๋์ค์ฝ๋:
# lobby_cam_01:
# - rtsp://${RTSP_USER}:${RTSP_PASSWORD}@192.168.0.15:554/stream
# - 'ffmpeg:lobby_cam_01#video=h264#audio=opus'
webrtc:
candidates:
- 192.168.0.10:8555 # โ Mac์ LAN IP:8555 (๋ธ๋ผ์ฐ์ ๊ฐ ๋ฟ๋ ์ฃผ์). ๊ฐ์ ๋จธ์ ์ด๋ฉด 127.0.0.1:8555
api:
listen: ":1984"
โ ๏ธ Mac WebRTC ์ฃผ์: Docker Desktop์ ์ปจํ ์ด๋ ๋คํธ์ํฌ๋ฅผ NATํ๋ฏ๋ก go2rtc๊ฐ ์๋ ๊ด๊ณ ํ๋ host candidate(์ปจํ ์ด๋ ๋ด๋ถ IP)๋ ๋ธ๋ผ์ฐ์ ๊ฐ ๋ฟ์ง ๋ชปํ๋ค.webrtc.candidates์ Mac์ ์ค์ ์ฃผ์(ipconfig getifaddr en0๋ก ํ์ธํ LAN IP, ๋๋ ๊ฐ์ ๋จธ์ ์ด๋ฉด127.0.0.1):8555๋ฅผ ๋ช ์ํด์ผ ์์์ด ํ๋ฅธ๋ค. ํฌํธ8555/tcp+udp๊ฐ ๋งคํ๋ผ ์์ด์ผ ํจ.
deploy/.env.secret (gitignore โ ํ๋ฌธ ์ปค๋ฐ ๊ธ์ง):
RTSP_USER=admin
RTSP_PASSWORD=...์ฌ์ฉ๋ฒ
cd deploy
# 1. ๊ธฐ๋ (deploy/docker-compose.yml ์๋ ์ธ์)
docker compose up -d
# 2. ์ํ ํ์ธ
docker compose ps
# 3. ๊ฐ ์๋น์ค ๊ฒ์ฆ
# - cctv-relay: ๋ธ๋ผ์ฐ์ ๋ก go2rtc Web UI ์ด์ด ์คํธ๋ฆผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ
open http://localhost:1984 # streams ๋ชฉ๋ก โ cafe_cam_01 โ WebRTC ์ฌ์ ํ์ธ
# - Home Assistant: ์จ๋ณด๋ฉ ํ๋ฉด
open http://localhost:8123
# - mosquitto: ๊ตฌ๋
/๋ฐํ ์๋ณต (mosquitto-clients ํ์)
mosquitto_sub -h localhost -p 1883 -t 'test/#' -v &
mosquitto_pub -h localhost -p 1883 -t test/ping -m hello
# 4. ๋ธ๋ผ์ฐ์ WebRTC ์ง์ ํ์ธ (BFF ์์ด go2rtc๋ก ๋ฐ๋ก)
open 'http://localhost:1984/webrtc.html?src=cafe_cam_01'
# 5. ๋ก๊ทธ / ์ ๋ฆฌ
docker compose logs -f cctv-relay
docker compose down # ๋ณผ๋ฅจ ์ ์ง
docker compose down -v # ๋ณผ๋ฅจ๊น์ง ์ญ์ (์ด๊ธฐํ)- ์นด๋ฉ๋ผ๋ฅผ ๋๋ฆฌ๋ ค๋ฉด
config/cctv-relay.yaml์streams:์ RTSP ํ ์ค ์ถ๊ฐ ํdocker compose restart cctv-relayโ ์ฝ๋ ๋ณ๊ฒฝ ๋ถํ์(SERVICES.md ยง2.5). - HA์์ MQTT๋ฅผ ์ฐ๋ ค๋ฉด ํตํฉ ์ถ๊ฐ ์ ๋ธ๋ก์ปค ํธ์คํธ๋ฅผ
mosquitto(compose ๋ด๋ถ ๋คํธ์ํฌ๋ช ), ํฌํธ1883์ผ๋ก ์ง์ ํ๋ค.
โ ๏ธ ๋ก์ปฌ ๋ธ๋ก์ปค ๋์(๊ถ์ฅ ํ๊ฒฝ์ ๋ฐ๋ผ): ๊ฐ๋ฐ์ฉ mosquitto๋ ๋ณดํต ์๊ฒฉ Mac Studio docker์์ ์์ ์คํ๋๋ฏ๋ก, ๊ทธ ๋ธ๋ก์ปค๋ฅผ ์ฌ์ฌ์ฉํ๋ค๋ฉด ์ compose์์mosquitto์๋น์ค๋ฅผ ๋นผ๊ณ HAยท๋ค๋ฅธ ํด๋ผ์ด์ธํธ์ ๋ธ๋ก์ปค ํธ์คํธ๋ฅผ Mac Studio ์ฃผ์๋ก ์ง์ ํ๋ค. ์ด ๊ฒฝ์ฐcctv-relay๋ ๋ธ๋ก์ปค์ ๋ฌด๊ดํ๋ฏ๋ก ๊ทธ๋๋ก ๋๋ค.
# 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 ๊ณ์ ๋ฑ๋ก, ํ ํฝ ์ ์ฉ, ํ ์คํธ ์ก์์ , ์ํ ํ์ธ)