콘텐츠로 이동

설치 및 환경 구성

2.1 솔루션 권장사항 — Ubuntu 26.04.1 LTS

섹션 제목: “2.1 솔루션 권장사항 — Ubuntu 26.04.1 LTS”

PureCVisor 2.0.0 Single Edge의 공개 설치 기준은 Ubuntu Server 26.04.1 LTS(Resolute Raccoon) amd64입니다.
Ubuntu 26.04 LTS는 2031년 4월까지 표준 보안 업데이트와 중요 수정이 제공되며, 26.04.1 설치 이미지는 26.04 출시 이후의 설치·기본 플랫폼 수정 사항을 포함합니다.

설치 기준

항목기능 확인용 최소 구성운영 권장 구성검증 기준 노드
CPU8코어, VT-x/AMD-V16코어 이상, IOMMUIntel Xeon E5-2697 v4 2소켓, 36코어/72스레드, VT-x·IOMMU
RAM32GiB64GiB 이상, ZFS 사용 시 ECC 권장128GB급, OS 인식 121GiB
시스템 디스크256GB SSDOS 전용 SSD 또는 mirror2TB NVMe의 ZFS root pool
VM 데이터512GB SSD별도 1TB 이상 NVMe mirror와 외부 백업단일 2TB NVMe 공유, 검증용이며 디스크 장애 이중화 없음
관리 네트워크1GbE고정 주소 1GbE 이상1GbE 관리 NIC, 설치자가 확인한 미사용 IPv4
게스트·스토리지 네트워크관리 NIC와 공유별도 10GbE 이상10GbE 전용 NIC, 호스트 L3 주소 없음

최소 구성은 제품 기능 확인용이며 동시에 실행할 VM의 vCPU·메모리·디스크를 포함하지 않습니다.
운영 노드는 호스트와 purecvisorsd용으로 최소 4 vCPU와 8GiB RAM을 남기고 나머지를 VM 할당 한도로 잡습니다.
VM 데이터 사용률은 80% 아래로 유지하고, snapshot과 같은 pool에만 있는 복사본을 백업으로 간주하지 않습니다.
검증 기준 노드의 단일 NVMe 구성은 재현·검증용이며 운영 장애 허용 구성이 아니므로, 운영 배포에서는 mirror와 별도 백업 대상을 사용합니다.

구성 요소26.04 기준검증 기준 노드 확인값
OSUbuntu Server 26.04.1 LTS amd64Ubuntu 26.04.1 LTS
커널Ubuntu generic 커널, cgroup v27.0.0-30-generic, cgroup2fs
C compilerGCC 14 이상, C23 -std=gnu23GCC 14.3
GLib2.88 계열2.88.0
libvirt12.0 계열12.0.0
QEMU/KVMQEMU 10.2 계열, /dev/kvm10.2.1, KVM 검증 통과
ZFS2.4 계열, 설치 커널과 같은 Ubuntu archive2.4.1
OVS/OVN기능 사용 시 OVS 3.7·OVN 26.03 계열OVS 3.7.1·OVN 26.03.0

패키지 적용 원칙

  • 표의 patch version은 2026-08-30 기준 검증값이며 고정 핀이 아닙니다.
  • resolute-updatesresolute-security의 최신 호환 package를 함께 적용합니다.
  • Ubuntu 26.04의 HWE virtualization stack은 커널, libvirt, QEMU와 ZFS를 함께 업데이트한 뒤 재검증합니다.
  • Ubuntu 24.04 guest clone 검증 이력은 guest 호환 범위이며 host OS 기준을 24.04로 낮추지 않습니다.
역할운영 원칙
관리 NIC<management-interface>ip -br link에서 확인한 실제 이름, host L3와 default route를 유지하며 bridge/dedicated에 넣지 않음
관리 주소<unused-management-ipv4>/<prefix-length>관리망 대역 내 미사용 IPv4를 선택하고 router DHCP reservation 또는 정적 Netplan으로 고정
gateway<default-gateway-ipv4>기존 default route에서 확인하고 관리 주소와 같은 대역인지 검증
DNS<dns-ipv4>현재 resolver 정책을 유지하고 설치 전후 DNS 응답 확인
제품 HTTPShttps://<management-ipv4>:443기본 purecvisorsd 자체 HTTPS, NGINX 불필요
로컬 복구 HTTPhttp://127.0.0.1:8080loopback에서만 접근, 외부 공개 금지
기본 VM NATpcvnat0, 10.78.0.1/24관리망·기존 libvirt/LXC/VPC 대역과 중복 금지
선택형 guest uplink<guest-uplink-interface>주소·default route·master가 없음을 확인한 뒤에만 bridge/dedicated 후보로 사용

주소 선택 원칙

  • 문서의 길이 표시는 그대로 복사할 예시 IP가 아닙니다.
  • 설치자는 router·DHCP 할당표와 현재 lease를 확인해 동적 pool 외부이거나 reservation으로 보호된 미사용 IPv4를 선택해야 합니다.

권장 방식은 현재 Netplan renderer와 DHCP를 유지하고 router에서 <management-interface>의 lease를 선택한 IPv4로 예약하는 것입니다.
이 방식은 host에서 주소를 중복 선언하지 않으면서 서비스 주소를 고정합니다.
router reservation을 사용할 수 없을 때만 아래 정적 Netplan 절차를 사용합니다.

인증서 생성 전 확정

  • hostname과 시간대를 확정합니다.
  • 관리 IPv4 또는 운영 DNS 이름을 확정합니다.
  • 이미 인증서를 운영 중인 노드의 hostname이나 관리 IP를 바꾸면 SAN과 접속 URL도 함께 갱신합니다.
Terminal window
sudo hostnamectl set-hostname purecvisor-edge-01
sudo timedatectl set-timezone Asia/Seoul
sudo apt update
sudo apt full-upgrade -y
sudo apt install -y ca-certificates curl git jq cpu-checker libvirt-clients
test "$(stat -fc %T /sys/fs/cgroup)" = cgroup2fs
kvm-ok
sudo virt-host-validate qemu
timedatectl status

커널, ZFS 또는 QEMU가 갱신됐다면 이 단계에서 한 번 재부팅하고 같은 검사를 다시 실행합니다.
virt-host-validate/dev/kvm, /dev/vhost-net, /dev/net/tun, IOMMU 항목은 PASS여야 합니다.
confidential guest 기능을 사용하지 않는 Single Edge 노드의 SEV/TDX 경고는 기능 미사용 상태로 기록할 수 있지만 KVM 또는 IOMMU 실패는 설치 전에 해결합니다.

권장 방식은 router에서 DHCP reservation을 설정한 뒤 host 설정을 그대로 유지하는 것입니다.
MAC 주소는 router 관리 화면에서 직접 확인하고 공개 문서나 이력에 기록하지 않습니다.

