kanpic 관리자 가이드 (System Administrator Manual)

이 문서의 화면 캡처는 모두 v0.242.0 을 실제로 띄워 1440×900 에서 찍은 것이며, 등장하는 이름·이메일·워크북은 전부 예시용 가짜 데이터입니다.


1. 개요 및 시스템 아키텍처

본 문서는 kanpic 플랫폼의 설치, 배포, 보안 설정, Keycloak OIDC 통합, 데이터베이스 마이그레이션 및 일상 운영 관리를 담당하는 시스템 관리자를 위한 기술 가이드입니다.

graph TB
    subgraph ClientZone [클라이언트 영역]
        WebBrowser[React Single Page Application]
    end

    subgraph ServerZone [kanpic 서버 영역]
        GoMonolith[kanpic Go Monolith Server]
        HTTPAPI[HTTP REST API / Workspace Engine]
        MCPEndpoint[Model Context Protocol /mcp]
        FormulaEngine[Go In-Memory Formula Worker]
    end

    subgraph InfraZone [데이터베이스 & 인증]
        PostgreSQL[(PostgreSQL 16 Server)]
        Keycloak[Keycloak OIDC Authentication Server]
    end

    WebBrowser -->|HTTP REST / WS| HTTPAPI
    WebBrowser -->|MCP Stream| MCPEndpoint
    HTTPAPI --> FormulaEngine
    HTTPAPI -->|SQL Migration / Query| PostgreSQL
    HTTPAPI -->|OIDC Validation| Keycloak

2. 배포 및 아키텍처 특성

kanpic은 인프라 복잡도를 극소화하고 폐쇄망 환경에서의 안정성을 극대화하기 위해 Redis-Free 인메모리 단일 바이너리/컨테이너 아키텍처를 채택하고 있습니다.

2.1 구성 요소

구성 요소 무엇인가 필수 비고
kanpic 컨테이너 Go 단일 바이너리. REST·WebSocket·MCP·정적 자산을 한 프로세스가 낸다 필수 기본 8080/tcp
PostgreSQL 유일한 영구 저장소. 워크북·설정·로그·API 키·세션이 모두 여기 있다 필수 16 이상(compose 예시는 postgres:17-alpine)
Keycloak(OIDC) 조직 계정 로그인 선택 없으면 bootstrap 관리자 로그인만 쓴다
사내 LLM Gateway Workbook Agent(AI) 선택 OpenAI 호환 /v1
사내 SMTP 릴레이 공유·댓글·멘션·지켜보기 알림 메일 선택  
프레젠테이션 서비스(Ptium) 선택 범위로 슬라이드 만들기 선택  

Redis, 메시지 브로커, 별도의 파일 저장소는 쓰지 않습니다.

자원
포트 컨테이너 8080/tcp 하나. 외부에는 리버스 프록시의 443 만 연다
볼륨 애플리케이션 컨테이너는 상태가 없다. PostgreSQL 데이터 디렉터리만 볼륨으로 잡는다
바깥으로 나가는 연결 PostgreSQL, (설정한 경우) Keycloak · LLM Gateway · SMTP · 프레젠테이션 서비스 · external.allowed_hosts 에 적은 곳뿐

2.2 릴리즈 자산으로 설치

GitHub Release 의 kanpic-vX.Y.Z.tar.gz 는 Docker 이미지 아카이브입니다. 폐쇄망에서는 이 파일과 .sha256 만 옮기면 됩니다. 런타임에 인터넷이 필요하지 않습니다.

# 1. 받은 파일이 온전한지 확인하고 이미지를 올린다
VERSION=v0.242.0
sha256sum -c "kanpic-${VERSION}.tar.gz.sha256"
gzip -dc "kanpic-${VERSION}.tar.gz" | docker load

# 2. compose 파일을 쓴다 (이미지 태그를 방금 올린 버전으로)
cat > compose.yaml <<'YAML'
name: kanpic
services:
  api:
    image: kanpic:v0.242.0
    environment:
      POSTGRES_DSN: postgres://kanpic:${POSTGRES_PASSWORD}@postgres:5432/kanpic?sslmode=disable
      BOOTSTRAP_ADMIN_ID: ${BOOTSTRAP_ADMIN_ID}
      BOOTSTRAP_ADMIN_PASSWORD: ${BOOTSTRAP_ADMIN_PASSWORD}
    ports: ["8080:8080"]
    depends_on:
      postgres: { condition: service_healthy }
    restart: unless-stopped
  postgres:
    image: postgres:17-alpine
    environment:
      POSTGRES_DB: kanpic
      POSTGRES_USER: kanpic
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U kanpic -d kanpic"]
      interval: 2s
      timeout: 3s
      retries: 15
    volumes: [kanpic-postgres:/var/lib/postgresql/data]
volumes:
  kanpic-postgres:
YAML

# 3. 최초 관리자 계정과 DB 비밀번호를 넘겨 띄운다
#    (셸 기록에 남지 않게 read 로 받거나 배포 도구의 비밀 저장소를 쓴다)
read -rs POSTGRES_PASSWORD; export POSTGRES_PASSWORD
read -rs BOOTSTRAP_ADMIN_PASSWORD; export BOOTSTRAP_ADMIN_PASSWORD
export BOOTSTRAP_ADMIN_ID=admin
docker compose up -d

# 4. 떴는지 확인한다
curl -fsS http://localhost:8080/healthz
curl -fsS http://localhost:8080/api/v1/version

스키마는 서버가 시작할 때 migrations/ 를 순서대로 적용하므로 따로 할 일이 없습니다. 브라우저로 http://<서버>:8080 을 열어 BOOTSTRAP_ADMIN_ID 로 로그인하면 관리자 콘솔이 열립니다. 이후 설정은 모두 /admin 화면에서 하고, 바꿀 때마다 설정 버전이 생깁니다.

BOOTSTRAP_ADMIN_IDBOOTSTRAP_ADMIN_PASSWORD둘 다 넣거나 둘 다 빼야 합니다. 하나만 넣으면 서버가 시작하지 않습니다(BOOTSTRAP_ADMIN_ID and BOOTSTRAP_ADMIN_PASSWORD must be configured together). 둘 다 빼면 로그인 없이 최초 설정을 할 수 있는 개방형 모드가 되므로, 운영 배포에서는 반드시 지정합니다.

