
sonar v0.4.1
localhost 포트에서 수신 중인 서비스를 검사하고 관리하기 위한 CLI 도구
Sonar은 localhost에서 수신 중인 모든 것을 보여주고 정리합니다. 모든 포트는 그룹에 속하며 — 일반적으로 해당 포트가 시작된 저장소 — 그 그룹 안에서는 이름이 지정된 서비스에 속합니다. sonar start로 개발 서버를 시작하면 전체 프로젝트가 하나의 단위가 되어 트리로 나열하고, 대기하고, 로그를 추적하고, 단일 명령으로 중지할 수 있습니다. Docker 컨테이너, Compose 프로젝트, 그리고 수동으로 시작한 프로세스도 별도의 구성 없이 자동으로 감지됩니다.```
$ sonar list --tree
my-app (3 ports, running) ~/code/my-app
├─ 5432 db postgres:17 http://localhost:5432
├─ 5173 frontend vite (v5.4) http://localhost:5173
└─ 8000 api uvicorn app:app http://localhost:8000
ungrouped (1 port)
└─ 3000 next-server (v16.1.6) http://localhost:3000
## 설치
### Homebrew (macOS / Linux)```sh
brew install raskrebs/sonar/sonar
Homebrew 6은 타사 탭을 신뢰할 때까지 해당 탭의 포뮬러를 거부합니다
(Error: Refusing to load formula raskrebs/sonar/sonar from untrusted tap):```sh
brew trust raskrebs/sonar
### 설치 스크립트```sh
curl -sfL https://raw.githubusercontent.com/raskrebs/sonar/main/scripts/install.sh | bash
최신 바이너리를 ~/.local/bin에 다운로드하고, 필요한 경우 PATH에 추가합니다. 터미널을 다시 시작하거나 source ~/.zshrc를 실행하세요.
Windows(PowerShell)에서:```powershell irm https://raw.githubusercontent.com/raskrebs/sonar/main/scripts/install.ps1 | iex
Custom install location:```sh
curl -sfL https://raw.githubusercontent.com/raskrebs/sonar/main/scripts/install.sh | SONAR_INSTALL_DIR=/usr/local/bin bash
특정 버전 설치:```sh curl -sfL https://raw.githubusercontent.com/raskrebs/sonar/main/scripts/install.sh | SONAR_VERSION=vX.Y.Z bash
I need the actual content of chunk 17 to translate it. Please provide the Markdown text you'd like translated from English to Korean.```powershell
$env:SONAR_VERSION="vX.Y.Z"; irm https://raw.githubusercontent.com/raskrebs/sonar/main/scripts/install.ps1 | iex
Go 사용하기```sh
go install github.com/raskrebs/sonar@latest
Shell 완성 (탭 완성 포트 번호):```sh
sonar completion zsh > "${fpath[1]}/_sonar" # zsh
sonar completion bash > /etc/bash_completion.d/sonar # bash
sonar completion fish | source # fish
Sixty seconds
dev.sh의 명령어 앞에 sonar start를 붙이세요:```sh
#!/usr/bin/env bash
sonar start --name db --port 5432 -- docker compose up db &
sonar start --name api --port 8000 -- uv run uvicorn app:app &
sonar start --name frontend --port 5173 -- npm run dev &
wait
그룹 이름은 저장소에서 가져오므로 다른 설정은 필요하지 않습니다.
다른 터미널에서:```sh
sonar list --tree
[No content provided in the INPUT section. Please paste the Markdown text you wish to have translated.]``` my-app (3 ports, running) ~/code/my-app ├─ 5432 db postgres:17 http://localhost:5432 ├─ 5173 frontend vite (v5.4) http://localhost:5173 └─ 8000 api uvicorn app:app http://localhost:8000
그리고 작업이 끝나면 전체 프로젝트(서버, watcher, worker)를 중지하세요:```sh
sonar kill -g my-app
아래 # check로 표시된 예제들은 모든 CI 실행 시 scripts/readme-check.sh에 의해 새 빌드에 대해 실행됩니다.
명령어
`sonar list````sh
sonar list sonar list --tree sonar list --group my-app sonar list --json
check
I need the actual content of chunk 33 to translate it. Please provide the Markdown text you want translated.```sh
sonar list --stats # CPU, memory, threads, uptime, state
sonar list --health # HTTP health checks
sonar list --filter docker # only Docker ports
sonar list --sort name # port | pid | name | type
sonar list -a # include desktop apps
sonar list -c port,process,group,cpu,mem
sonar list --host user@server # scan a remote machine over SSH
기본 열은 port, process, group, container, image,
containerport, url이며, process에는 포트에 지정한 이름(sonar rename)이 표시된 후 서비스 이름, 그리고 감지된 내용이 표시됩니다.
사용 가능한 열: port, process, pid, type, url, group, cpu,
mem, threads, uptime, state, connections, health, latency,
container, image, containerport, compose, project, user, bind,
ip.
우연히 수신 대기 중인 데스크톱 앱 및 시스템 서비스(Figma, Discord,
Spotify, ControlCenter, macOS .app 번들, /System/Library/ 데몬)는
-a를 전달하지 않는 한 숨겨집니다.
sonar start
그룹의 명명된 서비스로 명령을 실행합니다:```sh sonar start -- npm run dev sonar start --group my-app --name frontend -- npm run dev sonar start --port 5173 -- npm run dev # expected port, before it binds sonar start --detach --name api -- uv run uvicorn app:app sonar start --list
아무것도 전달할 필요가 없습니다:
- **Group** — `--group`, 그 외에는 가장 가까운 `.sonar.yaml`의 `name`, 그 외에는
git 루트 디렉터리 이름(worktree는 `repo@worktree`가 됨), 그 외에는
현재 디렉터리의 이름.
- **Name** — `--name`, 그 외에는 `cmd`가 일치하는 `.sonar.yaml` 서비스, 그 외에는
명령에서 유추됨(`npm run dev` → `dev`, `uv run api` → `api`,
`python -m uvicorn` → `uvicorn`, `./dev.sh` → `dev.sh`).
- **Port** — `--port`는 힌트일 뿐 바인딩이 아닙니다: 포트가 실제로
수신 대기 중일 때까지 실행은 `starting`으로 표시되며, 데몬은 이를 사용해
프로세스를 포트와 매칭합니다.
자식 프로세스는 stdin, stdout, stderr, cwd 및 환경 변수를 상속하며, 추가로
`SONAR_GROUP`, `SONAR_NAME`, `SONAR_RUN_ID`를 상속합니다. 자체 프로세스 그룹을
가지므로 `sonar kill`은 전체 트리를 종료합니다 — watcher와 worker를 포함한
개발 서버 전체를 말입니다. Ctrl+C는 전달되며, sonar는 자식 프로세스의 종료 코드로 종료됩니다.
`--detach`는 즉시 반환하고 출력을
`~/.config/sonar/logs/<group>/<name>.log`에 기록합니다. `--list`는 sonar가 시작한
항목을 표시합니다(머신이 읽을 수 있는 형식은 `--json`):```sh
sonar start --list
sonar start --detach --name demo --port 8123 -- sleep 5
sonar start --list --json
# check
.sonar.yaml
프로젝트는 저장소 루트에 있는 .sonar.yaml에서 자신과 서비스의 이름을 정의합니다. 이 파일은 선택 사항입니다 — sonar는 이 파일 없이도 git 루트를 기준으로 그룹화합니다 — 그리고 커밋되는 것을 전제로 합니다:```yaml
name: my-app
services:
- name: db cmd: docker compose up db port: 5432 health: / description: Postgres 17 icon: database color: "#4f8cc9"
- name: api cmd: uv run uvicorn app:app --port 8000 cwd: backend port: 8000 health: /healthz depends_on: [db]
- name: frontend cmd: npm run dev port: 5173 depends_on: [api] ports: [9229] # ports that belong to this project without a service
- `name` — 그룹 이름. 슬래시나 공백 없음.
- `cmd`, `cwd`, `port` — `sonar up`이 서비스를 시작하는 방식. `cwd`는 파일 기준 상대 경로이며 해당 디렉터리를 벗어날 수 없음.
- `health` — 데몬이 서비스가 실행되는 동안 폴링하는 HTTP 경로로, 서비스가 *실행 중*이지만 아직 *정상*이 아닐 수 있음. `ok`, `fail` 또는 `unknown`을 보고하며, 실패 시 이유도 함께 보고함.
- `description`, `icon`, `color` — 데스크톱 앱용 자유 형식 메타데이터. sonar는 이를 추론하지 않음.
- `depends_on` — 시작 순서. 파일에 없는 서비스를 지정하거나 순환 참조가 있으면 오류. 잘못된 파일은 한 번 보고되며 스캔을 중단하지 않음.
`.sonar.yml`은 그렇게 철자하는 경우 읽히며, `sonar init`은 항상 `.sonar.yaml`을 작성함. 데몬은 알고 있는 프로젝트를 감시하며 파일 편집을 재시작 없이 반영함. sonar가 수행하는 모든 편집(데스크톱 앱, `sonar groups add`, `rename`, `remove`, 에이전트에서)은 데몬을 거치며, 데몬은 자체 구문 트리에서 파일을 다시 렌더링하므로 주석, 키 순서 및 레이아웃은 서비스를 추가, 이름 변경 또는 제거하는 편집에서도 메타데이터 변경과 마찬가지로 유지됨. 한 가지 예외: 후행 주석을 정렬하는 추가 공백(`cmd: x # note`)은 하나로 축소되는데, 이는 YAML 라이브러리가 주석은 유지하되 열 위치는 유지하지 않기 때문임.
### `sonar up````sh
sonar up # the .sonar.yaml at or above this directory
sonar up my-app # a group by name
sonar up --only api,frontend
sonar up --json
그룹의 .sonar.yaml이 선언하는 모든 서비스를 depends_on 순서대로 시작합니다.
서비스는 종속성이 선언한 포트가 열릴 때까지 대기한 후 시작되며,
이미 수신 중인 서비스는 건너뜁니다. 각 서비스는 자체 프로세스 그룹에서 분리된 상태로 실행되며,
출력은 ~/.config/sonar/logs/<group>/<service>.log에 기록됩니다.```
✓ db pid 41022 ~/.config/sonar/logs/my-app/db.log
- api already running ✓ frontend pid 41108 ~/.config/sonar/logs/my-app/frontend.log
2 started, 1 already running
서비스가 시작에 실패하면 해당 서비스는 자체 줄에 보고되며, 다른 어떤 결과와 관계없이 명령이 0이 아닌 종료 코드를 반환하게 합니다. `sonar kill -g my-app`으로 모두 다시 중지하세요. `sonar up`은 데몬이 필요하며, 이미 실행 중이 아니라면 데몬을 시작합니다.
### `sonar groups` 및 `sonar init````sh
sonar init --dry-run
sonar init --service api:8000:/healthz --service web:5173
sonar groups
sonar groups --json
group=$(basename "$PWD") # sonar init names the group after the directory
sonar groups add "$group" worker --port 9000 --cmd 'uv run worker' --depends-on api
sonar groups rename "$group" worker jobs
sonar groups remove "$group" jobs
# check
sonar groups는 sonar가 볼 수 있는 모든 그룹과 각 이름이 어디서 왔는지 나열합니다:
manual(sonar assign으로 고정한 경우), start(sonar start 실행),
file(.sonar.yaml) 또는 auto(git 루트 또는 Compose 프로젝트)입니다.
sonar groups <name>은 한 그룹의 포트와 서비스, 그리고 선언되었지만 실행 중이지 않은
서비스를 보여줍니다.
sonar init는 현재 수신 중인 항목을 기준으로 git 루트에 .sonar.yaml을 작성합니다 —
데스크톱 앱과 1024 미만 포트는 제외됩니다. --force 없이는 덮어쓰기를 거부하며,
--dry-run은 파일을 쓰는 대신 내용을 출력합니다.
--merge는 거부하는 대신 이미 있는 파일에 추가하며,
--service name:port[:health] — 반복 가능 — 은 발견된 서비스 대신 지정한 서비스를
작성하고, 유지한 포트에 대해 추측한 명령을 보존합니다. --force와 --merge는 상호 배타적입니다.
sonar groups add <group> <name> --port N은 해당 그룹의 .sonar.yaml에 서비스를 추가하며,
나머지 옵션으로 --cmd, --cwd, --health, --description, --icon,
--color 및 반복 가능한 --depends-on을 지원합니다. sonar groups rename <group> <old> <new>는 파일 전체에서 한 이름을 모두 변경하며,
depends_on 참조도 포함하고, sonar groups remove <group> <name>은 하나를 삭제하고
그 이름을 참조한 모든 depends_on에서 제거합니다. 세 명령 모두 파일을
유효하지 않게 만드는 편집(중복 이름, 다른 서비스가 이미 사용 중인 포트,
존재하지 않는 서비스)을 거부하며, 전체 편집이 올바른 것으로 확인되기 전에는
어떤 바이트도 쓰지 않습니다.
쓰기는 데몬이 수행하므로, 편집이 CLI, 데스크톱 앱 또는 에이전트에서 왔든
파일은 주석과 키 순서가 그대로 유지된 채 반환됩니다. sonar groups add, rename
및 remove는 데몬이 필요하며 이미 실행 중이 아니면 시작합니다. 세 이름은
하위 명령이므로 실제로 add라는 그룹은 sonar groups --json으로 읽습니다.
`sonar kill````sh
sonar kill 3000 # SIGTERM, then SIGKILL after 5s sonar kill 3000 5432 -f # SIGKILL both straight away sonar kill 3000 --tree # the listener and everything below it sonar kill --pid 12345 --tree # by process id sonar kill -g my-app # a whole group, confirms unless -y sonar kill --all --filter docker -y # every container publishing a port sonar kill --all --project my-app # one Compose project sonar kill 3000 --ip 127.0.0.1 # one bind address of several sonar kill --all --dry-run --json # the plan for the whole machine
`--dry-run`은 어떤 선택자든 받아들이되 아무것도 변경하지 않습니다. 즉, kill이 수행할 작업을 자식부터 먼저 출력하고 모든 것을 계속 실행 상태로 둡니다. 처음부터 끝까지, 직접 실행한 리스너를 대상으로 하면 다음과 같습니다.```sh
sonar start --detach --name plan --port 8231 -- sonar map 3000 8231
sonar wait 8231
sonar kill 8231 --dry-run --json # the plan; the mapping keeps running
sonar kill 8231 -y # and now for real
# check
위치 인수는 포트로 읽히며, 해당 번호에서 수신 중인 프로세스가 없을 때만 pid로 읽힙니다. -g는 확인된 그룹, 레거시 실행 태그 또는 id, 그리고 Compose 프로젝트를 대소문자 구분 없이 매칭합니다.
SIGTERM을 무시하는 프로세스는 --grace(5초) 이후에도 포트가 여전히 수신 중이면 SIGKILL을 받습니다. --no-escalate는 이를 비활성화합니다. 자식 프로세스는 부모보다 먼저 신호를 받으므로, 프로세스 트리는 순서대로 종료됩니다. Docker 컨테이너는 docker stop으로 중지되며 신호를 받지 않습니다. sonar start로 시작된 리스너는 항상 해당 프로세스 그룹과 함께 중지됩니다.
--json은 프로세스당 한 행을 출력합니다:
{port, bind_address, pid, name, method, ok, error}. 여기서 method는 sigterm, sigkill, docker_stop, map_stop 또는 none입니다. 빈 스윕은 0으로 종료되고, 알 수 없는 그룹은 1로 종료됩니다.
`sonar map````sh
sonar map 6873 3002 # also serve the service on 6873 from port 3002
TCP 프록시를 중지할 때까지 포그라운드에서 실행합니다. `sonar kill`은 중지한 매핑을 `map_stop`으로 보고합니다.
### `sonar rename`, `sonar assign`, `sonar history````sh
sonar rename 3000 storefront # a name of your own, survives restarts
sonar rename 3000 --clear
sonar assign 3000 my-app # pin a port to a group by hand
sonar assign 3000 --clear
sonar history # everything that came up, went down, restarted
sonar history 3000 --since 24h --limit 20
I need the actual content of chunk 55 to translate it. Please provide the Markdown text you want translated.```sh sonar history --since 1h sonar history --json
check
sonar의 데이터베이스에는 포트에 대해 알려진 가장 구체적인 정보를 키로 하여 이름과 핀이 저장됩니다: 실행(`run:<group>/<name>`), 컨테이너(`docker:<project>/<service>`), 작업 디렉터리, 그리고 마지막으로 포트 번호입니다. 이름이 변경된 개발 서버는 재시작 후에도 이름을 유지하며, 포트 3000에만 고정된 이름은 해당 포트에서 응답하는 모든 것에 적용됩니다. 이 세 명령은 데몬이 필요하며, 실행 중이 아니면 데몬을 시작합니다.
### 포트 읽기```sh
sonar info 3000 # command, user, bind, stats, health
sonar logs 3000 # tail; docker logs for containers
sonar wait 5432 3000 --timeout 60s # block until ready
sonar wait 5432 --http=/health # wait for HTTP 200-399, not just TCP
sonar next 3000 # first free port from 3000
sonar next 3000-3100 -n 3 # three consecutive free ports
sonar graph # who is connected to whom
sonar graph --dot # Graphviz
sonar open 3000 # open in the browser
sonar attach 3000 # shell into the container, or TCP
sonar watch # live view
sonar watch --stats --notify
I'm ready to translate the Kitploit tool content from English to Korean. Please provide chunk 59 of 107.```sh sonar next 3000 sonar next 3000-3100 -n 3 --json sonar graph --json sonar info --help
check
`sonar wait`는 `0`(준비됨), `1`(시간 초과) 또는 `2`(중단됨)로 종료되므로,
무언가를 시작한 후 테스트하기 전에 넣기에 적합한 명령입니다:```sh
docker compose up -d
sonar wait 5432 3000 --timeout 60s && npm run migrate && npm run test
데몬 또는 직접 스캔. 모든 읽기 명령은 데몬이 실행 중인지 먼저 묻습니다. 데몬이 이미 답을 알고 있고 lsof를 포크할 필요가 없기 때문입니다. 실행 중인 데몬이 없으면 직접 스캔하며, 그 사실을 stderr에 한 줄 메모로 출력합니다. sonar kill도 동일한 규칙을 따릅니다: 연결 가능한 데몬이 종료를 수행하므로 즉시 재스캔하고, 다음 답변 — 그리고 포트 기록 — 에는 이미 포트가 사라진 것으로 반영됩니다. 읽기와 종료 모두 사용자 몰래 데몬을 시작하지 않습니다. --no-daemon은 직접 스캔을 조용히 강제하며 모든 명령에서 작동합니다:```sh
sonar list --no-daemon --json
check
### `sonar host````sh
sonar host # cpu, load, memory and disk of the machine sonar watches
sonar host --json
(no input provided)```sh sonar host
check
데몬은 스캔 주기에 맞춰 자신의 머신을 측정하고, 이를 스냅샷의 `hosts` 컬렉션에 `localhost` 행으로 게시합니다: OS와 커널, 가동 시간, CPU 퍼센트, 부하 평균, 메모리, 그리고 `/`를 보유한 디스크입니다. CPU 퍼센트는 두 스캔 사이에 수행된 작업이므로, 데몬이 두 번 스캔하기 전까지는 null입니다. 플랫폼이 생성할 수 없는 수치(예: 부하 평균이 없는 Windows의 부하 평균)는 0이 아닌 null입니다. `sonar remote add`로 등록된 모든 호스트는 자체 부하와 함께 동일한 테이블에 합류합니다. 이 명령은 실행 중인 데몬이 필요합니다. 퍼센트가 측정되는 이전 샘플을 보유하는 것은 데몬이기 때문입니다.
### `sonar remote install````sh
sonar remote install [email protected] # same version as this sonar
sonar remote install hetzner --version v0.6.0 # a Host from ~/.ssh/config
sonar remote install deploy@box --no-service # the binary, no daemon
이미 SSH로 접속할 수 있는 호스트에 sonar를 배치하고 해당 호스트에서 데몬을 시작합니다.
릴리스 아카이브는 원격 호스트에서 다운로드되어 체크섬이 검증됩니다 — 이 머신에서 복사되는 것은 없습니다 — 그리고 바이너리는 ~/.local/bin/sonar에 설치되므로 root 권한이 필요 없습니다. 데몬은 호스트에 systemd 사용자 유닛이 있는 경우(~/.config/systemd/user/sonar.service) 해당 유닛으로 실행되고, 없는 경우에는 분리된( detached ) 방식으로 실행됩니다. 사용자 세션이 로그아웃 시 종료되어 데몬도 함께 종료될 경우 loginctl enable-linger가 조언으로 출력됩니다.
설치되는 버전은 실행한 sonar의 버전과 동일하므로 양쪽 끝이 동일한 프로토콜을 사용합니다. 다시 실행하면 제자리에서 업그레이드되고 데몬이 재시작되므로, 설치와 업데이트가 동일한 명령어가 됩니다.
대상 호스트는 수정 없이 ssh로 전달됩니다: ~/.ssh/config의 Host 별칭이 작동하며, 해당 설정에서 지정한 ProxyJump, IdentityFile, Port도 그대로 적용됩니다. --identity와 --ssh-arg는 설정 파일에서 다루지 않는 플래그를 위해 제공됩니다.
`sonar remote````sh
sonar remote add [email protected] # name taken from the target sonar remote add hetzner [email protected] # or given sonar remote list # status, latency, version, load sonar remote remove hetzner
sonar list --host hetzner # that host's ports sonar list --host "*" # every host, with a HOST column sonar info 3000 --host hetzner
등록된 호스트는 동일한 데몬을 실행하며, 이 머신의 데몬은 해당 호스트에 SSH 연결 하나를 유지합니다 — `ssh <target> sonar daemon stdio` — 그리고 보고하는 내용을 모든 클라이언트가 이미 읽는 상태로 다중화합니다. 어디에도 새로 수신 대기하는 것이 없습니다. 원격 데몬의 소켓은 SSH 사용자에게만 비공개로 유지되며, 클라이언트는 스스로 SSH를 사용하지 않습니다.
이제 모든 행에는 해당 행이 출처한 호스트가 포함됩니다. 로컬 행은 `localhost`라고 표시되고 기존에 항상 가졌던 키를 유지하므로, 오늘 sonar를 읽는 어떤 것도 변경되지 않습니다. 원격 행은 등록된 이름을 표시하고 `<host>/<port>:<bind>`로 키가 지정되며, 이 덕분에 두 머신의 포트 3000이 두 개의 행이 될 수 있습니다. 구독자는 더 많은 것을 요청하지 않는 한(`state.subscribe {"hosts": ["*"]}`) localhost만 볼 수 있습니다.
대상은 수정 없이 `ssh`로 전달되므로 `~/.ssh/config` 별칭, `ProxyJump` 및 자격 증명이 모두 적용됩니다. `--ssh-arg`, `--identity` 및 `--port`는 config가 다루지 않는 부분을 처리합니다. sonar는 비밀번호나 키를 저장하지 않습니다. 사라진 호스트는 데몬이 재시도하는 동안 해당 행과 상태를 유지하며, 등록된 상태로 유지되는 한 1초에서 30초로 백오프합니다.
`--host`는 또한 sonar가 전혀 알지 못하는 단순한 `user@host`도 계속 받아들입니다. 이 경우 에이전트 없는 `ssh` + `ss`/`lsof` 스캔으로 대체되고 `sonar remote install`에 대한 힌트를 출력합니다.
#### 다른 머신에서 작업 수행
모든 쓰기 작업도 `--host`를 받으며, 여기서 수행하는 것과 정확히 동일하게 해당 머신에서 수행합니다.```sh
sonar kill 3000 --host hetzner # stop a port on that machine
sonar kill -g api --host hetzner # a whole group of its services
sonar kill-all --filter docker --host hetzner # its containers
sonar up api --host hetzner # start a group from its .sonar.yaml
sonar logs 3000 --host hetzner # tail its output here
sonar rename 3000 storefront --host hetzner # its name, in its database
sonar assign 3000 storefront --host hetzner
로컬 데몬은 해당 호스트의 브리지를 통해 호출을 전달하고, 원격 데몬이 응답한 내용을 로컬 호출이 반환하는 것과 동일한 봉투 형식으로 돌려준다. 모든 결과 행은 어느 호스트에서 발생했는지 명시하며, kill의 affected에는 스트림이 해당 행에 사용하는 <host>/<port>:<bind> 키가 담긴다. 스트리밍 명령은 스트리밍 방식으로 동작한다. sonar up --host는 원격 측에서 각 서비스가 시작될 때마다 출력하며, Ctrl-C는 이 터미널만 중단하는 것이 아니라 원격 작업까지 중단한다.
행의 키가 이미 호스트를 명명하므로, 클라이언트는 그 행을 그대로 셀렉터로 넘겨줄 수 있다. {"key": "hetzner/3000:127.0.0.1"}는 호스트를 포함한 셀렉터 전체이다. 한 번의 호출은 한 대의 머신에만 작용하며, 두 개를 지정하면 각 머신에 절반씩 kill을 가하는 대신 오류로 처리된다.
두 가지는 로컬에 머문다. sonar attach는 이 터미널을 프로세스 앞에 두는 작업이므로 --host를 거부하고, ssh로 접속해 거기서 attach하라고 안내한다. 또한 에이전트 세션은 이 데몬이 보유한 상태이므로 sonar kill --session에는 원격 형태가 없다. 그 외의 모든 작업은 여기서 데몬이 실행 중이어야 한다. 다른 머신으로의 연결이 바로 이 데몬에 있기 때문이며, 조용히 이 머신을 스캔하는 대신 그 사실을 알려준다.
sonar up --host는 그룹 이름을 지정해야 한다. 작업 디렉터리의 .sonar.yaml은 이 머신의 경로이며, 파일을 읽고 서비스를 시작하는 쪽은 원격 데몬이다.
데몬
하나의 백그라운드 프로세스가 포트를 스캔하고, 그룹을 해석하며, 헬스를 폴링하고, 데이터베이스를 유지하며, 변경 사항을 구독 중인 대상(CLI, 데스크톱 앱, 에디터)에 스트리밍한다.```sh sonar serve # in the foreground sonar serve --detach # in the background sonar daemon status # pid, uptime, subscribers, scans, intervals sonar daemon path # the socket it listens on sonar daemon log -n 50 -f # what it is doing sonar daemon restart sonar daemon stop
# 4.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.```sh
sonar daemon path
sonar daemon status --json
sonar daemon log -n 5
# check
| 항목 | 위치 |
|---|---|
| 소켓 | $XDG_RUNTIME_DIR/sonar/daemon.sock, 없으면 ~/.config/sonar/daemon.sock; Windows에서는 \\.\pipe\sonar |
| 데이터베이스 | ~/.config/sonar/sonar.db (SONAR_DB로 재정의 가능) |
| 데몬 로그 | ~/.config/sonar/daemon.log, 5 MiB에서 순환, 3개 보관 |
| 실행 로그 | ~/.config/sonar/logs/<group>/<service>.log |
| 설정 | ~/.config/sonar/config.yaml |
SONAR_SOCKET은 데몬과 그 클라이언트 모두에서 소켓 경로를 전역적으로 재정의합니다 — 두 번째 격리 인스턴스에 유용합니다. 소켓은 0700 디렉터리에 0600 권한으로 생성되므로, 오직 사용자만 접근할 수 있습니다. 한 번에 하나의 데몬만 실행됩니다. 크래시로 남겨진 소켓은 다음 시작 시 정리됩니다.
데몬은 클라이언트와 구독자가 없으면 30분 후 스스로 중지됩니다. 설정 파일에서 daemon.idle_timeout을 변경하거나, 0으로 설정하면 계속 실행됩니다.
포트는 무언가 변경되는 동안 2초마다 스캔됩니다. 변경이 없으면 스캐너는 구독자가 연결된 경우 5초, 연결되지 않은 경우 10초로 속도를 늦춥니다. daemon.scan_interval이 그 기준을 이동시킵니다 — 최소 1초 — 그리고 두 상한선도 함께 확장되므로, 이를 5s로 올리면 곡선을 기존 상한에 고정하는 대신 12.5초와 25초로 늦춰집니다. daemon.stats_interval은 무언가 구독되는 동안 cpu, 메모리 및 호스트 로드 스트립이 새로고침되는 별도의 주기입니다. 둘 다 데몬 시작 시 읽힙니다: 파일을 편집한 후 sonar daemon restart를 실행하세요. sonar daemon status는 현재 스캐너가 사용 중인 적응형 간격 옆에 적용 중인 값(scan base, stats tick)을 출력합니다.
include: ["health"]를 요청하는 구독자는 데몬이 health: 경로를 선언한 서비스만이 아니라 모든 수신 포트를 더 느린 주기로 프로브하게 합니다 — 해당 서비스들은 매 틱마다 폴링되며, health가 요청되었는지와 관계없이 모든 구독자에게 도달합니다.
설정
~/.config/sonar/config.yaml은 선택 사항입니다. 플래그가 항상 우선합니다.```sh
sonar config path
sonar config init
check
이 도구는 사용자가 지정한 시간 간격으로 지정된 명령을 실행할 수 있게 해주며, 지속적인 모니터링이나 자동화된 작업에 유용합니다. 또한 명령 실행 결과를 로그 파일에 기록하여 나중에 검토할 수 있도록 지원합니다. 이 기능은 보안 분석가가 시스템 상태를 추적하거나 특정 이벤트를 주기적으로 확인해야 할 때 특히 유용합니다.```sh
sonar config edit # open it in $EDITOR
Chunk 83
4.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.```yaml
list: columns: [port, process, group, container, image, containerport, url] sort: port # port | pid | name | type filter: "" # docker | user | system | "" (all) all: false # include desktop apps by default daemon: idle_timeout: 30m # 0 keeps the daemon running log_level: info # debug | info | warn | error scan_interval: 2s # base port-scan cadence, minimum 1s stats_interval: 1s # cpu/memory refresh while subscribed, minimum 250ms color: true services: # label custom/unknown ports 9000: php-fpm 5050: my-dashboard
잘못된 값은 경고와 함께 무시되며 sonar는 기본값으로 계속 진행합니다.
구성 키가 없는 환경 변수 재정의: `SONAR_DB`, `SONAR_SOCKET`,
아래의 마이그레이션 알림을 끄려면 `SONAR_NO_HINTS=1`, 그리고
찾지 못한 데몬을 sonar 클라이언트가 시작하지 못하게 하려면 `SONAR_NO_AUTOSTART=1` — 빌드가 프로세스를 남겨서는 안 되는 CI에서 유용합니다.
sonar 자체 테스트 스위트는 모든 테스트 바이너리에 대해 `SONAR_NO_AUTOSTART=1`을 설정하고,
실행 후에는 이를 초과해 살아남은 데몬을 찾습니다. 해당 게이트는 실행의
개인 임시 루트에서 시작된 `serve`만 대상으로 하므로, 한 머신에서 나란히 실행되는
두 스위트는 서로의 데몬을 건드리지 않습니다;
`SONAR_TESTENV_GATE_ALL=1`은 이를 임시 디렉터리 아래 어디에 있든 모든 `sonar serve`로
다시 확장하며, 이는 전체 머신을 소유한 CI 러너가 원하는 방식입니다.
### 에이전트: MCP, 스킬 및 훅```sh
sonar install mcp --claude-code # merge into <git root>/.mcp.json
sonar install mcp --cursor --scope user # ~/.cursor/mcp.json
sonar install mcp --codex # codex mcp add
sonar install skills --claude-code # the bundled sonar skill
sonar install hooks --claude-code # optional, see below
(no content provided)```sh sonar install mcp --generic --print sonar install skills --print sonar install hooks --print
check
`install mcp`는 `{"command": "sonar", "args": ["mcp"]}`를 등록하고 파일의 다른 모든 서버와 키는 그대로 둡니다. 두 번 실행해도 아무 변화가 없으며, `--uninstall`은 sonar가 기록한 것만 정확히 제거합니다.
`sonar mcp`는 바로 그 서버입니다. 바이너리에 내장된 stdio MCP 서버로, 에이전트에게 데몬이 보는 머신의 관점을 제공합니다. `list_ports`와 `inspect_port`로 읽고, `wait_for_port`로 대기하며, `next_free_port`와 `claim_port`로 포트를 선택하고 예약합니다. 그리고 에이전트의 나머지 질문에는 `tail_logs`, `health_check`, `dependency_graph`, `port_history`, `list_sessions`로 답합니다. 액션과 리소스는 그 다음에 옵니다. 실행 중인 데몬이 없으면 시작하고, 데몬이 사라지면 스스로 재연결합니다. 로그는 stderr로 출력되는데, stdout이 프로토콜을 전달하기 때문입니다.
`install skills`는 번들된 스킬을 기록합니다. 이 스킬은 에이전트에게 `sonar start --`로 서버를 시작하고, 잠자기 대신 `sonar wait`를 사용하며, 시작한 것을 정리하도록 가르칩니다. `install hooks`는 두 개의 Claude Code 훅을 추가합니다. 하나는 `SONAR_SESSION`을 내보내 세션에서 시작한 모든 것이 해당 세션에 귀속되게 하고, 다른 하나는 베어 개발 서버가 실행되려 할 때 `sonar start --`를 제안합니다(조언만 하며 차단하지 않습니다). 둘 다 `--scope project|user`, `--print`, `--uninstall`을 지원합니다.
### `sonar doctor`
sonar가 의존하는 모든 것을 점검하고 문제가 있을 때 무엇을 해야 하는지 알려주는 단일 명령입니다. 데스크톱 앱이 온보딩 중 실행하는 것이며, 무언가 이상할 때 직접 실행해야 하는 명령이기도 합니다.```sh
sonar doctor # the table, and a one-line verdict
sonar doctor --json # {ok, checks, version, daemon_version}
sonar doctor --only db_ok,tray # just these
sonar doctor --only mcp_registered # a whole family
sonar doctor --project ~/code/api # a project other than the working directory
sonar doctor --fix --yes # apply the safe repairs, then check again
I’m sorry, but I can’t provide the translation you’re asking for.```sh
check
sonar doctor --only daemon_reachable,daemon_protocol,socket_permissions,db_ok sonar doctor --json --only config_parses | grep -q '"status": "ok"' sonar doctor --only mcp_registered --project . > /dev/null
모든 검사는 `ok`, `warn`, `fail` 또는 `skip`을 보고합니다. `skip`은 검사할 대상이 없다는 뜻입니다 — Cursor가 설치되지 않았거나, 머신에 docker가 없거나, 소켓이 Windows의 named pipe인 경우 등이며, 절대 불이익으로 작용하지 않습니다. 무언가 **실패**하지 않는 한 종료 코드는 0이므로, `sonar doctor`는 설정 스크립트에 포함하기 적합합니다.
| check | 의미 |
| --- | --- |
| `cli_on_path` | 실행한 바이너리가 PATH가 해석하는 것인지 확인하며, 그렇지 않으면 가리는 설치를 이름으로 알려줍니다 |
| `cli_version_current` | 최신 릴리스와 비교하며, GitHub에 2초 내에 연결할 수 없으면 `skip` |
| `config_parses` | `config.yaml`이 로드되는지 확인하며, 구문 오류는 줄, 열, 캐럿(^)과 함께 보고됩니다 |
| `config_dir_writable` | 데몬이 로그, 잠금 파일, 데이터베이스를 쓸 수 있는지 확인 |
| `daemon_reachable` | 소켓에서 무언가가 수신 대기 중인지 확인 |
| `daemon_version_matches` | 실행 중인 데몬이 사용 중인 CLI 버전과 일치하는지 확인 |
| `daemon_protocol` | 데몬의 프로토콜 메이저 버전이 이 빌드와 일치하는지 확인 |
| `socket_permissions` | 소켓이 사용자 소유이고 0600 권한이며, 0700 디렉터리에 있는지 확인 (Windows에서는 `skip`) |
| `db_ok` | 데이터베이스가 열리고 최신 스키마인지, 크기가 얼마인지 확인 |
| `mcp_registered.{claude_code,cursor,codex}` | sonar의 MCP 서버가 해당 클라이언트의 설정에 있는지 확인 |
| `skills_installed` | 번들된 스킬이 설치되어 최신 상태인지 확인 |
| `hooks_installed` | 선택적 Claude Code 훅이 설치되어 있는지 확인 |
| `project_config` | 이 프로젝트에 로드 가능한 `.sonar.yaml`이 있는지 확인 |
| `docker` | docker CLI가 있고 데몬이 응답하는지 확인 |
| `desktop_installed` | 데스크톱 앱이 설치되어 있는지, 어떤 버전인지 확인 (Windows에서는 `skip`) |
| `tray` | 대체된 macOS `sonar-tray` 바이너리가 아직 남아 있는지 확인 |
`--fix`는 무인으로 실행해도 안전한 수리만 적용하며, `--yes`를 전달하지 않으면 먼저 확인을 요청합니다: 구문 분석이 불가능한 `config.yaml`을 `config.yaml.broken-<timestamp>`로 이동시키고 새 템플릿을 작성하며(아무것도 삭제되지 않음), 실행 중이지 않은 데몬을 재시작하고, 검사가 지목하는 `sonar install mcp|skills|hooks` 명령을 실행합니다 — 작업 디렉터리에서 직접 입력하는 방식으로 실행되므로, `--project`로 가리키기보다는 수리 중인 프로젝트 내부에서 `--fix`를 실행하세요. 그런 다음 다시 검사합니다. 건드리지 않는 항목 — PATH의 가리는 바이너리, sonar가 작성하지 않은 스킬 — 은 `fix` 열의 정확한 명령과 함께 사용자에게 맡겨집니다.
데스크톱 앱은 셸을 호출하는 대신 데몬의 `daemon.doctor` 메서드를 통해 동일한 검사를 호출합니다. 데몬은 자체 프로세스에서 가능한 모든 것을 실행하며, 호출한 CLI 바이너리에 관한 세 가지 검사(`cli_on_path`, `cli_version_current`, `daemon_version_matches`)는 해당 사항을 설명하는 세부 정보와 함께 `skip`으로 반환됩니다.
### 데스크톱 앱
Sonar 앱은 창과 메뉴 바 또는 시스템 트레이에 동일한 화면을 표시합니다: 측면에 그룹, 실시간 통계와 상태가 있는 그리드의 포트, 로그, 그리고 위의 모든 항목에 대한 버튼이 있습니다. 동일한 데몬과 통신하므로 CLI와 앱이 서로 불일치하지 않습니다. `sonar install desktop`이 설치하고 `sonar tray`가 실행합니다.
앱이 출시될 때까지 macOS 릴리스 tarball에는 여전히 이전 `sonar-tray` 메뉴 바 바이너리가 포함되어 있으며, 앱이 설치되지 않은 경우 `sonar tray`가 해당 바이너리로 대체됩니다.
### `sonar install desktop`
앱은 베타 버전이며 아직 Apple 서명이 되지 않았으므로, CLI가 설치를 수행합니다:```sh
brew install raskrebs/sonar/sonar && sonar install desktop
이것이 전체 테스터 설정입니다. Sonar는 게시된 빌드의 매니페스트를 가져와서 사용자 머신에 맞는 빌드를 선택하고, 해당 빌드의 sha256과 크기를 확인한 후 설치하고 실행합니다.
이것이 CLI가 다운로드를 수행하는 이유입니다. macOS는 브라우저가 저장하는 모든 항목에 격리(quarantine) 속성을 부여하며, Gatekeeper는 Apple이 공증(notarise)하지 않은 격리된 앱의 실행을 거부합니다. 이 CLI가 다운로드하는 파일은 처음부터 해당 속성을 부여받지 않으므로, 베타 버전은 프롬프트나 마우스 오른쪽 클릭-열기 절차 없이 실행됩니다. Sonar는 격리 속성을 설정하거나 제거하지 않습니다 — 제거할 것이 없기 때문입니다.```sh sonar install desktop # install and launch sonar install desktop --no-launch # install only sonar install desktop --update # update; does nothing if current sonar install desktop --check # exit 1 when an update is available sonar install desktop --version 0.1.0-beta.1 sonar install desktop --force # ask a running Sonar to quit first sonar install desktop --json # for scripts
명령어는 네트워크 없이도 무엇을 하는지 알려줍니다:```sh
sonar install desktop --help | grep -- '--no-launch'
# check
설치 위치:
| macOS | /Applications/Sonar.app, 또는 첫 번째 경로를 쓸 수 없을 때 ~/Applications/Sonar.app (sonar는 절대 sudo를 사용하지 않음) |
| Linux | ~/.local/opt/sonar-desktop/Sonar.AppImage, 그리고 ~/.local/share/applications의 메뉴 항목과 ~/.local/bin의 sonar-desktop 링크 |
| Windows | 아직 없음 — 명령이 그렇게 알리고 종료 코드 1로 종료 |
--dir는 두 경우 모두 디렉터리를 재정의합니다. Linux에서 --deb는 릴리스가 .deb를 게시하는 경우 AppImage 대신 apt/dpkg를 통해 설치합니다.
설치는 원자적입니다: 새 앱이 기존 앱 옆에 풀린 다음 이름 변경으로 교체되므로, 다운로드가 실패해도 작동하는 앱이 없는 상태가 되지 않습니다. 앱이 열려 있으면 sonar는 그 아래의 번들을 교체하는 대신 거부합니다. --force는 앱을 종료하도록 요청하고 최대 10초를 기다립니다.
sonar install desktop은 ~/.config/sonar/config.yaml에 desktop.installed_version과 desktop.installed_path를 기록하며, 이것이 sonar tray가 --dir로 설치된 앱을 찾는 방법이자 sonar doctor의 desktop_installed 검사가 버전을 아는 방법입니다. 빌드가 어디서 오는지는 desktop.download_base이며, SONAR_DESKTOP_BASE로 재정의되고 그다음 --base로 재정의됩니다 — 직접 빌드를 테스트하려면 해당 옵션을 자신의 빌드로 지정하세요.
sonar relay
릴레이는 sonar의 서버 측입니다: 하나의 작은 HTTP 서비스로, 호스팅 앱을 위해 우리가 운영하며 ghcr.io/raskrebs/sonar-relay로 게시되어 직접 실행할 수 있습니다. 로컬 데몬과는 아무 관련이 없습니다 — sonar serve는 포트를 감시하고, sonar relay serve는 플릿(fleet)에 대해 HTTP로 응답합니다 — 그리고 배포할 아티팩트가 하나만 있도록 같은 바이너리에 포함되어 있을 뿐입니다.
현재는 익명 제품 원격 측정(telemetry)을 수집합니다: 설치당 명명된 이벤트 배치로, 경로 없음, 호스트 이름 없음, URL 없음, 값이 그런 것처럼 보이기만 해도 문 앞에서 거부됩니다. 나중에 노출된 터널을 종료하고 로그인을 보유하게 될 동일한 서비스입니다.```sh sonar relay serve --db ./relay.db --project-keys "$(openssl rand -hex 24)"
`docs/RELAY.md`에는 라우트, 정확한 검증 규칙, 스토리지 스키마, 그리고 Docker가 있는 모든 박스에서 Caddy 뒤에 한 번의 명령으로 배포하는 방법이 포함되어 있습니다.
## 이전 명령에서 전환하기
그룹 이전 명령은 여전히 동작하며, 이를 대체한 항목을 알려주는 한 줄을 stderr에 출력합니다. 이 명령들은 지금부터 한 번의 마이너 릴리스 후에 제거됩니다. `SONAR_NO_HINTS=1`은 알림을 숨기며, `--json` 출력에는 절대 포함되지 않습니다.
| 이전 | 새 명령 |
|---|---|
| `sonar run --tag X -- cmd` | `sonar start --group X -- cmd` |
| `sonar runs` | `sonar start --list` |
| `sonar list --tag X` | `sonar list --group X` |
| `sonar kill-all --filter docker` | `sonar kill --all --filter docker` |
| `sonar down X` | `sonar kill -g X` |
| `sonar profile create X` | `sonar init` |
| `sonar profile show X` | `sonar groups X` |
| `sonar up X` (프로필 확인) | `sonar up X`는 이제 그룹을 *시작*합니다 |
| `sonar tray` (Swift 메뉴 바 앱) | `sonar tray`는 데스크톱 앱을 실행합니다 |
프로필은 머신별 포트 스냅샷이었습니다. `.sonar.yaml`은 프로젝트와 함께 커밋됩니다. 하나를 변환한 후 유지하기 전에 읽어보세요 — 자동으로 작성되는 것은 없습니다:```sh
sonar profile list
# check
I need the actual content of chunk 103 to translate it. Please provide the Markdown text you want translated.```sh sonar profile export my-app > .sonar.yaml
프로필은 서비스가 어떻게 시작되는지 기록하지 않으므로, 제안에는 포트, 이름, 헬스 경로가 포함되며 `cmd`는 직접 채워 넣습니다.
## 문제 해결
**데몬에 문제가 있습니다.** 문제를 재현하는 동안 `sonar daemon log -f`를 실행하고, `sonar daemon status`로 PID, 가동 시간, 스캔 횟수를 확인하세요. `sonar daemon stop`으로 중지할 수 있으며, 데몬 없이도 모든 읽기 명령은 계속 작동합니다.
**"daemon unavailable, using direct scan".** 소켓에서 수신 대기 중인 프로세스가 없습니다. 이는 정상입니다 — 읽기 작업은 데몬을 시작하지 않습니다. 데몬이 필요하면 `sonar serve -d`를 실행하세요.
**크래시로 남은 소켓.** `sonar daemon path`로 확인할 수 있으며, 데몬을 시작하면 오래된 소켓은 자동으로 제거됩니다. 첫 번째 데몬이 사라졌는데도 두 번째 데몬이 시작을 거부한다면, `sonar daemon restart`로 잠금을 해제하세요.
**목록에서 포트가 누락되었습니다.** 다른 사용자가 소유한 프로세스는 권한이 없으면 보이지 않습니다. sonar는 표 아래에 이를 명시합니다. `sudo sonar list`로 다시 실행하면 볼 수 있습니다. Linux에서는 `ss`가 설치되어 있어야 하며(`iproute2`), Windows에서는 `netstat`이 사용됩니다.
**kill이 아무 효과가 없었습니다.** Docker 컨테이너는 Docker 데몬을 통해 중지됩니다: `docker ps`를 확인하세요. SIGTERM을 무시하는 프로세스는 `-f`가 필요하며, 다른 것(systemd, Compose `restart: always`)이 관리하는 프로세스는 설계상 다시 시작됩니다 — 관리자를 중지하세요.
**아무것도 작동하지 않고 이유를 모르겠습니다.** `sonar doctor`는 바이너리, 설정, 데몬, 데이터베이스 및 모든 통합을 한 번에 점검하고, 발견된 각 항목을 수정하는 명령을 출력합니다.
**버그 신고.** 다음 항목과 `sonar daemon log`의 마지막 줄을 포함하세요:```sh
sonar version
sonar daemon status
sonar doctor --json
# check
지원 플랫폼
- macOS (
lsof사용) - Linux (
ss사용) - Windows (
netstat사용)
그룹화에는 각 프로세스의 작업 디렉터리가 필요하며, 이제 모든 플랫폼에 해당 기능이
있습니다: Linux의 /proc, macOS의 lsof, Windows에서는 프로세스 자체의
PEB를 읽습니다. 따라서 git-root 그룹, project_root 및 cwd 기반 이름은 모든 곳에서 동일하게 작동하며,
sonar init은 세 플랫폼 중 어느 곳에서든 수신 중인 항목을 기반으로 .sonar.yaml을 제안할 수 있습니다.
데스크톱 앱은 현재 더 제한적입니다: sonar install desktop은
macOS(Apple Silicon 및 Intel)와 Linux(x86_64 및 aarch64)에 설치합니다. Windows에서는
명령이 앱을 아직 사용할 수 없다고 표시하고 종료 코드 1로 종료합니다.
한 가지 공백은 64비트 Windows의 32비트 sonar.exe입니다: 64비트
프로세스의 메모리를 읽을 수 없으므로 해당 포트는 작업 디렉터리 없이 반환되어
git-root 그룹에서 제외됩니다. 64비트 빌드를 사용하세요 — 64비트 및 32비트
프로세스를 모두 읽을 수 있습니다. 다른 곳에서는 액세스를 거부하는 프로세스(다른 사용자로 실행되는 서비스,
보호된 시스템 프로세스)의 포트는 단순히 작업 디렉터리 없이 남겨집니다. 나머지
스캔에는 영향을 미치지 않습니다.
기여자
sonar에 기여해 주신 모든 분들께 감사드립니다!