Terminal window
MGMT_NIC="<management-interface>"
NODE_IPV4="<unused-management-ipv4>"
PREFIX_LENGTH="<prefix-length>"
GATEWAY_IPV4="<default-gateway-ipv4>"
DNS_IPV4="<dns-ipv4>"
sudo netplan get
ip -4 address show dev "${MGMT_NIC}"
ip route show default
resolvectl status "${MGMT_NIC}"
# 재부팅 후에도 아래 세 값이 유지되는지 확인
# address: ${NODE_IPV4}/${PREFIX_LENGTH}
# default route: ${GATEWAY_IPV4} via ${MGMT_NIC}
# DNS server: ${DNS_IPV4}

DHCP reservation을 사용할 수 없는 환경에서는 console 또는 BMC 접속을 확보한 뒤 /etc/netplan/60-purecvisor-mgmt.yaml을 다음과 같이 만듭니다.
먼저 모든 <...> 값을 해당 노드의 실제 값으로 바꾸고, 기존 sudo netplan get에서 확인한 renderer 정책을 유지합니다.
다른 Netplan 파일이 <management-interface>를 함께 정의하면 먼저 중복을 제거합니다.

network:
version: 2
ethernets:
<management-interface>:
dhcp4: false
dhcp6: false
addresses:
- <unused-management-ipv4>/<prefix-length>
routes:
- to: default
via: <default-gateway-ipv4>
nameservers:
addresses:
- <dns-ipv4>
Terminal window
sudo chown root:root /etc/netplan/60-purecvisor-mgmt.yaml
sudo chmod 600 /etc/netplan/60-purecvisor-mgmt.yaml
sudo netplan generate
sudo netplan try --timeout 120
MGMT_NIC="<management-interface>"
GATEWAY_IPV4="<default-gateway-ipv4>"
ip -4 address show dev "${MGMT_NIC}"
ip route show default
resolvectl status "${MGMT_NIC}"
ping -c 3 "${GATEWAY_IPV4}"

SSH 연결만 있는 상태에서 netplan apply를 바로 실행하지 않습니다.
netplan try 확인에 실패하거나 시간이 끝나면 이전 설정으로 돌아가므로 console에서 원인을 수정합니다.

방법 A — .deb 바이너리 패키지 (권장, Ubuntu 26.04)

섹션 제목: “방법 A — .deb 바이너리 패키지 (권장, Ubuntu 26.04)”

릴리스 .deb(purecvisor-single_<version>_amd64.deb)로 데몬·CLI·UI·systemd 유닛을 한 번에 설치합니다.
런타임 의존성인 libvirt, QEMU, dnsmasq, nftables와 iproute2는 apt가 자동 해결하며 OVS, OVN, ZFS는 package Recommends로 설치됩니다.

Terminal window
# release 디렉터리에서 서명 또는 전달받은 SHA256SUMS를 먼저 검증
sha256sum -c SHA256SUMS --ignore-missing
# 의존성 자동 해결 포함 설치
sudo apt install -y ./purecvisor-single_2.0.0_amd64.deb
# 최초 설치는 sample 기반 daemon.conf를 만들고 서비스는 enable만 한다.
sudoedit /etc/purecvisor/daemon.conf
sudo chown root:root /etc/purecvisor/daemon.conf
sudo chmod 600 /etc/purecvisor/daemon.conf
sudo systemctl enable --now libvirtd
sudo systemctl start purecvisorsd
systemctl --no-pager --full status purecvisorsd
pcvctl --version
  • 설치 위치: 바이너리 /usr/local/bin/{purecvisorsd,pcvctl}, UI /usr/local/share/purecvisor/ui/, 유닛 /etc/systemd/system/purecvisorsd.service, 설정 /etc/purecvisor/.
  • 기본 모드는 선택한 관리 IPv4의 데몬 자체 HTTPS :443과 loopback HTTP 127.0.0.1:8080입니다.
  • NGINX는 외부 TLS 종료가 필요한 경우에만 별도 설치하며 기본 의존성이 아닙니다.
  • 업그레이드: 새 .deb로 같은 명령 재실행.
    기존 daemon.conf는 보존된다.
    (conffile prompt 시 --force-confold로 유지 또는 --force-confnew로 새 유닛 채택).
  • 제거: sudo apt remove purecvisor-single (설정 보존) / sudo apt purge purecvisor-single (설정 포함 제거).
Terminal window
sudo apt update && sudo apt install -y \
build-essential gcc-14 make pkg-config ccache \
libglib2.0-dev \
libvirt-dev libvirt-clients libvirt-daemon-system qemu-system-x86 ovmf \
libguestfs-tools \
libsoup-3.0-dev libjson-glib-dev \
libvirt-glib-1.0-dev liblxc-dev \
zfsutils-linux lxc lxc-utils \
libsqlite3-dev libssl-dev \
libcap-dev libseccomp-dev libreadline-dev liburing-dev \
libbpf-dev libxml2-dev \
protobuf-c-compiler libprotobuf-c-dev

참고: libvirt-glib-1.0-devlibvirt-gobject-1.0.pc도 함께 제공합니다.
별도의 libvirt-gobject-1.0-dev 패키지는 존재하지 않습니다.
libbpf-dev 부재 시 D07 eBPF 경로가 stub 으로 빌드되고(런타임 DEGRADED_NO_BTF 폴백), libxml2-devlibvirt-gconfig-1.0.pc 의 전이 의존이라 없으면 pkg-config 해석 전체가 비어 glib.h 부터 못 찾는 것처럼 보일 수 있다.
테스트 스위트 실행에는 추가로 wireguard-tools·sqlite3·openvswitch-switch· python3-pytest(게이트 자체테스트)가 필요하다.

운영 필수: libguestfs-tools는 일반 VM 복제의 Guest reset 경로에 필요하다.
이 패키지가 없으면 virt-sysprep, virt-customize, virt-filesystems, guestfish를 실행할 수 없어 guest_reset=true clone이 preflight에서 거부된다.

공통 런타임 의존성 (사용 기능별)

섹션 제목: “공통 런타임 의존성 (사용 기능별)”

이 절의 런타임 구성은 .deb 패키지와 소스 빌드에 공통으로 적용합니다.
.deb 설치에서는 libvirt, QEMU, dnsmasq-base, nftables와 iproute2를 package Depends로 자동 설치합니다.
OVS, OVN과 ZFS는 package Recommends로 설치되며, --no-install-recommends를 사용했다면 필요한 기능의 패키지를 직접 설치합니다.
일반 VM 복제, LXC와 iSCSI initiator는 해당 기능을 사용할 때 명시적으로 런타임 패키지를 설치합니다.

ZFS는 선택형 런타임입니다 — ZFS backend를 선택한 LXC에는 필수