2.3 환경 변수 전수

서버가 읽는 환경 변수는 이것이 전부입니다(cmd/api/main.go). 나머지 서비스 설정은 환경 변수가 아니라 관리자 콘솔에 저장되며, 그 목록은 3.5 절의 설정 키 표에 있습니다.

이름 기본값 필수 설명
POSTGRES_DSN 없음 필수 PostgreSQL 접속 문자열. 없으면 서버가 POSTGRES_DSN is required 를 남기고 종료합니다. 예: postgres://kanpic:<비밀번호>@postgres:5432/kanpic?sslmode=require
BOOTSTRAP_ADMIN_ID 빈 값 선택(짝) 로컬 관리자 로그인 아이디. 예: admin
BOOTSTRAP_ADMIN_PASSWORD 빈 값 선택(짝) 로컬 관리자 비밀번호. 충분히 길게 정하고 배포 도구의 비밀 저장소로 주입합니다
PORT 8080 선택 서버가 열 포트

3. 관리자 콘솔 (/admin) 및 주요 관리 기능

관리자 계정으로 로그인 후 상단 프로필 메뉴의 [관리자 콘솔] 또는 /admin 경로로 이동하여 시스템 전반을 관리할 수 있습니다.

관리자 콘솔 개요 — 사용자·부서·워크북·공유 규모 카드와 점검이 필요한 항목 목록

왼쪽 목록이 콘솔의 전부입니다: 개요 · 시스템 설정 · 사용자 및 역할 · 워크북 거버넌스 · 부서 및 공유 · AI 호출 이력 · 알림 메일 · 방문자 추적 · 서버 로그 · API 키 현황 · 시스템 상태. 각 화면은 /admin?tab=<이름> 주소를 그대로 가지므로 북마크하거나 링크로 넘길 수 있습니다.

3.1 관리자 개요와 워크북 거버넌스

/admin개요 화면으로 시작합니다. 사용자·부서·워크북·공유 규모를 카드로 보여 주고, 점검이 필요한 항목(링크가 있는 모든 사용자에게 공개, 조직 전체 공개, 소유자가 없거나 정지된 워크북, 대기 중인 액세스 요청)을 클릭하면 해당 목록으로 바로 이동합니다.

워크북 거버넌스 화면은 관리자가 소유하지 않은 워크북까지 포함해 전체 목록을 보여 줍니다.

필터 용도
전체 활성 워크북 전체
링크 공개 링크가 있는 모든 사용자 범위로 열려 있는 워크북
조직 전체 조직 내 모든 사용자 범위로 열려 있는 워크북
소유자 문제 소유자가 비어 있거나 정지된 계정인 워크북
잠든 워크북 1년 이상 손대지 않은 워크북
휴지통 삭제되어 복원 가능한 워크북

워크북 거버넌스 — 거르개(전체·링크 공개·조직 전체·소유자 문제·잠든 워크북·휴지통)와 워크북마다 소유자·공유 범위·시트 수·최근 수정

각 행에서 공개 해제(링크 액세스를 제한됨으로 되돌리기), 소유권 이전, 휴지통 이동, 휴지통 항목의 복원을 실행할 수 있습니다.

조직 차원의 상한은 시스템 설정으로 강제합니다.

3.2 사용자 및 역할 관리

사용자 및 역할 — 전체 사용자·관리자·정지된 계정 수와 사용자마다 역할·부서·워크북 수·마지막 접속·상태

/admin사용자 및 역할 에서 계정 상태와 kanpic 역할을 관리합니다. 신원 자체는 OIDC 공급자(또는 bootstrap 로그인)가 소유하고, kanpic은 접근 통제에 필요한 상태만 보관합니다.

전역 관리자가 부서 및 공유 화면에서 부서마다 부서 관리자 를 지정할 수 있습니다.

부서 관리자는 자기 부서와 그 아래 부서의 구성원 계정 만 다룹니다. 부서가 나뉘어도 맡은 사람이 바뀌지 않도록 아래 부서까지 봅니다 — 팀이 둘로 갈라졌다고 관리자가 절반을 못 보게 되면 위임이 끊긴 것을 아무도 알아채지 못합니다.

열어 준 것 열지 않은 것
맡은 구성원 목록 보기 워크북 소유권 이전
계정 정지·해제, 메모 개요(조직 전체 숫자)
kanpic 역할 부여·회수 워크북 거버넌스
모든 세션 종료 설정·로그·API 키·AI 정책·메일
  사용자 등록·일괄 등록

REST 계약은 GET/POST /api/v1/admin/users, GET/PATCH /api/v1/admin/users/{userId}, POST /api/v1/admin/users/{userId}/roles, DELETE /api/v1/admin/users/{userId}/roles/{role}, DELETE /api/v1/admin/users/{userId}/sessions이며 모두 관리자 세션 또는 admin.* scope를 요구합니다.

API 키는 소유자 계정을 따릅니다. 소유자가 정지되면 그 키로 보낸 요청도 함께 차단됩니다.

3.3 부서 계층 및 워크북 공유 통제

/admin부서 및 공유 에서 조직의 부서 계층을 관리합니다. 부서는 워크북 공유의 기본 단위이므로 관리자만 변경할 수 있습니다.

부서 및 공유 — 부서 수와 구성원 배치 수, 상위/하위 관계가 보이는 부서 목록

상위 부서에 공유하면 하위 부서 구성원까지 권한을 상속합니다. 예를 들어 경영지원본부에 편집자 권한을 주면 재무팀, 인사팀 구성원 모두가 편집자가 됩니다.

권한 판정은 소유자 → 관리자 → 개인 공유 → 부서 공유 → 역할 공유 → 링크 액세스를 모두 계산해 가장 높은 권한을 적용합니다. 관리자 역할(auth.oidc.admin_roles)을 가진 사용자는 감사와 복구를 위해 모든 워크북에 소유자 권한으로 접근하며, 이때 공유 창에는 관리자 권한으로 접근 중입니다로 표시됩니다.

