설치
PlantPulse Studio 를 고객사 서버 한 대에 설치하는 절차입니다. 도커 컨테이너 4종(웹 · 서버 · 빌더 사이드카 · 도커 프록시)으로 구성되며, 설치 스크립트가 도커 설치부터 기동까지 자동으로 처리합니다.
외부 PG 를 씁니다 — 플랫폼이 쓰는 것과 같은 PG 를 나눠 쓰면 됩니다. 예전에는 번들 컨테이너를 띄우는 모드가 있었지만 없앴습니다(2026-08-28) — 한 서버에서 5432 를 두고 다투는 사고가 실제로 있었습니다.
인터넷이 되지 않는 폐쇄망이라면 이 문서 대신 폐쇄망 설치를 보세요.
1. 사전 요구사항
서버 사양
| 항목 | 최소 | 권장 | 비고 |
|---|---|---|---|
| CPU | 4 코어 | 8 코어 이상 | 앱 세션마다 빌드가 돌아갑니다 |
| 메모리 | 8 GB | 16 GB 이상 | 아래 표 참조 |
| 디스크 | 50 GB 여유 | 100 GB 이상 | 컨테이너 이미지만 약 6 GB |
| OS | Linux (x86_64) | RHEL 9 / Rocky 9 / Ubuntu 22.04 이상 | systemd + 도커가 돌면 됩니다 |
메모리 산정 근거 — 스택이 고정으로 잡는 상한값입니다.
| 컨테이너 | 메모리 상한 |
|---|---|
| studio-server (오케스트레이터) | 2 GB |
| agent-server (빌더 사이드카) | 2 GB (AGENT_SERVER_MEMORY 로 조정) |
| studio-web (nginx) | 256 MB |
| docker-proxy (도커 창구) | 128 MB |
| 앱 세션 컨테이너 1개당 | 2 GB |
앱 세션 컨테이너는 사용자가 프로젝트를 열 때마다 하나씩 뜨고, 30분간 쓰지 않으면 자동 회수됩니다. 동시 작업자 수만큼 2 GB 를 더해 잡으세요.
16 GB 메모리 · 8 코어 · 100 GB 디스크를 기준으로 잡으면 여유롭습니다.
소프트웨어
- Docker Engine + Docker Compose v2 (v2.22 이상) 설치되어 있지 않으면 원라인 설치 스크립트가 자동으로 설치합니다.
curl,tar(대부분의 배포판에 기본 포함)
포트와 방화벽
스택은 host 네트워크로 동작합니다. 즉 컨테이너 포트가 호스트에 그대로 열리므로, 접근 통제는 오직 서버 방화벽으로만 이뤄집니다.
| 포트 | 용도 | 방화벽 |
|---|---|---|
| 80 | 스튜디오 UI (nginx) | 개방 |
| 5171 | 앱 리스너 — 프리뷰 · 배포앱 · QR 접속 | 개방(필수) |
| 5170 | 스튜디오 API | 웹이 내부 프록시 — 개방 불필요(진단용) |
| 8000 | 빌더 사이드카(agent-server) | 루프백 전용 — 절대 개방 금지 |
| 5172 | MCP 도구 브리지 | 루프백 전용 — 절대 개방 금지 |
| 2375 | 도커 데몬 창구(docker-proxy) | 127.0.0.1 에만 바인드 — 스택에서 유일하게 host 네트워크가 아닌 컨테이너 |
프리뷰(/preview/…)와 배포앱(/apps/…)은 보안 경계 때문에 스튜디오와 다른 오리진
(기본 5171 포트)에서 서빙됩니다. 채팅으로 만든 앱의 자바스크립트가 스튜디오 로그인
토큰에 접근하지 못하게 하는 장치입니다. 5171 을 열지 않으면 화면이 비어 보입니다.
두 포트는 내부 부품 간 통신 전용이며 인증 절차가 없습니다. 외부에 노출되면 전체 사용자 워크스페이스를 읽고 쓸 수 있게 됩니다. 스택은 기본적으로 루프백에만 바인드하지만, 방화벽에서도 절대 열지 마세요.
레지스트리 접근
이미지는 KOPENS 레지스트리(docker.kopens.io)에서 받습니다. 설치 전에 한 번 로그인하세요.
계정은 KOPENS 운영팀이 발급합니다.
docker login docker.kopens.io
2. 데이터베이스 준비
스튜디오는 외부 PostgreSQL 을 씁니다. 플랫폼이 쓰는 것과 같은 PG 를 나눠 쓰면 됩니다.
DATABASE_URL 은 필수입니다비어 있으면 설치가 그 자리에서 멈추고 무엇이 비었는지 알려 줍니다 — 그럴듯한 기본값을 지어내지 않습니다. 그럴듯한 기본값이 엉뚱한 곳을 가리켜 사고가 난 적이 있어서입니다.
DATABASE_URL=postgres://`<사용자>`:`<비밀번호>`@`<DB호스트>`:5432/`<DB명>`
데이터베이스와 계정을 먼저 만듭니다
🔴 서버는 «있는» 데이터베이스에 마이그레이션만 합니다. DB 와 역할을 만들지는 않습니다. 만들어 주는 스크립트가 따로 있습니다.
# .env 의 DATABASE_URL 이 가리키는 DB · 역할을 만든다
bin/provision-platform-db.sh
# 관리자 계정을 따로 줘야 하는 경우
ADMIN_URL=postgres://`<관리자>`:`<비밀번호>`@`<호스트>`:5432/postgres bin/provision-platform-db.sh
# 무엇을 할지 보기만 한다
DRYRUN=1 bin/provision-platform-db.sh
AI 쪽과 같은 규칙입니다.
3. 설치 (원라인 · 권장)
서버에 root 로 접속해 한 줄을 실행합니다. 스크립트가 도커 확인·설치 → 패키지 내려받기 →
.env 생성 → 스택 기동까지 수행합니다.
curl -fsSL https://product.kopens.io/plantpulse-studio/install.sh | sudo bash
설치 위치는 /opt/kopens/plantpulse-studio-docker 이고, 이후 모든 운영 명령은 이 디렉터리에서
실행합니다.
값을 미리 정해 무인 설치
환경변수는 bash 앞에 붙여야 파이프를 타고 전달됩니다.
curl -fsSL https://product.kopens.io/plantpulse-studio/install.sh \
| sudo PLATFORM_API_TARGET=https://192.168.0.41 \
DATA_ROOT=/data1/pp-studio \
PS_ANTHROPIC_API_KEY=sk-ant-... \
bash
값을 물어보게 하기
curl -fsSL https://product.kopens.io/plantpulse-studio/install.sh | sudo INTERACTIVE=1 bash
이미 설치된 서버에서 다시 실행하면 사이드카에 값이 있어 프롬프트를 통째로 건너뜁니다. 위 화면은 깨끗한 박스의 첫 설치입니다.
묻는 것은 다섯뿐입니다(2026.09 부터). 나머지 일곱은 기본값으로 정하고, 정해진 값들을 설치 끝에 목록으로 찍습니다 — 숨기는 것이 아니라 보여주고 넘어갑니다.
| 순서 | 묻는 것 | 변수 |
|---|---|---|
| 1 | 플랫폼 API 서버 (https://…) | PP_API_URL |
| 2 | 플랫폼 API 토큰 (X-API-Key) | PP_API_TOKEN |
| 3 | AI 게이트웨이 주소 — /v1 까지 포함 | PI_LLM_URL |
| 4 | AI 게이트웨이 키 (LiteLLM 마스터 키) | PI_LLM_API_KEY |
| 5 | AI 모델 이름 — 게이트웨이에 등록된 이름 | AI_MODEL |
필수 값이 비면 설치가 멈춥니다. 「설치는 끝난 것처럼 보이는데 스택이 안 뜨는」 상태를
막으려는 의도된 동작입니다. DATABASE_URL 은 묻지 않습니다 — PostgreSQL 이 스택에 함께 오고
설치가 스스로 채웁니다.
묻지 않고 기본값을 쓰는 것 일곱 — DATA_ROOT(/var/lib/pp-studio) · AI_PROVIDER(openai-compatible) ·
PP_LANG(en) · PP_TZ(Asia/Seoul) · APPS_PORT(5171) · REGISTRY(docker.kopens.io/ps) ·
PP_IMAGE_TAG(현재 캘린더 버전).
게이트웨이가 서빙하지 않는 모델 이름을 적으면 틀린 키와 똑같이 실패합니다. 그래서 기본값에서 빼고 묻는 값으로 올렸습니다.
바꾸려면 환경변수로 주거나 PP_ADVANCED=1 로 실행해 전부 묻게 하면 됩니다.
PS_ANTHROPIC_API_KEY 는 이제 묻지 않습니다클라우드 Claude 를 쓸 때만 필요한 값인데 이 제품은 게이트웨이가 전제라, 사실상 모두가 엔터로
넘기던 질문이었습니다. 기능은 그대로이고 넣는 길이 셋입니다 — 설치 때 환경변수로,
나중에 bin/passwd.sh PS_ANTHROPIC_API_KEY(값을 생략하면 프롬프트로 받아 명령행에 남지 않습니다),
또는 화면의 설정 > AI 입니다. PP_ADVANCED=1 로도 묻지 않습니다.
같은 서버에서 설치 스크립트를 재실행하면 코드만 갱신되고 .env 와 로컬 백업(dist/)은
보존됩니다. 업그레이드 용도로 그대로 써도 됩니다.
CDN 캐시가 남아 있을 수 있습니다. URL 뒤에 ?v=$(date +%s) 를 붙여 다시 받으세요.
값을 틀리게 주면 설치 전에 멈춥니다
기계를 건드리기 전에 — 도커를 깔기도 전에 — 받은 자격증명으로 실제 호출을 한 번씩 해 봅니다.
플랫폼은 GET /api/v5/site?limit=1, 게이트웨이는 GET /v1/models 입니다.
거부만 멈춥니다. 「못 물어봤다」는 멈추지 않습니다.
| 응답 | 판정 | 설치 |
|---|---|---|
2xx | 받아들여짐 | 계속 |
401 · 403 | 자격증명이 틀림 | 중단 |
400 | 설치 스크립트의 요청이 잘못됨 — 제품 결함이니 알려 주십시오 | 중단 |
5xx · 도달 실패 | 판정 불가 | 경고만, 계속 |
5xx 를 401 과 같은 칸에 넣지 않은 것은 의도입니다 — 부하가 걸린 플랫폼이 503 을 내고 3분 뒤
같은 토큰이 401 을 낸 사례가 있었습니다. 점검 중이거나 스튜디오보다 늦게 뜨는 게이트웨이를
「틀린 키」로 몰면 멀쩡한 설치가 거부됩니다.
모델 이름도 같이 봅니다. 게이트웨이의 /v1/models 응답을 읽어 AI_MODEL 이 그 목록에 있는지
확인하고, 없으면 서빙하는 목록을 찍고 멈춥니다. 목록을 읽지 못했을 때는 판정하지 않습니다 —
답이 비었는데 「없다」고 말하는 것은 실패할 수 없는 검사와 같은 거짓이기 때문입니다.
PP_SKIP_CRED_CHECK=1자리표시자 자격증명으로 설치 자체만 시험할 때 쓰는 문입니다(제품의 릴리즈 게이트가 이 경로를 씁니다). 운영 설치에서 이 값을 켜는 것은, 첫 사용에서 날 실패를 설치 시점에서 뒤로 미루는 것뿐입니다.
4. 설치 (수동)
패키지를 이미 받아 놓았거나 .env 를 직접 작성하려는 경우입니다.
cd /opt/kopens/plantpulse-studio-docker
sudo cp .env.example /etc/kopens/plantpulse-studio.env
sudo vi /etc/kopens/plantpulse-studio.env # 아래 표를 보고 값 채우기
sudo chmod 600 /etc/kopens/plantpulse-studio.env
bash bin/up.sh
/etc/kopens/plantpulse-studio.env 하나다환경변수 파일은 설치 디렉터리 안이 아니라 repo 트리 밖인
/etc/kopens/plantpulse-studio.env 에 둡니다. 권한은 0600 입니다. platform · ai · studio
세 제품이 같은 규약으로 /etc/kopens/plantpulse-<제품>.env 를 씁니다.
설치 디렉터리 루트의 .env 는 옛 경로입니다. compose 도 나머지 스크립트도 이제 그
파일을 읽지 않으므로, 거기를 고쳐도 스택은 바뀌지 않습니다. 기존 설치의 .env 는
bin/install.sh 가 새 경로로 옮겨 줍니다(새로 만들지 않습니다 — 새로 만들면 DB 는 옛
비밀번호인데 env 는 새 기본값이 되어 다음 재기동에서 죽습니다).
환경변수 주요 항목
| 항목 | 설명 | 기본값 |
|---|---|---|
REGISTRY | 이미지 레지스트리. 비우면 pull 없이 로컬 이미지만 사용(에어갭) | docker.kopens.io/ps |
PP_IMAGE_TAG | 이미지 태그. 비우면 VERSION 의 칼버를 따라갑니다. 못 박으려면 스냅샷 태그(2026.09-20260911) 권장 | (빈 값) |
DATA_ROOT | 모든 데이터(워크스페이스 · 빌드 · 상태 · DB)가 저장될 경로 | /var/lib/pp-studio |
PLATFORM_API_TARGET | 실데이터·인증을 위임할 PlantPulse 플랫폼 주소 | https://192.168.0.41 |
APPS_PORT | 앱 리스너 포트(프리뷰·배포앱) | 5171 |
PP_LANG | 웹 기본 언어 ko / en | en |
PP_TZ | 타임존(IANA ID). "오늘 · 어제 · 지난주" 해석 기준 | Asia/Seoul |
STUDIO_CORS_ORIGINS | 다른 도메인에서 API 를 부를 때만 지정. 기본은 차단 | 비움 |
DATABASE_URL | 외부 PostgreSQL 접속 주소 — 위 2절 참조 | 비움 (필수) |
PI_LLM_API_KEY · PP_API_TOKEN 등 | AI · 플랫폼 키 — 비밀 관리 참조 | 비움 (필수) |
계정은 환경변수로 나르지 않습니다. 서버가 계정이 하나도 없을 때 부트스트랩 관리자를 DB 에 심고, 그 뒤로는 앱의 환경설정 → 사용자 관리에서 계정을 추가·삭제하고 역할(관리자 / 빌더 / 뷰어)을 줍니다. 아래 7. 첫 로그인 참조.
PP_LANG=ko 를 직접 넣으세요원라인 설치가 만드는 .env 에는 PP_LANG · PP_TZ 줄이 없어 기본값(en / Asia/Seoul)이
적용됩니다. 한국어로 띄우려면 .env 에 PP_LANG=ko 를 추가하고 bash bin/restart.sh.
.env 를 고친 뒤에는 반드시 bin/restart.shdocker restart 는 .env 를 다시 읽지 않습니다. 값이 반영되지 않은 채로 "왜 안 바뀌지"를
한참 찾게 되는 대표적인 함정입니다. 자세한 내용은 비밀 관리에 있습니다.
5. 스택 구성 확인
설치가 끝나면 컨테이너 넷이 떠 있어야 합니다. PostgreSQL 은 여기 없습니다 — 외부 PG 를 씁니다.
| 컨테이너 | 역할 |
|---|---|
plantpulse-studio-web | 스튜디오 UI(nginx :80) — 정적 화면 + API 프록시 |
plantpulse-studio-server | 오케스트레이터(:5170) — 에이전트 · 세션 · 배포 · 앱 리스너(:5171) |
plantpulse-studio-agent-server | 빌더 사이드카 — 앱 코드를 작성하는 코딩 엔진 |
plantpulse-studio-docker-proxy | 도커 데몬 창구. 127.0.0.1 에만 열립니다 |
docker.sock 을 서버에 물리지 않고 도커 프록시를 거칩니다. 세션 컨테이너를 만들고
지우려면 데몬이 필요한데, 소켓을 그대로 주면 그것은 호스트 전체 권한과 같습니다.
프록시만 host 네트워크를 쓰지 않고 127.0.0.1:2375 에 게시합니다. host 네트워크였다면
2375 는 방화벽 말고는 아무것도 지키지 않는 열린 데몬이 됐을 것입니다.
세션 컨테이너를 만들려면 POST /containers/create 가 필요하고, 그것을 부를 수 있는
쪽은 / 를 마운트한 컨테이너도 만들 수 있습니다. 프록시의 통제 단위는
엔드포인트이지 파라미터가 아닙니다.
이 프록시가 실제로 하는 일은 둘입니다 — 쓰지 않는 API 표면(볼륨 · 시크릿 · 설정 · 스웜 · 노드 · 플러그인 · 시스템 이벤트)을 잘라 내는 것, 그리고 데몬 접근을 한 지점으로 모아 감사 가능하게 만드는 것. 허용 범위는 studio-server 가 실제로 쓰는 것만으로 좁혀져 있습니다(컨테이너 · 이미지 · 빌드 · exec · 네트워크 · 레지스트리 로그인).
그러니 이 스택을 신뢰 경계 안쪽에 두세요. 앱을 만드는 사람이 곧 호스트에 대한 권한을 갖는 것으로 보고 운영해야 합니다.
여기에 더해, 사용자가 프로젝트를 열면 앱 세션 컨테이너가 프로젝트마다 하나씩 뜹니다. 스택 컨테이너가 아니라 서버가 필요할 때 만들고 유휴 30분 후 회수합니다.
6. 기동 확인
cd /opt/kopens/plantpulse-studio-docker
bash bin/status.sh # 컨테이너 상태 + 헬스 + 세션 컨테이너 수
curl -s localhost:5170/health
설치 직후에는 종합 점검 스크립트를 한 번 돌리는 것을 권장합니다. 헬스 · 웹 응답 · 실제 로그인 · 세션 런타임 이미지 · 컨테이너 상태를 모두 검사하고, 하나라도 실패하면 오류로 끝납니다.
bash bin/smoke-install.sh
✓ health
✓ client-config → {"lang":"ko",...}
✓ web 200
✓ 로그인(부트스트랩 계정)
✓ 세션 런타임 이미지
✓ 컨테이너 running(...)
✅ 설치 스모크 통과
앱 세션 컨테이너는 plantpulse-studio-runtime 이미지를 씁니다. bin/up.sh 가 로컬에 없을 때
내려받아 태그하지만, 실패하면 ⚠️ 세션 런타임 이미지 없음 경고가 뜨고 프로젝트 열기가
실패합니다. 이때는 레지스트리 로그인 상태를 확인하고 bash bin/up.sh 를 다시 실행하세요.
이미 로컬에 있으면 up.sh 는 건드리지 않습니다 — 새 버전으로 바꾸려면
bin/update.sh 입니다.
7. 첫 로그인
브라우저에서 http://<서버IP>/ 로 접속합니다.
계정이 하나도 없는 상태에서 서버가 처음 뜨면 부트스트랩 관리자를 DB 에 심습니다.
| 항목 | 값 |
|---|---|
| 아이디 | admin |
| 비밀번호 | admin123! |
부팅 로그에도 남습니다 — 부트스트랩 관리자 생성: admin — 로그인 후 비밀번호를 바꿀 것.
모든 설치에서 같은 값입니다. 로그인하자마자 환경설정 → 사용자 관리에서 바꾸세요 (비밀번호 재설정, 6자 이상). 환경변수 파일을 고치는 것으로는 바뀌지 않습니다 — 서버는 로그인할 때 ①DB → ②환경변수 순으로 보고 DB 에서 잡히면 거기서 끝냅니다.
로그인 후 할 일:
- 환경설정 → 사용자 관리 — 부트스트랩 관리자 비밀번호 변경, 현장 사용자 계정 생성, 역할(관리자 / 빌더 / 뷰어) 지정
- 비밀번호 · API 키 변경 — DB 계정과 AI 키를
bin/passwd.sh로 한 번에 회전. 커미셔닝 때 한 번만 하면 됩니다 - 환경설정 → AI — AI 프로바이더와 키 연결 상태 확인
- 환경설정 → 플랫폼 — 플랫폼 주소·키 연결 확인(실데이터 조회에 필요)
자세한 화면 설명은 시작하기와 환경설정(관리자)에 있습니다.
8. 일상 운영 명령
모든 명령은 설치 디렉터리(/opt/kopens/plantpulse-studio-docker)에서 실행합니다.
bash bin/up.sh # 시작(레지스트리에서 최신 이미지 pull 포함). 종료 코드 0 = «준비됨»
bash bin/down.sh # 중지 — 데이터는 그대로 보존, 세션 컨테이너도 정리
bash bin/restart.sh # 재기동(.env 변경 반영)
bash bin/status.sh # 상태 요약
bash bin/logs.sh # 서버 로그 팔로우(기본 studio-server)
bash bin/logs.sh studio-web # 특정 서비스 로그
bash bin/update.sh # 이미지 갱신 + 재기동 → [업데이트](#9-업데이트)
bash bin/remove.sh # 컨테이너·네트워크 제거. **데이터는 보존이 기본**
start.sh · stop.sh 라는 옛 이름도 그대로 동작합니다세 제품(Platform · AI · Studio)이 같은 이름·같은 뜻의 공통 동사를 쓰도록 정리됐습니다 —
up · down · restart · status · logs · update · remove.
옛 이름은 지우지 않았습니다. bin/start.sh 와 bin/stop.sh 는 계속 같은 일을 합니다
(CI 가 원격 호스트에서 그 이름을 직접 부릅니다). 기존 런북을 한꺼번에 고치지 않아도 됩니다.
down.sh 가 컴포즈만 부르지 않는 이유Studio 서버가 앱을 열 때 만드는 세션 컨테이너(plantpulse-studio=1 라벨)는 컴포즈가
관리하지 않습니다. docker compose down 만 부르면 그것들이 그대로 돌아갑니다 —
「내렸는데 컨테이너가 살아 있다」가 되는 자리입니다. down.sh 는 그것까지 정리합니다.
remove.sh 는 데이터를 지우지 않습니다 — 지우려면 명시해야 합니다bash bin/remove.sh # 컨테이너·네트워크만. 데이터 그대로
bash bin/remove.sh --volumes # 데이터까지 (확인을 묻습니다)
bash bin/remove.sh --volumes --yes # 자동화용 — 묻지 않습니다
이 스택의 진짜 데이터는 named volume 이 아니라 DATA_ROOT 바인드 마운트(기본
/var/lib/pp-studio)에 있습니다 — PG 데이터 · 워크스페이스 · 빌드 산출물 · 상태.
docker compose down -v 로는 지워지지 않지만, 운영자가 「제거」라고 할 때는 그것까지
뜻하는 경우가 많습니다. 되돌릴 수 없으므로 명시적으로 요청할 때만 건드립니다.
세션 컨테이너만 따로 보려면,
docker ps --filter label=plantpulse-studio=1
9. 업데이트
bin/update.sh 가 이미지를 다시 받고 재기동까지 합니다. 설치 스크립트를 다시 실행하면
운영 스크립트까지 함께 갱신됩니다(.env · 백업은 보존).
=== "이미지만 갱신"
cd /opt/kopens/plantpulse-studio-docker
bash bin/update.sh
up.sh(옛 start.sh)만으로는 «세션 런타임» 이 옛 이미지로 남습니다up.sh 는 컴포즈 서비스의 이미지를 받아 옵니다. 그런데 사용자 앱이 실제로 도는
세션 런타임 이미지는 컴포즈 서비스가 아니라서 compose pull 에 걸리지 않고,
up.sh 는 그것이 로컬에 없을 때만 받아 옵니다 — 이미 있으면 그대로 둡니다.
그래서 up.sh 만 돌리면 나머지는 새 이미지인데 세션 런타임만 조용히 옛 버전으로
남습니다. update.sh 는 그 하나를 강제로 다시 받는 것이 up.sh 와의 차이입니다.
=== "패키지까지 갱신"
curl -fsSL https://product.kopens.io/plantpulse-studio/install.sh | sudo bash
버전 고정
버전 표기는 YYYY.MM 형식입니다(예: 2026.08). 자동 갱신을 원치 않는 운영 환경에서는
.env 에 태그를 고정하세요.
TAG=2026.08
bash bin/backup.sh 를 먼저 돌려 두세요. 절차는 백업과 복구에 있습니다.
이미지 서명 검증(선택)
배포된 이미지가 KOPENS 가 서명한 것인지 확인할 수 있습니다. cosign 이 설치돼 있어야 합니다.
cd /opt/kopens/plantpulse-studio-docker
bash bin/verify-images.sh
[verify] ✅ docker.kopens.io/ps/plantpulse-studio-server:2026.08
[verify] ✅ docker.kopens.io/ps/plantpulse-studio-web:2026.08
[verify] ✅ docker.kopens.io/ps/plantpulse-studio-runtime:2026.08
다음 단계
- 비밀 관리 — AI · 플랫폼 키를 안전하게 넣는 방법
- 도메인과 리버스 프록시 — IP 대신 도메인·HTTPS 로 서비스하기
- 백업과 복구 — 자동 백업 설치와 복구 리허설
- 문제 해결 — 증상별 원인·조치표