PureCVisor 데몬과 Web UI, REST API, CLI는 zvol_pool이나 ZFS volume이 없어도 시작하고 동작합니다.
VM 생성 요청에서 storage_type을 생략하면 설정된 ZFS dataset을 먼저 확인하고, 사용할 수 없으면 [storage] image_dir에 qcow2 파일 디스크를 생성합니다.
ZFS를 사용하지 않는 노드는 zfsutils-linux를 생략할 수 있지만 image_dir이 존재하고 쓰기 가능해야 하며 qemu-img를 사용할 수 있어야 합니다.
storage_type=zvol을 명시한 VM 생성, ZFS snapshot·rollback·send/receive 기반 backup과 storage_backend=zfs인 LXC에는 ZFS가 필요합니다.
선택형 Btrfs LXC의 소스 버전·설정·검증 범위는 4.1절을 따릅니다.

Terminal window
# 일반 VM 복제의 Guest reset
sudo apt install -y libguestfs-tools
# LXC 컨테이너
sudo apt install -y lxc lxc-utils
# 선택: ZFS zvol·snapshot·backup 또는 ZFS backend의 LXC
sudo apt install -y zfsutils-linux
# 선택: Btrfs backend의 LXC 점검 도구
sudo apt install -y btrfs-progs
# OVS 오버레이 네트워크
sudo apt install -y openvswitch-switch

OVN을 사용하지 않고 OVS bridge만 사용하는 노드는 이 절을 건너뜁니다.
OVN Single Edge 구성은 NB DB, SB DB, ovn-northdovn-controller를 같은 노드에 설치하고 로컬 OVS를 해당 Southbound DB에 연결합니다.
이 명령은 Multi Edge나 클러스터 제어면을 구성하지 않습니다.

purecvisor-ovn-singlescripts/install-ovn-single.sh는 동일한 스크립트의 설치본과 저장소 원본입니다.
두 명령을 모두 실행하지 말고 PureCVisor를 설치한 방식에 맞는 한 경로만 선택합니다.

PureCVisor 설치 방식선택할 명령실행 위치
릴리스 .deb 패키지purecvisor-ovn-single패키지가 /usr/local/sbin/에 설치하므로 어느 디렉터리에서나 실행
Git 저장소 소스 빌드scripts/install-ovn-single.shPureCVisor 저장소 최상위 디렉터리에서 실행

일반 실행은 openvswitch-switch, ovn-central, ovn-host를 설치하고 관련 서비스를 활성화합니다.
이어서 OVS의 system ID, 로컬 Southbound DB socket, Geneve encap IPv4를 설정하고 NB/SB 동기화와 local chassis 등록이 끝날 때까지 최대 30초 동안 확인합니다.
성공 메시지가 출력되면 별도의 즉시 검증은 필요하지 않습니다.

--encap-ip에는 Geneve endpoint로 사용할, 이미 이 host에 할당된 IPv4를 지정합니다.
옵션을 생략하면 default route 조회 결과의 source IPv4를 자동 선택하므로 관리망과 overlay transport망이 분리된 환경에서는 명시적으로 지정합니다.

.deb로 설치한 노드는 다음 명령을 사용합니다.

Terminal window
sudo purecvisor-ovn-single --encap-ip <ovn-encap-ipv4>

소스 빌드 노드는 저장소 최상위 디렉터리에서 다음 명령을 사용합니다.

Terminal window
sudo scripts/install-ovn-single.sh --encap-ip <ovn-encap-ipv4>

--verify-only는 패키지 설치나 OVS 설정을 다시 수행하지 않습니다.
재부팅 후 또는 장애 점검 시 처음 선택한 경로에만 붙여 OVS·OVN 서비스, NB/SB 동기화, local chassis 등록 상태를 재검사합니다.

Terminal window
# .deb 설치 노드
sudo purecvisor-ovn-single --verify-only
# 소스 빌드 노드
sudo scripts/install-ovn-single.sh --verify-only
Terminal window
# iSCSI 스토리지 — 이니시에이터(클라이언트)만 패키지가 필요하다.
# 타겟(서버) 측은 D4 전환으로 커널 LIO(configfs)를 직접 쓰므로 `tgt` 패키지가 **불요**하다.
sudo apt install -y open-iscsi

.deb 패키지는 /etc/modules-load.d/purecvisor-lio.conf를 conffile로 설치합니다.
소스 빌드 결과를 직접 배포하는 경우에만 다음 파일을 수동으로 배치합니다.

Terminal window
# iSCSI 타겟용 커널 모듈 로드 — deb 를 설치했다면 이미 깔려 있다(conffile).
# 아래 블록은 deb 를 쓰지 않는 배포(소스 빌드 + scp)에서만 필요하다.
# ⚠ 모듈명 행에 주석을 붙이지 말 것 — modules-load.d(5) 는 행 전체를 모듈명으로
# 읽어 4개 전부 'Failed to find module' 이 되는데도 유닛은 성공으로 끝난다(P13).
sudo tee /etc/modules-load.d/purecvisor-lio.conf >/dev/null <<'EOF'
# Developer note — 신입 개발자:
# 이 modules-load.d 정본은 부팅 시 LIO 3종과 bridge conntrack 모듈을 순서대로 요청한다.
# LIO가 없으면 iSCSI export가 degraded되고 nf_conntrack_bridge가 없으면 bridge 보안그룹의
# stateful nft 규칙이 실패한다. 행 끝 주석은 모듈명의 일부가 되므로 설명은 별도 행에 둔다.
#
# [쉬운 설명] 운영·플랫폼 엔지니어:
# iSCSI 디스크 내보내기와 bridge 연결 추적에 필요한 커널 부품을 데몬보다 먼저 준비한다.
# PRIVDROP-1 시정 뒤 데몬 자식은 CAP_SYS_MODULE을 받지 않으므로 이 파일이 유일한 적재 정본이다.
# PureCVisor 2.0.0 — 커널 LIO + bridge conntrack 모듈 로드.
# 이 파일은 deb 패키징 산출물이며 정본은 저장소의
# packaging/deb/purecvisor-lio.conf 이고 deb 가 /etc/modules-load.d/ 로 설치한다.
# deb 를 쓰지 않는 배포(소스 빌드 + scp)에서는 운영자가 같은 내용을 손으로 배치한다.
# ⚠⚠ 행 끝 주석 금지(P13) — modules-load.d(5) 는 행 전체를 모듈명으로 읽는다.
# 주석을 모듈명 뒤에 붙이면 4개 전부 'Failed to find module' 이 되고,
# 그런데도 systemd-modules-load.service 는 Finished(성공)로 끝난다 → 조용한 degraded.
# target_core_mod : LIO 코어 — configfs `target` 디렉터리를 만든다
# iscsi_target_mod : iSCSI 패브릭 — mkdir 을 받아야 <root>/iscsi 를 만든다(지연 생성)
# target_core_iblock : IBLOCK 백스토어 — zvol 블록 디바이스 export
# nf_conntrack_bridge: Linux bridge family의 stateful nft conntrack 지원
target_core_mod
iscsi_target_mod
target_core_iblock
nf_conntrack_bridge
EOF
sudo systemctl restart systemd-modules-load
Terminal window
# 런타임 전제 무접근 계약 게이트 필수
sudo apt install -y strace
# 정적 분석
sudo apt install -y cppcheck
# 메모리 검사
sudo apt install -y valgrind
# 커버리지 리포트
sudo apt install -y lcov