REST GET /api/v1/departments와 MCP spreadsheet.department.list는 모든 로그인 사용자가 사용할 수 있고(공유 대상 선택에 필요), 생성·수정·삭제·구성원 변경은 admin.* scope 또는 관리자 세션만 허용합니다.

3.4 개인 API 키 통제 및 회전 (Key Rotation)

API 키 현황 — 소유자와 키 이름, 앞자리, scope, 최근 사용과 상태

3.5 설정 화면과 설정 키

시스템 설정 화면은 자주 쓰는 것을 카드로 묶어 보여 주고(Keycloak OIDC 간편 연결, 사내 AI Gateway 간편 연결 …), 그 아래에서 개별 키를 직접 다룰 수 있습니다. 값을 저장할 때마다 설정 스냅샷 revision 이 생기므로 설정 버전 목록에서 이전 상태로 되돌릴 수 있습니다.

시스템 설정 — Keycloak OIDC 간편 연결 카드와 설정 검증·연결 테스트 단추

기본값이 있는 설정 키는 다음과 같습니다(internal/settings/repository.go). 비밀 설정으로 표시된 것은 저장한 뒤 화면에 다시 나오지 않습니다.

기본값 설명
branding.product_name kanpic 화면에 표시할 제품명
localization.locale ko-KR 기본 로케일
localization.timezone Asia/Seoul 기본 시간대
editor.autosave_batch_ms 250 자동 저장 배치 간격(ms)
editor.max_cells_per_operation 1000 쓰기 요청당 최대 셀 수
auth.oidc.enabled false Keycloak OIDC 로그인 사용
auth.oidc.issuer_url 빈 값 Keycloak Realm Issuer URL
auth.oidc.client_id kanpic Keycloak Client ID
auth.oidc.client_secret 빈 값 Confidential Client Secret (비밀 설정)
auth.oidc.ca_pem 빈 값 사내 CA 인증서 PEM (비밀 설정)
auth.oidc.scopes openid, profile, email OIDC Scope
auth.oidc.admin_roles kanpic-admin 관리자 권한으로 인정할 Keycloak Role
auth.session_hours 8 로그인 세션 유지 시간
server.public_url 빈 값 프록시 외부 공개 URL. 비우면 요청 Host 사용
files.max_import_mb 20 Import 파일 최대 크기(MB)
files.max_image_mb 5 시트에 넣는 이미지 한 장의 최대 크기(MB)
ai.enabled false AI 기능 사용
ai.gateway_url 빈 값 사내 OpenAI 호환 LLM Gateway
ai.model kanpic-default AI 작업에 사용할 모델
ai.api_key 빈 값 LLM Gateway API Key (비밀 설정)
ai.ca_pem 빈 값 사내 LLM Gateway CA 인증서 PEM (비밀 설정)
ai.timeout_seconds 30 AI Gateway 요청 제한 시간(초)
ai.max_input_cells 200 AI 에 전달할 선택 범위 최대 셀 수
ai.max_changes 100 AI 계획 한 건의 최대 변경 셀 수
ai.max_output_tokens 0 응답 최대 토큰. 0이면 컨텍스트 길이에서 자동 계산
ai.history_retention_days 0 AI 호출 이력 보존 기간(일). 0이면 계속 보관
presentation.enabled false 선택 범위로 프레젠테이션 만들기 사용
presentation.provider ptium 프레젠테이션 서비스 종류
presentation.base_url 빈 값 프레젠테이션 서비스 주소
presentation.api_key 빈 값 프레젠테이션 서비스 API Key (비밀 설정)
presentation.timeout_seconds 60 프레젠테이션 서비스 요청 제한 시간(초)
presentation.default_template_id 빈 값 기본 템플릿 ID. 비우면 서비스 기본 디자인
presentation.max_cells 5000 한 프레젠테이션이 읽을 최대 셀 수
mail.enabled false 이벤트 알림 메일 발송 사용
mail.smtp_host 빈 값 사내 SMTP 서버 주소
mail.smtp_port 25 사내 릴레이 25, STARTTLS 587, TLS 465
mail.security auto auto, none, starttls, tls
mail.username 빈 값 비우면 인증 없이 발송
mail.password 빈 값 SMTP 비밀번호 (비밀 설정)
mail.from_address 빈 값 보내는 주소. 비우면 kanpic@SMTP호스트
mail.from_name kanpic 보내는 사람 이름
mail.base_url 빈 값 메일 본문 링크에 쓰는 kanpic 주소
mail.skip_tls_verify false 사설 인증서 SMTP 의 인증서 검증 생략
mail.timeout_seconds 10 SMTP 연결 제한 시간(초)
mail.notify_share · notify_comment · notify_watch · notify_mention · notify_access_request 모두 true 이벤트별 발송 여부
analytics.enabled false 방문자 추적 코드 삽입 사용
analytics.provider none none, ga4, gtm, matomo, custom
analytics.measurement_id 빈 값 GA4 측정 ID(G-) 또는 GTM 컨테이너 ID(GTM-)
analytics.matomo_url · analytics.matomo_site_id 빈 값 Matomo 서버 주소와 사이트 ID
analytics.custom_snippet 빈 값 직접 입력하는 추적 코드(HTML)
analytics.allowed_hosts 빈 값 추적 코드가 접속할 추가 도메인. 쉼표로 구분
analytics.include_admin false 관리자·개인 설정 화면에도 삽입
analytics.placement head 삽입 위치: head 또는 body
automation.enabled false 워크북 자동화 실행 사용
automation.max_cells_per_run 1000 자동화 실행 한 건의 최대 변경 셀 수
automation.max_runs_per_hour 100 워크북별 시간당 자동화 실행 한도
automation.scheduler_poll_seconds 15 스케줄 자동화 확인 주기(초)
external.enabled false 수식의 외부 호출(WEBSERVICE, IMPORTDATA) 사용
external.allowed_hosts 빈 목록 외부 호출을 허용할 호스트. 비어 있으면 아무 데도 부르지 않음
external.timeout_seconds 10 외부 호출 한 건의 제한 시간(초)
external.max_kb 1024 외부 호출 응답의 최대 크기(KB)
external.cache_seconds 300 같은 주소의 응답을 다시 쓰는 시간(초)
sharing.max_link_access anyone 허용할 최대 링크 액세스 범위
sharing.default_link_access restricted 새 워크북의 기본 링크 액세스
mcp.enabled true MCP Gateway 사용
observability.log_retention_days 30 서버 로그 보존 일수

