단일 바이너리로 묶겠습니다. 다행히 필요한 의존성이 이미 다 링크돼 있습니다 — libwebsockets(HTTP·WS 서버), libsodium(Ed25519), jansson(JSON). Node.js 런타임을 넣을 필요가 없습니다. 먼저 진입점과 lws 사용 방식을 확인하겠습니다. 단일 실행파일로 통합하기 위해 아키텍처를 설계했습니다 단일 실행파일로 통합하기 위해 아키텍처를 설계했습니다 구조를 잡겠습니다. lws 하나로 HTTP·WS·릴레이를 모두 처리합니다. 서버 구성 요소와 세션 관리 시스템을 설계했습니다 서버 구성 요소와 세션 관리 시스템을 설계했습니다 이제 서버입니다. lws 하나에 HTTP·UI 웹소켓·에이전트 릴레이를 모두 얹습니다. Libwebsockets server providing HTTP API, UI push, and agent relay Libwebsockets server providing HTTP API, UI push, and agent relay 이제 웹 UI를 바이너리에 박고 진입점을 연결하겠습니다. CMake 문법 오류를 진단하고 해결책을 발견했습니다 CMake 문법 오류를 진단하고 해결책을 발견했습니다 CMake는 한 줄에 두 명령을 못 씁니다. 고치겠습니다. Fix the CMake syntax and rebuild Fix the CMake syntax and rebuild 자산 임베딩이 됐습니다. 헤더 누락만 고치면 됩니다. 명령 3개 실행함 명령 3개 실행함 단일 바이너리 완성입니다. 이제 실제로 돌려서 검증하겠습니다. Run console mode and verify embedded UI and API Run console mode and verify embedded UI and API 웹 UI가 바이너리에서 그대로 나옵니다. 이제 에이전트를 붙여 전체 흐름을 검증하겠습니다. 환경 변수 요구사항 검증 중입니다 환경 변수 요구사항 검증 중입니다 ingest는 동작합니다. 명령 채널이 PSK나 클라이언트 인증서를 요구하네요. 조건을 확인하겠습니다. 보안 옵션을 검토하고 단일 실행파일 통합 방안을 수립했습니다 보안 옵션을 검토하고 단일 실행파일 통합 방안을 수립했습니다 명령 채널이 PSK/mTLS를 요구합니다. WireGuard 위에서 도는 로컬 구성을 위해 명시적 opt-in 경로를 추가하겠습니다. 설정 검증 통과 후 TLS 연결 문제를 추적 중입니다 설정 검증 통과 후 TLS 연결 문제를 추적 중입니다 설정은 통과했는데 cmd_client_start 가 실패합니다. 클라이언트가 항상 TLS를 켜는 것 같습니다. 명령 3개 실행함 명령 3개 실행함 cmd_client_start 가 mTLS 인증서를 필수로 검사합니다. 평문 경로를 인식하도록 고치겠습니다. WebSocket 프로토콜 불일치 문제를 진단했습니다 WebSocket 프로토콜 불일치 문제를 진단했습니다 콘솔이 에이전트를 등록하지 못했습니다. 서브프로토콜 이름이 안 맞는 듯합니다. Find the WebSocket subprotocol name the agent requests Find the WebSocket subprotocol name the agent requests 프로토콜 이름이 miho-relay-v1 입니다. 콘솔 쪽을 맞추겠습니다. 동기화 문제를 파악하고 이중 컨텍스트 구조로 재설계했습니다 동기화 문제를 파악하고 이중 컨텍스트 구조로 재설계했습니다 교착입니다. HTTP 콜백이 lws 서비스 스레드에서 도는데, 거기서 결과를 기다리니 같은 스레드가 결과를 받을 수 없습니다. 컨텍스트를 분리하겠습니다. 명령 2개 실행함 명령 2개 실행함 완전히 동작합니다. Node.js를 걷어내고 문서·패키징을 정리하겠습니다. 명령 3개 실행함, 파일 생성됨, 파일 읽음 명령 3개 실행함, 파일 생성됨, 파일 읽음 단일 바이너리 완성입니다. Node.js가 없어졌습니다. mihosrcd 에이전트 mihosrcd --console 제어평면 (웹 UI + 릴레이 + 서명) 1.4MB 파일 하나에 웹 UI까지 다 들어 있습니다. 필요한 게 이미 다 있었습니다 필요 이미 링크돼 있던 것 HTTP + WebSocket 서버 libwebsockets Ed25519 서명 libsodium JSON jansson 프레임 프로토콜 frame.c — 에이전트와 같은 파일 마지막이 제일 큽니다. Node.js로 프레임을 따로 구현했을 때는 와이어 포맷이 어긋날 위험이 상존했는데, 이제 같은 mf_encode 를 쓰니 그럴 수가 없습니다. 웹 UI는 CMake가 빌드 시점에 C 배열로 박습니다. xxd 같은 외부 도구도 안 씁니다. 구현 중 부딪힌 것 — 교착 처음엔 HTTP와 릴레이를 lws 컨텍스트 하나에 vhost 두 개로 얹었더니 명령이 전부 15초 타임아웃 으로 죽었습니다. 원인은 명확했습니다. HTTP 콜백이 lws 서비스 스레드에서 도는데 거기서 명령 결과를 기다렸습니다. 그 결과를 받아야 할 게 바로 그 스레드입니다. 자기가 자기를 기다리는 구조였습니다. 릴레이를 별도 컨텍스트 + 전용 스레드로 분리해서 해결했습니다. 검증 빌드 eBPF × console 4조합 전부 성공 웹 UI / /style.css /app.js 200, MIME 정상, 404 처리 서명명령 sooke/shutdown HTTP 200 30 ms bsde/start HTTP 200 24 ms ghost/start HTTP 422 VIRT_DOMAIN_NOT_FOUND 20 ms UPID PVE Task 딥링크용 UPID 가 결과에 실림 HA "sooke 는 HA 대상" 경고 확인 압축본을 다시 풀어 빌드하고 실행까지 확인했습니다. 하나 추가한 것 평문 ws:// 경로를 열었습니다. TLS 인증서 없이 돌리면 PSK도 mTLS도 성립하지 않는데, 명령의 인가는 Ed25519 서명이 담당하므로 신뢰된 링크(WireGuard) 위에서는 유효합니다. 다만 MIHO_INSECURE=1 로 명시적 opt-in을 요구하고 경고를 찍습니다. 공개망이면 MIHO_CONSOLE_TLS_CERT 를 설정해 WSS를 쓰십시오. 실행 sh mihosrcd --console # → 서명 공개키 출력. 모든 에이전트에 넣으십시오. 자세한 건 CONSOLE.md 에 있습니다. 콘솔에 인증이 없으니 리버스 프록시 뒤에 두셔야 합니다.