make check-runtime-prereqsstrace -f -e trace=%file로 BPF --verify-only 검증이 설치 root, config, PKI, JWT, 기존 BPF 목적지에 접근하지 않음을 확인한다.
이 보안 게이트에서는 strace 미설치를 skip으로 처리하지 않으며, Ubuntu에서는 sudo apt install strace로 설치한 뒤 다시 실행한다.

Terminal window
git clone https://github.com/HardcoreMonk/purecvisor.git purecvisor-single
cd purecvisor-single
명령설명출력
make singleSingle Edge 빌드bin/purecvisorsd
make cliCLI 빌드bin/pcvctl
make all전체 빌드데몬 + CLI
make release릴리스 빌드 (-O2, NDEBUG, 하드닝)최적화된 바이너리
make clean빌드 산출물 정리-
Terminal window
# 클린 빌드 (경고 0 확인)
make clean && make single
# 빌드 결과 확인
ls -lh bin/
# bin/purecvisorsd (~1.9MB, Single Edge)
# bin/pcvctl (CLI)
Terminal window
# 전체 unit·integration test
make test
# 공개 경계와 계약 게이트 전체
make check-all
# 정적 분석
make cppcheck
# 메모리 누수 검사
make memcheck
# 코드 커버리지 HTML 리포트
make coverage-html
# 결과: coverage_report/html/index.html
# CI용 TAP 형식 테스트 출력
make test-tap

2.4 디렉터리 구조 생성과 배포 런타임 전제

섹션 제목: “2.4 디렉터리 구조 생성과 배포 런타임 전제”
Terminal window
# 런타임 디렉터리
sudo install -d -m 0700 /etc/purecvisor/pki
sudo install -d -m 0755 /etc/purecvisor/plugins.d
sudo install -d -m 0755 /etc/purecvisor/seccomp
sudo install -d -m 0755 /var/lib/purecvisor
sudo install -d -m 0700 /var/run/purecvisor
sudo install -d -m 0750 /var/log/purecvisor

ZFS volume은 서비스 시작의 필수 조건이 아닙니다.
ZFS를 사용하지 않는 노드는 image_dir을 준비하고 VM 생성 시 storage_type=qcow2 또는 raw를 지정하거나 자동 감지를 사용합니다.

Terminal window
sudo install -d -m 0711 /var/lib/libvirt/images
sudo test -d /var/lib/libvirt/images
sudo test -w /var/lib/libvirt/images
command -v qemu-img
[storage]
image_dir = /var/lib/libvirt/images
iso_dirs = /data/iso,/var/lib/libvirt/images

자동 감지에서 zvol_pool을 찾지 못하면 image_dir에 qcow2 디스크를 생성합니다.
storage_type=zvol을 명시하면 폴백하지 않고 지정한 ZFS 부모 dataset이 없다는 오류로 요청을 종료합니다.
ZFS 미사용 노드에서는 VM file disk와 명시적으로 선택한 Btrfs LXC를 사용할 수 있습니다.
ZFS snapshot·rollback·send/receive backup과 기본 ZFS LXC에는 ZFS가 필요합니다.
Btrfs LXC의 준비 조건은 4.1절을 따릅니다.

ZFS 기능을 사용하는 경우에만 zvol_pool에 실제로 존재하는 ZFS dataset을 지정합니다.
설치 전에 ZFS zvol 또는 file image 중 주 저장 방식을 결정하고 설정과 실제 경로를 일치시킵니다.

Ubuntu가 rpool 단일 NVMe에 설치된 검증 노드는 새 설치일 때만 다음 전용 dataset을 만들 수 있습니다.
기존 VM이 있는 노드에서는 이 명령으로 경로를 바꾸지 말고 별도 migration과 rollback 계획을 사용합니다.

Terminal window
sudo zpool status
sudo zfs list
# 새 설치 전용 예시
sudo zfs create -o mountpoint=none -o canmount=off rpool/data/purecvisor
sudo zfs create -o mountpoint=none -o canmount=off -o compression=lz4 \
rpool/data/purecvisor/vms
sudo install -d -m 0711 /var/lib/libvirt/images
sudo install -d -m 0755 /data/iso
[storage]
zvol_pool = rpool/data/purecvisor/vms
image_dir = /var/lib/libvirt/images
iso_dirs = /data/iso,/var/lib/libvirt/images
Terminal window
sudo zfs list rpool/data/purecvisor/vms
sudo test -d /var/lib/libvirt/images
sudo test -d /data/iso

운영 권장 구성은 OS rpool과 분리된 mirror data pool을 만들고 zvol_pool=pcvpool/vms를 사용하는 방식입니다.
단일 디스크 rpool 예시는 검증 환경 재현용이며 디스크 장애에 대한 중복성을 제공하지 않습니다.

기본 scripts/deploy.shmake releasemake bpf를 실행한다.
--skip-build 에서도 daemon/CLI, BPF object, manifest 존재 여부와 helper의 --verify-only 검사를 SSH/SCP보다 앞선 로컬 preflight에서 수행하며, 실패하면 원격 상태를 바꾸기 전에 배포를 중단한다.
--verify-only는 BPF staging만 검증하는 전용 경로이며 설치 대상 root, daemon.conf, PKI, JWT, 기존 BPF 목적지에는 접근하지 않는다.

원격 설치 helper의 계약은 다음과 같다.

  • 최초 배포는 /etc/purecvisor/pki를 mode 0700으로 준비한다.
    helper는 TLS 인증서 내용을 만들지 않으며, 기존 cert/key를 덮어쓰지 않고 보존한다.
    인증서와 키가 모두 없을 때의 자가서명 생성은 데몬의 TLS 초기화가 담당한다.
  • 배포 helper가 관리하는 값은 [daemon] jwt_secret이다.
    키가 누락됐거나 빈 값이면 Python CSPRNG secrets.token_hex(32)로 64자리 소문자 hex를 만들어 /etc/purecvisor/daemon.conf에 영속화하고 파일 mode를 0600으로 고정한다.
    충분한 길이인 32바이트 이상의 기존 JWT와 인증서는 내용 그대로 보존한다.
  • 비어 있지 않은 기존 JWT가 32바이트 미만이거나 placeholder류 또는 반복 문자처럼 명백히 weak하면 자동 회전으로 예고 없이 기존 토큰을 무효화하지 않는다.
    배포를 fail-closed로 거부하고 운영자가 값을 명시적으로 교체하도록 요구한다.
  • BPF 배포는 manifest.json의 schema와 SHA-256을 검증하고 성공한 object와 manifest만 /usr/lib/purecvisor/bpf에 설치한다.
    디렉터리는 0755, 파일은 0644이며 manifest를 마지막 commit marker로 교체한다.
  • 원격 staging은 추측 불가능한 이름과 mode 0700으로 만들고, helper 실패 시 서비스를 시작하지 않는다.
    성공과 실패 모두 EXIT 경로에서 helper와 BPF staging을 cleanup하여 정리한다.
  • 이 배포는 커널 lsm=bpf 활성화나 커널 명령행 변경, 재부팅을 자동으로 수행하지 않는다.
    BPF LSM이 필요하면 별도 승인된 운영 절차로 변경하고 재부팅해야 한다.