4. OIDC / Keycloak 연동 설정

kanpic은 기업용 SSO 구축을 위해 Keycloak과의 OIDC PKCE 인증을 지원합니다.

flowchart LR
    A[Keycloak Realm 설정] --> B[Client와 Redirect URI 등록]
    B --> C[관리자 화면에서 OIDC 값 저장]
    C --> D[설정 검증과 연결 테스트]
    D --> E[OIDC 활성화]

4.1 관리자 화면 설정 명세

설정 키 기본값 설명
auth.oidc.enabled false 검증과 연결 시험을 마친 뒤 SSO 로그인 활성화
auth.oidc.issuer_url 빈 값 Keycloak Realm Issuer URL
auth.oidc.client_id kanpic OIDC Client ID
auth.oidc.client_secret 빈 값 Public Client는 비우고 Confidential Client만 입력하는 비밀 설정
auth.oidc.scopes openid, profile, email 요청할 OIDC scope 목록
auth.oidc.admin_roles kanpic-admin 관리자 권한으로 인정할 Keycloak role 목록
auth.oidc.ca_pem 빈 값 폐쇄망 사설 CA 인증서 PEM 비밀 설정
server.public_url 빈 값 리버스 프록시 외부 주소. 비우면 요청 Host 사용

각 저장·수정·삭제는 설정 스냅샷 revision을 생성합니다. 전체 검증으로 필수값과 타입을 확인한 뒤 연결 테스트로 Issuer discovery와 PostgreSQL 상태를 시험합니다. 문제가 생기면 설정 버전 목록에서 이전 revision을 복원할 수 있습니다. 서버 시작에 필요한 환경 변수는 POSTGRES_DSN 하나이며, bootstrap 로그인 보호가 필요할 때만 BOOTSTRAP_ADMIN_IDBOOTSTRAP_ADMIN_PASSWORD를 함께 추가합니다.


5. 사내 AI Gateway 설정과 안전 정책

관리자 콘솔의 사내 AI Gateway 간편 연결 카드에서 OpenAI 호환 /v1 Gateway를 설정합니다. 설정값은 환경 변수가 아니라 PostgreSQL의 관리 설정으로 저장되며, 다른 설정과 동일하게 변경 revision, 검증, 연결 테스트와 이전 버전 복원을 지원합니다.

설정 키 기본값 설명
ai.enabled false 검증과 연결 테스트가 끝난 뒤 Workbook Agent 활성화
ai.gateway_url 빈 값 사내 vLLM 또는 OpenAI 호환 Gateway URL
ai.model kanpic-default 요청에 사용할 배포 모델 이름
ai.api_key 빈 값 Gateway Bearer API Key 비밀 설정
ai.ca_pem 빈 값 폐쇄망 사설 CA 인증서 PEM 비밀 설정
ai.timeout_seconds 30 Gateway 호출 1회당 제한 시간
ai.max_input_cells 200 한 계획에 전달할 선택 범위 최대 셀 수
ai.max_changes 100 한 계획에서 허용할 최대 변경 셀 수
ai.max_output_tokens 0 응답 최대 토큰. 0이면 모델의 컨텍스트 길이에서 자동 계산
ai.history_retention_days 0 AI 호출 이력 보존 기간(일). 0이면 계속 보관

응답 길이는 기본적으로 서버가 정합니다. GET /v1/models 응답의 max_model_len(vLLM) 또는 context_length를 읽어 컨텍스트 길이를 파악하고, 프롬프트 추정 토큰과 여유분을 뺀 값을 그 요청의 max_tokens로 사용합니다. 값은 10분간 캐시하며, 컨텍스트 길이를 제공하지 않는 Gateway에서는 첫 호출이 8K 컨텍스트 안에 들도록 입력 크기를 반영한 보수적 상한을 쓰고 응답이 실제로 잘리면 상한을 키웁니다. ai.max_output_tokens를 지정하면 작은 값도 포함해 그 상한이 항상 우선합니다. ai.model에 적은 이름이 목록에 없고 모델이 하나뿐이면 그 모델을 사용합니다.

알림 메일 (SMTP)

콘솔의 알림 메일 화면에서 사내 SMTP를 연결하고 이벤트 알림 발송을 관리합니다. 사내 릴레이를 쓰는 경우 SMTP 서버 주소만 넣으면 됩니다. 사용자 이름을 비워 두면 인증 없이 발송하고, 포트는 25가 기본이며 465를 입력하면 별도 설정 없이 TLS로 접속합니다.

기본값 설명
mail.enabled false 알림 메일 발송 사용
mail.smtp_host 빈 값 사내 SMTP 서버 주소
mail.smtp_port 25 사내 릴레이 25, STARTTLS 587, TLS 465
mail.security auto auto는 서버가 STARTTLS를 광고할 때만 사용. none, starttls, tls 지정 가능
mail.username 빈 값 비우면 인증 없이 발송. 값이 있으면 PLAIN·LOGIN·CRAM-MD5 중 서버가 지원하는 방식 사용
mail.password 빈 값 SMTP 비밀번호(비밀 설정)
mail.from_address 빈 값 보내는 주소. 비우면 kanpic@SMTP호스트
mail.from_name kanpic 보내는 사람 이름
mail.base_url 빈 값 메일 본문 링크에 쓰는 kanpic 주소
mail.skip_tls_verify false 사설 인증서 SMTP의 인증서 검증 생략
mail.timeout_seconds 10 SMTP 연결 제한 시간
mail.notify_share true 워크북 공유 알림
mail.notify_comment true 댓글·답글 알림
mail.notify_mention true 멘션 알림
mail.notify_access_request true 액세스 요청과 처리 결과 알림

연결 확인 은 릴레이에 접속해 인사(EHLO)까지만 하고 메일을 보내지 않습니다. 테스트 메일 보내기 는 지정한 주소로 실제 메일을 한 통 보냅니다. 두 기능 모두 결과를 화면에 그대로 보여 주므로 방화벽이나 인증 문제를 바로 확인할 수 있습니다.

발송되는 이벤트는 다음과 같습니다.

메일은 요청 처리와 분리된 백그라운드에서 보내므로 SMTP가 느려도 화면이 기다리지 않습니다. 실패하면 2초 뒤 한 번 더 시도하고, 모든 시도는 발송 이력 표에 시각·이벤트·수신자·상태·오류와 함께 남습니다. 수신자 주소는 사용자 디렉터리의 이메일에서 찾으며, 이메일이 없는 계정은 조용히 건너뜁니다.

GET  /api/v1/admin/mail/deliveries?status=sent|failed|queued&limit=100
POST /api/v1/admin/mail:test   {"recipient":"admin@corp.example"}

방문자 추적 (Analytics)

콘솔의 방문자 추적 화면에서 웹사이트 방문자 데이터를 수집하는 자바스크립트를 넣습니다. 제공자를 고르고 식별자만 입력하면 삽입할 코드와 그 코드가 필요로 하는 콘텐츠 보안 정책(CSP)이 함께 만들어지므로, 정책을 따로 손볼 필요가 없습니다.

기본값 설명
analytics.enabled false 추적 코드 삽입 사용
analytics.provider none none, ga4, gtm, matomo, custom
analytics.measurement_id 빈 값 GA4 측정 ID(G-) 또는 GTM 컨테이너 ID(GTM-)
analytics.matomo_url 빈 값 자체 호스팅 Matomo 주소
analytics.matomo_site_id 빈 값 Matomo 사이트 ID
analytics.custom_snippet 빈 값 직접 입력하는 <script> 코드. 최대 8KB
analytics.allowed_hosts 빈 값 코드에서 찾지 못한 도메인을 직접 추가. 쉼표로 구분
analytics.include_admin false 관리자·개인 설정 화면에도 삽입
analytics.placement head head 또는 body 끝에 삽입

보안 정책과의 관계

kanpic은 script-src 'self' 기반의 엄격한 CSP를 보내며 unsafe-inline을 쓰지 않습니다. 추적 코드는 요청마다 새로 만드는 nonce를 붙여 삽입하고 같은 nonce를 응답 헤더의 script-src에 넣기 때문에, 정책을 느슨하게 만들지 않고도 인라인 코드가 실행됩니다. 페이지 응답은 Cache-Control: no-store이므로 nonce가 재사용되지 않습니다.

차단된 요청 확인과 허용

추적이 켜져 있는 동안에는 정책에 report-uri가 함께 나가므로, 브라우저가 무엇을 막았는지 서버로 알려 줍니다. 콘솔의 차단된 요청 카드에서 막힌 주소·지시문·횟수·마지막 시각을 확인하고 이 도메인 허용을 누르면 analytics.allowed_hosts에 추가되어 스크립트·수집 요청·이미지 전송이 함께 열립니다.

GET    /api/v1/admin/analytics/violations
DELETE /api/v1/admin/analytics/violations
POST   /api/v1/admin/analytics/violations:allow   {"origin":"https://collector.corp.example"}

수집 도구가 브라우저 밖으로 데이터를 보내는 만큼, 개인정보 처리방침과 사내 규정에 맞는 도구인지 확인한 뒤 사용하십시오. 폐쇄망에서는 Matomo 자체 호스팅이나 사내 수집기를 직접 입력으로 연결하는 방식을 권장합니다.


AI 호출 이력

콘솔의 AI 호출 이력 화면은 조직 전체의 AI 요청을 한 곳에서 보여 줍니다. 사용자, 워크북, 작업 유형, 상태, 기간, 요청 문장으로 걸러 보고 행을 누르면 요청·요약·설명·변경 셀·이벤트 타임라인을 확인합니다. Workbook Agent의 대화, Run, Plan Step, Tool Call, ChangeSet과 검증 결과는 별도 PostgreSQL 감사 테이블에도 연결되어 사용자 요청부터 실제 도구 실행과 Undo까지 추적할 수 있습니다. 상단 카드에는 전체 요청 수, 적용·실패 건수, 입력과 응답 토큰 합계가 표시되고 아래에는 현재 필터 기준 사용량 상위 사용자가 나옵니다.

GET    /api/v1/admin/ai/actions?status=&mode=&actor=&q=&since=&until=&limit=&offset=&format=csv
GET    /api/v1/admin/ai/actions/{actionId}
DELETE /api/v1/admin/ai/actions?before=YYYY-MM-DD

세 경로 모두 관리자만 호출할 수 있습니다. 목록은 워크북 제목과 계획 이벤트에 기록된 토큰 사용량을 함께 반환합니다.

한 계획은 잘리거나 유효하지 않은 계획 응답을 최대 3회까지 교정하고, 일시 오류·컨텍스트 재조정·호환 옵션 전환을 별도 예산으로 처리하되 전체 Gateway 호출은 최대 5회로 제한합니다. 전체 계획 단계에는 115초 상한이 있고 선택적인 /models 조회는 최대 5초만 사용하므로 API 응답 제한 시간보다 오래 백그라운드 호출을 이어가지 않습니다. 429와 5xx 응답, 연결 실패는 Retry-After를 최대 5초 범위에서 반영해 재시도합니다. JSON 문법 오류, Markdown·추론 태그가 섞인 응답, 필수 필드 누락과 안전 검증 실패에는 제한된 교정 사유를 모델에 다시 전달합니다. 파서는 vLLM의 reasoning_content·reasoning, 여는 태그 없이 끝나는 </think>, 문자열·객체 envelope와 OpenAI 네이티브 tool_calls/function_call을 정규화하며, 여러 완성 계획이나 여러 도구 인자 객체가 섞인 모호한 응답은 실행하지 않습니다. HTTP 400/422가 출력까지 합친 컨텍스트 초과이면 응답 상한을 줄이고 JSON 모드와 이전 교정 사유를 유지합니다. 입력 프롬프트 자체가 모델 한도를 넘으면 반복 호출하지 않고 범위 축소나 새 대화를 안내하며, 실제 response_format 비호환일 때만 해당 옵션을 빼고 한 번 더 시도합니다. ai.timeout_seconds는 115초 전체 상한 안에서 각 호출에 따로 적용됩니다. 재시도한 모든 호출의 입력·응답 토큰 합계와 시도 횟수는 ai_action_events 페이로드에 남고 사용자 화면에도 표시됩니다.