JWT 로드에는 두 경로가 공존한다.
데몬 시작 시 PCV_SECRET_AUTH_JWT_SECRET과 정확한 [auth] jwt_secret을 먼저 읽는다.
둘 다 없으면 PURECVISOR_JWT_SECRET, [daemon] jwt_secret을 호환 fallback으로 찾고, 마지막으로 다른 섹션을 검색한다.
따라서 deploy helper의 [daemon] jwt_secret 영속화가 모든 runtime override의 단일 정본이라는 뜻은 아니다.
[auth] 또는 환경변수 override를 운영자가 사용한다면 32바이트 이상 값을 별도로 보장해야 한다.

TLS 배포 모드 선택: purecvisorsd 자체 HTTPS 또는 선택형 NGINX 외부 TLS 종료

섹션 제목: “TLS 배포 모드 선택: purecvisorsd 자체 HTTPS 또는 선택형 NGINX 외부 TLS 종료”

이 절은 제품 노드의 REST·Web UI·WebSocket 진입 경계를 설명한다.
공개 문서 도메인 purecvisor.site는 GitHub Pages가 정적 파일과 HTTPS를 제공하므로 아래 제품 노드 NGINX 구성과 무관하다.
제품 노드는 다음 두 모드 중 하나를 명시적으로 사용한다.
NGINX는 필수 의존성이 아니라 외부 TLS 종료를 선택한 노드에서만 필수인 구성 요소다.

항목purecvisorsd 자체 HTTPS선택형 NGINX 외부 TLS 종료
외부 TLS 소유자purecvisorsdNGINX
NGINX 필요 여부불필요필수
외부 진입https://<node>:443 → 데몬https://<node>:443 → NGINX → 데몬
데몬 평문 리스너127.0.0.1:<rest_port> 복구·로컬 점검용127.0.0.1:8080 프록시 upstream 전용
핵심 설정https_enabled=true, https_port=443https_enabled=false, bind_plaintext=loopback
권장 용도별도 proxy 운영이 필요 없는 단순한 단일 노드공인 인증서, 중앙 보안 헤더·rate limit, WebSocket proxy, maintenance fallback이 필요한 환경
정상 TLS healthmode=internal, enabled=true, degraded=false, status=okmode=external_termination, enabled=false, degraded=false, status=disabled_by_config
# 모드 A — purecvisorsd 자체 HTTPS
클라이언트 ── HTTPS :443 ──> purecvisorsd
└─ HTTP 127.0.0.1:<rest_port> (로컬 복구·점검)
# 모드 B — 선택형 NGINX 외부 TLS 종료
클라이언트 ── HTTPS :443 ──> NGINX ── HTTP 127.0.0.1:8080 ──> purecvisorsd

자체 HTTPS는 기본 전송 모드다.
데몬이 인증서를 로드하고 외부 HTTPS listener를 직접 소유한다.
[server] bind_plaintext=loopback을 유지하면 평문 REST는 로컬 복구와 점검에만 사용되고 원격 자격증명·JWT·API key가 HTTP로 노출되지 않는다.

[tls]
https_enabled = true
https_port = 443
cert = /etc/purecvisor/pki/node.crt
key = /etc/purecvisor/pki/node.key
[server]
bind_plaintext = loopback
  • cert와 key가 모두 없으면 데몬이 자체서명 쌍을 원자적으로 생성한다.
    둘 중 하나만 있으면 사용자 자산을 덮어쓰지 않고 TLS 초기화를 실패 처리한다.
  • 운영자가 제공한 cert/key 쌍은 자동 생성보다 우선하며 배포 helper가 내용을 교체하지 않는다.
  • TLS 초기화나 HTTPS bind가 실패하면 외부 평문으로 확장하지 않는다.
    데몬은 루프백 HTTP와 UDS만 남긴 degraded 상태로 기동해 복구 경로는 보존하고 외부 표면은 닫는다.
  • 이 모드에서는 NGINX service, NGINX vhost와 PCV_NGINX_BIND_IP가 필요하지 않다.
Terminal window
NODE_IPV4="<configured-management-ipv4>"
systemctl is-active purecvisorsd
curl -ksS "https://${NODE_IPV4}/api/v1/health" | jq '{status, tls: .checks.tls}'
sudo ss -lntp | grep -E '127\.0\.0\.1:8080|0\.0\.0\.0:443'
# 기대: mode=internal, enabled=true, degraded=false, status=ok

모드 B — 선택형 NGINX 외부 TLS 종료

섹션 제목: “모드 B — 선택형 NGINX 외부 TLS 종료”

외부 종료 모드에서는 NGINX가 인증서와 :443을 소유하고 REST·Web UI·WebSocket 요청을 데몬의 루프백 HTTP로 전달한다.
데몬 자체 HTTPS는 의도적으로 끄되 평문 upstream은 반드시 loopback으로 제한한다.

[daemon]
rest_port = 8080
[tls]
https_enabled = false
[server]
bind_plaintext = loopback

NGINX vhost는 최소한 TLS 인증서, HTTP→HTTPS 정책, proxy_pass http://127.0.0.1:8080, WebSocket Upgrade, 보안 헤더와 forwarded header 덮어쓰기를 포함해야 한다.
vhost와 인증서를 먼저 프로비저닝한 다음 opt-in 배포를 실행한다.

Terminal window
PCV_NODES=<ip> \
PCV_NGINX_BIND_IP=<ip> \
scripts/deploy.sh --no-local
sudo nginx -t
systemctl is-active nginx purecvisorsd
curl -ksS https://<ip>/api/v1/health | jq '.checks.tls'
# 기대: mode=external_termination, enabled=false, degraded=false,
# status=disabled_by_config

enabled=false는 외부 통신이 평문이라는 뜻이 아니라 데몬 대신 NGINX가 TLS를 정상 종료한다는 뜻이다.
X-Forwarded-ForX-Forwarded-Proto는 loopback peer에서 온 요청만 신뢰한다.

  • https_enabled=false인데 정상 NGINX listener가 없으면 외부 HTTPS 진입점이 사라진다.
  • 외부 종료 모드에서 bind_plaintext=all을 쓰거나 값을 누락하면 데몬이 시작을 거부한다.
  • 같은 IP의 :443을 NGINX와 purecvisorsd가 동시에 소유하도록 구성하지 않는다.
  • PCV_NGINX_BIND_IP를 다음 배포에서 생략해도 기존 외부 종료 설정이 자체 HTTPS로 자동 복귀하지 않는다.
    이 환경변수가 없으면 배포 스크립트는 기존 NGINX·daemon 전송 설정을 보존한다.
  • 두 모드 전환은 :443 소유자가 바뀌는 maintenance 작업이다.
    기존 설정·인증서를 백업하고 새 listener와 health를 검증할 rollback 가능한 순서로 수행한다.
  • PCV-NGINX-TRUST-BOUNDARY: host-loopback을 만족하지 못해 신뢰하지 않는 로컬 프로세스가 loopback upstream에 접근할 수 있는 호스트에서는 NGINX 외부 종료 모드를 활성화하지 않는다.

선택형 NGINX 외부 TLS 종료 설치와 복구

PCV_NGINX_BIND_IP는 명시적으로 설정한 경우에만 활성화되는 opt-in이다.
값이 없으면 nginx 설정, systemd drop-in, daemon.conf의 전송 모드를 바꾸지 않는다.
활성화하면 nginx가 지정한 LAN 주소의 외부 TLS를 종료하고, purecvisorsd127.0.0.1 루프백 HTTP만 수신한다.

로컬 노드 배포:

Terminal window
NODE_IPV4="<configured-management-ipv4>"
PCV_NODES="" \
PCV_NGINX_BIND_IP="${NODE_IPV4}" \
scripts/deploy.sh --nodes local

원격 노드 배포(ssh 경유, <ip> 를 대상 노드로):

Terminal window
PCV_NODES=<ip> \
PCV_NGINX_BIND_IP=<ip> \
scripts/deploy.sh --no-local

nginx vhost 자체는 이 트랜잭션의 관리 대상이 아니다 — 도메인 노드는 ops/nginx/purecvisor.example.com, LAN IP 노드는 ops/nginx/purecvisor-lan-ip.conf.template(+ purecvisor-common.conf http 전제)를 먼저 프로비저닝한다.

설치 트랜잭션은 기존 인증서(cert)와 개인 키(key)를 내용 그대로 보존하고, nginx 설정·systemd drop-in·daemon 설정의 이전 상태를 백업한다.
설치 후 LAN health 검증이 실패하면 rollback으로 이 세 설정을 함께 복원하며 보존된 인증서와 키를 삭제하거나 재생성하지 않는다.
rollback 자체가 실패하면 서비스를 추측 상태로 재시작하지 않고 복구 자료를 남긴다.

부팅 시 wait-for-local-ip가 주소 준비를 기다린다.
제한 시간 안에 IP가 없으면 exit 75로 끝나 systemd가 재시도한다.
반면 nginx -t 문법 오류는 exit 1이며 RestartPreventExitStatus=1로 재시작 방지한다.
패키지 업데이트 뒤에는 vendor 유닛의 ExecStartPre가 정확히 /usr/sbin/nginx -t -q -g 'daemon on; master_process on;'인지 확인하고, drop-in이 IP 대기와 이 검사를 모두 유지하는지 재검증한다.

Vendor 명령이 불일치하면 설치기는 변경 전에 실패한다.
이 실패는 기존 상태를 변경하지 않고 보존한다.
Vendor 계약은 자동 동기화하지 않는다.
재시도 전에 installer 계약과 테스트를 갱신하고 재검토한다.

정상 health의 전체 서비스 status=ok와 TLS check는 정확히 mode=external_termination, enabled=false, degraded=false, status=disabled_by_config여야 한다.
이 값은 TLS가 꺼져 노출됐다는 뜻이 아니라 외부 TLS 종료가 정상 선택됐다는 뜻이다.
X-Forwarded-ForX-Forwarded-Proto 같은 프록시 헤더는 연결 peer가 127.0.0.1 또는 ::1인 경우에만 신뢰하며, 비루프백 클라이언트가 보낸 같은 헤더는 신원·scheme 판정에 사용하지 않는다.

PCV-NGINX-TRUST-BOUNDARY: host-loopback은 이 모드가 호스트의 모든 루프백 프로세스를 privileged/trusted host boundary로 신뢰한다는 뜻이다.
PCV-NGINX-COUNTERFACTUAL: untrusted-local-process처럼 신뢰하지 않는 로컬 사용자나 프로세스가 daemon의 루프백 HTTP에 직접 접속할 수 있으면 전달 헤더를 위조할 수 있으므로 이 모드를 활성화하지 않는다.
전용 호스트·최소 사용자, 서비스 sandbox/MAC, 로컬 방화벽 또는 network namespace로 직접 루프백 접근을 제한한다.
원격 클라이언트는 loopback peer가 아니고 nginx가 전달 헤더를 덮어쓰므로 이 신뢰 경계를 직접 위조할 수 없다.

Terminal window
sudo cp systemd/purecvisorsd.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now purecvisorsd

서비스 상태 확인:

Terminal window
sudo systemctl status purecvisorsd
# 로그 확인 (실시간)
journalctl -u purecvisorsd -f

설정한 관리 IPv4에서 기본 모드가 정상 기동하면 journal에 다음 listener가 기록됩니다.

HTTPS listening on https://0.0.0.0:443 (HSTS enabled)
HTTP/2 support enabled (TLS via ALPN negotiation)
REST API listening on http://127.0.0.1:8080 + https://0.0.0.0:443/api/v1

외부 평문 0.0.0.0:8080이 보이면 정상 기준이 아닙니다.
ss -lntp[server] bind_plaintext=loopback을 확인한 뒤 서비스를 다시 시작합니다.

설정 파일 경로: /etc/purecvisor/daemon.conf

설정 우선순위: 환경 변수 > daemon.conf > 컴파일 기본값

REST rest_port의 컴파일 기본값은 80입니다.
아래 Single Edge 권장 예시는 8080을 loopback 복구 listener로 두고 purecvisorsd가 설정한 관리 IPv4의 443 HTTPS를 직접 소유합니다.
NGINX 외부 TLS 종료를 선택한 경우에만 같은 127.0.0.1:8080을 proxy upstream으로 사용합니다.