전체 검증은 URL·모델·타입·제한값과 CA PEM을 검사하고, 연결 테스트는 Gateway의 GET /v1/models를 호출합니다. API Key와 CA 원문은 비밀 설정으로 저장되며 조회 응답에 다시 노출되지 않습니다. 완전 폐쇄망에서는 ai.gateway_url을 사내 vLLM 또는 사내 LLM Gateway 주소로 지정하면 외부 인터넷 연결 없이 동작합니다.

서버는 셀 내용을 신뢰할 수 없는 데이터로 취급하고 사용자가 선택한 범위의 비어 있지 않은 셀만 Gateway에 전달합니다. 수식 생성·오류 수정 응답은 =로 시작하는 수식만, 데이터 정제 응답은 JSON 스칼라 값 또는 명시적 셀 비우기만 허용합니다. 범위 요약·수식 설명·이상치 탐지는 항상 읽기 전용이며, 발견 항목은 선택 범위의 서버 셀 스냅샷과 결합해 표시합니다. 선택 범위 밖 변경·발견 항목, 중복 좌표, 객체·배열·null 값과 최대 변경 수 초과는 서버가 거부합니다. 실제 쓰기는 사용자가 미리보기를 승인한 뒤에만 실행되며 계획 당시 워크북 버전과 각 셀의 이전 값을 다시 확인합니다. 모든 계획·승인·Undo는 멱등 키, revision, 모델명, 도구명과 결과를 ai_actions, ai_action_events, audit_logs에 보존합니다.


6. Model Context Protocol (MCP) 서버 연동

kanpic은 AI 에이전트 및 LLM이 스프레드시트 데이터를 안전하게 제어할 수 있도록 /mcp HTTP JSON-RPC 2.0 표준 엔드포인트를 제공합니다.

6.1 MCP 스코프 및 인증

6.2 워크북 자동화 실행 정책

관리자 콘솔의 워크북 자동화 실행 정책 카드는 자동화 전체 활성화와 실행 한도를 관리합니다. 설정은 다른 관리자 설정처럼 PostgreSQL에 저장되고 CRUD, revision 이력, 이전 버전 복원, 전체 검증과 실제 저장소 연결 테스트를 지원합니다.

설정 키 기본값 유효 범위 설명
automation.enabled false boolean 실제 수동·셀 변경·예약 자동화 실행 허용. 정의 CRUD와 쓰기 없는 검증은 계속 가능. 꺼져 있으면 워크북의 자동화 패널이 그 사실과 이유를 표시
automation.max_cells_per_run 1000 1~10,000 한 실행이 변경할 수 있는 최대 셀 수
automation.max_runs_per_hour 100 1~10,000 워크북 하나에서 최근 1시간 동안 시작할 수 있는 실행 수
automation.scheduler_poll_seconds 15 5~300 PostgreSQL에서 실행 예정 자동화를 확인하는 주기(초)

정책 검증은 값 유형과 범위를 검사합니다. 저장소 준비 상태 테스트는 자동화가 활성화된 경우 PostgreSQL의 예약 및 웹훅 감사 컬럼을 실제로 조회해 최신 마이그레이션까지 적용됐는지 확인합니다. 운영 활성화 전에는 낮은 한도로 수동 실행과 Undo를 확인한 다음 셀 변경·일정·웹훅 트리거를 켜는 것을 권장합니다. 확인 주기는 서버 재시작 없이 다음 스케줄러 tick부터 반영됩니다.

자동화 정의는 이름별 유일성과 revision 기반 낙관적 잠금을 사용하며 삭제는 soft delete로 처리됩니다. 실행은 기준 워크북 버전, 작업 정의, 변경 전 셀 스냅샷, 예약 기준 시각, 실제 셀 작업 및 Undo 작업 ID를 automation_runs에 보존합니다. 수동 실행은 검증 응답의 자동화 revision과 워크북 base_version을 모두 제출해야 하며 둘 중 하나라도 바뀌면 409로 중단됩니다. 수동·웹훅 재시도는 사용자·자동화·멱등 키로, 셀 변경 재전송은 원본 operation_id로, 예약 실행은 자동화와 scheduled_for 조합으로 중복 제거합니다. 중복 실행 결과는 협업 이벤트를 다시 발행하지 않습니다. 웹훅 payload 원문은 보존하지 않고 API 키 UUID, SHA-256과 byte 수만 저장합니다. 다중 인스턴스의 시간당 실행 한도 판정과 실행 행 생성은 워크북 단위 PostgreSQL advisory lock 안에서 처리되고, 같은 예약을 조회해도 서버 권위 셀 작업과 유일 제약으로 한 번만 반영됩니다. 셀 적용 뒤 실행 상태 기록이 일시 실패한 예약은 running 상태와 due 시각을 유지해 같은 실행 ID로 복구하며, 실행 성공 알림과 다음 시각 갱신 실패를 분리합니다. 실행은 정확한 기준 버전 및 셀 스냅샷이 일치할 때만 적용되고, 성공·변경 없음·실패·Undo는 구조화 로그와 감사 로그에서 추적할 수 있습니다. 실행 행을 만들기 전에 거절된 셀 변경 트리거도 failed 실행과 사유를 남기므로 사용자 화면의 실행 이력에서 확인할 수 있으며, 시간당 한도 초과처럼 매 편집마다 반복되는 거절은 자동화별로 시간당 한 행으로 합칩니다.

업그레이드 시 024_automation_rate_admission.sql이 자동 적용되어 실제로 접수된 실행과 실행 제한으로 거부된 예약 이력을 구분합니다. 기존 실행 이력은 접수된 실행으로 유지됩니다. 자체 REST/MCP 클라이언트가 수동 실행을 호출한다면 :test 응답의 automation_revisionbase_version을 각각 expected_revision, expected_base_version으로 보내도록 함께 업그레이드해야 합니다.


7. DB 마이그레이션 & 백업 복구 (Backup & Disaster Recovery)

7.1 자동 DDL 마이그레이션 (migrations/)

kanpic 서버 기동 시 migrations/ 내의 DDL SQL 파일(001_initial.sql ~ 033_presentations.sql)을 자동 순차 실행하여 스키마를 최신 상태로 유지합니다. 015_automations.sql은 자동화 정의·revision·soft delete를 저장하는 automations와 실행 스냅샷·상태·작업·Undo·멱등 정보를 저장하는 automation_runs를 추가합니다. 016_scheduled_automations.sql은 다음 실행 시각, 예약 기준 시각, skipped 상태, due 조회 인덱스와 예약 중복 방지 유일 인덱스를 추가합니다. 017_webhook_automations.sql은 웹훅 trigger 상태, 호출 API 키 참조, payload digest·크기와 키별 조회 인덱스를 추가하며, 024_automation_rate_admission.sql은 실행 제한에 실제로 접수된 이력만 포함하도록 구분 컬럼과 조회 인덱스를 추가합니다. 031_conditional_rank.sql은 조건부 서식 규칙 종류에 상위·하위 N개(rank)를, 032_conditional_icon_set.sql은 아이콘 집합(icon_set)과 그 종류·순서 뒤집기 컬럼을 더하고, 033_presentations.sql은 워크북에서 만든 프레젠테이션의 출처(원본 범위와 그때의 워크북 버전)를 저장하는 presentations를 추가합니다.

7.2 백업 및 복구 명령어 (pg_dump)

# PostgreSQL 백업 수행 (폐쇄망 환경)
docker exec -t kanpic-postgres pg_dump -U kanpic_user -d kanpic_db -F c -b -v -f /backups/kanpic_dump_$(date +%Y%m%d).bak

# 복구 수행
docker exec -i kanpic-postgres pg_restore -U kanpic_user -d kanpic_db -v /backups/kanpic_dump_20260731.bak

백업 대상은 PostgreSQL 하나입니다. 워크북·설정·설정 버전·API 키 해시·세션·서버 로그가 모두 그 안에 있고, 애플리케이션 컨테이너에는 보관할 상태가 없습니다.

7.2-1 상태 점검

확인할 것 방법 정상
컨테이너가 살아 있는가 GET /healthz 200
어떤 빌드가 떠 있는가 GET /api/v1/version {"product":"kanpic","version":"v0.242.0",…}
저장소가 붙어 있는가 /admin시스템 상태 kanpic API ok, PostgreSQL 연결됨

시스템 상태 — kanpic API 버전과 PostgreSQL 연결 상태. Redis 는 초기 버전에서 쓰지 않는다

/healthz/api/v1/version 은 로그인 없이 열려 있으므로 그대로 컨테이너 health check 와 로드밸런서 점검에 씁니다. 나머지 경로는 인증을 요구합니다.

7.2-2 업그레이드와 되돌리기

# 1. 새 버전 이미지를 올린다
NEW=v0.243.0
sha256sum -c "kanpic-${NEW}.tar.gz.sha256"
gzip -dc "kanpic-${NEW}.tar.gz" | docker load

# 2. 올리기 직전 상태를 받아 둔다 (스키마 변경은 되돌아가지 않는다)
docker compose exec -T postgres pg_dump -U kanpic -d kanpic -F c > kanpic-before-${NEW}.dump

# 3. compose 의 image 태그를 새 버전으로 바꾸고 애플리케이션만 다시 띄운다
docker compose up -d api

# 4. 확인
curl -fsS http://localhost:8080/healthz && curl -fsS http://localhost:8080/api/v1/version

공개 링크 일괄 해제

/admin워크북 거버넌스 에서 링크 공개조직 전체 를 고르면 목록 위에 이 목록 전체 공개 해제 가 나옵니다.

링크 공개 47개 를 보고 마흔일곱 번 누르는 사람은 없습니다. 몇 개는 남고, 남은 것은 아무도 세지 않습니다.

API 키 폐기

/adminAPI 키 현황 에서 키마다 폐기 를 누르면 그 키만 끊습니다.

키 하나가 새어 나갔을 때 지금까지는 그 사람 계정을 통째로 정지하는 수밖에 없었습니다. 그러면 키와 상관없는 그 사람의 일까지 멈춥니다. 새어 나간 것만 끊는 편이 맞습니다.

7.3 관리자 행위 기록 (감사 추적)

관리자 콘솔에서 사람에게 영향을 준 일 은 서버 로그에 남습니다. 계정 등록·정지·수정, 역할 부여·회수, 세션 종료, 사용자 일괄 등록, 워크북 소유권 이전(하나씩·일괄), 부서 관리자 지정·해제입니다.

/admin서버 로그 에서 관리자 행위만 을 켜면 그것만 모아 봅니다. 각 줄에는 이렇게 적힙니다.

admin.action user.status
{"actor":"lead.kim","target":"team.park","status":"suspended","scope":"department"}

새 저장소를 만들지 않았습니다. 서버 로그가 이미 PostgreSQL에 쌓이고, 기간으로 거를 수 있고, CSV로 나갑니다 — 감사에서 달라고 하는 것이 그것입니다.

7.4 서버 로그 내보내기 (감사 대응)

서버 로그 — 최근 로그와 오류 수, 레벨·검색어·기간 거르개, 줄마다 시각·레벨·메시지·속성·Trace ID

/admin서버 로그 에서 레벨·검색어와 함께 기간 으로 거를 수 있습니다. 시작·끝 날짜만 적으면 그 날이 통째로 들어갑니다 — 끝 날짜의 기록이 빠지면 아무도 알아채지 못하기 때문입니다.