/etc/purecvisor/daemon.conf
# Single Edge 권장 baseline
# ─────────────────────────────────────────────
# [daemon] 데몬 기본 설정
# ─────────────────────────────────────────────
[daemon]
# UDS 소켓 경로
socket_path = /var/run/purecvisor/daemon.sock
# REST API 포트 (1-65535, CAP_NET_BIND_SERVICE 필요)
rest_port = 8080
# 자체 HTTPS에서는 loopback 복구 listener, 선택형 NGINX에서는 proxy upstream
# 첫 로그인 전에 12자 이상의 임시 bootstrap 비밀번호를 설정
# 전용 admin 생성 뒤에는 빈 값으로 바꾸고 재기동해 bootstrap 계정을 비활성화할 수 있음
admin_user = admin
admin_password = <운영자가 sudoedit로 설정>
# 그레이스풀 드레인 타임아웃 (초, 최소 5)
drain_timeout = 30
# 워커 스레드 풀 크기 (1-64)
pool_max_conn = 16
# 배포 helper가 누락/빈 값만 64자리 hex로 영속화
jwt_secret =
# ─────────────────────────────────────────────
# [storage] 스토리지 설정
# ─────────────────────────────────────────────
[storage]
# ZFS zvol 풀 경로
zvol_pool = rpool/data/purecvisor/vms
# qcow2/raw 이미지 폴백 디렉터리
image_dir = /var/lib/libvirt/images
# ISO 스캔 디렉터리 (콤마 구분, .iso/.img 파일)
iso_dirs = /data/iso,/var/lib/libvirt/images
# ─────────────────────────────────────────────
# [tls] HTTPS 설정
# ─────────────────────────────────────────────
[tls]
# 기본 true. false는 nginx 외부 TLS 종료 + server.bind_plaintext=loopback에서만 허용
https_enabled = true
# 인증서와 키가 모두 없으면 이 기본 경로에 자가서명 쌍을 생성
cert = /etc/purecvisor/pki/node.crt
key = /etc/purecvisor/pki/node.key
# 선택적 client mTLS 신뢰 CA
ca = /etc/purecvisor/pki/ca.crt
# https_enabled=false일 때 반드시 loopback을 명시
[server]
bind_plaintext = loopback
# ─────────────────────────────────────────────
# [auth] 인증 설정
# ─────────────────────────────────────────────
[auth]
# 선택적 JWT runtime override. PCV_SECRET_AUTH_JWT_SECRET이 이 값보다 우선한다.
# 미설정 시 위 [daemon] jwt_secret 호환 fallback을 사용한다.
# jwt_secret = <32바이트 이상의 운영자 관리 값>
# 셀프 회원가입 활성화 여부
# true이면 로그인 랜딩의 회원가입으로 VIEWER 계정을 생성할 수 있습니다.
allow_self_register = false
# ─────────────────────────────────────────────
# [alert] 알림 엔진 설정
# ─────────────────────────────────────────────
[alert]
enabled = true
# CPU 임계값 (%)
cpu_warn = 80
cpu_crit = 95
# 메모리 임계값 (%)
mem_warn = 85
mem_crit = 95
# 디스크 임계값 (%)
disk_warn = 80
disk_crit = 90
# 데이터 풀 임계값 (%)
data_pool_warn = 80
data_pool_crit = 90
# 지속 평가 기간 (초, 임계값 초과 지속 시간)
eval_period = 30
# 동일 경보 중복 억제 기간 (초)
dedup_window = 300
# Webhook URL
webhook_url = https://hooks.slack.com/services/T00/B00/xxx
webhook_crit_url = https://events.example.com/purecvisor/critical
# HMAC 서명 비밀키. 평문 대신 ENC: 값 또는 PCV_SECRET_ALERT_WEBHOOK_SECRET 사용을 권장
webhook_secret = ENC:...
# Webhook 포맷: slack | telegram | generic
webhook_format = slack
telegram_chat_id =
# ─────────────────────────────────────────────
# [network] 관리형 기본 네트워크 (VP-1/VP-6, 2026-07)
# ─────────────────────────────────────────────
[network]
# vm create 시 network_bridge 미지정이면 부착되는 기본 NAT 네트워크.
# 데몬이 기동 시 브릿지+NAT(nftables)+DHCP/DNS(dnsmasq)를 멱등 보장한다.
# 마이그레이션: 구 [vm] default_bridge 키는 폐기됨(설정돼 있어도 무시) —
# 이 섹션으로 이관할 것.
default_bridge = pcvnat0
# 기본 네트워크 게이트웨이 CIDR (호스트 주소 포함 표기)
default_subnet = 10.78.0.1/24
# 0이면 기동 시 기본 네트워크 보장을 건너뜀
default_ensure = 1
# 호스트 방화벽(UFW/iptables FORWARD DROP) 자동 공존. "auto"면 관리형
# NAT 설정 시 필요한 allow 룰을 자동 삽입(AUDIT 로그 기록), "off"면
# 데몬이 호스트 방화벽을 건드리지 않음 (공존 실패 시 게스트 네트워크
# 불통은 운영자 책임).
firewall_integration = auto
# ─────────────────────────────────────────────
# [security_group] 보안 그룹
# ─────────────────────────────────────────────
[security_group]
# vnet 캐시 주기 재동기화 간격(초). 0 또는 음수면 타이머 비활성.
resync_interval_sec = 300
# ─────────────────────────────────────────────
# [cpu] CPU 할당 설정
# ─────────────────────────────────────────────
[cpu]
# 오버커밋 허용 여부
allow_overcommit = false
# 격리 코어 목록 (예: 4-7,12-15)
# isolated_cores = 4-7
# ─────────────────────────────────────────────
# [update] 버전 알림 설정
# ─────────────────────────────────────────────
[update]
# 버전 알림 기능 on/off. true면 데몬이 GitHub 공개 repo 최신 릴리스를 조회해
# 대시보드 툴바에 상태 배지(최신 / 업데이트 가능 여부, 예: `✓ vX.Y.Z` / `↑ vX.Y.Z`)로
# 표시한다 — 읽기 전용 정보 표시만이며 자동 다운로드/설치는 하지 않는다.
# air-gapped/외부망 차단 환경은 false로 꺼서 아웃바운드 호출 자체를 막을 것.
check_enabled = true
# 조회 대상 GitHub Releases API URL (기본값: purecvisor 공개 repo의 latest release)
check_url = https://api.github.com/repos/HardcoreMonk/purecvisor/releases/latest
# 조회 주기(시간 단위). 이 시간 이내에는 재조회하지 않는다(캐시 재사용).
# 1 미만 값은 기본값 24로 보정된다.
check_interval_hours = 24

[update] 설정 변경은 SIGHUP 핫리로드 대상이 아닙니다 — 데몬 재시작 (systemctl restart purecvisorsd) 후에만 반영됩니다.

데몬 시작 시 다음 항목이 자동 검증됩니다:

  • rest_port: 1-65535 범위
  • drain_timeout: 5초 이상
  • pool_max_conn: 1-64 범위
  • admin_password: 미설정 시 bootstrap admin 비활성화, 12자 미만이면 보안 경고
  • zvol_pool: ZFS 기능 사용 시 실제 dataset 존재 확인
  • 경로 존재 여부 (socket_path, [tls] cert/key 등)

범위를 벗어나면 PCV_LOG_WARN으로 경고 후 기본값을 사용합니다.

데몬 재시작 없이 설정을 다시 로드할 수 있습니다:

Terminal window
sudo kill -SIGHUP $(pidof purecvisorsd)
Terminal window
# 시스템 전체 설치 (bash + zsh)
make install-completion
# 현재 사용자만 설치
make install-completion-user
# 적용 확인 (새 셸에서)
pcvctl <TAB><TAB>