CSV 내보내기 는 화면에 건 조건 그대로 내려받습니다. 화면과 내보내기가 같은 물음을 쓰므로, 감사에 넘긴 파일과 화면에서 본 것이 어긋나지 않습니다.

curl -H "X-Kanpic-Actor: admin" \
  "https://kanpic.example/api/v1/admin/logs.csv?from=2026-01-01&to=2026-01-31&level=ERROR" \
  -o kanpic-logs-1월.csv

8. 장애 대응 (증상 → 확인할 곳 → 조치)

로그는 JSON 한 줄에 하나씩 표준 출력과 PostgreSQL 양쪽에 남습니다. 컨테이너에서는 docker compose logs -f api, 화면에서는 /admin서버 로그 로 같은 것을 봅니다.

증상 확인할 곳 로그에 찍히는 문구 조치
컨테이너가 곧바로 죽는다 docker compose logs api POSTGRES_DSN is required 환경 변수 POSTGRES_DSN 을 넣습니다(2.3).
컨테이너가 곧바로 죽는다 같은 곳 BOOTSTRAP_ADMIN_ID and BOOTSTRAP_ADMIN_PASSWORD must be configured together 둘 다 넣거나 둘 다 뺍니다.
컨테이너가 뜨지 않고 DB 오류 같은 곳 database startup failed DSN 의 호스트·사용자·비밀번호·sslmode, 네트워크와 PostgreSQL 상태를 확인합니다. 시작 시 30초 제한이 있습니다.
업그레이드 뒤 서버가 뜨지 않는다 같은 곳 apply migration <파일> … 실패한 마이그레이션 파일 이름을 확인하고, 되돌려야 하면 7.2-2 의 덤프로 복구합니다.
떴는데 화면이 비어 있다 GET /healthz, GET /api/v1/version kanpic API started 서버는 정상입니다. 리버스 프록시가 /·/api·/ws 를 모두 넘기는지, server.public_url 이 맞는지 봅니다.
사용자가 403 만 받는다 /admin → 사용자 및 역할 응답 account_suspended (정지된 계정입니다. 관리자에게 문의하세요.) 계정 정지를 풀거나, 정지된 소유자의 API 키를 쓰는 연동이 아닌지 확인합니다.
SSO 로그인이 되지 않는다 /admin → 시스템 설정 → 연결 테스트 검증 결과에 OIDC를 사용할 때 필수입니다. Issuer URL·Client ID 를 채우고, 사설 CA 면 auth.oidc.ca_pem 을 넣습니다.
AI 패널이 비활성이라고 나온다 /admin → 시스템 설정 → 사내 AI Gateway 검증 결과에 AI를 사용할 때 필수입니다. ai.gateway_urlai.model 을 채우고 연결 테스트 뒤 ai.enabled 를 켭니다.
알림 메일이 오지 않는다 /admin → 알림 메일 → 연결 확인 · 테스트 메일 보내기 화면에 릴레이 응답이 그대로 방화벽·포트(25/587/465)·인증 방식을 확인합니다. 사설 인증서면 mail.skip_tls_verify 를 검토합니다.
자동화가 돌지 않는다 /admin → 서버 로그(검색어 automation) automation scheduler tick failed · scheduled automation failed · automation run failed automation.enabled, 시간당 한도(automation.max_runs_per_hour), 실행 한 건의 셀 한도를 확인합니다.
IMPORTDATA·WEBSERVICE#VALUE! 만 돌려준다 /admin → 시스템 설정 → external external.enabledexternal.allowed_hosts 를 확인합니다. 허용 목록이 비어 있으면 아무 데도 부르지 않습니다. 원격 장애는 최대 30초, 그 밖의 응답은 external.cache_seconds 동안 캐시됩니다.
로그가 너무 짧게만 남는다 /admin → 시스템 설정 observability.log_retention_days(기본 30)를 늘립니다. 감사에 넘길 것은 CSV 로 미리 내보내 둡니다(7.4).

trace_id 는 요청 하나를 끝까지 따라갑니다. 사용자가 겪은 오류를 재현할 수 없을 때는 그 시각·계정으로 로그를 걸러 trace_id 를 찾은 뒤, 그 값으로 다시 걸러 한 요청의 전 구간을 봅니다.


9. 보안 및 컴플라이언스 (Security Checklists)

[!IMPORTANT] 운영 서버 보안 체크리스트

  1. 기본 관리자 계정의 초기 비밀번호 변경 필수
  2. 관리자 설정의 OIDC Client Secret은 화면에 재노출하지 말고 설정 변경·복원 권한을 관리자에게만 부여
  3. PostgreSQL TLS 1.3 통신 적용 및 8080 포트 리버스 프록시(Nginx/HAProxy) SSL 오프로딩 적용

기본값 중 배포 전에 바꿔야 하는 것

설정 기본값 왜 바꾸는가
BOOTSTRAP_ADMIN_ID · BOOTSTRAP_ADMIN_PASSWORD 없음 둘 다 비우면 로그인 없이 최초 설정을 할 수 있는 개방형 모드가 됩니다. 운영에서는 반드시 지정하고, 비밀번호는 배포 도구의 비밀 저장소로 주입합니다.
sharing.max_link_access anyone 링크를 아는 사람이면 열 수 있는 범위까지 허용됩니다. 조직 정책에 맞춰 organization 이나 restricted 로 낮춥니다.
sharing.default_link_access restricted 그대로 두는 편이 안전합니다. 올리면 새로 만드는 워크북이 전부 열린 채로 시작합니다.
POSTGRES_DSNsslmode 예시는 disable 같은 호스트가 아니면 require 이상으로 올립니다.
mail.skip_tls_verify false 켜면 SMTP 인증서를 검증하지 않습니다. 사설 인증서를 쓰는 동안만 켭니다.
external.enabled · external.allowed_hosts false · 빈 목록 수식이 바깥을 부르게 하려면 부를 곳을 목록으로 못 박습니다. 켜고 목록을 비워 두면 아무 데도 부르지 않습니다.
observability.log_retention_days 30 감사 요구 기간에 맞춥니다.

외부에 열면 안 되는 것

인증 연동