pcvctl은 화면 출력 형식과 무관하게 다음 프로세스 종료상태를 사용한다.
JSON-RPC 오류의 정확한 code가 필요하면 --format=json 응답을 함께 파싱한다.

종료코드의미
0명령 성공 또는 help/version 같은 정상 로컬 동작
1실행·UDS 전송·응답 프로토콜/파싱·JSON-RPC 거절 실패
2알 수 없는 명령, 필수 인자 누락, 잘못된 사용법
Terminal window
if pcvctl vm start web-prod; then
echo "started"
else
rc=$?
echo "pcvctl failed: exit=$rc" >&2
fi

2026-08-09 이전에는 화면에 RPC 오류가 표시돼도 exit 0이 반환될 수 있었다.
현재 계약은 옵트인 없이 기본 동작이므로, 기존 자동화는 set -e, &&, if 분기가 달라질 수 있다.

배포 대상 노드 자체에서 실행할 때는 deploy target을 local로 명시합니다.
PCV_NODES는 저장된 원격 node 목록이 섞이는 것을 막고 --nodes local은 현재 host만 변경합니다.

Terminal window
# 릴리스 빌드 + BPF + 현재 노드 배포
PCV_NODES="" scripts/deploy.sh --nodes local
# 디버그 빌드도 현재 노드로 제한
PCV_NODES="" scripts/deploy.sh --nodes local --debug
# 이미 검증한 산출물 재배포
PCV_NODES="" scripts/deploy.sh --nodes local --skip-build

Web UI 배포는 HTML/JS/CSS만 복사하면 끝나지 않습니다.
운영 브라우저는 CSP, PWA manifest, Service Worker 캐시를 함께 검증하므로 다음 자산이 같은 릴리스 단위로 배포되어야 합니다.

  • ui/index.html, ui/docs.html, ui/guide.html, ui/guide-content.md
  • ui/samples/design-system-preview.html, ui/samples/design-borrowing-mockup.html과 관련 ui/samples/*.html
  • ui/app.js, ui/app.bundle.js, ui/i18n.js, ui/sw.js
  • ui/modules/*.js
  • ui/manifest.json, ui/icon-192.png, ui/icon-512.png
  • ui/vendor/chart.umd.min.js
  • ui/vendor/novnc/novnc.esm.js
  • ui/vendor/pretendard/pretendard.css, ui/vendor/pretendard/woff2/*.woff2
  • ui/vendor/coolicons/coolicons.svg

번들 파이프라인의 정본은 Makefile의 ui-bundle 타깃입니다 — ui/modules/*.jsui/app.bundle.js로 다시 묶고(UI_MODULES 등록 누락 가드 + esbuild 민파이/소스맵) ui/sw.jsCACHE_NAMEUI_CACHE_INPUTS에 등록된 전체 선캐시 자산 해시로 bump 합니다.
따라서 docs.html, style.css, 아이콘처럼 bundle 밖의 정적 자산만 바뀌어도 기존 precache가 무효화됩니다.
scripts/bundle-ui.sh는 그 타깃에 위임하는 하위호환 진입점이며, 추가로 로컬 데몬 서빙 경로(/usr/local/share/purecvisor/ui)에 산출물을 복사합니다(PCV_NO_DEPLOY=1로 생략).
UI 모듈, WebSocket, metrics, 정적 자산 목록을 바꾸면 배포 전에 다음 순서로 확인합니다.

Terminal window
PCV_NO_DEPLOY=1 scripts/bundle-ui.sh
python3 scripts/check_ui_bundle_fresh.py
for f in ui/app.js ui/modules/*.js ui/vendor/chart.umd.min.js ui/vendor/novnc/novnc.esm.js; do node -c "$f"; done
git diff --check -- ui scripts src/api/rest_server.c
rg -n "iconify|code\.iconify|api\.iconify|api\.unisvg|api\.simplesvg|cdn\.jsdelivr|fonts\.googleapis|fonts\.gstatic|sourceMappingURL" ui/index.html ui/docs.html ui/guide.html ui/app.bundle.js ui/sw.js ui/vendor
rg -n "customConfirm\([^\\n]*<[^\\n]*>|<br><b>|idx \|\| selectedVmIndex" ui/modules ui/app.bundle.js

rg 명령은 결과가 없어야 정상입니다.
운영 CSP를 넓혀 외부 아이콘 API나 CDN sourcemap을 허용하지 말고, 필요한 런타임 자산은 ui/vendor/ 또는 inline SVG처럼 로컬 자산으로 고정합니다.
customConfirm() 호출부는 HTML 조각을 넘기지 않고 plain text와 \n만 사용합니다.

배포 완료 판정은 저장소 산출물, 설치 파일과 설정한 관리 IPv4에서 실제 제공하는 파일을 함께 비교합니다.
초기 자체서명 인증서를 사용하는 동안만 curl -k를 사용하며 운영 CA 인증서를 설치한 뒤에는 -k 없이 검증합니다.

Terminal window
NODE_IPV4="<configured-management-ipv4>"
curl -ksS "https://${NODE_IPV4}/api/v1/health" \
| jq '{status,service,version,node_name,tls:.checks.tls}'
curl -ksS -o /tmp/pcv-live-app.bundle.js \
"https://${NODE_IPV4}/ui/app.bundle.js?v=2.0.0"
sha256sum ui/app.bundle.js \
/usr/local/share/purecvisor/ui/app.bundle.js \
/tmp/pcv-live-app.bundle.js
rg -n "iconify|code\.iconify|api\.iconify|api\.unisvg|api\.simplesvg|cdn\.jsdelivr|fonts\.googleapis|fonts\.gstatic|sourceMappingURL" \
/tmp/pcv-live-app.bundle.js

세 SHA-256 값은 같아야 하며 외부 런타임 참조를 찾는 rg 명령은 결과가 없어야 합니다.
health의 status=ok, version=2.0.0, TLS mode=internal, enabled=true, degraded=false, status=ok를 확인합니다.

Terminal window
# 로그 로테이션 설치
sudo cp systemd/purecvisor.logrotate /etc/logrotate.d/purecvisor

기본 설정:

  • 일일 회전
  • 30일 보존
  • 압축 활성화
  • /var/log/purecvisor/*.log
  • /var/log/ovn/ovn-controller.log 별도 보호
    • 일일 회전
    • size 50M, maxsize 200M
    • copytruncate 사용

Single Edge OVN local controller 준비 경로는 ovn-controller 재기동 직후 파일 로그 레벨을 ERR로 낮춥니다.
OVNSB commit failed 같은 INFO 폭주가 재발하더라도 운영 로그가 무제한으로 불어나는 것을 막기 위한 안전장치입니다.
OVN 구축 성공은 ovn-nbctl 설치 여부가 아니라 Northbound/Southbound DB 조회, ovn-northd 동기화, ovn-controller active, 로컬 Chassis 등록까지 모두 확인한 상태를 뜻합니다.
단일 노드는 DB를 TCP로 공개하지 않고 로컬 Unix socket을 사용합니다.